Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Testing

A test is a function that returns i64: 0 passes, anything else fails. After this page you can write tests, read a failure, and run the full check ladder before you push.

Run a project’s tests

Save these four files in a new testing-demo/ directory (the src/ files go in testing-demo/src/).

schema = "semaprax.manifest.v1"

[package]
name = "testing-demo"
version = "0.1.0"

[modules]
entry = "demo.app"
sources = ["src/app.spx", "src/core.spx", "src/tests.spx"]
tests = ["demo.tests"]

[exports]
web = ["demo.divide"]
module demo.core;

@id("demo.divide")
fn divide(left: i64, right: i64) -> i64
    requires right != 0
    ensures result * right <= left
{
    left / right
}
module demo.app;
use function @id("demo.divide") from demo.core as divide;

@id("demo.main")
fn main() -> i64
{
    divide(84, 2)
}
module demo.tests;
use function @id("demo.divide") from demo.core as divide;

@id("demo.tests.test_divide")
fn test_divide() -> i64
{
    if divide(84, 2) == 42 { 0 } else { 1 }
}

@id("demo.tests.test_divide_by_one")
fn test_divide_by_one() -> i64
{
    if divide(7, 1) == 7 { 0 } else { 2 }
}

@id("demo.tests.main")
fn main() -> i64
{
    0
}
semaprax test .
project tests passed (2 named cases)

test runs the test module’s main, then every zero-parameter test_* function in that module. Each needs its own @id. The module must be in the manifest’s tests entry. A test_* function with another shape is not a case.

OutputMeaning
project tests passedmain returned 0 and there are no named cases.
project tests passed (N named cases)main and all N cases returned 0.
failed <id>: returned 2That case returned 2. Use distinct codes per assertion.
project tests failed: 1 of 2 named cases in demo.testsThe summary line. Exit code is 1.

Options: --json for the full case list (semaprax.project-execution.v1), --max-steps N, --max-bytes N. Tests run in the bounded interpreter only. They do not exercise native or Wasm output, so build and test those targets separately.

Read a contract failure

A failed requires or ensures fails the case that triggered it and names the clause and arguments. Change divide(7, 1) to divide(7, 0) and run again:

failed demo.tests.test_divide_by_one: language status {"schema":"semaprax.status.v1","domain_id":"semaprax.contract.v1","code":1,"class":"contract","retryable":false}
  contract: requires right != 0 in demo.divide
  arguments: left = 7, right = 0

Write the contract first, then test valid inputs at the edges: zero, one item, the largest accepted value. Do not keep a deliberately invalid call in the normal suite: it fails the suite. Put it in a separate fixture and assert the failure status from your harness. For expected user-facing errors, return Result and test the error arm. See Contracts and effects.

Prove the test can fail

Change == 42 to == 41, run semaprax test ., confirm it fails, then change it back. A test that cannot fail checks nothing. Keep one purpose per test: test_divide_by_one is easier to diagnose than test_everything.

More test tools

ToolUse it to
semaprax properties <file>Generate bounded inputs from signatures and contracts and evaluate them. Scalar, effect-free functions only. Options: --max-cases, --max-functions, --max-bytes, --seed.
semaprax interpret <file> --function <id> --arg 1 --arg 2Call one function with scalar literals.
semaprax assurance-policy <file> --profile <p>Check that each obligation is met statically, by guard or by test evidence.
semaprax network-run <project> --fixture f.jsonRun a network command against a recorded fixture.
Law filesProve or check declared laws. See Laws and proofs.

A single file given to properties or interpret needs a fn main() -> i64 (SPX-T105 otherwise).

The check ladder

Run these in order and stop at the first failure:

semaprax fmt . --check        # canonical layout, manifest order
semaprax check .              # types, contracts, effects, ownership
semaprax test .               # executable checks
semaprax build . --target web -o dist/web   # target acceptance
semaprax lock . --compare base.lock         # CI: fail on breaking interfaces

fmt . parses every file before it rewrites any. check, test, build and fmt accept ., a directory, semaprax.toml or --manifest-path.

Check handbook examples

If you edit these docs, run from the repository root:

/usr/bin/python3 scripts/check-handbook.py --compiler /absolute/path/to/semaprax

It formats temporary copies, checks and runs every marked example, compares output, and verifies links and chapter navigation. --structure-only skips the compiler. Unmarked snippets are not run.

References: Project Test Cases v1, Property-Test Generation v1.