-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)).