-module(dream_test@sandbox). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/dream_test/sandbox.gleam"). -export([run_isolated/2, default_config/0]). -export_type([sandbox_config/0, sandbox_result/0, worker_message/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. -type sandbox_config() :: {sandbox_config, integer()}. -type sandbox_result() :: {sandbox_completed, dream_test@types:assertion_result()} | sandbox_timed_out | {sandbox_crashed, binary()}. -type worker_message() :: {test_completed, dream_test@types:assertion_result()} | {worker_down, binary()}. -file("src/dream_test/sandbox.gleam", 52). ?DOC(" Spawn a worker process that runs the test and sends the result.\n"). -spec spawn_worker( gleam@erlang@process:subject(dream_test@types:assertion_result()), fun(() -> dream_test@types:assertion_result()) ) -> gleam@erlang@process:pid_(). spawn_worker(Result_subject, Test_function) -> proc_lib:spawn( fun() -> Result = Test_function(), gleam@erlang@process:send(Result_subject, Result) end ). -file("src/dream_test/sandbox.gleam", 73). ?DOC(" Map a test result to a WorkerMessage.\n"). -spec map_result_to_message(dream_test@types:assertion_result()) -> worker_message(). map_result_to_message(Result) -> {test_completed, Result}. -file("src/dream_test/sandbox.gleam", 84). ?DOC(" Format an exit reason as a string for error reporting.\n"). -spec format_exit_reason(gleam@erlang@process:exit_reason()) -> binary(). format_exit_reason(Reason) -> case Reason of normal -> <<"normal"/utf8>>; killed -> <<"killed"/utf8>>; {abnormal, Dynamic_reason} -> gleam@string:inspect(Dynamic_reason) end. -file("src/dream_test/sandbox.gleam", 78). ?DOC(" Map a process down event to a WorkerMessage.\n"). -spec map_down_to_message(gleam@erlang@process:down()) -> worker_message(). map_down_to_message(Down) -> Reason = format_exit_reason(erlang:element(4, Down)), {worker_down, Reason}. -file("src/dream_test/sandbox.gleam", 63). ?DOC(" Build a selector that waits for either test completion or process down.\n"). -spec build_result_selector( gleam@erlang@process:subject(dream_test@types:assertion_result()) ) -> gleam@erlang@process:selector(worker_message()). build_result_selector(Result_subject) -> _pipe = gleam_erlang_ffi:new_selector(), _pipe@1 = gleam@erlang@process:select(_pipe, Result_subject), _pipe@2 = gleam_erlang_ffi:map_selector( _pipe@1, fun map_result_to_message/1 ), gleam@erlang@process:select_monitors(_pipe@2, fun map_down_to_message/1). -file("src/dream_test/sandbox.gleam", 106). ?DOC(" Handle a message from the worker or monitor.\n"). -spec handle_worker_message(worker_message()) -> sandbox_result(). handle_worker_message(Message) -> case Message of {test_completed, Result} -> {sandbox_completed, Result}; {worker_down, Reason} -> {sandbox_crashed, Reason} end. -file("src/dream_test/sandbox.gleam", 114). ?DOC(" Handle timeout by killing the worker and returning timeout result.\n"). -spec handle_timeout(gleam@erlang@process:pid_()) -> sandbox_result(). handle_timeout(Worker_pid) -> gleam@erlang@process:kill(Worker_pid), sandbox_timed_out. -file("src/dream_test/sandbox.gleam", 93). ?DOC(" Wait for the worker to complete or timeout.\n"). -spec wait_for_result( sandbox_config(), gleam@erlang@process:selector(worker_message()), gleam@erlang@process:pid_(), gleam@erlang@process:monitor() ) -> sandbox_result(). wait_for_result(Config, Selector, Worker_pid, _) -> case gleam_erlang_ffi:select(Selector, erlang:element(2, Config)) of {ok, Message} -> handle_worker_message(Message); {error, nil} -> handle_timeout(Worker_pid) end. -file("src/dream_test/sandbox.gleam", 39). ?DOC( " Run a test function in an isolated process with timeout.\n" "\n" " The test function runs in a separate BEAM process that is monitored.\n" " If the process completes normally, its result is returned.\n" " If the process crashes or times out, an appropriate SandboxResult is returned.\n" ). -spec run_isolated( sandbox_config(), fun(() -> dream_test@types:assertion_result()) ) -> sandbox_result(). run_isolated(Config, Test_function) -> Result_subject = gleam@erlang@process:new_subject(), Worker_pid = spawn_worker(Result_subject, Test_function), Worker_monitor = gleam@erlang@process:monitor(Worker_pid), Selector = build_result_selector(Result_subject), wait_for_result(Config, Selector, Worker_pid, Worker_monitor). -file("src/dream_test/sandbox.gleam", 120). ?DOC(" Default configuration with a 5 second timeout.\n"). -spec default_config() -> sandbox_config(). default_config() -> {sandbox_config, 5000}.