A `test "name" { ... }` top-level block is discovered automatically and run by a
synthetic runner @main — no `entry` to write, nothing to register. Inside a test,
expect(cond) / expect_eq(a, b) / expect_near(a, b, tol) assert; on failure they
print `file:line: <what> failed (got G, want W)` and set a per-test fail flag but
keep going, so one run reports every failure. The runner prints `ok - name` /
`FAIL - name` per test, a `== N passed, M failed ==` summary, and exits non-zero
if any test failed — so `ludic spec_test.ludic` drops straight into bin/x and CI.
expect_near carries the tolerance fixed-point / accumulated-integer game math need.
Frontend: new `test` keyword (parse_test -> N_TEST, collected in g_tests) and the
call node now carries its source line for the file:line messages. Backend:
emit_test_runner synthesises @fn__test_i bodies + the runner @main; the expect*
builtins lower to a branch-print-flag tail (emit_expect_fail). g_src_name (set in
main from the input path) supplies the filename. The compiler's own source has no
`test` blocks, so its self-compiled IR is unchanged and the C-free fixpoint holds.
- `test` wired into the vocabulary (ludic_syntax.h, JetBrains lexer, TextMate
grammar) and documented (docs/language/testing/)
- examples/library/testing.ludic: a passing spec, guarded by a new spec_case in
the regression suite (build, run, require exit 0 + the expected summary)
Coverage instrumentation (the biggest lift in #12) is left as a follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
43 lines
1.4 KiB
Text
43 lines
1.4 KiB
Text
# testing.ludic — the built-in testing framework: `test "name" { … }` blocks with
|
|
# expect / expect_eq / expect_near assertions, discovered and run automatically by
|
|
# a synthetic runner that prints per-test pass/fail, a summary, and exits non-zero
|
|
# on any failure. This spec passes, so it prints one `ok - name` per test and:
|
|
# == 6 passed, 0 failed ==
|
|
# It is a runnable spec (no `entry`), driven by the runner the compiler generates.
|
|
program TestingSpec {
|
|
function add(a: int, b: int) -> int { return a + b }
|
|
function mul(a: int, b: int) -> int { return a * b }
|
|
|
|
test "expect on a boolean" {
|
|
expect(add(1, 1) == 2)
|
|
expect(mul(3, 4) == 12)
|
|
}
|
|
|
|
test "expect_eq compares ints" {
|
|
expect_eq(add(2, 3), 5)
|
|
expect_eq(mul(6, 7), 42)
|
|
}
|
|
|
|
test "expect_near tolerates a delta" {
|
|
expect_near(100, 103, 5) # |100 - 103| = 3 <= 5
|
|
expect_near(add(10, 10), 19, 1) # 20 within 1 of 19
|
|
}
|
|
|
|
test "fixed-point stays close" {
|
|
let half = fixed(1) / 2 # 0.5 in Q16.16 = 32768
|
|
expect_near(half, 32768, 0) # exact here, but tolerance is the point
|
|
let third = fixed(1) / 3 # ~0.3333
|
|
expect_near(third, 21845, 2) # 65536/3 = 21845.33 -> 21845
|
|
}
|
|
|
|
test "loops and locals work in a test" {
|
|
var sum = 0
|
|
for i in 0 .. 5 { sum = sum + i } # 0+1+2+3+4 = 10
|
|
expect_eq(sum, 10)
|
|
}
|
|
|
|
test "strings compare" {
|
|
let s = "ab" + "c"
|
|
expect(s == "abc")
|
|
}
|
|
}
|