dream_test/sandbox
Process isolation for test execution (sandboxing).
This module provides the core mechanism for running a function in an isolated BEAM process with timeout support. It is used internally by the runner and is also useful directly in tests that need strong crash/timeout boundaries.
What problem does this solve?
Sometimes you want to run code that might:
panic- hang (never return)
- take “too long” and should be timed out
run_isolated runs the function in a separate BEAM process, so the caller
stays healthy even if the function crashes.
Selective crash reports
When a test process crashes (e.g. panic as "boom"), Erlang can print a
crash report (=CRASH REPORT==== ...). This is useful when debugging, but
noisy during normal runs.
SandboxConfig.show_crash_reports lets you choose:
False(default): suppress crash reports and returnSandboxCrashed(...)True: allow crash reports to print, and still returnSandboxCrashed(...)
Example
import dream_test/matchers.{be_equal, or_fail_with, should}
import dream_test/sandbox.{
SandboxCompleted, SandboxConfig, SandboxCrashed, SandboxTimedOut,
}
import dream_test/unit.{describe, it}
fn loop_forever() {
loop_forever()
}
pub fn tests() {
describe("Sandboxing", [
it("run_isolated returns SandboxCompleted(value) on success", fn() {
let config = SandboxConfig(timeout_ms: 100, show_crash_reports: False)
let result = sandbox.run_isolated(config, fn() { 123 })
result
|> should
|> be_equal(SandboxCompleted(123))
|> or_fail_with("expected SandboxCompleted(123)")
}),
it(
"run_isolated returns SandboxTimedOut when the function is too slow",
fn() {
let config = SandboxConfig(timeout_ms: 10, show_crash_reports: False)
let result = sandbox.run_isolated(config, loop_forever)
result
|> should
|> be_equal(SandboxTimedOut)
|> or_fail_with("expected SandboxTimedOut")
},
),
it("run_isolated returns SandboxCrashed when the function panics", fn() {
let config = SandboxConfig(timeout_ms: 100, show_crash_reports: False)
let result = sandbox.run_isolated(config, fn() { panic as "boom" })
let did_crash = case result {
SandboxCrashed(_) -> True
_ -> False
}
did_crash
|> should
|> be_equal(True)
|> or_fail_with("expected SandboxCrashed(...)")
}),
])
}
Types
Configuration for sandboxed test execution.
timeout_ms: how long to wait for the worker to finish before killing itshow_crash_reports: whether to allow the BEAM to print crash reports
Example
import dream_test/sandbox.{SandboxConfig}
let config = SandboxConfig(timeout_ms: 1_000, show_crash_reports: False)
Parameters
timeout_ms: how long to wait before returningSandboxTimedOutshow_crash_reports: whether to allow the BEAM to print crash reports
Returns
A SandboxConfig value.
pub type SandboxConfig {
SandboxConfig(timeout_ms: Int, show_crash_reports: Bool)
}
Constructors
-
SandboxConfig(timeout_ms: Int, show_crash_reports: Bool)
Result of running a function in an isolated sandbox.
This is intentionally simple so it can be used in higher-level code (like a runner) without pulling in reporter concerns.
Variants
SandboxCompleted(value): the function completed successfully and returnedvalueSandboxTimedOut: the function did not finish withinSandboxConfig.timeout_msSandboxCrashed(reason): the function crashed;reasonis a best-effort description
pub type SandboxResult(a) {
SandboxCompleted(a)
SandboxTimedOut
SandboxCrashed(reason: String)
}
Constructors
-
SandboxCompleted(a)Function completed successfully and returned a value.
-
SandboxTimedOutTest did not complete within the timeout period.
-
SandboxCrashed(reason: String)Test process crashed with the given reason.
Values
pub fn default_config() -> SandboxConfig
Default configuration with a 5 second timeout.
Uses show_crash_reports: False.
Returns
A SandboxConfig suitable for most tests.
Parameters
None.
pub fn run_isolated(
config config: SandboxConfig,
test_function test_function: fn() -> a,
) -> SandboxResult(a)
Run a test function in an isolated process with timeout.
The test function runs in a separate BEAM process that is monitored. If the process completes normally, its result is returned. If the process crashes or times out, an appropriate SandboxResult is returned.
Example
import dream_test/matchers.{be_equal, or_fail_with, should}
import dream_test/sandbox.{
SandboxCompleted, SandboxConfig, SandboxCrashed, SandboxTimedOut,
}
import dream_test/unit.{describe, it}
fn loop_forever() {
loop_forever()
}
pub fn tests() {
describe("Sandboxing", [
it("run_isolated returns SandboxCompleted(value) on success", fn() {
let config = SandboxConfig(timeout_ms: 100, show_crash_reports: False)
let result = sandbox.run_isolated(config, fn() { 123 })
result
|> should
|> be_equal(SandboxCompleted(123))
|> or_fail_with("expected SandboxCompleted(123)")
}),
it(
"run_isolated returns SandboxTimedOut when the function is too slow",
fn() {
let config = SandboxConfig(timeout_ms: 10, show_crash_reports: False)
let result = sandbox.run_isolated(config, loop_forever)
result
|> should
|> be_equal(SandboxTimedOut)
|> or_fail_with("expected SandboxTimedOut")
},
),
it("run_isolated returns SandboxCrashed when the function panics", fn() {
let config = SandboxConfig(timeout_ms: 100, show_crash_reports: False)
let result = sandbox.run_isolated(config, fn() { panic as "boom" })
let did_crash = case result {
SandboxCrashed(_) -> True
_ -> False
}
did_crash
|> should
|> be_equal(True)
|> or_fail_with("expected SandboxCrashed(...)")
}),
])
}
Parameters
config: sandbox configuration (timeout + crash report behavior)test_function: function to run in an isolated process
Returns
A SandboxResult(a) describing completion, timeout, or crash.
pub fn with_crash_reports(
config config: SandboxConfig,
) -> SandboxConfig
Convenience helper for enabling crash reports in the sandbox.
This is useful when debugging failures locally.
Example
let config = sandbox.default_config() |> sandbox.with_crash_reports()
Parameters
config: an existingSandboxConfig
Returns
A new SandboxConfig with show_crash_reports: True.