Skip to Content

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"); }
  • @Test marks public, parameterless, module-level functions (async is legal here too).
  • mush test file.ms runs 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 skipped

The 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 fail

The Assertion Family

FunctionBehavior
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

  • @Skip skips a case: @[Skip("not ready")] prints SKIP ... (not ready).
  • mush test file --filter two filters by display-name (file::test) substring, case-insensitive; --list lists every case name; --json / --junit produce machine-readable reports; --verbose makes 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 run on it executes main (and with no main it compiles as a library file).
  • The ms-corpus summary 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.
Last updated on October 11, 2026