Skip to content

Testing

Write @test functions and run them with atoll test.

Updated View as Markdown

A test is an ordinary function marked @test. atoll test discovers them, runs each one, and reports a summary.

fn add(a: int, b: int): int {
    return a + b
}

@test
fn add_sums_two_numbers(): void {
    if add(2, 3) != 5 {
        println("expected 5")
    }
}
$ atoll test calc.at
test add_sums_two_numbers ... ok

result: 1 ok, 0 failed, 0 ignored

@test is per-function and never inherited from a containing type — methods inside a decorated struct are not tests.

How a test fails

A test fails by returning through the error channel. Any other completion is a pass. So an assertion is an if that raises a declared error:

error TestFailure {
    Mismatch { expected: int, actual: int }
}

fn add(a: int, b: int): int { return a + b }

@test
fn add_is_correct(): void ! TestFailure {
    got := add(2, 3)
    if got != 5 {
        error Mismatch { expected: 5, actual: got }
    }
}

@test
fn deliberately_fails(): void ! TestFailure {
    error Mismatch { expected: 1, actual: 2 }
}
$ atoll test calc.at
test add_is_correct ... ok
test deliberately_fails ... FAILED (tag=1 value=0)

result: 1 ok, 1 failed, 0 ignored

The runner reports the raw error tag and payload rather than a rendered message, so give variants names and fields that identify the failure on their own.

There is no assertion library in the prelude. Write the comparison you mean, and factor repeated checks into helper functions that raise on mismatch.

Skipping a test

@ignore on a @test function marks it as skipped. It is still discovered and reported, so it does not disappear silently.

@test
@ignore
fn slow_case(): void {
    println("not run by default")
}
$ atoll test --list calc.at
calc.at:5  add_sums_two_numbers
calc.at:12  slow_case (ignored)

2 test(s) discovered

Running a subset

Flag Effect
--list Print discovered tests and exit without running them
--filter <SUBSTR> Only run (or list) tests whose name contains the substring
-O <LEVEL> Compile at the given optimization level
$ atoll test --filter correct calc.at
test add_is_correct ... ok

result: 1 ok, 0 failed, 0 ignored

Passing a directory instead of a file compiles every .at file beneath it as one project and runs all the tests it finds, so tests can live beside the code they cover across a multi-module project.

Exit codes

atoll test exits 0 when every discovered test passed, 1 when at least one failed, and 2 on an I/O error such as an unreadable input. That makes it usable directly as a CI gate.

Benchmarks

@bench marks a function for atoll bench, which invokes each one repeatedly and reports min/mean/max/stddev in nanoseconds. --list and --format json work the same way as for tests.

fn add(a: int, b: int): int { return a + b }

@bench
fn bench_add(): void {
    mut total := 0
    for i in 0..1000 {
        total = add(total, i)
    }
}

Benchmark at -O2 (the default) unless you are specifically measuring the effect of a lower optimization level — -O0 skips most of the pipeline and its numbers do not describe a shipped build.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close