-module(dream_test@process). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/dream_test/process.gleam"). -export([start_counter_with/1, start_counter/0, get_count/1, increment/1, decrement/1, set_count/2, unique_port/0, start_actor/2, call_actor/3, default_poll_config/0, quick_poll_config/0, await_some/2, await_ready/2]). -export_type([counter_message/0, port_selection/0, poll_config/0, poll_result/1]). -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( " Process helpers for tests that need actors or async operations.\n" "\n" " When tests run in isolated BEAM processes, any processes spawned by the\n" " test are automatically terminated when the test ends. This module provides\n" " helpers for common patterns.\n" "\n" " ## Auto-Cleanup\n" "\n" " Processes started with these helpers are linked to the test process.\n" " When the test completes (pass, fail, or timeout), all linked processes\n" " are automatically cleaned up. No manual teardown needed.\n" "\n" " ## Quick Start\n" "\n" " ```gleam\n" " import dream_test/process.{start_counter, get_count, increment}\n" " import dream_test/unit.{describe, it}\n" " import dream_test/assertions/should.{should, equal, or_fail_with}\n" "\n" " pub fn tests() {\n" " describe(\"Counter\", [\n" " it(\"increments correctly\", fn() {\n" " let counter = start_counter()\n" " increment(counter)\n" " increment(counter)\n" "\n" " get_count(counter)\n" " |> should()\n" " |> equal(2)\n" " |> or_fail_with(\"Counter should be 2\")\n" " }),\n" " ])\n" " }\n" " ```\n" "\n" " ## Available Helpers\n" "\n" " | Helper | Purpose |\n" " |---------------------|----------------------------------------------|\n" " | `start_counter` | Simple counter actor for testing state |\n" " | `start_actor` | Generic actor with custom state and handler |\n" " | `unique_port` | Generate random port for test servers |\n" " | `await_ready` | Poll until a condition is true |\n" " | `await_some` | Poll until a function returns Ok |\n" ). -type counter_message() :: increment | decrement | {set_count, integer()} | {get_count, gleam@erlang@process:subject(integer())}. -type port_selection() :: {port, integer()} | random_port. -type poll_config() :: {poll_config, integer(), integer()}. -type poll_result(HVR) :: {ready, HVR} | timed_out. -file("src/dream_test/process.gleam", 104). -spec handle_counter_message(integer(), counter_message()) -> gleam@otp@actor:next(integer(), counter_message()). handle_counter_message(State, Message) -> case Message of increment -> gleam@otp@actor:continue(State + 1); decrement -> gleam@otp@actor:continue(State - 1); {set_count, Value} -> gleam@otp@actor:continue(Value); {get_count, Reply_to} -> gleam@erlang@process:send(Reply_to, State), gleam@otp@actor:continue(State) end. -file("src/dream_test/process.gleam", 95). ?DOC( " Start a counter actor with a specific initial value.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let counter = start_counter_with(100)\n" " decrement(counter)\n" " get_count(counter) // -> 99\n" " ```\n" ). -spec start_counter_with(integer()) -> gleam@erlang@process:subject(counter_message()). start_counter_with(Initial) -> Started@1 = case begin _pipe = gleam@otp@actor:new(Initial), _pipe@1 = gleam@otp@actor:on_message( _pipe, fun handle_counter_message/2 ), gleam@otp@actor:start(_pipe@1) end of {ok, Started} -> Started; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"dream_test/process"/utf8>>, function => <<"start_counter_with"/utf8>>, line => 96, value => _assert_fail, start => 2774, 'end' => 2888, pattern_start => 2785, pattern_end => 2796}) end, erlang:element(3, Started@1). -file("src/dream_test/process.gleam", 81). ?DOC( " Start a counter actor initialized to 0.\n" "\n" " The counter is linked to the test process and will be automatically\n" " cleaned up when the test ends.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let counter = start_counter()\n" " increment(counter)\n" " increment(counter)\n" " get_count(counter) // -> 2\n" " ```\n" ). -spec start_counter() -> gleam@erlang@process:subject(counter_message()). start_counter() -> start_counter_with(0). -file("src/dream_test/process.gleam", 131). ?DOC( " Get the current value from a counter.\n" "\n" " This is a synchronous call that blocks until the counter responds.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let counter = start_counter()\n" " increment(counter)\n" " let value = get_count(counter) // -> 1\n" " ```\n" ). -spec get_count(gleam@erlang@process:subject(counter_message())) -> integer(). get_count(Counter) -> gleam@otp@actor:call( Counter, 1000, fun(Field@0) -> {get_count, Field@0} end ). -file("src/dream_test/process.gleam", 148). ?DOC( " Increment a counter by 1.\n" "\n" " This is an asynchronous send—it returns immediately.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let counter = start_counter()\n" " increment(counter)\n" " increment(counter)\n" " get_count(counter) // -> 2\n" " ```\n" ). -spec increment(gleam@erlang@process:subject(counter_message())) -> nil. increment(Counter) -> gleam@erlang@process:send(Counter, increment). -file("src/dream_test/process.gleam", 164). ?DOC( " Decrement a counter by 1.\n" "\n" " This is an asynchronous send—it returns immediately.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let counter = start_counter_with(10)\n" " decrement(counter)\n" " get_count(counter) // -> 9\n" " ```\n" ). -spec decrement(gleam@erlang@process:subject(counter_message())) -> nil. decrement(Counter) -> gleam@erlang@process:send(Counter, decrement). -file("src/dream_test/process.gleam", 180). ?DOC( " Set a counter to a specific value.\n" "\n" " This is an asynchronous send—it returns immediately.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let counter = start_counter()\n" " set_count(counter, 42)\n" " get_count(counter) // -> 42\n" " ```\n" ). -spec set_count(gleam@erlang@process:subject(counter_message()), integer()) -> nil. set_count(Counter, Value) -> gleam@erlang@process:send(Counter, {set_count, Value}). -file("src/dream_test/process.gleam", 215). ?DOC( " Generate a unique port number for test servers.\n" "\n" " Returns a random port between 10,000 and 60,000. This range avoids:\n" " - Well-known ports (0-1023)\n" " - Registered ports commonly used by services (1024-9999)\n" " - Ports near the upper limit that some systems reserve\n" "\n" " Using random ports prevents conflicts when running tests in parallel.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let port = unique_port()\n" " start_server(port)\n" " // port is something like 34521\n" " ```\n" ). -spec unique_port() -> integer(). unique_port() -> Base = 10000, Range = 50000, Base + gleam@int:random(Range). -file("src/dream_test/process.gleam", 255). ?DOC( " Start a generic actor with custom state and message handler.\n" "\n" " The actor is linked to the test process and will be automatically\n" " cleaned up when the test ends.\n" "\n" " ## Parameters\n" "\n" " - `initial_state` - The actor's starting state\n" " - `handler` - Function `fn(state, message) -> actor.Next(state, message)`\n" "\n" " ## Example\n" "\n" " ```gleam\n" " pub type TodoMessage {\n" " Add(String)\n" " GetAll(Subject(List(String)))\n" " }\n" "\n" " let todos = start_actor([], fn(items, msg) {\n" " case msg {\n" " Add(item) -> actor.continue([item, ..items])\n" " GetAll(reply) -> {\n" " process.send(reply, items)\n" " actor.continue(items)\n" " }\n" " }\n" " })\n" "\n" " process.send(todos, Add(\"Write tests\"))\n" " process.send(todos, Add(\"Run tests\"))\n" " let items = call_actor(todos, GetAll, 1000)\n" " // items == [\"Run tests\", \"Write tests\"]\n" " ```\n" ). -spec start_actor(HWA, fun((HWA, HWB) -> gleam@otp@actor:next(HWA, HWB))) -> gleam@erlang@process:subject(HWB). start_actor(Initial_state, Handler) -> Started@1 = case begin _pipe = gleam@otp@actor:new(Initial_state), _pipe@1 = gleam@otp@actor:on_message(_pipe, Handler), gleam@otp@actor:start(_pipe@1) end of {ok, Started} -> Started; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"dream_test/process"/utf8>>, function => <<"start_actor"/utf8>>, line => 259, value => _assert_fail, start => 6685, 'end' => 6790, pattern_start => 6696, pattern_end => 6707}) end, erlang:element(3, Started@1). -file("src/dream_test/process.gleam", 288). ?DOC( " Call an actor and wait for a response.\n" "\n" " This is a convenience wrapper around `actor.call` that makes the parameter\n" " order more ergonomic for piping.\n" "\n" " ## Parameters\n" "\n" " - `subject` - The actor to call\n" " - `make_message` - Function that creates the message given a reply subject\n" " - `timeout_ms` - How long to wait for a response\n" "\n" " ## Example\n" "\n" " ```gleam\n" " pub type Msg {\n" " GetValue(Subject(Int))\n" " }\n" "\n" " let value = call_actor(my_actor, GetValue, 1000)\n" " ```\n" ). -spec call_actor( gleam@erlang@process:subject(HWF), fun((gleam@erlang@process:subject(HWH)) -> HWF), integer() ) -> HWH. call_actor(Subject, Make_message, Timeout_ms) -> gleam@otp@actor:call(Subject, Timeout_ms, Make_message). -file("src/dream_test/process.gleam", 332). ?DOC( " Default polling configuration.\n" "\n" " - 5 second timeout\n" " - Check every 50ms\n" "\n" " Good for operations that might take a few seconds.\n" ). -spec default_poll_config() -> poll_config(). default_poll_config() -> {poll_config, 5000, 50}. -file("src/dream_test/process.gleam", 343). ?DOC( " Quick polling configuration.\n" "\n" " - 1 second timeout\n" " - Check every 10ms\n" "\n" " Good for fast local operations like servers starting.\n" ). -spec quick_poll_config() -> poll_config(). quick_poll_config() -> {poll_config, 1000, 10}. -file("src/dream_test/process.gleam", 458). -spec poll_check_result( integer(), integer(), fun(() -> {ok, HWW} | {error, any()}) ) -> poll_result(HWW). poll_check_result(Remaining_ms, Interval_ms, Check) -> case Check() of {ok, Value} -> {ready, Value}; {error, _} -> gleam_erlang_ffi:sleep(Interval_ms), poll_until_ok(Remaining_ms - Interval_ms, Interval_ms, Check) end. -file("src/dream_test/process.gleam", 447). -spec poll_until_ok(integer(), integer(), fun(() -> {ok, HWR} | {error, any()})) -> poll_result(HWR). poll_until_ok(Remaining_ms, Interval_ms, Check) -> case Remaining_ms =< 0 of true -> timed_out; false -> poll_check_result(Remaining_ms, Interval_ms, Check) end. -file("src/dream_test/process.gleam", 415). ?DOC( " Wait until a function returns Ok.\n" "\n" " Polls the check function at regular intervals until it returns `Ok(value)`\n" " or the timeout is reached. The value is returned in `Ready(value)`.\n" "\n" " ## Use Cases\n" "\n" " - Waiting for a record to appear in a database\n" " - Waiting for an async job to complete\n" " - Waiting for a resource to become available\n" "\n" " ## Example\n" "\n" " ```gleam\n" " // Wait for user to appear in database\n" " case await_some(default_poll_config(), fn() { find_user(user_id) }) {\n" " Ready(user) -> {\n" " user.name\n" " |> should()\n" " |> equal(\"Alice\")\n" " |> or_fail_with(\"User should be Alice\")\n" " }\n" " TimedOut -> fail_with(\"User never appeared in database\")\n" " }\n" " ```\n" ). -spec await_some(poll_config(), fun(() -> {ok, HWK} | {error, any()})) -> poll_result(HWK). await_some(Config, Check) -> poll_until_ok(erlang:element(2, Config), erlang:element(3, Config), Check). -file("src/dream_test/process.gleam", 433). -spec poll_check_bool(integer(), integer(), fun(() -> boolean())) -> poll_result(boolean()). poll_check_bool(Remaining_ms, Interval_ms, Check) -> case Check() of true -> {ready, true}; false -> gleam_erlang_ffi:sleep(Interval_ms), poll_until_true(Remaining_ms - Interval_ms, Interval_ms, Check) end. -file("src/dream_test/process.gleam", 422). -spec poll_until_true(integer(), integer(), fun(() -> boolean())) -> poll_result(boolean()). poll_until_true(Remaining_ms, Interval_ms, Check) -> case Remaining_ms =< 0 of true -> timed_out; false -> poll_check_bool(Remaining_ms, Interval_ms, Check) end. -file("src/dream_test/process.gleam", 385). ?DOC( " Wait until a condition returns True.\n" "\n" " Polls the check function at regular intervals until it returns `True`\n" " or the timeout is reached.\n" "\n" " ## Use Cases\n" "\n" " - Waiting for a server to start accepting connections\n" " - Waiting for a file to appear\n" " - Waiting for a service to become healthy\n" "\n" " ## Example\n" "\n" " ```gleam\n" " // Wait for server to be ready\n" " case await_ready(quick_poll_config(), fn() { is_port_open(port) }) {\n" " Ready(True) -> {\n" " // Server is up, proceed with test\n" " make_request(port)\n" " |> should()\n" " |> be_ok()\n" " |> or_fail_with(\"Request should succeed\")\n" " }\n" " TimedOut -> fail_with(\"Server didn't start in time\")\n" " }\n" " ```\n" ). -spec await_ready(poll_config(), fun(() -> boolean())) -> poll_result(boolean()). await_ready(Config, Check) -> poll_until_true(erlang:element(2, Config), erlang:element(3, Config), Check).