-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(LCN, LCO, LCP, LCQ) :: {recorder, drift:stepper(LCN, LCO), fun((drift:context(LCO, LCP), LCN, LCO) -> drift:step(LCN, LCO, LCP, LCQ)), fun((message(LCO, LCP, LCQ)) -> binary()), gleam@option:option(fun((LCN) -> binary())), gleam@option:option(integer()), integer(), binary(), list(LCP), boolean()}. -type message(LCR, LCS, LCT) :: {input, LCR} | {output, LCS} | {error, LCT}. -file("src/drift/record.gleam", 35). ?DOC(" Creates a new recorder, with the given state, behavior, and formatting.\n"). -spec new( LCU, fun((drift:context(LCV, LCW), LCU, LCV) -> drift:step(LCU, LCV, LCW, LCZ)), fun((message(LCV, LCW, LCZ)) -> binary()), gleam@option:option(fun((LCU) -> binary())) ) -> recorder(LCU, LCV, LCW, LCZ). 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(LEK, LEL, LEM, LEN), binary()) -> recorder(LEK, LEL, LEM, LEN). 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(LFE, LFF, LFG, LFH), fun((recorder(LFE, LFF, LFG, LFH), list(LFG)) -> recorder(LFE, LFF, LFG, LFH)) ) -> recorder(LFE, LFF, LFG, LFH). 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(LGB, LGC, LGD, LGE)) -> recorder(LGB, LGC, LGD, LGE). 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(LGN, LGO, LGP, LGQ), gleam@option:option(LGO), binary() ) -> recorder(LGN, LGO, LGP, LGQ). 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(LDM, LDN, LDO, LDP), LDN) -> recorder(LDM, LDN, LDO, LDP). 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(LDY, LDZ, LEA, LEB), integer()) -> recorder(LDY, LDZ, LEA, LEB). 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.