Testing
Tests live next to the code as test blocks:
Int add(Int a, Int b) { return a + b; }
test "add: identity" { expect(add(0, 5)).toEqual(5); expect(add(5, 0)).toEqual(5);}
test "strings and lists" { expect("resid").toHaveLength(5); expect("resid").toMatch("^re[a-z]+d$"); expect([1, 2, 3]).toContain(3);}Run them with residc test:
$ residc test math_test.resid
math_test ✓ add: identity (0ms) ✓ strings and lists (0ms)
Failures: 0 | Passed: 2 | Duration: 0ms--filter REGEX runs matching tests; --format pretty|tap|json chooses the
report.
The exit status says what happened:
| Code | |
|---|---|
| 0 | every test passed |
| 1 | a test ran and failed |
| 2 | the file did not compile, or the invocation was malformed |
So a file that does not build is never mistaken for a test that failed — worth knowing in CI, where the two want different responses.
A whole tree
Section titled “A whole tree”residc test with no file runs every *_test.resid under a root, which is
the current directory unless --root says otherwise:
$ residc test --root srctest: 3 test file/s under srcsrc/math_test.resid
math_test ✓ add: identity (0ms)
Failures: 0 | Passed: 1 | Duration: 0mssrc/net/http_test.resid...---2 file/s passed, 1 failed, 0 did not compileA test block is an entry point like main, so each test file is its own
program: you see one summary per file, and the closing tally counts files.
A failing file is reported and the rest still run. A file that does not
compile is counted apart from a failing one, and the compile error wins the
exit status. Per-file binaries go to target/resid unless
--testbin-dir says otherwise. Build output (target), VCS metadata and
node_modules are never searched.
A whole package
Section titled “A whole package”To run a package’s tests with its dependencies resolved and their capability
ceilings applied, use resid-manifest test. It resolves dependencies, writes
the depmap, then hands discovery to the driver:
$ resid-manifest test resid.toml ./residctest: 3 test file/s under /path/to/pkg/srcsrc/math_test.resid...---3 file/s passed, 0 failed, 0 did not compileA test file’s import "pkgname"; finds the same dependency the package’s
own root does, inside the same ceiling. See
resid-manifest.
For a suite assembled from data at run time — a table of cases generated by a
loop, say — lib/testing.resid registers cases explicitly and runs them in
one binary:
import "testing.resid";
Int add(Int a, Int b) { return a + b; }
Int main() { List(TestCase) cases = [ test_case("add: identity", lambda() { expect(add(0, 5)).toEqual(5) }), test_case("add: zero", lambda() { expect(add(0, 0)).toEqual(0) }) ]; return run_tests("math", cases);}RESID_TEST_FORMAT and RESID_TEST_FILTER apply here too.
Matchers
Section titled “Matchers”expect(value) wraps a value of any type, and the matcher is typed against
it — so a mismatched matcher is a compile error, not a runtime one:
error[E0001]: type error: call toEqual expects (Int), got (Str) --> math_test.resid:2:57| Matcher | Checks |
|---|---|
toEqual(v) / toBe(v) |
equal value |
toNotEqual(v) |
different value |
toBeTrue() / toBeFalse() |
a Bool |
toBeNull() |
an Option is None |
toContain(x) |
a list holds x |
toHaveLength(n) |
a list or string length |
toMatch(re) |
a string matches a regular expression |
toBeCloseTo(x, tol) |
a Float within tol |
toThrow() |
the given closure aborts |
toSatisfy(pred) |
the predicate holds |
Every matcher is an assertion returning nothing, so none of them can be assigned, interpolated, or negated — a test states what must hold, not what it holds.
toThrow takes its closure from a typed binding, since an inline lambda
has no expected type to conform to:
Void closure() f = lambda() { expect(1).toEqual(2); };expect(f).toThrow();Capabilities
Section titled “Capabilities”A test block is an entry point, so if the code under test needs a
capability, the block declares it:
@requires(filesystem(readonly))test "reads the config" { expect(filesystem.exists("config.toml")).toBeTrue();}Authority enters a test exactly as it enters main, and the enclosing
program’s or sandbox’s ceiling still bounds it.
Not implemented
Section titled “Not implemented”Property-based testing (forall, generators, shrinking), snapshot testing,
custom matchers, parallel test execution, per-test timeouts, and per-test
result values. SPEC-testing.md is the full design document, with a
section-by-section account of what exists.
