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([max_concurrency/2, default_timeout_ms/2, exit_on_failure/1, progress_reporter/2, results_reporters/2, output/2, filter_tests/2, silent/1, new/1, add_suites/2, add_suites_with_config/3, has_failures/1, run/1]).
-export_type([test_info/0, run_builder/1, suite_run/1, output/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 provides a **pipe-friendly builder API** for running suites and\n"
" collecting `dream_test/types.TestResult` values.\n"
"\n"
" ## When should I use this?\n"
"\n"
" - Always: the runner is how you execute suites built with `dream_test/unit`,\n"
" `dream_test/unit_context`, or `dream_test/gherkin/feature`.\n"
"\n"
" ## What does the runner do?\n"
"\n"
" - Runs groups sequentially, tests in parallel (bounded by `max_concurrency`)\n"
" - Sandboxes tests and hooks (timeouts + crash isolation)\n"
" - Optionally drives an event-based reporter\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream_test/matchers.{be_equal, or_fail_with, should}\n"
" import dream_test/parallel\n"
" import dream_test/reporters/bdd\n"
" import dream_test/reporters/progress\n"
" import dream_test/runner\n"
" import dream_test/unit.{describe, it}\n"
"\n"
" pub fn tests() {\n"
" describe(\"Example\", [\n"
" it(\"works\", fn() {\n"
" 1 + 1\n"
" |> should\n"
" |> be_equal(2)\n"
" |> or_fail_with(\"math should work\")\n"
" }),\n"
" ])\n"
" }\n"
"\n"
" pub fn main() {\n"
" let db_config =\n"
" parallel.ParallelConfig(max_concurrency: 1, default_timeout_ms: 60_000)\n"
"\n"
" runner.new([])\n"
" |> runner.add_suites([tests()])\n"
" |> runner.add_suites_with_config(db_config, [db_tests()])\n"
" |> runner.progress_reporter(progress.new())\n"
" |> runner.results_reporters([bdd.new()])\n"
" |> runner.exit_on_failure()\n"
" |> runner.run()\n"
" }\n"
" ```\n"
).
-type test_info() :: {test_info,
binary(),
list(binary()),
list(binary()),
dream_test@types:test_kind()}.
-opaque run_builder(JSL) :: {run_builder,
list(suite_run(JSL)),
dream_test@parallel:parallel_config(),
gleam@option:option(fun((test_info()) -> boolean())),
boolean(),
gleam@option:option(dream_test@reporters@progress:progress_reporter()),
list(dream_test@reporters@types:results_reporter()),
gleam@option:option(output())}.
-type suite_run(JSM) :: {suite_run,
dream_test@types:root(JSM),
gleam@option:option(dream_test@parallel:parallel_config())}.
-type output() :: {output, fun((binary()) -> nil), fun((binary()) -> nil)}.
-file("src/dream_test/runner.gleam", 350).
?DOC(
" Set the maximum number of concurrently running tests.\n"
"\n"
" - `1` gives fully sequential test execution.\n"
" - Higher values increase parallelism.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream_test/matchers.{be_equal, or_fail_with, should}\n"
" import dream_test/reporters/bdd\n"
" import dream_test/reporters/progress\n"
" import dream_test/runner\n"
" import dream_test/unit.{describe, it}\n"
" import gleam/io\n"
"\n"
" pub fn tests() {\n"
" describe(\"Sequential tests\", [\n"
" it(\"first test\", fn() {\n"
" // When tests share external resources, run them sequentially\n"
" 1 + 1\n"
" |> should\n"
" |> be_equal(2)\n"
" |> or_fail_with(\"Math works\")\n"
" }),\n"
" it(\"second test\", fn() {\n"
" 2 + 2\n"
" |> should\n"
" |> be_equal(4)\n"
" |> or_fail_with(\"Math still works\")\n"
" }),\n"
" ])\n"
" }\n"
"\n"
" pub fn main() {\n"
" // Sequential execution for tests with shared state\n"
" runner.new([tests()])\n"
" |> runner.max_concurrency(1)\n"
" |> runner.default_timeout_ms(30_000)\n"
" |> runner.progress_reporter(progress.new())\n"
" |> runner.results_reporters([bdd.new()])\n"
" |> runner.exit_on_failure()\n"
" |> runner.run()\n"
" }\n"
" ```\n"
"\n"
" ## Parameters\n"
"\n"
" - `builder`: the runner builder you’re configuring\n"
" - `max`: maximum number of concurrently running tests (use `1` for fully sequential)\n"
"\n"
" ## Returns\n"
"\n"
" The updated `RunBuilder(ctx)`.\n"
).
-spec max_concurrency(run_builder(JTB), integer()) -> run_builder(JTB).
max_concurrency(Builder, Max) ->
{parallel_config, _, Timeout} = erlang:element(3, Builder),
{run_builder,
erlang:element(2, Builder),
{parallel_config, Max, Timeout},
erlang:element(4, Builder),
erlang:element(5, Builder),
erlang:element(6, Builder),
erlang:element(7, Builder),
erlang:element(8, Builder)}.
-file("src/dream_test/runner.gleam", 409).
?DOC(
" Set the default timeout (milliseconds) applied to tests without an explicit timeout.\n"
"\n"
" Tests that exceed the timeout are killed and reported as `TimedOut`.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream_test/matchers.{be_equal, or_fail_with, should}\n"
" import dream_test/reporters/bdd\n"
" import dream_test/reporters/progress\n"
" import dream_test/runner\n"
" import dream_test/unit.{describe, it}\n"
" import gleam/io\n"
"\n"
" pub fn tests() {\n"
" describe(\"Runner config demo\", [\n"
" it(\"runs with custom config\", fn() {\n"
" 1 + 1\n"
" |> should\n"
" |> be_equal(2)\n"
" |> or_fail_with(\"Math works\")\n"
" }),\n"
" ])\n"
" }\n"
"\n"
" pub fn main() {\n"
" runner.new([tests()])\n"
" |> runner.max_concurrency(8)\n"
" |> runner.default_timeout_ms(10_000)\n"
" |> runner.progress_reporter(progress.new())\n"
" |> runner.results_reporters([bdd.new()])\n"
" |> runner.exit_on_failure()\n"
" |> runner.run()\n"
" }\n"
" ```\n"
"\n"
" ## Parameters\n"
"\n"
" - `builder`: the runner builder you’re configuring\n"
" - `timeout_ms`: timeout in milliseconds applied to tests without an explicit timeout\n"
"\n"
" ## Returns\n"
"\n"
" The updated `RunBuilder(ctx)`.\n"
).
-spec default_timeout_ms(run_builder(JTE), integer()) -> run_builder(JTE).
default_timeout_ms(Builder, Timeout_ms) ->
{parallel_config, Max, _} = erlang:element(3, Builder),
{run_builder,
erlang:element(2, Builder),
{parallel_config, Max, Timeout_ms},
erlang:element(4, Builder),
erlang:element(5, Builder),
erlang:element(6, Builder),
erlang:element(7, Builder),
erlang:element(8, Builder)}.
-file("src/dream_test/runner.gleam", 465).
?DOC(
" Exit the BEAM with a non-zero code if any tests fail.\n"
"\n"
" Useful for CI pipelines.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream_test/matchers.{be_equal, or_fail_with, should}\n"
" import dream_test/reporters/bdd\n"
" import dream_test/reporters/progress\n"
" import dream_test/runner\n"
" import dream_test/unit.{describe, it}\n"
" import gleam/io\n"
"\n"
" pub fn tests() {\n"
" describe(\"Example\", [\n"
" it(\"works\", fn() {\n"
" 1 + 1\n"
" |> should\n"
" |> be_equal(2)\n"
" |> or_fail_with(\"math should work\")\n"
" }),\n"
" ])\n"
" }\n"
"\n"
" pub fn main() {\n"
" runner.new([tests()])\n"
" |> runner.progress_reporter(progress.new())\n"
" |> runner.results_reporters([bdd.new()])\n"
" |> runner.exit_on_failure()\n"
" |> runner.run()\n"
" }\n"
" ```\n"
"\n"
" ## Parameters\n"
"\n"
" - `builder`: the runner builder you’re configuring\n"
"\n"
" ## Returns\n"
"\n"
" The updated `RunBuilder(ctx)`.\n"
).
-spec exit_on_failure(run_builder(JTH)) -> run_builder(JTH).
exit_on_failure(Builder) ->
{run_builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
erlang:element(4, Builder),
true,
erlang:element(6, Builder),
erlang:element(7, Builder),
erlang:element(8, Builder)}.
-file("src/dream_test/runner.gleam", 473).
?DOC(
" Attach a progress reporter (live output during the run).\n"
"\n"
" This reporter is driven by `TestFinished` events in completion order.\n"
" It is intended for a single in-place progress bar UI.\n"
).
-spec progress_reporter(
run_builder(JTK),
dream_test@reporters@progress:progress_reporter()
) -> run_builder(JTK).
progress_reporter(Builder, Reporter) ->
{run_builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
erlang:element(4, Builder),
erlang:element(5, Builder),
{some, Reporter},
erlang:element(7, Builder),
erlang:element(8, Builder)}.
-file("src/dream_test/runner.gleam", 484).
?DOC(
" Attach results reporters (printed at the end, in the order provided).\n"
"\n"
" Results reporters receive the full traversal-ordered results list from the\n"
" `RunFinished` event, so their output is deterministic under parallel execution.\n"
).
-spec results_reporters(
run_builder(JTN),
list(dream_test@reporters@types:results_reporter())
) -> run_builder(JTN).
results_reporters(Builder, Reporters) ->
{run_builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
erlang:element(4, Builder),
erlang:element(5, Builder),
erlang:element(6, Builder),
Reporters,
erlang:element(8, Builder)}.
-file("src/dream_test/runner.gleam", 508).
?DOC(
" Configure output sinks for `runner.run()`.\n"
"\n"
" This is how you route reporter output (stdout vs stderr, capturing output in\n"
" tests, etc).\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream_test/runner\n"
" import gleam/io\n"
"\n"
" pub fn main() {\n"
" runner.new([tests()])\n"
" |> runner.output(runner.Output(out: io.print, error: io.eprint))\n"
" |> runner.run()\n"
" }\n"
" ```\n"
).
-spec output(run_builder(JTR), output()) -> run_builder(JTR).
output(Builder, Output) ->
{run_builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
erlang:element(4, Builder),
erlang:element(5, Builder),
erlang:element(6, Builder),
erlang:element(7, Builder),
{some, Output}}.
-file("src/dream_test/runner.gleam", 575).
?DOC(
" Filter which tests are executed.\n"
"\n"
" The predicate receives `TestInfo` (name, full name, effective tags, kind).\n"
" Tags include inherited group tags.\n"
"\n"
" Groups with no selected tests in their entire subtree are skipped entirely,\n"
" including hooks.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream_test/matchers.{be_equal, or_fail_with, should, succeed}\n"
" import dream_test/reporters/bdd\n"
" import dream_test/reporters/progress\n"
" import dream_test/runner.{type TestInfo}\n"
" import dream_test/unit.{describe, it, with_tags}\n"
" import gleam/io\n"
" import gleam/list\n"
"\n"
" pub fn tests() {\n"
" describe(\"Filtering tests\", [\n"
" it(\"smoke\", fn() {\n"
" 1 + 1\n"
" |> should\n"
" |> be_equal(2)\n"
" |> or_fail_with(\"math should work\")\n"
" })\n"
" |> with_tags([\"smoke\"]),\n"
" it(\"slow\", fn() { Ok(succeed()) })\n"
" |> with_tags([\"slow\"]),\n"
" ])\n"
" }\n"
"\n"
" pub fn only_smoke(info: TestInfo) -> Bool {\n"
" list.contains(info.tags, \"smoke\")\n"
" }\n"
"\n"
" pub fn main() {\n"
" runner.new([tests()])\n"
" |> runner.filter_tests(only_smoke)\n"
" |> runner.progress_reporter(progress.new())\n"
" |> runner.results_reporters([bdd.new()])\n"
" |> runner.exit_on_failure()\n"
" |> runner.run()\n"
" }\n"
" ```\n"
"\n"
" ## Parameters\n"
"\n"
" - `builder`: the runner builder you’re configuring\n"
" - `predicate`: function that decides whether a test should run\n"
"\n"
" ## Returns\n"
"\n"
" The updated `RunBuilder(ctx)`.\n"
).
-spec filter_tests(run_builder(JTX), fun((test_info()) -> boolean())) -> run_builder(JTX).
filter_tests(Builder, Predicate) ->
{run_builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
{some, Predicate},
erlang:element(5, Builder),
erlang:element(6, Builder),
erlang:element(7, Builder),
erlang:element(8, Builder)}.
-file("src/dream_test/runner.gleam", 677).
-spec default_output() -> output().
default_output() ->
{output, fun gleam_stdlib:print/1, fun gleam_stdlib:print_error/1}.
-file("src/dream_test/runner.gleam", 685).
-spec discard_output(binary()) -> nil.
discard_output(_) ->
nil.
-file("src/dream_test/runner.gleam", 681).
-spec silent_output() -> output().
silent_output() ->
{output, fun discard_output/1, fun discard_output/1}.
-file("src/dream_test/runner.gleam", 516).
?DOC(" Disable all reporter output (still returns results from `runner.run()`).\n").
-spec silent(run_builder(JTU)) -> run_builder(JTU).
silent(Builder) ->
{run_builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
erlang:element(4, Builder),
erlang:element(5, Builder),
erlang:element(6, Builder),
erlang:element(7, Builder),
{some, silent_output()}}.
-file("src/dream_test/runner.gleam", 874).
-spec suite_run_config(
dream_test@parallel:parallel_config(),
gleam@option:option(dream_test@parallel:parallel_config())
) -> dream_test@parallel:parallel_config().
suite_run_config(Default_config, Override) ->
case Override of
none ->
Default_config;
{some, Config} ->
Config
end.
-file("src/dream_test/runner.gleam", 917).
-spec render_results_reporter(
dream_test@reporters@types:results_reporter(),
list(dream_test@types:test_result())
) -> binary().
render_results_reporter(Reporter, Results) ->
case Reporter of
{bdd, Config} ->
dream_test@reporters@bdd:render(Config, Results);
{json, Config@1} ->
dream_test@reporters@json:render(Config@1, Results)
end.
-file("src/dream_test/runner.gleam", 903).
-spec write_results_reporters_loop(
list(dream_test@reporters@types:results_reporter()),
list(dream_test@types:test_result()),
fun((binary()) -> nil)
) -> nil.
write_results_reporters_loop(Reporters, Results, Write) ->
case Reporters of
[] ->
nil;
[Reporter | Rest] ->
Write(render_results_reporter(Reporter, Results)),
write_results_reporters_loop(Rest, Results, Write)
end.
-file("src/dream_test/runner.gleam", 927).
-spec output_out(output()) -> fun((binary()) -> nil).
output_out(Output) ->
{output, Out, _} = Output,
Out.
-file("src/dream_test/runner.gleam", 831).
-spec run_suites_with_progress(
list(suite_run(any())),
dream_test@parallel:parallel_config(),
gleam@option:option(dream_test@reporters@progress:progress_reporter()),
output(),
integer(),
integer(),
list(dream_test@types:test_result())
) -> {list(dream_test@types:test_result()), integer()}.
run_suites_with_progress(
Suite_runs,
Default_config,
Progress_reporter,
Output,
Total,
Completed,
Acc
) ->
case Suite_runs of
[] ->
{Acc, Completed};
[{suite_run, Suite, Override} | Rest] ->
Suite_config = suite_run_config(Default_config, Override),
Parallel_result = dream_test@parallel:run_root_parallel_with_reporter(
{run_root_parallel_with_reporter_config,
Suite_config,
Suite,
Progress_reporter,
output_out(Output),
Total,
Completed}
),
{run_root_parallel_with_reporter_result,
Results,
Completed_after_suite,
Next_progress_reporter} = Parallel_result,
run_suites_with_progress(
Rest,
Default_config,
Next_progress_reporter,
Output,
Total,
Completed_after_suite,
lists:append(Acc, Results)
)
end.
-file("src/dream_test/runner.gleam", 884).
-spec write_progress_event(
dream_test@reporters@progress:progress_reporter(),
dream_test@reporters@types:reporter_event(),
output()
) -> nil.
write_progress_event(Reporter, Event, Output) ->
case dream_test@reporters@progress:handle_event(Reporter, Event) of
none ->
nil;
{some, Text} ->
(output_out(Output))(Text)
end.
-file("src/dream_test/runner.gleam", 895).
-spec write_results_reporters(
list(dream_test@reporters@types:results_reporter()),
list(dream_test@types:test_result()),
output()
) -> nil.
write_results_reporters(Reporters, Results, Output) ->
write_results_reporters_loop(Reporters, Results, output_out(Output)).
-file("src/dream_test/runner.gleam", 964).
-spec suites_to_suite_runs_default(
list(dream_test@types:root(JWH)),
list(suite_run(JWH))
) -> list(suite_run(JWH)).
suites_to_suite_runs_default(Suites, Acc_rev) ->
case Suites of
[] ->
lists:reverse(Acc_rev);
[Suite | Rest] ->
suites_to_suite_runs_default(
Rest,
[{suite_run, Suite, none} | Acc_rev]
)
end.
-file("src/dream_test/runner.gleam", 226).
?DOC(
" Create a new runner builder for a list of suites.\n"
"\n"
" The type parameter `ctx` is the suite context type. For `dream_test/unit`\n"
" suites this is `Nil`. For `dream_test/unit_context` suites it is your custom\n"
" context type.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream_test/matchers.{be_equal, or_fail_with, should}\n"
" import dream_test/reporters/bdd\n"
" import dream_test/reporters/progress\n"
" import dream_test/runner\n"
" import dream_test/unit.{describe, it}\n"
" import gleam/io\n"
"\n"
" pub fn tests() {\n"
" describe(\"Example\", [\n"
" it(\"works\", fn() {\n"
" 1 + 1\n"
" |> should\n"
" |> be_equal(2)\n"
" |> or_fail_with(\"math should work\")\n"
" }),\n"
" ])\n"
" }\n"
"\n"
" pub fn main() {\n"
" runner.new([tests()])\n"
" |> runner.progress_reporter(progress.new())\n"
" |> runner.results_reporters([bdd.new()])\n"
" |> runner.exit_on_failure()\n"
" |> runner.run()\n"
" }\n"
" ```\n"
"\n"
" ## Parameters\n"
"\n"
" - `suites`: the test suites you want to run (often just `[tests()]`)\n"
"\n"
" ## Returns\n"
"\n"
" A `RunBuilder(ctx)` you can pipe through configuration helpers and finally\n"
" `runner.run()`.\n"
).
-spec new(list(dream_test@types:root(JSN))) -> run_builder(JSN).
new(Suites) ->
Config = dream_test@parallel:default_config(),
{run_builder,
suites_to_suite_runs_default(Suites, []),
Config,
none,
false,
none,
[],
none}.
-file("src/dream_test/runner.gleam", 978).
-spec suites_to_suite_runs_with_override(
dream_test@parallel:parallel_config(),
list(dream_test@types:root(JWO)),
list(suite_run(JWO))
) -> list(suite_run(JWO)).
suites_to_suite_runs_with_override(Config, Suites, Acc_rev) ->
case Suites of
[] ->
lists:reverse(Acc_rev);
[Suite | Rest] ->
suites_to_suite_runs_with_override(
Config,
Rest,
[{suite_run, Suite, {some, Config}} | Acc_rev]
)
end.
-file("src/dream_test/runner.gleam", 993).
-spec append_suite_runs_default(
list(suite_run(JWV)),
list(dream_test@types:root(JWV))
) -> list(suite_run(JWV)).
append_suite_runs_default(Existing, Suites) ->
lists:append(Existing, suites_to_suite_runs_default(Suites, [])).
-file("src/dream_test/runner.gleam", 255).
?DOC(
" Append suites to the run, using the builder’s current execution config.\n"
"\n"
" This is useful when you want to build up your suite list incrementally\n"
" (especially when some suites need a different execution config).\n"
"\n"
" Suites added with `add_suites` will run using the runner’s current execution\n"
" config (configured via `max_concurrency` / `default_timeout_ms`).\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" runner.new([])\n"
" |> runner.add_suites([unit_suite()])\n"
" |> runner.add_suites([integration_suite()])\n"
" |> runner.run()\n"
" ```\n"
).
-spec add_suites(run_builder(JSR), list(dream_test@types:root(JSR))) -> run_builder(JSR).
add_suites(Builder, Suites) ->
Appended = append_suite_runs_default(erlang:element(2, Builder), Suites),
{run_builder,
Appended,
erlang:element(3, Builder),
erlang:element(4, Builder),
erlang:element(5, Builder),
erlang:element(6, Builder),
erlang:element(7, Builder),
erlang:element(8, Builder)}.
-file("src/dream_test/runner.gleam", 1000).
-spec append_suite_runs_with_override(
list(suite_run(JXC)),
dream_test@parallel:parallel_config(),
list(dream_test@types:root(JXC))
) -> list(suite_run(JXC)).
append_suite_runs_with_override(Existing, Config, Suites) ->
lists:append(
Existing,
suites_to_suite_runs_with_override(Config, Suites, [])
).
-file("src/dream_test/runner.gleam", 287).
?DOC(
" Append suites to the run, using an explicit execution config override.\n"
"\n"
" This lets you run suites with different concurrency/timeout policies in a\n"
" single runner invocation (for example: DB suites sequential, unit suites\n"
" parallel).\n"
"\n"
" The override applies only to the suites added by this call. Reporting, output,\n"
" filtering, and exit behavior remain global runner settings.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream_test/parallel\n"
"\n"
" let db_config =\n"
" parallel.ParallelConfig(max_concurrency: 1, default_timeout_ms: 60_000)\n"
"\n"
" runner.new([])\n"
" |> runner.add_suites([unit_suite()])\n"
" |> runner.add_suites_with_config(db_config, [db_suite()])\n"
" |> runner.max_concurrency(50)\n"
" |> runner.default_timeout_ms(5_000)\n"
" |> runner.run()\n"
" ```\n"
).
-spec add_suites_with_config(
run_builder(JSW),
dream_test@parallel:parallel_config(),
list(dream_test@types:root(JSW))
) -> run_builder(JSW).
add_suites_with_config(Builder, Config, Suites) ->
Appended = append_suite_runs_with_override(
erlang:element(2, Builder),
Config,
Suites
),
{run_builder,
Appended,
erlang:element(3, Builder),
erlang:element(4, Builder),
erlang:element(5, Builder),
erlang:element(6, Builder),
erlang:element(7, Builder),
erlang:element(8, Builder)}.
-file("src/dream_test/runner.gleam", 1054).
?DOC(
" Return `True` if the list contains any failing statuses.\n"
"\n"
" This treats `Failed`, `SetupFailed`, and `TimedOut` as failures.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream_test/matchers.{be_equal, or_fail_with, should, succeed}\n"
" import dream_test/runner\n"
" import dream_test/unit.{describe, it}\n"
"\n"
" pub fn tests() {\n"
" describe(\"has_failures\", [\n"
" it(\"passes\", fn() { Ok(succeed()) }),\n"
" ])\n"
" }\n"
"\n"
" fn failing_suite() {\n"
" describe(\"failing suite\", [\n"
" it(\"fails\", fn() {\n"
" 1\n"
" |> should\n"
" |> be_equal(2)\n"
" |> or_fail_with(\"intentional failure for has_failures example\")\n"
" }),\n"
" ])\n"
" }\n"
"\n"
" pub fn main() {\n"
" let results = runner.new([failing_suite()]) |> runner.run()\n"
"\n"
" results\n"
" |> runner.has_failures()\n"
" |> should\n"
" |> be_equal(True)\n"
" |> or_fail_with(\"expected failures to be present\")\n"
" }\n"
" ```\n"
"\n"
" ## Parameters\n"
"\n"
" - `results`: list of `TestResult` values returned by `runner.run`\n"
"\n"
" ## Returns\n"
"\n"
" `True` when any result has status `Failed`, `SetupFailed`, or `TimedOut`.\n"
).
-spec has_failures(list(dream_test@types:test_result())) -> boolean().
has_failures(Results) ->
case Results of
[] ->
false;
[R | Rest] ->
case erlang:element(4, R) of
failed ->
true;
setup_failed ->
true;
timed_out ->
true;
_ ->
has_failures(Rest)
end
end.
-file("src/dream_test/runner.gleam", 689).
-spec maybe_exit_on_failure(boolean(), list(dream_test@types:test_result())) -> nil.
maybe_exit_on_failure(Should_exit, Results) ->
Should_halt = Should_exit andalso has_failures(Results),
case Should_halt of
true ->
erlang:halt(1);
false ->
nil
end.
-file("src/dream_test/runner.gleam", 952).
-spec count_tests_in_children(list(dream_test@types:node_(any())), integer()) -> integer().
count_tests_in_children(Children, Acc) ->
case Children of
[] ->
Acc;
[Child | Rest] ->
count_tests_in_children(Rest, Acc + count_tests_in_suite(Child))
end.
-file("src/dream_test/runner.gleam", 944).
-spec count_tests_in_suite(dream_test@types:node_(any())) -> integer().
count_tests_in_suite(Node) ->
case Node of
{test, _, _, _, _, _} ->
1;
{group, _, _, Children} ->
count_tests_in_children(Children, 0);
_ ->
0
end.
-file("src/dream_test/runner.gleam", 936).
-spec count_total_tests_from_list(list(suite_run(any())), integer()) -> integer().
count_total_tests_from_list(Suite_runs, Acc) ->
case Suite_runs of
[] ->
Acc;
[{suite_run, Suite, _} | Rest] ->
count_total_tests_from_list(
Rest,
Acc + count_tests_in_suite(erlang:element(3, Suite))
)
end.
-file("src/dream_test/runner.gleam", 932).
-spec count_total_tests(list(suite_run(any()))) -> integer().
count_total_tests(Suite_runs) ->
count_total_tests_from_list(Suite_runs, 0).
-file("src/dream_test/runner.gleam", 793).
-spec filter_children(
list(dream_test@types:node_(JVB)),
list(binary()),
list(binary()),
fun((test_info()) -> boolean()),
list(dream_test@types:node_(JVB)),
boolean()
) -> {list(dream_test@types:node_(JVB)), boolean()}.
filter_children(Children, Scope, Inherited_tags, Predicate, Acc_rev, Has_tests) ->
case Children of
[] ->
{lists:reverse(Acc_rev), Has_tests};
[Child | Rest] ->
{Maybe_child, Child_has_tests} = filter_node(
Child,
Scope,
Inherited_tags,
Predicate
),
Next_has_tests = Has_tests orelse Child_has_tests,
case Maybe_child of
none ->
filter_children(
Rest,
Scope,
Inherited_tags,
Predicate,
Acc_rev,
Next_has_tests
);
{some, Kept} ->
filter_children(
Rest,
Scope,
Inherited_tags,
Predicate,
[Kept | Acc_rev],
Next_has_tests
)
end
end.
-file("src/dream_test/runner.gleam", 740).
-spec filter_node(
dream_test@types:node_(JUV),
list(binary()),
list(binary()),
fun((test_info()) -> boolean())
) -> {gleam@option:option(dream_test@types:node_(JUV)), boolean()}.
filter_node(Node, Scope, Inherited_tags, Predicate) ->
case Node of
{test, Name, Tags, Kind, Run, Timeout_ms} ->
Full_name = lists:append(Scope, [Name]),
Effective_tags = lists:append(Inherited_tags, Tags),
Info = {test_info, Name, Full_name, Effective_tags, Kind},
case Predicate(Info) of
true ->
{{some, {test, Name, Tags, Kind, Run, Timeout_ms}}, true};
false ->
{none, false}
end;
{group, Name@1, Tags@1, Children} ->
Next_scope = lists:append(Scope, [Name@1]),
Next_tags = lists:append(Inherited_tags, Tags@1),
{Filtered_children, Has_tests} = filter_children(
Children,
Next_scope,
Next_tags,
Predicate,
[],
false
),
case Has_tests of
true ->
{{some, {group, Name@1, Tags@1, Filtered_children}}, true};
false ->
{none, false}
end;
{before_all, _} ->
{{some, Node}, false};
{before_each, _} ->
{{some, Node}, false};
{after_each, _} ->
{{some, Node}, false};
{after_all, _} ->
{{some, Node}, false}
end.
-file("src/dream_test/runner.gleam", 728).
-spec filter_root(dream_test@types:root(JUR), fun((test_info()) -> boolean())) -> {gleam@option:option(dream_test@types:root(JUR)),
boolean()}.
filter_root(Suite, Predicate) ->
{root, Seed, Tree} = Suite,
{Maybe_tree, Has_tests} = filter_node(Tree, [], [], Predicate),
case Maybe_tree of
none ->
{none, Has_tests};
{some, Next_tree} ->
{{some, {root, Seed, Next_tree}}, Has_tests}
end.
-file("src/dream_test/runner.gleam", 707).
-spec filter_suite_runs(
list(suite_run(JUK)),
fun((test_info()) -> boolean()),
list(suite_run(JUK))
) -> list(suite_run(JUK)).
filter_suite_runs(Suite_runs, Predicate, Acc_rev) ->
case Suite_runs of
[] ->
lists:reverse(Acc_rev);
[{suite_run, Suite, Override} | Rest] ->
{Maybe, _} = filter_root(Suite, Predicate),
case Maybe of
none ->
filter_suite_runs(Rest, Predicate, Acc_rev);
{some, Filtered} ->
filter_suite_runs(
Rest,
Predicate,
[{suite_run, Filtered, Override} | Acc_rev]
)
end
end.
-file("src/dream_test/runner.gleam", 697).
-spec apply_test_filter(
list(suite_run(JUE)),
gleam@option:option(fun((test_info()) -> boolean()))
) -> list(suite_run(JUE)).
apply_test_filter(Suite_runs, Predicate) ->
case Predicate of
none ->
Suite_runs;
{some, P} ->
filter_suite_runs(Suite_runs, P, [])
end.
-file("src/dream_test/runner.gleam", 623).
?DOC(
" Run all suites and return a list of `TestResult`.\n"
"\n"
" If a progress reporter is attached, the runner will emit progress output during\n"
" the run. Results reporters print at the end of the run.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream_test/matchers.{be_equal, or_fail_with, should}\n"
" import dream_test/reporters/bdd\n"
" import dream_test/reporters/progress\n"
" import dream_test/runner\n"
" import dream_test/unit.{describe, it}\n"
"\n"
" pub fn tests() {\n"
" describe(\"Example\", [\n"
" it(\"works\", fn() {\n"
" 1 + 1\n"
" |> should\n"
" |> be_equal(2)\n"
" |> or_fail_with(\"math should work\")\n"
" }),\n"
" ])\n"
" }\n"
"\n"
" pub fn main() {\n"
" runner.new([tests()])\n"
" |> runner.progress_reporter(progress.new())\n"
" |> runner.results_reporters([bdd.new()])\n"
" |> runner.exit_on_failure()\n"
" |> runner.run()\n"
" }\n"
" ```\n"
"\n"
" ## Parameters\n"
"\n"
" - `builder`: the fully configured runner builder\n"
"\n"
" ## Returns\n"
"\n"
" A list of `TestResult` values, in deterministic order.\n"
).
-spec run(run_builder(any())) -> list(dream_test@types:test_result()).
run(Builder) ->
Selected_runs = apply_test_filter(
erlang:element(2, Builder),
erlang:element(4, Builder)
),
Total = count_total_tests(Selected_runs),
Output@1 = case erlang:element(8, Builder) of
{some, Output} ->
Output;
none ->
default_output()
end,
Completed0 = 0,
Completed1 = case erlang:element(6, Builder) of
none ->
Completed0;
{some, Progress_reporter} ->
write_progress_event(
Progress_reporter,
{run_started, Total},
Output@1
),
Completed0
end,
{Results, Completed2} = run_suites_with_progress(
Selected_runs,
erlang:element(3, Builder),
erlang:element(6, Builder),
Output@1,
Total,
Completed1,
[]
),
case erlang:element(6, Builder) of
none ->
nil;
{some, Progress_reporter@1} ->
write_progress_event(
Progress_reporter@1,
{run_finished, Completed2, Total, Results},
Output@1
)
end,
write_results_reporters(erlang:element(7, Builder), Results, Output@1),
maybe_exit_on_failure(erlang:element(5, Builder), Results),
Results.