Packages

Record logs for drift steppers, and use them in snapshot tests.

Current section

Files

Jump to
drift_record src drift@record.erl
Raw

src/drift@record.erl

-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),
<<<<"<flushed "/utf8, What/binary>>/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 = (<<<<<<Log/binary, "<-- "/utf8>>/binary,
(pad_lines(Head))/binary>>/binary,
"\n"/utf8>>),
output_list(_pipe, false, Rest);
{false, [Head@1 | Rest@1]} ->
_pipe@1 = (<<<<<<Log/binary, " "/utf8>>/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 = <<<<Log@2/binary, "===== Stopped!\n"/utf8>>/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 = <<<<<<Log@2/binary, " !! "/utf8>>/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.