-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_ready/2, await_some/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 Dream Test runs a test, it runs in an isolated BEAM process. Any\n" " processes you spawn inside a test can be *linked* to that test process, so\n" " they automatically die when the test ends (pass, fail, timeout, crash).\n" "\n" " This module gives you a few “batteries included” patterns:\n" "\n" " - A simple counter actor (`start_counter`) you can use to test stateful code.\n" " - A generic actor starter (`start_actor`) + a pipe-friendly call helper (`call_actor`).\n" " - “wait until ready” polling (`await_ready` / `await_some`) for async systems.\n" " - A safe-ish random port helper (`unique_port`) for test servers.\n" "\n" " ## Example\n" " Use this inside an `it` block.\n" "\n" " ```gleam\n" " let counter = process.start_counter()\n" " process.increment(counter)\n" " process.increment(counter)\n" "\n" " process.get_count(counter)\n" " |> should\n" " |> be_equal(2)\n" " |> or_fail_with(\"expected counter to be 2\")\n" " ```\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(JLS) :: {ready, JLS} | timed_out. -file("src/dream_test/process.gleam", 119). -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", 110). ?DOC( " Start a counter actor with a specific initial value.\n" "\n" " ## Parameters\n" "\n" " - `initial`: The initial count value.\n" "\n" " ## Returns\n" "\n" " A `Subject(CounterMessage)` for the started counter.\n" "\n" " ## Example\n" " Use this inside an `it` block.\n" "\n" " ```gleam\n" " let counter = process.start_counter_with(10)\n" " process.decrement(counter)\n" "\n" " process.get_count(counter)\n" " |> should\n" " |> be_equal(9)\n" " |> or_fail_with(\"expected counter to be 9 after decrement\")\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 => 111, value => _assert_fail, start => 3029, 'end' => 3143, pattern_start => 3040, pattern_end => 3051}) end, erlang:element(3, Started@1). -file("src/dream_test/process.gleam", 83). ?DOC( " Start a counter actor initialized to 0.\n" "\n" " The counter is linked to the test process and will be automatically cleaned\n" " up when the test ends.\n" "\n" " ## Returns\n" "\n" " A `Subject(CounterMessage)` you can pass to the other counter helpers (or\n" " send messages to directly).\n" "\n" " ## Example\n" " Use this inside an `it` block.\n" "\n" " ```gleam\n" " let counter = process.start_counter()\n" " process.increment(counter)\n" " process.increment(counter)\n" "\n" " process.get_count(counter)\n" " |> should\n" " |> be_equal(2)\n" " |> or_fail_with(\"expected counter to be 2\")\n" " ```\n" ). -spec start_counter() -> gleam@erlang@process:subject(counter_message()). start_counter() -> start_counter_with(0). -file("src/dream_test/process.gleam", 160). ?DOC( " Get the current value from a counter.\n" "\n" " This is a synchronous call that blocks until the counter responds.\n" "\n" " ## Parameters\n" "\n" " - `counter`: The counter actor to query.\n" "\n" " ## Returns\n" "\n" " The current counter value.\n" "\n" " ## Example\n" "\n" " Use this inside an `it` block.\n" "\n" " ```gleam\n" " let counter = process.start_counter()\n" " process.increment(counter)\n" "\n" " process.get_count(counter)\n" " |> should\n" " |> be_equal(1)\n" " |> or_fail_with(\"expected counter to be 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", 191). ?DOC( " Increment a counter by 1.\n" "\n" " This is an asynchronous send—it returns immediately.\n" "\n" " ## Parameters\n" "\n" " - `counter`: The counter actor to increment.\n" "\n" " ## Returns\n" "\n" " `Nil`. (The message is sent asynchronously.)\n" "\n" " ## Example\n" "\n" " Use this inside an `it` block.\n" "\n" " ```gleam\n" " let counter = process.start_counter()\n" " process.increment(counter)\n" " process.increment(counter)\n" "\n" " process.get_count(counter)\n" " |> should\n" " |> be_equal(2)\n" " |> or_fail_with(\"expected counter to be 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", 221). ?DOC( " Decrement a counter by 1.\n" "\n" " This is an asynchronous send—it returns immediately.\n" "\n" " ## Parameters\n" "\n" " - `counter`: The counter actor to decrement.\n" "\n" " ## Returns\n" "\n" " `Nil`. (The message is sent asynchronously.)\n" "\n" " ## Example\n" "\n" " Use this inside an `it` block.\n" "\n" " ```gleam\n" " let counter = process.start_counter_with(10)\n" " process.decrement(counter)\n" "\n" " process.get_count(counter)\n" " |> should\n" " |> be_equal(9)\n" " |> or_fail_with(\"expected counter to be 9 after decrement\")\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", 252). ?DOC( " Set a counter to a specific value.\n" "\n" " This is an asynchronous send—it returns immediately.\n" "\n" " ## Parameters\n" "\n" " - `counter`: The counter actor to set.\n" " - `value`: The new value to set.\n" "\n" " ## Returns\n" "\n" " `Nil`. (The message is sent asynchronously.)\n" "\n" " ## Example\n" "\n" " Use this inside an `it` block.\n" "\n" " ```gleam\n" " let counter = process.start_counter()\n" " process.set_count(counter, 42)\n" "\n" " process.get_count(counter)\n" " |> should\n" " |> be_equal(42)\n" " |> or_fail_with(\"expected counter to be 42 after set_count\")\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", 304). ?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" " Use this in test setup, before starting a server.\n" "\n" " ```gleam\n" " process.unique_port()\n" " |> should\n" " |> be_between(10_000, 60_000)\n" " |> or_fail_with(\"expected unique_port to be within 10k..60k\")\n" " ```\n" ). -spec unique_port() -> integer(). unique_port() -> Base = 10000, Range = 50000, Base + gleam@int:random(Range). -file("src/dream_test/process.gleam", 338). ?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" " ## Returns\n" "\n" " A `Subject(msg)` you can send messages to (or call with `call_actor`).\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let todos = process.start_actor([], handle_todo_message)\n" "\n" " erlang_process.send(todos, Add(\"Write tests\"))\n" " erlang_process.send(todos, Add(\"Run tests\"))\n" "\n" " process.call_actor(todos, GetAll, 1000)\n" " |> should\n" " |> be_equal([\"Write tests\", \"Run tests\"])\n" " |> or_fail_with(\"expected items to be preserved in insertion order\")\n" " ```\n" ). -spec start_actor(JMB, fun((JMB, JMC) -> gleam@otp@actor:next(JMB, JMC))) -> gleam@erlang@process:subject(JMC). 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 => 342, value => _assert_fail, start => 8471, 'end' => 8576, pattern_start => 8482, pattern_end => 8493}) end, erlang:element(3, Started@1). -file("src/dream_test/process.gleam", 374). ?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" " ## Returns\n" "\n" " The reply value from the actor.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " process.call_actor(todos, GetAll, 1000)\n" " |> should\n" " |> be_equal([\"Write tests\", \"Run tests\"])\n" " |> or_fail_with(\"expected items to be preserved in insertion order\")\n" " ```\n" ). -spec call_actor( gleam@erlang@process:subject(JMG), fun((gleam@erlang@process:subject(JMI)) -> JMG), integer() ) -> JMI. call_actor(Subject, Make_message, Timeout_ms) -> gleam@otp@actor:call(Subject, Timeout_ms, Make_message). -file("src/dream_test/process.gleam", 433). ?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" "\n" " ## Returns\n" "\n" " `PollConfig(timeout_ms: 5000, interval_ms: 50)`.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " process.default_poll_config()\n" " |> should\n" " |> be_equal(process.PollConfig(timeout_ms: 5000, interval_ms: 50))\n" " |> or_fail_with(\"expected default_poll_config to be 5000ms/50ms\")\n" " ```\n" ). -spec default_poll_config() -> poll_config(). default_poll_config() -> {poll_config, 5000, 50}. -file("src/dream_test/process.gleam", 457). ?DOC( " Quick polling configuration.\n" "\n" " - 1 second timeout\n" " - Check every 10ms\n" "\n" " Good for fast local operations like servers starting.\n" "\n" " ## Returns\n" "\n" " `PollConfig(timeout_ms: 1000, interval_ms: 10)`.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " process.quick_poll_config()\n" " |> should\n" " |> be_equal(process.PollConfig(timeout_ms: 1000, interval_ms: 10))\n" " |> or_fail_with(\"expected quick_poll_config to be 1000ms/10ms\")\n" " ```\n" ). -spec quick_poll_config() -> poll_config(). quick_poll_config() -> {poll_config, 1000, 10}. -file("src/dream_test/process.gleam", 560). -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", 549). -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", 506). ?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" " ## Parameters\n" "\n" " - `config`: Poll timeout + interval.\n" " - `check`: A zero-arg function returning `Bool`.\n" "\n" " ## Returns\n" "\n" " `Ready(True)` when the check returns `True`, otherwise `TimedOut`.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " process.await_ready(process.quick_poll_config(), always_true)\n" " |> should\n" " |> be_equal(process.Ready(True))\n" " |> or_fail_with(\"expected await_ready to return Ready(True)\")\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). -file("src/dream_test/process.gleam", 585). -spec poll_check_result( integer(), integer(), fun(() -> {ok, JMX} | {error, any()}) ) -> poll_result(JMX). 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", 574). -spec poll_until_ok(integer(), integer(), fun(() -> {ok, JMS} | {error, any()})) -> poll_result(JMS). 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", 542). ?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" " ## Parameters\n" "\n" " - `config`: Poll timeout + interval.\n" " - `check`: A zero-arg function returning `Result(value, error)`.\n" "\n" " ## Returns\n" "\n" " `Ready(value)` when the check returns `Ok(value)`, otherwise `TimedOut`.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " process.await_some(process.default_poll_config(), always_ok_42)\n" " |> should\n" " |> be_equal(process.Ready(42))\n" " |> or_fail_with(\"expected await_some to return Ready(42)\")\n" " ```\n" ). -spec await_some(poll_config(), fun(() -> {ok, JML} | {error, any()})) -> poll_result(JML). await_some(Config, Check) -> poll_until_ok(erlang:element(2, Config), erlang:element(3, Config), Check).