dev-coverage 0.9.1

Code coverage with regression gates for Rust. Wraps cargo-llvm-cov: line / function / region kill rates, stored baselines, machine-readable verdicts. Part of the dev-* verification collection.
Documentation

What it does

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.

Why a separate crate

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.

Quick start

[dependencies]
dev-coverage = "0.9"

One-time tool install:

cargo install cargo-llvm-cov

Drive 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 types

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.

Baseline workflow

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.

Producer integration

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.

Examples

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).

Requirements

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-cov

The crate's own dependency footprint is small: dev-report, serde, serde_json.

Migration from 0.1.0

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.

The dev-* collection

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:

Status

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.

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.