Files
solvecraft/docs/mutation-testing.md
T
Jackson Oursland bc68e14c3e Mutation testing: judge kernel mutants by the black-box suites (#2660)
The kernel will move from truck to solidcore (#1597). The tests that must
catch regressions across that switch are the ones that don't depend on the
kernel: the reference tests and the robustness goldens. cargo-mutants checks
whether they would notice a change in the kernel's behaviour.

- apps/solidcraft-cli/tests/oracle_cases.rs: the reference tests as a cargo
  test (ignored; CI keeps replaying them through cargo xtask oracle), run
  over one process per core.
- .cargo/mutants.toml: mutants are judged by that test and the pull-request
  sample of the robustness goldens, not by the kernel's own unit tests.
- A mutants profile (ci with incremental builds): about 15 s to rebuild after
  a kernel change instead of about 3 minutes.
- docs/mutation-testing.md: how to run it, its cost, two cargo-mutants 27.1
  problems and the pilot's results (15% of sampled kernel mutants caught).

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 18:10:36 -04:00

70 lines
3.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Mutation testing
SolidCraft will move from truck to solidcore (#1597). The tests that have to catch regressions
across that switch are the ones that don't depend on the kernel: the reference tests
([docs/oracle.md](oracle.md)), the robustness goldens ([docs/robustness.md](robustness.md)) and
the engine's command tests. Most of the kernel's own unit tests call truck-specific code and go
away with it.
Mutation testing checks those suites. [cargo-mutants](https://mutants.rs) makes small changes to
the source, one at a time (`*` becomes `/`, a condition is negated, a function returns a default
value), rebuilds, and runs the tests. A mutant the tests don't catch is a change in behaviour
nobody would notice, and solidcore could get the same thing wrong without anyone noticing.
## What judges a mutant
`.cargo/mutants.toml` judges mutants by the black-box suites only:
- `oracle_suite` (`apps/solidcraft-cli/tests/oracle_cases.rs`): every case in `tests/oracle` with
expected values and no `known_failure` must pass, as `cargo xtask oracle` requires. It is
`#[ignore]`d so CI doesn't replay the oracle twice.
- The pull-request sample of the robustness goldens: every 8th seed, as `ci.yml` runs it.
The kernel's unit tests are left out on purpose. A mutant that only they catch marks a behaviour
the black-box suites would miss once the kernel is swapped.
## Running it
```
cargo install cargo-mutants --locked
CARGO_TARGET_DIR=$PWD/target cargo mutants --in-place \
-f crates/kernel/src/measure.rs \
--test-package solidcraft-cli --test-package solidcraft-engine \
--baseline skip --timeout 900 --build-timeout 900
```
- Use a worktree you don't otherwise touch: `--in-place` writes each mutant into the source tree
and restores it afterwards.
- `--shard 0/200 --sharding round-robin` with `-d crates/kernel` takes an even sample (every 200th
mutant) of the whole crate.
- The `mutants` profile in `Cargo.toml` is the `ci` profile with incremental builds. It cuts the
rebuild after a kernel change from about 3 minutes to about 15 seconds.
- A mutant costs 1.5–5 minutes on a 12-core Mac, mostly the tests. The kernel has about 40,000
mutants, so run a sample or one file at a time, never the whole crate. This is a tool for
finding gaps, not a CI gate.
Two problems with cargo-mutants 27.1 are behind the flags above:
- The baseline (the unmutated run) ignores `--test-package` and tests the mutated crate, which
fails. Skip it with `--baseline skip`, set `--timeout` yourself, and check the unmutated tree
passes first.
- `--re` and `--exclude-re` don't filter "delete field" mutants. Saved diffs in
`mutants.out/diff/` name the mutation, not the file, in their `+++` line: apply one with
`patch <file> < <diff>`.
## Pilot results (October 2026, `cb857f1f`)
| Sample | Mutants that build | Caught by the black-box suites |
|---|---:|---:|
| `crates/kernel/src/measure.rs`, every mutant | 85 | 85% |
| Whole kernel, every 200th mutant | 173 | 15% (95% interval 10–21%) |
Out of 49 of the kernel sample's survivors, the kernel's own unit tests caught 21 (43%) and
nothing caught 28 (57%). About 11% of survivors swap `>` and `>=` on floating-point values that
are never exactly equal, so they don't change behaviour.
The black-box suites are weakest on tessellation and the display mesh (0 of 19 mutants caught),
offset, shell, emboss and lip (0 of 14), blends (15%) and STEP/IGES exchange (17%). For a
survivor that a kernel unit test catches, that test's design is the starting point for a new
oracle case or engine test.