Reference: Continuous Verification & Mutation Testing (cargo-mutants)
How we verify every PCB Reviewer feature with cargo test and measure real assertion coverage with cargo mutants across Acts I–VII.
1. Why Mutation Testing Beats Line Coverage in egui
Traditional line coverage only tells you whether a line of Rust code executed during a test—not whether your test would fail if that line were wrong. Turns out, in an immediate-mode GUI, calling app.ui(...) executes almost every line of your panel layout even if your test contains zero assertions!
So, we use Martin Pool's cargo-mutants to test our tests: it copies your project to a scratch directory, systematically injects small faults (mutants—such as flipping true to false, replacing + with -, or emptying a method body), and runs cargo test against each mutant.
flowchart LR
Source["Rust Source Code<br/>(e.g. isolate_top_copper)"] --> Mutate["cargo mutants injects fault<br/>(e.g. show_top_copper = false)"]
Mutate --> Run["Runs cargo test<br/>in scratch build dir"]
Run -->|"Test FAILS"| Caught["CAUGHT MUTANT<br/>(Your test assertion works!)"]
Run -->|"Test PASSES"| Missed["MISSED MUTANT<br/>(Coverage gap: add assertion!)"]
How cargo mutants distinguishes verified logic (Caught) from unasserted code paths (Missed).
2. Essential Commands & Outcomes
# 1. Install cargo-mutants once (works with stable Rust, no nightly required)
cargo install --locked cargo-mutants
# 2. Run the unit & integration test suite
cargo test
# 3. Run mutation testing across the current crate and list any missed mutants
cargo mutants
# 4. Mutate only a single file or function during fast iteration
cargo mutants -f src/main.rs
| Outcome | Meaning | Action to Take |
|---|---|---|
| Caught | cargo test failed when this mutation was injected. Your test suite actively guards this line of code. |
Celebrate—this behaviour is locked down. |
| Missed | cargo test still passed even with the mutated code! Either no test exercises this path or an assertion is missing. |
Add a unit/harness assertion that fails under the mutated value. |
| Unviable | The mutated code failed to type-check or compile (Rust's type system caught it before tests even ran). | Ignored automatically by cargo mutants. |
| Timeout | The mutation caused an infinite loop in a test. | Inspect loop termination bounds. |
3. Scoping Visual Entrypoints with .cargo/mutants.toml
In a desktop application, fn main() opens a blocking OS window via eframe::run_native, which should not run inside headless unit tests. Create .cargo/mutants.toml in your crate root to explicitly track which functions are excluded from mutation until a higher-tier harness is introduced:
# .cargo/mutants.toml
# Reuse the target directory across scratch builds so mutation runs finish quickly.
copy_target = true
# Explicitly exclude the OS window launcher and raw panel draw loop in Act I;
# we remove "ui" from this list in Lesson 0007 when we add egui_kittest!
exclude_re = [
"replace main",
"<impl eframe::App for PcbReviewerApp>::ui",
]
4. Act IV Multi-Crate Workspace & proptest Patterns (0015–0018)
Once domain state lives in crates/pcb-doc (L1, depending only on emath), you can run cargo test and cargo mutants on the domain crate in milliseconds without compiling or linking eframe:
# Run only L1 domain tests or mutations (zero egui/eframe linking overhead)
cargo test --manifest-path ./apps/0018-undo-redo-history-and-slider-coalescing/Cargo.toml -p pcb-doc
cargo mutants --manifest-path ./apps/0018-undo-redo-history-and-slider-coalescing/Cargo.toml -p pcb-doc --in-place
# Filter cargo-mutants to specific functions across the workspace
cargo mutants --manifest-path ./apps/0018-undo-redo-history-and-slider-coalescing/Cargo.toml --workspace --in-place -F "BoardHistory|record_edit|undo|redo"
Combine proptest! with Arc::ptr_eq to assert both value equivalence and copy-on-write pointer sharing across randomized drag sequences:
proptest! {
#[test]
fn prop_coalesced_drag_restores_exact_initial_state_in_single_undo(
drag_steps in prop::collection::vec(10.0_f32..90.0_f32, 2..12),
) {
let initial = BoardModel::default();
let mut working = initial.clone();
let mut history = BoardHistory::default();
for x in &drag_steps {
let before = working.clone();
working.move_review_pin_x(PinId(1), *x);
history.record_edit("Drag Pin #1 X", before, Some("drag_pin_1_x"));
}
history.commit_coalesce();
prop_assert_eq!(history.undo_len(), 1);
let undone_label = history.undo(&mut working);
prop_assert_eq!(undone_label.as_deref(), Some("Drag Pin #1 X"));
prop_assert_eq!(&working, &initial);
prop_assert_eq!(working.shared_arc_buffers(&initial), 4);
}
}
5. Act V panic_hunt, gpu_lock() Snapshot Digests & Scorecards (0019–0022)
In Act V, we harden the multi-crate workspace with three production gates:
- Tier 5
panic_huntAdversarial Command Fuzzing (0019): Iterates over every registeredCommandSpecinCOMMAND_SPECS(assertingCOMMAND_SPECS.len() >= COMMAND_PARITY_FLOOR) and feeds malformed/out-of-bounds JSON payloads intoBoardModel::execute_command, assertingErr(EngineError::BadParams)and zero panics. - Tier 2 + Tier 7 Spatial Culling Equivalence & Budgets (
0020): Usesproptest!to proveculled_pads_in_rectandculled_traces_in_rectmatch reference filtering across arbitrary camera viewports whilecheck_cull_budgetenforces microsecond budgets. - Tier 3/6
gpu_lock()&FrameSnapshotDigest(0022): Acquireslet _gpu = gpu_lock();(OnceLock<Mutex<()>>withPoisonError::into_inner) on the first line of snapshot tests, computes a deterministic 64-bit FNV-1a digest over visible layer bitmasks and culled geometry counts, and verifies release readiness viaQualityScorecard::is_release_ready().
6. Act VI Real-File KiCad Corpus Oracles & Async Turso SQL Harnesses (0023–0026)
In Act VI, we expand the workspace to 3 crates (crates/kicad-sexpr at L0 Standalone, crates/pcb-doc at L1/L4, and pcb-reviewer at L6/L7) and add three new verification gates:
- Tier 1 + Tier 2
L0Standalone Parser &proptestRound-Tripping (0023): Enforcesvalidate_l0_standalone_manifestoncrates/kicad-sexpr/Cargo.tomland usesproptest!(prop_sexpr_parser_roundtrips_arbitrary_footprints) to verifyparse_sexpr_stracross arbitrary KiCad S-expression trees in ~15 seconds for all 38L0mutants. - Tier 4 Pinned
.kicad_pcbCorpus Oracles & Ratchet Floors (0024): Runsrun_pinned_kicad_corpus()acrossPINNED_KICAD_CORPUS, assertingpassed_files >= CORPUS_PASS_FLOOR,roundtrip_files >= CORPUS_ROUNDTRIP_FLOOR, andreloaded == board(from_kicad_pcb_sexpr↔to_kicad_pcb_sexpr). - Tier 2 Async
#[tokio::test]SQL AST, Atomic Rollback & Audit Hash-Chain Gates (0025–0026): Validates SQL DDL/DML/transaction statements viaturso_parser::parser::Parser, verifies foreign-key rejection on unregisteredboard_ids, tests atomic rollback on invalid status transitions insideexecute_atomic_signoff_batch, and verifies tamper detection inverify_audit_chain().
7. Act VII Agentic MCP Duplex Round-Trips, Constant-Time Auth & Cropped PNG Oracles (0027–0030)
In Act VII, we verify every layer of our Model Context Protocol (rmcp) stack without external network calls or cloud LLM dependencies:
- Tier 2
schemarsJSON Schema & Bounded Query Invariants (0027): Asserts thatspatial_query_json_schema()emits mandatory tool parameter definitions and thatquery_spatial_neighborhood,query_net_topology, andrun_custom_drcenforce Euclidean radius bounds,max_itemstruncation flags, and pad-clearance distance thresholds. - Tier 6 In-Memory
tokio::io::duplexMCP Client-Server Acceptance (0028): SpawnsPcbMcpServerandMockMcpClientovertokio::io::duplex(4096)inside#[tokio::test], asserting full JSON-RPC 2.0 tool discovery (list_all_tools), typedcall_toolexecution,kicad://resource reads, and prompt templates. - Tier 6 Constant-Time Bearer Auth, Cropped PNG Magic & Agent Playbook Gates (
0029–0030): Verifies thatverify_bearer_tokenrejects empty or mismatched tokens via constant-time XOR accumulation,capture_cropped_region_pngenforces pixel budgets (width_px * height_px <= 16_384) andPNG_MAGIC_HEADER(\x89PNG\r\n\x1a\n), andMcpInspectorWorkbench::run_power_integrity_playbooklogs all 4 JSON-RPC frames across bothHeadlessDuplexandBridgedLoopbackmodes.
8. Progression of Testing Across the Seven Acts
| Curriculum Stage | Testing Mechanism Introduced | What cargo test + cargo mutants Verifies |
|---|---|---|
| Lessons 0001–0002 & Act I (0003–0006) | #[cfg(test)], #[test], assert_eq!, pure domain/maths methods |
Default layer visibility flags, preset state transitions, Rect bounds, TSTransform pan/zoom round-trips, layer Z-ordering, and net hit-testing. |
| Act II (0007–0010) | egui_kittest::Harness (headless UI queries & simulated key presses) |
Un-skips eframe::App::ui! Verifies rendered schematic labels, dual-viewport cross-probing, BOM filtering, and F / M / Esc hotkeys. |
| Act III (0011–0014) | Workflow state-machine tests & Harness::new_ui widget mounts |
Review pin CRUD in HashMap, Open → Resolved → Verified triage rules, DRC clearance bounds, and custom egui::Widget layer swatches. |
| Act IV (0015–0018) | Workspace layer gates, panic-free lints & proptest! |
#![deny(clippy::unwrap_used)], sub-second cargo mutants -p pcb-doc, corrupt JSON rejection, and Arc::make_mut undo/redo snapshot invariants. |
| Act V (0019–0022) | panic_hunt command fuzzer, perf budgets & gpu_lock() snapshots |
Adversarial JSON fuzzing of every CommandSpec, spatial culling equivalence, background worker cancellation, and offscreen wgpu PNG visual parity. |
| Act VI (0023–0026) | Real-file KiCad corpus oracles & #[tokio::test] SQL harnesses |
nom S-expression parser edge cases, pinned .kicad_pcb oracle pass/round-trip ratchets, Turso (libsql) transactions, and docs/scorecard.md. |
| Act VII (0027–0030) | schemars schema gates, tokio::io::duplex MCP client & PNG magic |
Bounded spatial/electrical LLM queries, rmcp tool/resource/prompt round-trips, constant-time bearer auth, cropped PNG headers, and agent playbooks. |