#test Region
Purpose
The #test region contains test cases for the module. These are compiled and run via tape test <file> — they’re not included in normal builds.
#test
test "addition works" {
@assert(1 + 1 == 2);
}
test "string concatenation" {
let result = "hello" + " " + "world";
@assert(result == "hello world");
}Test syntax
Each test is declared with test "name" { body }. The name becomes part of the generated function name (spaces and hyphens become underscores).
test "descriptive name" {
// test body — regular tape code
@assert(some_condition);
}Tests return 0 on success. If any assertion fails, the test returns -1. If skipped, it returns -2.
Assertions
| Assertion | Purpose | Desugars to |
|---|---|---|
@assert(cond) | Fails if condition is false | if (!cond) { report; return -1; } |
@assert_err(expr) | Expects expression to be non-zero (error) | if (expr != 0) { report; return -1; } |
@assert_trap(expr) | Expects expression to cause a trap | trap_begin; expr; if (!trap_end) fail |
@fail(msg) | Unconditional failure with message | report(msg); return -1; |
@skip(cond) | Skip test if condition is true | if (cond) { return -2; } |
@assert with comparison operators (==, !=, <, >, <=, >=) reports both left and right values on failure via __test_assert_fail_cmp. Other conditions use __test_assert_fail with the expression text.
Setup and teardown
#test
setup {
// runs before each test
}
teardown {
// runs after each test
}
test "example" {
@assert(1 == 1);
}setup and teardown are optional. They run before/after every test function in the file.
Table-driven tests
Generate multiple test cases from data rows:
test "squares" with [1, 1], [2, 4], [3, 9], [4, 16] as (input, expected) {
@assert(input * input == expected);
}Each row [values...] becomes a separate test function (__test_squares_0, __test_squares_1, etc.). The as (names...) clause binds row values to local variables in the body.
Helper functions
Regular function declarations are allowed inside #test:
#test
fn make_test_data() -> i64 {
return 42;
}
test "uses helper" {
let x = make_test_data();
@assert(x == 42);
}Module substitution
For dependency injection in tests, use --substitute:
tape test app.tape --substitute io=./mock_io.tapeThis replaces the io module with a mock implementation during test compilation.
#test are parsed but skipped. The design allows #test to import modules directly — either for test-only utilities, or to override a module that #code imports (acting as inline substitution without the --substitute flag).Running tests
tape test module.tape # run all tests in file
tape test module.tape --substitute M=path # with module substitutionOutput
Tests print results as they run:
test "addition_works" ... ok
test "string_concatenation" ... FAILED
test "skipped_example" ... skipped
results: 1 passed, 1 failed, 1 skipped, 3 totalExit code is 0 if all pass, 1 if any fail.
What #test supports
| Feature | Status |
|---|---|
test "name" { ... } | Implemented |
setup { ... } / teardown { ... } | Implemented |
@assert, @fail, @skip | Implemented |
@assert_err, @assert_trap | Implemented |
Table-driven tests (with [...] as (...)) | Implemented |
Helper fn declarations | Implemented |
--substitute for mocking | Implemented |
--filter for test name matching | Not implemented |
| Test isolation (state reset between tests) | Not implemented |
Last modified: