Wraps cargo-llvm-cov and gates PR coverage against a stored baseline. Line, function, and region kill rates as machine-readable verdicts.
dev-* verification collection.Also available as the
coverage feature of the dev-tools umbrella crate — one dependency, every verification layer.
dev-coverage drives cargo-llvm-cov
against your project, parses the JSON output, and emits results as a
dev-report::Report. It compares against a
stored baseline so CI gates, release pipelines, and AI assistants can
act on coverage regressions without scraping free-form text.
Test coverage is the single most direct way to ask "how much of this code is actually exercised?" Without it, every other quality check is an opinion. With it, you can answer "did this PR drop line coverage below 80%?" or "did this PR introduce a 5-point regression vs. main?" as a yes/no decision.
dev-coverage makes those questions programmable, not interactive.
[dependencies]
dev-coverage = "0.9"One-time tool install:
cargo install cargo-llvm-covDrive it from code:
use dev_coverage::{CoverageRun, CoverageThreshold};
let run = CoverageRun::new("my-crate", "0.1.0");
let result = run.execute()?;
let threshold = CoverageThreshold::min_line_pct(80.0);
let check = result.into_check_result(threshold);
// `check` is a `dev_report::CheckResult` ready to push into a Report.
# Ok::<(), dev_coverage::CoverageError>(())| Threshold | What it measures |
|---|---|
CoverageThreshold::MinLinePct |
Percent of executable lines exercised by tests. |
CoverageThreshold::MinFunctionPct |
Percent of functions called by at least one test. |
CoverageThreshold::MinRegionPct |
Percent of basic blocks (branch points) exercised. |
Line coverage is the most common. Region coverage is the strictest.
The headline feature beyond raw measurement: persist a baseline, then flag regressions on the next run.
use dev_coverage::{
Baseline, BaselineStore, CoverageRun, JsonFileBaselineStore,
};
let run = CoverageRun::new("my-crate", "0.1.0");
let result = run.execute()?;
let store = JsonFileBaselineStore::new("coverage-baselines");
// Compare against last run on main.
if let Some(baseline) = store.load("main", "my-crate")? {
let diff = result.diff(&baseline, /* tolerance_pct */ 1.0);
if diff.regressed {
eprintln!(
"coverage regressed: line {:+.2}pp, function {:+.2}pp, region {:+.2}pp",
diff.line_pct_delta,
diff.function_pct_delta,
diff.region_pct_delta,
);
}
}
// Persist for next time.
store.save("main", &result.to_baseline())?;
# Ok::<(), Box<dyn std::error::Error>>(())JsonFileBaselineStore writes one <root>/<scope>/<name>.json per
baseline with atomic write-temp-rename semantics; a partial write that
survives a crash will not corrupt the comparison on the next run.
CoverageProducer plugs coverage into a multi-producer pipeline driven
by dev-tools:
use dev_coverage::{CoverageProducer, CoverageRun, CoverageThreshold};
use dev_report::Producer;
let producer = CoverageProducer::new(
CoverageRun::new("my-crate", "0.1.0"),
CoverageThreshold::min_line_pct(80.0),
);
let report = producer.produce();
println!("{}", report.to_json().unwrap());When a baseline is wired in via with_baseline, the producer pushes a
second CheckResult named coverage::regression::<subject> carrying
the deltas in its detail field, with verdict Fail (Error) if the
regression exceeds the tolerance.
| File | What it shows |
|---|---|
examples/basic.rs |
Run coverage against the current crate; emit a CheckResult. |
examples/with_threshold.rs |
Every CoverageThreshold variant against a constructed result. |
examples/baseline.rs |
Save a baseline, then diff a new run against it. |
examples/producer.rs |
Wrap a run in CoverageProducer (gated by DEV_COVERAGE_EXAMPLE_RUN). |
cargo-llvm-cov must be
installed on the system. The crate detects absence and surfaces a
CoverageError::ToolNotInstalled rather than panicking.
cargo install cargo-llvm-covThe crate's own dependency footprint is small: dev-report, serde,
serde_json.
CoverageResult gained branch_pct, total_functions,
covered_functions, total_regions, covered_regions, and files.
If you constructed CoverageResult literals in 0.1.0, fill in the
new fields:
# use dev_coverage::CoverageResult;
let _r = CoverageResult {
name: "x".into(),
version: "0.1.0".into(),
line_pct: 85.0,
function_pct: 90.0,
region_pct: 80.0,
// new in 0.9.0:
branch_pct: None,
total_lines: 100,
covered_lines: 85,
total_functions: 20,
covered_functions: 18,
total_regions: 50,
covered_regions: 40,
files: Vec::new(),
};The constructor surface (CoverageRun::new, CoverageThreshold::min_*,
CoverageResult::into_check_result) is unchanged.
dev-coverage ships independently and is also re-exported by the
dev-tools umbrella crate as
the coverage feature. Sister crates cover the other verification
dimensions:
dev-report— report schema everything emitsdev-fixtures— deterministic test fixturesdev-bench— performance and regression detectiondev-async— async runtime verificationdev-stress— stress and soak workloadsdev-chaos— fault injection and recovery testingdev-security— CVE / license / banned-crate auditdev-deps— unused / outdated dep detectiondev-ci— GitHub Actions workflow generatordev-fuzz— fuzz testing workflowdev-flaky— flaky-test detectiondev-mutate— mutation testing
v0.9.x is the pre-1.0 stabilization line. The API is feature-complete
for coverage measurement, baseline storage, and regression detection.
Production use is fine; 1.0 will pin the public API and the wire
format.
1.85 — pinned in Cargo.toml via rust-version and verified by the
MSRV job in CI.
Apache-2.0. See LICENSE.
Copyright © 2026 James Gober.