Sigil Testing Specification

Version: 1.1.1 Last Updated: 2026-03-24

Overview

Sigil tests are first-class declarations in the language. For the user-facing debugging workflow across inspect, run, test, replay, and stepping, see language/docs/DEBUGGING.md.

Current implemented testing surface includes:

  • top-level test "name" { ... }
  • optional explicit test effects
  • optional world { ... } clause on tests
  • compiler-owned and roots
  • CLI test discovery and execution

This spec describes the implemented system in the current compiler, not older design ideas such as TDAI, semantic maps, coverage mode, or generated tests.

Test Declaration

Canonical surface:

λmain()=>Unit=()

test "adds numbers" {
  1+1=2
}

Effectful test:

λmain()=>Unit=()

test "writes log" =>!Log {
  l _=(§io.println("x"):Unit);
  true
}

World-derived test:

λmain()=>Unit=()

test "captured log contains line" =>!Log world {
  c log=(†log.capture():†log.LogEntry)
} {
  l _=(§io.println("captured"):Unit);
  ※check::log.contains("captured")
}

Rules:

  • test description is a string literal
  • test descriptions may span lines; the newline becomes part of the description
  • test body is an expression block
  • test body must evaluate to Bool
  • true means pass
  • false means fail
  • test-local world { ... } is declaration-only and contains c bindings of †... entry values
  • test-local world bindings are visible only inside that test body

Test Location

test declarations are only valid in files under tests/.

Test files are ordinary .sigil files and may also declare:

  • λ
  • c
  • e

In projects with sigil.json, project-defined named types still live in src/types.lib.sigil and are referenced in tests through µ....

Test files are executable-oriented and must define main.

Runtime Worlds

Sigil tests run inside a compiler-owned runtime world.

Baseline world:

  • selected from config/.lib.sigil
  • exported as c world=(...:†runtime.World)

Test-local derivation:

  • test ... world { ... } { ... } overlays entries onto the selected env world
  • singleton entries such as †clock., †log., or †random.* replace that kind
  • topology-indexed †http. and †tcp. replace by dependency handle

Observation surface:

  • ※observe::... exposes raw traces
  • ※check::... exposes Bool helpers over those traces

Canonical example helpers include:

  • ※observe::http.requests
  • ※observe::log.entries
  • ※check::http.calledOnce
  • ※check::log.contains

CLI Surface

Current user-facing command shape:

cargo run -q -p sigil-cli --manifest-path language/compiler/Cargo.toml -- test

Common modes:

# all tests in current project tests/
cargo run -q -p sigil-cli --manifest-path language/compiler/Cargo.toml -- test

# one file or directory
cargo run -q -p sigil-cli --manifest-path language/compiler/Cargo.toml -- test projects/algorithms/tests

# filter by name substring
cargo run -q -p sigil-cli --manifest-path language/compiler/Cargo.toml -- test --match "cache"

For runtime-world projects:

  • --env is required

sigil test also enforces project source coverage:

  • every function in project src/*.lib.sigil must be executed by the suite
  • sum-returning project functions must observe each relevant output variant
  • missing surface coverage is reported as ordinary failing test results
  • suite-style runs (sigil test, sigil test path/to/tests/) enforce this gate
  • focused single-file runs (sigil test path/to/tests/file.sigil) skip the project-wide coverage gate

JSON Output

The machine contract is shared with language/spec/cli-json.md. This section keeps the testing-specific semantics and result model.

Default test output is JSON.

Current top-level shape:

  • formatVersion
  • command
  • ok
  • summary
  • results
  • optional error

Per-test results currently include:

  • id
  • file
  • name
  • status

- pass | fail | error | stopped

  • durationMs
  • location
  • optional failure
  • optional trace
  • optional breakpoints
  • optional replay
  • optional exception

summary also includes:

  • stopped

When a test description contains newlines, current JSON output preserves those newline characters in id, name, and description.

Debugger notes:

  • sigil test supports --trace, --trace-expr, breakpoints, --record, and --replay
  • stop-mode breakpoints stop only the current test and report status: "stopped"
  • sigil test --replay is artifact-owned and cannot be combined with --env
  • sigil debug test replays one exact results[].id through a file-backed stepping session
  • debug sessions are JSON-only and currently support snapshot, step-into, step-over, step-out, continue, and close
  • sigil debug test start also accepts repeatable --watch local(.field)* selectors and reports compact watch results on every snapshot

Current output does not include:

  • declaredEffects
  • assertion metadata
  • raw coverage traces

Normative references:

  • language/docs/TESTING_JSON_SCHEMA.md
  • language/spec/cli-json.md
  • language/spec/cli-json.schema.json