-module(drift@record). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/drift/record.gleam"). -export([new/4, flush/2, to_log/1, use_latest_outputs/2, discard/0, input/2, time_advance/2]). -export_type([recorder/4, message/3]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " Records the inputs an outputs of a drift stepper,\n" " given a pre-defined set of inputs. \n" " Intended for snapshot testing.\n" ). -opaque recorder(KRY, KRZ, KSA, KSB) :: {recorder, drift:stepper(KRY, KRZ), fun((drift:context(KRZ, KSA), KRY, KRZ) -> drift:step(KRY, KRZ, KSA, KSB)), fun((message(KRZ, KSA, KSB)) -> binary()), gleam@option:option(fun((KRY) -> binary())), gleam@option:option(integer()), integer(), binary(), list(KSA), boolean()}. -type message(KSC, KSD, KSE) :: {input, KSC} | {output, KSD} | {error, KSE}. -file("src/drift/record.gleam", 35). ?DOC(" Creates a new recorder, with the given state, behavior, and formatting.\n"). -spec new( KSF, fun((drift:context(KSG, KSH), KSF, KSG) -> drift:step(KSF, KSG, KSH, KSK)), fun((message(KSG, KSH, KSK)) -> binary()), gleam@option:option(fun((KSF) -> binary())) ) -> recorder(KSF, KSG, KSH, KSK). new(State, Apply_input, Formatter, Final_state_formatter) -> drift:reset_ids(), {Stepper, _} = drift:new(State, nil), {recorder, Stepper, Apply_input, Formatter, Final_state_formatter, none, 0, <<""/utf8>>, [], false}. -file("src/drift/record.gleam", 87). ?DOC( " Flushes all previous recorded inputs and outputs,\n" " and replaces the log with a message containing the provided description.\n" ). -spec flush(recorder(KTV, KTW, KTX, KTY), binary()) -> recorder(KTV, KTW, KTX, KTY). flush(Recorder, What) -> {recorder, erlang:element(2, Recorder), erlang:element(3, Recorder), erlang:element(4, Recorder), erlang:element(5, Recorder), erlang:element(6, Recorder), erlang:element(7, Recorder), <<<<">/binary, ">\n"/utf8>>, erlang:element(9, Recorder), erlang:element(10, Recorder)}. -file("src/drift/record.gleam", 96). ?DOC( " Turns the recorded steps into a string, which can be used with \n" " snapshot testing libraries (e.g. birdie).\n" ). -spec to_log(recorder(any(), any(), any(), any())) -> binary(). to_log(Recorder) -> gleam@string:trim_end(erlang:element(8, Recorder)). -file("src/drift/record.gleam", 103). ?DOC( " Applies the given function that produces the next state of the recorder\n" " from the outputs of the previously executed step.\n" " This is mostly required for testing continuations.\n" ). -spec use_latest_outputs( recorder(KUP, KUQ, KUR, KUS), fun((recorder(KUP, KUQ, KUR, KUS), list(KUR)) -> recorder(KUP, KUQ, KUR, KUS)) ) -> recorder(KUP, KUQ, KUR, KUS). use_latest_outputs(Recorder, With) -> With(Recorder, erlang:element(9, Recorder)). -file("src/drift/record.gleam", 112). ?DOC( " A utility function that creates an effect which only discards its input.\n" " You usually would not want to apply any side effects during snapshot testing.\n" ). -spec discard() -> drift:effect(any()). discard() -> drift:new_effect(fun(_) -> nil end). -file("src/drift/record.gleam", 116). -spec assert_ticks_exhausted(recorder(KVM, KVN, KVO, KVP)) -> recorder(KVM, KVN, KVO, KVP). assert_ticks_exhausted(Recorder) -> case erlang:element(6, Recorder) of {some, Next} when Next =< erlang:element(7, Recorder) -> Log = <<<<<<(erlang:element(8, Recorder))/binary, "!!!!! Next tick at "/utf8>>/binary, (erlang:integer_to_binary(Next))/binary>>/binary, " !!!!!\n"/utf8>>, {recorder, erlang:element(2, Recorder), erlang:element(3, Recorder), erlang:element(4, Recorder), erlang:element(5, Recorder), erlang:element(6, Recorder), erlang:element(7, Recorder), Log, erlang:element(9, Recorder), erlang:element(10, Recorder)}; _ -> Recorder end. -file("src/drift/record.gleam", 195). -spec pad_lines(binary()) -> binary(). pad_lines(Output) -> _pipe = gleam@string:split(Output, <<"\n"/utf8>>), gleam@string:join(_pipe, <<"\n "/utf8>>). -file("src/drift/record.gleam", 183). -spec output_list(binary(), boolean(), list(binary())) -> binary(). output_list(Log, First, Values) -> case {First, Values} of {_, []} -> Log; {true, [Head | Rest]} -> _pipe = (<<<<<>/binary, (pad_lines(Head))/binary>>/binary, "\n"/utf8>>), output_list(_pipe, false, Rest); {false, [Head@1 | Rest@1]} -> _pipe@1 = (<<<<<>/binary, (pad_lines(Head@1))/binary>>/binary, "\n"/utf8>>), output_list(_pipe@1, false, Rest@1) end. -file("src/drift/record.gleam", 132). -spec step_or_tick( recorder(KVY, KVZ, KWA, KWB), gleam@option:option(KVZ), binary() ) -> recorder(KVY, KVZ, KWA, KWB). step_or_tick(Recorder, Input, Description) -> gleam@bool:lazy_guard( erlang:element(10, Recorder), fun() -> Log = <<<<<<(erlang:element(8, Recorder))/binary, " =!!= Already stopped, ignoring:\n "/utf8>>/binary, (pad_lines(Description))/binary>>/binary, "\n"/utf8>>, {recorder, erlang:element(2, Recorder), erlang:element(3, Recorder), erlang:element(4, Recorder), erlang:element(5, Recorder), erlang:element(6, Recorder), erlang:element(7, Recorder), Log, erlang:element(9, Recorder), erlang:element(10, Recorder)} end, fun() -> Log@1 = <<<<<<(erlang:element(8, Recorder))/binary, " --> "/utf8>>/binary, (pad_lines(Description))/binary>>/binary, "\n"/utf8>>, Next = case Input of {some, Input@1} -> drift:step( erlang:element(2, Recorder), erlang:element(7, Recorder), Input@1, erlang:element(3, Recorder) ); none -> drift:tick( erlang:element(2, Recorder), erlang:element(7, Recorder), erlang:element(3, Recorder) ) end, Outputs = begin _pipe = erlang:element(2, Next), _pipe@1 = gleam@list:map( _pipe, fun(Field@0) -> {output, Field@0} end ), gleam@list:map(_pipe@1, erlang:element(4, Recorder)) end, Log@2 = output_list(Log@1, true, Outputs), Recorder@1 = {recorder, erlang:element(2, Recorder), erlang:element(3, Recorder), erlang:element(4, Recorder), erlang:element(5, Recorder), erlang:element(6, Recorder), erlang:element(7, Recorder), erlang:element(8, Recorder), erlang:element(2, Next), erlang:element(10, Recorder)}, case Next of {continue, _, Stepper, Next_tick} -> {recorder, Stepper, erlang:element(3, Recorder@1), erlang:element(4, Recorder@1), erlang:element(5, Recorder@1), Next_tick, erlang:element(7, Recorder@1), Log@2, erlang:element(9, Recorder@1), erlang:element(10, Recorder@1)}; {stop, _, State} -> Log@3 = <<<>/binary, (case erlang:element(5, Recorder@1) of {some, Format} -> <<(Format(State))/binary, "\n"/utf8>>; none -> <<""/utf8>> end)/binary>>, {recorder, erlang:element(2, Recorder@1), erlang:element(3, Recorder@1), erlang:element(4, Recorder@1), erlang:element(5, Recorder@1), none, erlang:element(7, Recorder@1), Log@3, erlang:element(9, Recorder@1), true}; {stop_with_error, _, Error} -> Log@4 = <<<<<>/binary, ((erlang:element(4, Recorder@1))({error, Error}))/binary>>/binary, "\n"/utf8>>, {recorder, erlang:element(2, Recorder@1), erlang:element(3, Recorder@1), erlang:element(4, Recorder@1), erlang:element(5, Recorder@1), none, erlang:element(7, Recorder@1), Log@4, erlang:element(9, Recorder@1), true} end end ). -file("src/drift/record.gleam", 58). ?DOC( " Applies the given input to the stepper, and returns a new recorder\n" " with the step recorded.\n" ). -spec input(recorder(KSX, KSY, KSZ, KTA), KSY) -> recorder(KSX, KSY, KSZ, KTA). input(Recorder, Input) -> Description = (erlang:element(4, Recorder))({input, Input}), step_or_tick(Recorder, {some, Input}, Description). -file("src/drift/record.gleam", 68). ?DOC( " Advances the simulated time, and returns a new recorder with the results\n" " of the possible timers being processed.\n" " Note that if the time encompasses multiple timer due times,\n" " all of them will be fired at the given time, not at their due time.\n" " This enables testing what happens if the timers fire late.\n" ). -spec time_advance(recorder(KTJ, KTK, KTL, KTM), integer()) -> recorder(KTJ, KTK, KTL, KTM). time_advance(Recorder, Duration) -> Time = erlang:element(7, Recorder) + Duration, Recorder@1 = begin Log = <<<<<<(erlang:element(8, Recorder))/binary, " ... "/utf8>>/binary, (erlang:integer_to_binary(Time))/binary>>/binary, " ms:\n"/utf8>>, {recorder, erlang:element(2, Recorder), erlang:element(3, Recorder), erlang:element(4, Recorder), erlang:element(5, Recorder), erlang:element(6, Recorder), Time, Log, erlang:element(9, Recorder), erlang:element(10, Recorder)} end, case erlang:element(6, Recorder@1) of {some, Next} when Next =< Time -> _pipe = Recorder@1, _pipe@1 = step_or_tick(_pipe, none, <<"Tick"/utf8>>), assert_ticks_exhausted(_pipe@1); _ -> Recorder@1 end.