A test "name": block is a function with no parameters that passes when it
finishes and fails when it panics. Put tests in the same file as the code they
test, and run them all with brine test. Each test runs in its own process, in
parallel with the others.
fn mean(xs: [float]) -> float: return sum(x for x in xs) / float(len(xs))
fn main(): print(mean([1.0, 2.0, 6.0]))
test "mean of three numbers": assert mean([1.0, 2.0, 6.0]) == 3.0
test "mean of one number": assert mean([4.5]) == 4.5brine test stats.br Built tests [dev] in 0.09stest mean of three numbers ... oktest mean of one number ... ok
test result: ok. 2 passed; 0 failed; finished in 0.02sWriting tests
Section titled “Writing tests”test "name":goes at the top level of any.brfile, next to the functions it tests. The name is any string.- The body is ordinary code. It can call any function in the file or in imported
modules, use
vars, loops andpar, and read files. assert condpanics whencondis false.assert cond, messageadds a message (anystr, often an f-string).- Any panic fails the test: a failed
assert, an out-of-range index, a missing map key, anunwrap()ofNone, orpanic(...). - Tests aren’t part of the program:
brine runandbrine buildleave them out.
assert is not only for tests. In normal code it’s a runtime check that always
runs, and the prover treats the condition as a known fact after it. See
contracts.
Failing tests
Section titled “Failing tests”fn mean(xs: [float]) -> float: return sum(x for x in xs) / float(len(xs))
fn main(): print(mean([1.0, 2.0, 6.0]))
test "mean of three numbers": assert mean([1.0, 2.0, 6.0]) == 3.0
test "mean of one number": assert mean([4.5]) == 4.5
test "mean of nothing": let empty: [float] = [] print("about to divide by", len(empty)) let m = mean(empty) assert m == 0.0, f"mean([]) was {m}" Built tests [dev] in 0.09stest mean of three numbers ... oktest mean of one number ... oktest mean of nothing ... FAILED
---- mean of nothing ----about to divide by 0
panic: assertion failed: m == 0.0 message: mean([]) was nan --> stats.br:17:5
test result: FAILED. 2 passed; 1 failed; finished in 0.02s- What a test prints is captured. It’s shown only when the test fails, in that test’s section, followed by the panic message, the failed condition and its location.
brine testexits with 0 when every test passes and 1 when any fails.- Results are listed in the order the tests appear, whichever finishes first.
Running tests
Section titled “Running tests”brine test # every test in the projectbrine test "mean" # only tests whose name contains "mean"brine test stats.br # the tests in one file (and the modules it imports)brine test stats.br "nothing" # one file, filteredbrine test --release # build the tests with optimizationsbrine test --json # one JSON object per test- In a project,
brine testbuildssrc/main.brand every module it imports, and runs all their tests. A module’s tests are named with the module first:geometry: a 2 by 3 rectangle. See modules. - The filter is a case-sensitive substring of the test name (including the
module:part). When nothing matches,brine testsays so and exits with 0. - Tests run in parallel, as many at a time as you have cores. Each one is a
separate process, so a panic in one test can’t affect another. Each test
process runs
parcode on 2 threads.
Tests that run the compiler
Section titled “Tests that run the compiler”While tests run, the environment variable BRINE holds the path of the brine
that is running them. A test can use it to check that a program compiles, or
that it doesn’t:
# Check that a program with a known mistake is rejected with the right error.
fn check_output(source: str) -> Result[str, str]: let path = os.temp_dir() + "/brine-test-" + str(os.pid()) + ".br" write_file(path, source)? let cmd = [getenv("BRINE").unwrap(), "check", path, "--color", "never"] let p = os.spawn(cmd, {}, true, false)? os.wait(p) let (out, err) = os.output(p) os.remove_file(path)? return Ok(out + err)
test "reading and changing in one expression is rejected": let src = "fn main():\n var xs = [1, 2]\n print(xs.pop(), xs)\n" let out = check_output(src).unwrap() assert "error[E0405]" in out, out Built tests [dev] in 0.10stest reading and changing in one expression is rejected ... ok
test result: ok. 1 passed; 0 failed; finished in 0.04sThe file name includes os.pid() because tests run at the same time; each test
process has its own id. See the os module for os.spawn.
JSON output
Section titled “JSON output”With --json, brine test prints one JSON object per test, in order, and nothing
else:
{"test":"mean of three numbers","ok":true,"seconds":0.022,"output":""}{"test":"mean of one number","ok":true,"seconds":0.026,"output":""}{"test":"mean of nothing","ok":false,"seconds":0.029,"output":"about to divide by 0\n\npanic: assertion failed: m == 0.0\n message: mean([]) was nan\n --> stats.br:17:5\n"}| Field | Type | Meaning |
|---|---|---|
test |
string | the test’s name |
ok |
bool | whether it passed |
seconds |
number | how long its process ran |
output |
string | everything it printed to stdout and stderr, including the panic message |
The exit code is the same as without --json. Agents and CI scripts can read the
lines one at a time. See the CLI.
- Tests live next to the code, in
test "name":blocks. - A test fails if it panics;
assert cond, messageis the usual way. brine test [file] [filter]runs every matching test, each in its own process, in parallel.- Output is shown only for failing tests (and always in
--json). $BRINEis the compiler running the tests.
Related: modules, error handling, built-in functions, the CLI.