-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, []).