Lyric v3.2.0

Testing

Tests live in a directory of their own — tests/ beside your lyric.json, or wherever its testRoot points — and only lyric test ever compiles them. A production build never sees a test file, so nothing has to be stripped and nothing ships by accident.

A test is a top-level function marked @Test, taking nothing and returning nothing. It fails by panicking, which is what the assertions in std.test do:

import std.test { Test, assertEq, assertTrue };

fn double(n: int): int {
    return n * 2;
}

@Test
pub fn doubling_doubles(): void {
    assertEq(double(2), 4);
}

@Test
pub fn doubling_keeps_the_sign(): void {
    assertTrue(double(-3) < 0, "the sign must survive");
}
lyric test
PASS math_tests.doubling_doubles
PASS math_tests.doubling_keeps_the_sign
2 test(s), all passed

The runner compiles every .lyr under the test root, finds the @Test functions through the module's attribute rows — the same machinery a host uses to find @System functions — and runs each in a fresh instance: module state cannot leak between tests, because no two tests share an instance. A failing assertion names both values (expected 5, got 4); a panic fails the test with its backtrace; the exit code is 0 when everything passed and 1 otherwise, which is all a CI step needs.

Test files are ordinary programs of your project: they import your modules through the sourceRoot the lyric.json declares, and the whole standard library and every capability are theirs — a test is your own code running on your own machine, the same standing lyric run has.

A project without a tests/ directory has no tests, and lyric test says so and exits 0. A testRoot your lyric.json names explicitly is a promise, though: if the directory is missing, that is an error.

assertEq wants [Equatable<T>, Display] — the comparison needs the one, naming both values in the failure needs the other. Every built-in scalar and string satisfy both; for your own types you declare the conformances, as chapter 7 shows.