Current section
Files
Jump to
Current section
Files
src/dream_test@runner.erl
-module(dream_test@runner).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/dream_test/runner.gleam").
-export([default_config/0, sequential_config/0, run_all_with_config/2, run_all/1, run_suite_with_config/2, run_suite/1, run_single_test/1, run_test_case/1, run_all_sequential/1]).
-export_type([runner_config/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(
" Test runner for dream_test.\n"
"\n"
" This module executes test cases and produces results. Tests run in isolated\n"
" BEAM processes by default, providing:\n"
"\n"
" - **Crash isolation** - A panicking test doesn't crash the runner\n"
" - **Timeout protection** - Hanging tests are killed after a timeout\n"
" - **Parallel execution** - Tests run concurrently for speed\n"
"\n"
" ## Basic Usage\n"
"\n"
" ```gleam\n"
" import dream_test/unit.{describe, it, to_test_cases}\n"
" import dream_test/runner.{run_all}\n"
" import dream_test/reporter/bdd.{report}\n"
" import gleam/io\n"
"\n"
" pub fn main() {\n"
" tests()\n"
" |> to_test_cases(\"my_test\")\n"
" |> run_all()\n"
" |> report(io.print)\n"
" }\n"
" ```\n"
"\n"
" ## Execution Modes\n"
"\n"
" | Function | Isolation | Parallel | Timeout | Use Case |\n"
" |------------------------|-----------|----------|---------|------------------------------|\n"
" | `run_all` | ✓ | ✓ (4) | 5s | Default for most tests |\n"
" | `run_all_with_config` | ✓ | Custom | Custom | Custom concurrency/timeout |\n"
" | `run_all_sequential` | ✗ | ✗ | ✗ | Debugging, simple tests |\n"
"\n"
" ## Custom Configuration\n"
"\n"
" ```gleam\n"
" import dream_test/runner.{run_all_with_config, RunnerConfig}\n"
"\n"
" // High concurrency for fast tests\n"
" let fast_config = RunnerConfig(\n"
" max_concurrency: 16,\n"
" default_timeout_ms: 1000,\n"
" )\n"
"\n"
" // Sequential with long timeout for integration tests \n"
" let integration_config = RunnerConfig(\n"
" max_concurrency: 1,\n"
" default_timeout_ms: 30_000,\n"
" )\n"
"\n"
" test_cases\n"
" |> run_all_with_config(fast_config)\n"
" |> report(io.print)\n"
" ```\n"
" tests()\n"
" |> to_test_cases(\"my_test\")\n"
" |> run_all()\n"
" |> report(io.print)\n"
" }\n"
).
-type runner_config() :: {runner_config, integer(), integer()}.
-file("src/dream_test/runner.gleam", 120).
?DOC(
" Default runner configuration.\n"
"\n"
" Returns a configuration with:\n"
" - 4 concurrent tests\n"
" - 5 second timeout per test\n"
"\n"
" This is suitable for most unit test suites.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let config = default_config()\n"
" // RunnerConfig(max_concurrency: 4, default_timeout_ms: 5000)\n"
" ```\n"
).
-spec default_config() -> runner_config().
default_config() ->
{runner_config, 4, 5000}.
-file("src/dream_test/runner.gleam", 140).
?DOC(
" Configuration for sequential execution.\n"
"\n"
" Returns a configuration with:\n"
" - 1 concurrent test (sequential)\n"
" - 5 second timeout per test\n"
"\n"
" Use this when debugging test failures or when tests must not run in parallel.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" test_cases\n"
" |> run_all_with_config(sequential_config())\n"
" |> report(io.print)\n"
" ```\n"
).
-spec sequential_config() -> runner_config().
sequential_config() ->
{runner_config, 1, 5000}.
-file("src/dream_test/runner.gleam", 173).
?DOC(
" Run all tests with custom configuration.\n"
"\n"
" Each test runs in an isolated BEAM process. If a test panics, it doesn't\n"
" affect other tests. If a test exceeds the timeout, it's killed and marked\n"
" as `TimedOut`.\n"
"\n"
" Results are returned in the same order as the input tests, regardless of\n"
" which tests finish first.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let config = RunnerConfig(max_concurrency: 8, default_timeout_ms: 10_000)\n"
"\n"
" tests()\n"
" |> to_test_cases(\"my_test\")\n"
" |> run_all_with_config(config)\n"
" |> report(io.print)\n"
" ```\n"
"\n"
" ## Parameters\n"
"\n"
" - `config` - Execution settings (concurrency, timeout)\n"
" - `test_cases` - List of test cases to run\n"
"\n"
" ## Returns\n"
"\n"
" List of `TestResult` in the same order as the input tests.\n"
).
-spec run_all_with_config(runner_config(), list(dream_test@types:test_case())) -> list(dream_test@types:test_result()).
run_all_with_config(Config, Test_cases) ->
Parallel_config = {parallel_config,
erlang:element(2, Config),
erlang:element(3, Config)},
dream_test@parallel:run_parallel(Parallel_config, Test_cases).
-file("src/dream_test/runner.gleam", 198).
?DOC(
" Run all tests with default configuration.\n"
"\n"
" This is the recommended way to run tests. Uses `default_config()`:\n"
" - 4 concurrent tests\n"
" - 5 second timeout per test\n"
" - Full process isolation\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" pub fn main() {\n"
" ```\n"
).
-spec run_all(list(dream_test@types:test_case())) -> list(dream_test@types:test_result()).
run_all(Test_cases) ->
run_all_with_config(default_config(), Test_cases).
-file("src/dream_test/runner.gleam", 284).
?DOC(
" Run a test suite with custom configuration.\n"
"\n"
" Use this when you need `before_all`/`after_all` hooks with custom\n"
" concurrency or timeout settings. For default settings, use `run_suite`.\n"
"\n"
" ## Execution Flow\n"
"\n"
" For each group in the suite:\n"
"\n"
" ```text\n"
" ┌─────────────────────────────────────────────────────────────┐\n"
" │ 1. Run before_all hooks (sequentially) │\n"
" │ └─ If any fail → mark all tests as SetupFailed, skip to 5│\n"
" │ │\n"
" │ 2. For each test: │\n"
" │ ├─ Run before_each hooks (outer → inner) │\n"
" │ ├─ Run test body │\n"
" │ └─ Run after_each hooks (inner → outer) │\n"
" │ │\n"
" │ 3. Tests run in parallel (up to max_concurrency) │\n"
" │ │\n"
" │ 4. Process nested groups (recurse) │\n"
" │ │\n"
" │ 5. Run after_all hooks (always, even on failure) │\n"
" └─────────────────────────────────────────────────────────────┘\n"
" ```\n"
"\n"
" ## Parallelism\n"
"\n"
" - Tests within a group run in parallel\n"
" - `before_all` and `after_all` are synchronization barriers\n"
" - Nested groups are processed after their parent's tests complete\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream_test/unit.{describe, it, before_all, after_all, to_test_suite}\n"
" import dream_test/runner.{run_suite_with_config, RunnerConfig}\n"
" import dream_test/reporter/bdd.{report}\n"
" import dream_test/types.{AssertionOk}\n"
" import gleam/io\n"
"\n"
" pub fn main() {\n"
" // Custom config for integration tests\n"
" let config = RunnerConfig(\n"
" max_concurrency: 2, // Limit parallelism for shared resources\n"
" default_timeout_ms: 30_000, // 30s timeout for slow operations\n"
" )\n"
"\n"
" integration_tests()\n"
" |> to_test_suite(\"integration_test\")\n"
" |> run_suite_with_config(config)\n"
" |> report(io.print)\n"
" }\n"
"\n"
" fn integration_tests() {\n"
" describe(\"Database\", [\n"
" before_all(fn() { start_database(); AssertionOk }),\n"
" before_each(fn() { begin_transaction(); AssertionOk }),\n"
"\n"
" it(\"creates users\", fn() { ... }),\n"
" it(\"queries users\", fn() { ... }),\n"
"\n"
" after_each(fn() { rollback_transaction(); AssertionOk }),\n"
" after_all(fn() { stop_database(); AssertionOk }),\n"
" ])\n"
" }\n"
" ```\n"
"\n"
" ## Parameters\n"
"\n"
" - `config` - Execution settings (concurrency, timeout)\n"
" - `suite` - The test suite from `to_test_suite`\n"
"\n"
" ## Returns\n"
"\n"
" List of `TestResult` for all tests in the suite.\n"
).
-spec run_suite_with_config(runner_config(), dream_test@types:test_suite()) -> list(dream_test@types:test_result()).
run_suite_with_config(Config, Suite) ->
Parallel_config = {parallel_config,
erlang:element(2, Config),
erlang:element(3, Config)},
dream_test@parallel:run_suite_parallel(Parallel_config, Suite).
-file("src/dream_test/runner.gleam", 367).
?DOC(
" Run a test suite with default configuration.\n"
"\n"
" This is the recommended way to run tests when you need `before_all`\n"
" or `after_all` hooks. It uses sensible defaults suitable for most\n"
" integration test suites.\n"
"\n"
" ## Default Configuration\n"
"\n"
" | Setting | Value | Meaning |\n"
" |--------------------|---------|--------------------------------------|\n"
" | `max_concurrency` | 4 | Up to 4 tests run in parallel |\n"
" | `default_timeout` | 5000ms | Each test has 5 seconds to complete |\n"
"\n"
" For custom settings, use `run_suite_with_config`.\n"
"\n"
" ## When to Use `run_suite` vs `run_all`\n"
"\n"
" ```gleam\n"
" // Use run_all when you DON'T need before_all/after_all\n"
" tests()\n"
" |> to_test_cases(\"my_test\")\n"
" |> run_all()\n"
"\n"
" // Use run_suite when you DO need before_all/after_all\n"
" tests()\n"
" |> to_test_suite(\"my_test\")\n"
" |> run_suite()\n"
" ```\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream_test/unit.{describe, it, before_all, after_all, to_test_suite}\n"
" import dream_test/runner.{run_suite}\n"
" import dream_test/reporter/bdd.{report}\n"
" import dream_test/types.{AssertionOk}\n"
" import gleam/io\n"
"\n"
" pub fn main() {\n"
" tests()\n"
" |> to_test_suite(\"my_integration_test\")\n"
" |> run_suite()\n"
" |> report(io.print)\n"
" }\n"
"\n"
" fn tests() {\n"
" describe(\"API client\", [\n"
" before_all(fn() {\n"
" start_mock_server()\n"
" AssertionOk\n"
" }),\n"
"\n"
" it(\"fetches data\", fn() { ... }),\n"
" it(\"handles errors\", fn() { ... }),\n"
"\n"
" after_all(fn() {\n"
" stop_mock_server()\n"
" AssertionOk\n"
" }),\n"
" ])\n"
" }\n"
" ```\n"
"\n"
" ## Parameters\n"
"\n"
" - `suite` - The test suite from `to_test_suite`\n"
"\n"
" ## Returns\n"
"\n"
" List of `TestResult` for all tests in the suite.\n"
).
-spec run_suite(dream_test@types:test_suite()) -> list(dream_test@types:test_result()).
run_suite(Suite) ->
run_suite_with_config(default_config(), Suite).
-file("src/dream_test/runner.gleam", 414).
?DOC(
" Run a single test directly without isolation.\n"
"\n"
" Executes the test function and returns a `TestResult`. No process isolation\n"
" or timeout protection. Useful for debugging individual tests.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let config = SingleTestConfig(\n"
" name: \"my test\",\n"
" full_name: [\"suite\", \"my test\"],\n"
" tags: [],\n"
" kind: Unit,\n"
" run: fn() { ... },\n"
" timeout_ms: None,\n"
" )\n"
"\n"
" let result = run_single_test(config)\n"
" ```\n"
).
-spec run_single_test(dream_test@types:single_test_config()) -> dream_test@types:test_result().
run_single_test(Config) ->
Assertion_result = (erlang:element(6, Config))(),
Failures = case Assertion_result of
assertion_ok ->
[];
{assertion_failed, Failure} ->
[Failure]
end,
Status = dream_test@types:status_from_failures(Failures),
{test_result,
erlang:element(2, Config),
erlang:element(3, Config),
Status,
0,
erlang:element(4, Config),
Failures,
erlang:element(5, Config)}.
-file("src/dream_test/runner.gleam", 439).
?DOC(
" Run a single test case directly without isolation.\n"
"\n"
" Convenience wrapper around `run_single_test` that unwraps the `TestCase`.\n"
).
-spec run_test_case(dream_test@types:test_case()) -> dream_test@types:test_result().
run_test_case(Test_case) ->
case Test_case of
{test_case, Config} ->
run_single_test(Config)
end.
-file("src/dream_test/runner.gleam", 445).
-spec run_all_from_list(
list(dream_test@types:test_case()),
list(dream_test@types:test_result())
) -> list(dream_test@types:test_result()).
run_all_from_list(Remaining, Accumulated) ->
case Remaining of
[] ->
lists:reverse(Accumulated);
[Head | Tail] ->
Result = run_test_case(Head),
run_all_from_list(Tail, [Result | Accumulated])
end.
-file("src/dream_test/runner.gleam", 390).
?DOC(
" Run all tests sequentially without process isolation.\n"
"\n"
" Tests run one at a time in the main process. No timeout protection.\n"
" Use this for:\n"
" - Debugging test failures\n"
" - When process isolation causes issues\n"
" - Very simple test suites\n"
"\n"
" **Warning:** A crashing test will crash the entire test run.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" // For debugging a specific failure\n"
" test_cases\n"
" |> run_all_sequential()\n"
" |> report(io.print)\n"
" ```\n"
).
-spec run_all_sequential(list(dream_test@types:test_case())) -> list(dream_test@types:test_result()).
run_all_sequential(Test_cases) ->
run_all_from_list(Test_cases, []).