Testing and Assertions
@Test on a public parameterless function makes it a test function; mush test runs each one and prints a summary. The assertion family lives in the std::Test module, and failures flow through fail.
A Test File
module Tests;
import std::Test.*;
@Test
public func add_works()
{
AssertEq(2 + 2, 4);
}
@Test
public func strings_repeat()
{
Test.AssertEq("ab" * 2, "abab");
}@Testmarks public, parameterless, module-level functions (async is legal here too).mush test file.msruns them one by one, printing PASS / FAIL / SKIP plus a summary line:
PASS testing.ms::add_works (24.1ms, 0 vticks)
PASS testing.ms::strings_repeat (1.7ms, 0 vticks)
ms-corpus: 2 passed, 0 failed, 0 skippedThe Output Line, Field by Field
(24.1ms, 0 vticks): the first number is real wall-clock time; the second is the virtual pump step count. Tests run on a virtual clock that advances one tick (≈16666.67µs) per step: a synchronous case that finishes immediately is 0 vticks, and await 1s is exactly 60. A FAIL line carries the structured fail payload and a log tail underneath (20 lines by default, gathering Test.Log and Debug.*):
FAIL basic.ms::d_fails (1.8ms, 0 vticks)
Test.AssertEqFailed(msg=AssertEq failed, expected=1, actual=2)
日志尾部:
before failThe Assertion Family
| Function | Behavior |
|---|---|
Test.Assert(cond, msg = "") | Fails when the condition is false |
Test.AssertEq(expected, actual, msg = "") | Fails on inequality, printing both sides |
Test.Fail(msg = "") | Fails outright |
Test.Log(msg) | Test log |
Test.WaitForTicks(n) | Advances the virtual clock by n ticks (see below) |
Test.BeforeTick(n) | Waits until just before tick n (identical to WaitForTicks on the pure-clock host) |
Assertion failures cannot be caught: the Assert family has no fail contract, and try? Assert(...) is rejected outright at compile time. An assertion failure always FAILs the case; consuming errors of your own inside a case (say, capture around a function you declared fail) still passes as usual.
Lifecycle: @Setup and @Teardown
Each case runs in an independent world: globals are reinitialized per declaration and cases share nothing (a global mutated by the previous case reads back as its initial value in the next). For cross-case initialization and cleanup use @Setup / @Teardown:
var counter: int = 0;
@Setup
public func before_each()
{
counter = 100;
}
@Teardown
public func after_each()
{
}- Multiple @Setup / @Teardown functions can be declared; they run one by one in declaration order, once before / after each case.
- A setup failure: the case FAILs with a
[setup-failed]annotation, and neither the case body nor the teardown runs. - The teardown runs regardless of outcome; a failure is written to the log. A PASS case with a
[teardown-error]annotation still counts as PASS, and a FAIL case’s failure reason is not overwritten.
Ticks and Time
The tick is the test’s frame unit: 1 tick = 50000/3 µs ≈ 16666.67µs (16666/16667/16667 alternating, zero drift). WaitForTicks(n) aligns a case to frame ticks: WaitForTicks(1) is 1 vtick, and WaitForTicks(60) is exactly equivalent to await 1s. You can also write it directly:
On a pure-clock host, BeforeTick(n) is synonymous with WaitForTicks. The test virtual clock caps at 36M µs (10 virtual minutes) by default; await 3600s FAILs with a [virtual-timeout] annotation.
Run Details
@Skipskips a case:@[Skip("not ready")]printsSKIP ... (not ready).mush test file --filter twofilters by display-name (file::test) substring, case-insensitive;--listlists every case name;--json/--junitproduce machine-readable reports;--verbosemakes PASS cases print their logs too.- Exit codes: 0 all passed, 1 any failure, 2 usage error.
- A corpus file that fails to compile gets one line,
FAIL file::<compile> (error), counted in the summary’s broken-files; it never vanishes silently. - Each case gets a default fuel of 50 million instructions; burning out FAILs it with
[fuel-exhausted].
Notes
- A test file is also an ordinary script:
mush runon it executes main (and with no main it compiles as a library file). - The
ms-corpussummary line aggregates the whole corpus: passed / failed / skipped, with broken-files appended when there are bad files. - For deterministic timing in tests see Virtual Clock.
