-module(lightspeed@testing).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/lightspeed/testing.gleam").
-export([trace/1, opcodes/1, run_with_log/1, run/1, instructions/1, replay/1, mounted_route/1, rendered_views/1, patches/1, pushed_events/1, subscriptions/1, navigation/1, shutdown_reason/1, telemetry/1, errors/1]).
-export_type([trace/0, log_entry/0, patch_record/0, event_record/0, simulation_state/0]).
-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(" Helpers for deterministic runtime tests.\n").
-type trace() :: {trace, list(binary())}.
-type log_entry() :: {log_entry,
integer(),
lightspeed@agent@isa:instruction(),
binary(),
binary()}.
-type patch_record() :: {patch_record, binary(), binary()}.
-type event_record() :: {event_record, binary(), binary()}.
-type simulation_state() :: {simulation_state,
gleam@option:option(binary()),
list(binary()),
list(patch_record()),
list(event_record()),
list(binary()),
gleam@option:option(binary()),
gleam@option:option(binary()),
list(binary()),
list(binary())}.
-file("src/lightspeed/testing.gleam", 48).
?DOC(" Build a trace from an instruction list.\n").
-spec trace(list(lightspeed@agent@isa:instruction())) -> trace().
trace(Instructions) ->
_pipe = Instructions,
_pipe@1 = gleam@list:map(_pipe, fun lightspeed@agent@isa:opcode/1),
{trace, _pipe@1}.
-file("src/lightspeed/testing.gleam", 55).
?DOC(" Extract opcodes from a trace.\n").
-spec opcodes(trace()) -> list(binary()).
opcodes(Trace) ->
erlang:element(2, Trace).
-file("src/lightspeed/testing.gleam", 130).
-spec new_state() -> simulation_state().
new_state() ->
{simulation_state, none, [], [], [], [], none, none, [], []}.
-file("src/lightspeed/testing.gleam", 250).
-spec remove_topic(binary(), list(binary())) -> list(binary()).
remove_topic(Topic, Topics) ->
case Topics of
[] ->
[];
[Entry | Rest] ->
case Entry =:= Topic of
true ->
remove_topic(Topic, Rest);
false ->
[Entry | remove_topic(Topic, Rest)]
end
end.
-file("src/lightspeed/testing.gleam", 239).
-spec contains_topic(binary(), list(binary())) -> boolean().
contains_topic(Topic, Topics) ->
case Topics of
[] ->
false;
[Entry | Rest] ->
case Entry =:= Topic of
true ->
true;
false ->
contains_topic(Topic, Rest)
end
end.
-file("src/lightspeed/testing.gleam", 235).
-spec append_error(simulation_state(), binary()) -> simulation_state().
append_error(State, Message) ->
{simulation_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
erlang:element(9, State),
[Message | erlang:element(10, State)]}.
-file("src/lightspeed/testing.gleam", 228).
-spec append_telemetry(simulation_state(), binary()) -> simulation_state().
append_telemetry(State, Message) ->
{simulation_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
[Message | erlang:element(9, State)],
erlang:element(10, State)}.
-file("src/lightspeed/testing.gleam", 166).
-spec apply_instruction(simulation_state(), lightspeed@agent@isa:instruction()) -> simulation_state().
apply_instruction(State, Instruction) ->
State@1 = append_telemetry(
State,
lightspeed@agent@isa:describe(Instruction)
),
case Instruction of
{mount, Route, _} ->
case erlang:element(2, State@1) of
none ->
{simulation_state,
{some, Route},
erlang:element(3, State@1),
erlang:element(4, State@1),
erlang:element(5, State@1),
erlang:element(6, State@1),
erlang:element(7, State@1),
erlang:element(8, State@1),
erlang:element(9, State@1),
erlang:element(10, State@1)};
{some, _} ->
append_error(State@1, <<"duplicate mount"/utf8>>)
end;
{render, View_id} ->
case erlang:element(2, State@1) of
none ->
append_error(State@1, <<"render before mount"/utf8>>);
{some, _} ->
{simulation_state,
erlang:element(2, State@1),
[View_id | erlang:element(3, State@1)],
erlang:element(4, State@1),
erlang:element(5, State@1),
erlang:element(6, State@1),
erlang:element(7, State@1),
erlang:element(8, State@1),
erlang:element(9, State@1),
erlang:element(10, State@1)}
end;
{patch, Target, Html} ->
case erlang:element(2, State@1) of
none ->
append_error(State@1, <<"patch before mount"/utf8>>);
{some, _} ->
Patch = {patch_record, Target, Html},
{simulation_state,
erlang:element(2, State@1),
erlang:element(3, State@1),
[Patch | erlang:element(4, State@1)],
erlang:element(5, State@1),
erlang:element(6, State@1),
erlang:element(7, State@1),
erlang:element(8, State@1),
erlang:element(9, State@1),
erlang:element(10, State@1)}
end;
{push_event, Name, Payload} ->
Event = {event_record, Name, Payload},
{simulation_state,
erlang:element(2, State@1),
erlang:element(3, State@1),
erlang:element(4, State@1),
[Event | erlang:element(5, State@1)],
erlang:element(6, State@1),
erlang:element(7, State@1),
erlang:element(8, State@1),
erlang:element(9, State@1),
erlang:element(10, State@1)};
{navigate, To} ->
{simulation_state,
erlang:element(2, State@1),
erlang:element(3, State@1),
erlang:element(4, State@1),
erlang:element(5, State@1),
erlang:element(6, State@1),
{some, To},
erlang:element(8, State@1),
erlang:element(9, State@1),
erlang:element(10, State@1)};
{subscribe, Topic} ->
case contains_topic(Topic, erlang:element(6, State@1)) of
true ->
State@1;
false ->
{simulation_state,
erlang:element(2, State@1),
erlang:element(3, State@1),
erlang:element(4, State@1),
erlang:element(5, State@1),
[Topic | erlang:element(6, State@1)],
erlang:element(7, State@1),
erlang:element(8, State@1),
erlang:element(9, State@1),
erlang:element(10, State@1)}
end;
{unsubscribe, Topic@1} ->
Subscriptions = remove_topic(Topic@1, erlang:element(6, State@1)),
{simulation_state,
erlang:element(2, State@1),
erlang:element(3, State@1),
erlang:element(4, State@1),
erlang:element(5, State@1),
Subscriptions,
erlang:element(7, State@1),
erlang:element(8, State@1),
erlang:element(9, State@1),
erlang:element(10, State@1)};
{shutdown, Reason} ->
{simulation_state,
erlang:element(2, State@1),
erlang:element(3, State@1),
erlang:element(4, State@1),
erlang:element(5, State@1),
erlang:element(6, State@1),
erlang:element(7, State@1),
{some, Reason},
erlang:element(9, State@1),
erlang:element(10, State@1)}
end.
-file("src/lightspeed/testing.gleam", 144).
-spec run_loop(
list(lightspeed@agent@isa:instruction()),
simulation_state(),
list(log_entry()),
integer()
) -> {simulation_state(), list(log_entry())}.
run_loop(Program, State, Log_rev, Step) ->
case Program of
[] ->
{State, lists:reverse(Log_rev)};
[Instruction | Rest] ->
State@1 = apply_instruction(State, Instruction),
Entry = {log_entry,
Step,
Instruction,
lightspeed@agent@isa:opcode(Instruction),
lightspeed@agent@isa:describe(Instruction)},
run_loop(Rest, State@1, [Entry | Log_rev], Step + 1)
end.
-file("src/lightspeed/testing.gleam", 66).
?DOC(" Run an ISA program and return a replayable instruction log.\n").
-spec run_with_log(list(lightspeed@agent@isa:instruction())) -> {simulation_state(),
list(log_entry())}.
run_with_log(Program) ->
run_loop(Program, new_state(), [], 0).
-file("src/lightspeed/testing.gleam", 60).
?DOC(" Run an ISA program through the deterministic test interpreter.\n").
-spec run(list(lightspeed@agent@isa:instruction())) -> simulation_state().
run(Program) ->
{State, _} = run_with_log(Program),
State.
-file("src/lightspeed/testing.gleam", 80).
?DOC(" Extract instructions from a log.\n").
-spec instructions(list(log_entry())) -> list(lightspeed@agent@isa:instruction()).
instructions(Log) ->
_pipe = Log,
gleam@list:map(_pipe, fun(Entry) -> erlang:element(3, Entry) end).
-file("src/lightspeed/testing.gleam", 73).
?DOC(" Replay a prior log into a deterministic interpreter state.\n").
-spec replay(list(log_entry())) -> simulation_state().
replay(Log) ->
_pipe = Log,
_pipe@1 = instructions(_pipe),
run(_pipe@1).
-file("src/lightspeed/testing.gleam", 86).
?DOC(" Route from the latest mount instruction, when mounted.\n").
-spec mounted_route(simulation_state()) -> gleam@option:option(binary()).
mounted_route(State) ->
erlang:element(2, State).
-file("src/lightspeed/testing.gleam", 91).
?DOC(" Rendered view ids in execution order.\n").
-spec rendered_views(simulation_state()) -> list(binary()).
rendered_views(State) ->
lists:reverse(erlang:element(3, State)).
-file("src/lightspeed/testing.gleam", 96).
?DOC(" Patches in execution order.\n").
-spec patches(simulation_state()) -> list(patch_record()).
patches(State) ->
lists:reverse(erlang:element(4, State)).
-file("src/lightspeed/testing.gleam", 101).
?DOC(" Pushed events in execution order.\n").
-spec pushed_events(simulation_state()) -> list(event_record()).
pushed_events(State) ->
lists:reverse(erlang:element(5, State)).
-file("src/lightspeed/testing.gleam", 106).
?DOC(" Subscriptions currently active at end of simulation.\n").
-spec subscriptions(simulation_state()) -> list(binary()).
subscriptions(State) ->
lists:reverse(erlang:element(6, State)).
-file("src/lightspeed/testing.gleam", 111).
?DOC(" Latest navigation target, when present.\n").
-spec navigation(simulation_state()) -> gleam@option:option(binary()).
navigation(State) ->
erlang:element(7, State).
-file("src/lightspeed/testing.gleam", 116).
?DOC(" Shutdown reason, when a shutdown instruction was executed.\n").
-spec shutdown_reason(simulation_state()) -> gleam@option:option(binary()).
shutdown_reason(State) ->
erlang:element(8, State).
-file("src/lightspeed/testing.gleam", 121).
?DOC(" Telemetry messages in execution order.\n").
-spec telemetry(simulation_state()) -> list(binary()).
telemetry(State) ->
lists:reverse(erlang:element(9, State)).
-file("src/lightspeed/testing.gleam", 126).
?DOC(" Validation errors in execution order.\n").
-spec errors(simulation_state()) -> list(binary()).
errors(State) ->
lists:reverse(erlang:element(10, State)).