What it does
dev-mutate wraps cargo-mutants
and emits results as a dev-report::Report.
It answers the question: is your test suite actually testing what
you think it is?
What is mutation testing?
A tool makes small deliberate changes to your code — flipping < to
>, changing + to -, removing a return, swapping a boolean.
Then it runs your tests against each mutation.
- Killed mutant: a test failed. Good — your tests caught the bug.
- Surviving mutant: all tests still passed despite the broken code. Bad — your tests aren't really testing that behavior.
The kill rate is the percent of mutants caught. High coverage with a low kill rate means lots of tests but they don't assert enough.
Quick start
[]
= "0.9"
One-time tool install:
Drive it from code:
use ;
let run = new;
let result = run.execute?;
let threshold = min_kill_pct;
let check = result.into_check_result;
println!;
# Ok::
Builder surface
| Method | What it does |
|---|---|
in_dir(path) |
Run cargo mutants from a different directory. |
workspace() |
Pass --workspace (mutate every workspace member). |
jobs(n) |
Pass --jobs <N> (parallel mutation runs). |
timeout(Duration) |
Per-mutant timeout (--timeout <secs>). |
exclude_re(pattern) |
Skip files matching the regex (--exclude-re <pattern>). Repeatable. |
file(pattern) |
Restrict to matching files (--file <pattern>). Repeatable. |
allow(description) / allow_all(iter) |
Reclassify known survivors as killed (e.g. replace + with -). |
Kill rate
kill_pct = killed / (killed + survived) * 100
Timeouts are excluded from both numerator and denominator — they don't reflect test quality, they reflect test speed.
Typical kill-rate targets
| Project type | Reasonable target |
|---|---|
| Library, production | 70–80% |
| Library, mature | 85%+ |
| Application | 50–60% |
| Cryptography / security | 95%+ |
Per-file breakdown
MutateResult::files is a sorted list of FileBreakdown records,
one per source file. Use weakest_files(n) to spotlight the lowest
kill-rate hotspots:
use MutateResult;
# let result: MutateResult = unimplemented!;
for f in result.weakest_files
Allow-list known false positives
use ;
let run = new
.allow
.allow_all;
let _result = run.execute?;
# Ok::
Allow-listed mutations are reclassified as killed (the user has explicitly declared them acceptable), so the kill rate goes up accordingly.
Producer integration
MutateProducer plugs the run into a multi-producer pipeline driven
by dev-tools:
use ;
use Producer;
let producer = new;
let report = producer.produce;
println!;
Subprocess failures map to a single failing CheckResult named
mutate::<subject> with Severity::Critical — the pipeline keeps
running.
Target-dir-lock note
Running MutateRun::execute() from inside another cargo
invocation that already holds the workspace target-dir lock will
deadlock — cargo mutants itself drives cargo test repeatedly.
Use a separate target dir:
CARGO_TARGET_DIR=/tmp/mutate-target
CARGO_TARGET_DIR=/tmp/mutate-target
Wire format
MutateResult, SurvivingMutant, and FileBreakdown are all
serde-derived. JSON uses snake_case field names:
Examples
| File | What it shows |
|---|---|
examples/basic.rs |
Run against the current crate; graceful tool-missing handling. |
examples/with_threshold.rs |
Constructed result; demonstrates meets and weakest_files. |
examples/with_limits.rs |
workspace + jobs + timeout + filters + allow-list. |
examples/producer.rs |
MutateProducer (gated by DEV_MUTATE_EXAMPLE_RUN). |
The dev-* suite
See dev-tools for the
umbrella crate covering the full suite.
Status
v0.9.x is the pre-1.0 stabilization line. Feature-complete for
mutation testing, per-file breakdown, threshold, allow-list, and
producer integration. 1.0 will pin the public API and the
kill-rate computation.
Minimum supported Rust version
1.85 — pinned in Cargo.toml via rust-version and verified by
the MSRV job in CI.
License
Apache-2.0. See LICENSE.