A pull-request-time gate that compares per-package line coverage produced
by cargo-llvm-cov against per-package thresholds carried in
Cargo.toml. The accompanying cargo-coverage-gate binary reads the
coverage lcov tracefile, resolves each package’s base policy from a small
three-layer lookup, applies any matching package target policy, and emits a
verdict table to stdout (and,
optionally, to a Markdown summary file for CI step summaries). A failing
verdict includes actionable details without relying on a later
coverage-service upload. A coverable line is a distinct LCOV DA: record.
Numeric failures show exact covered/coverable counts and uncovered ranges;
expect-no-coverable-lines failures show the unexpected coverable ranges;
and NO DATA explains that no records were attributed. Location lists are
bounded, with an exact count of omitted locations.
A workspace can define the default line-coverage threshold:
# Illustrative workspace policy.
[workspace.metadata.coverage-gate]
min-lines-percent = 80Individual packages can override it:
# Illustrative package policy, intentionally stricter than the workspace.
[package.metadata.coverage-gate]
min-lines-percent = 95For each workspace member, the base threshold is the first match among:
[package.metadata.coverage-gate] min-lines-percent = Nin the package’sCargo.toml,[workspace.metadata.coverage-gate] min-lines-percent = Nin the workspace rootCargo.toml, or- The built-in default of
100.0— full coverage required.
Setting min-lines-percent = 0.0 explicitly opts a package out of
gating: it always passes, regardless of attributed data. Thresholds must
be in the inclusive range 0.0..=100.0.
A package that legitimately contains no coverable lines (pure re-exports, type definitions, or a thin binary shim) can make that invariant explicit:
[package.metadata.coverage-gate]
expect-no-coverable-lines = trueThe gate passes only while the package has no attributed coverable lines
and fails as a regression if coverable code later appears. This differs
from min-lines-percent = 0, which keeps passing if the package grows
coverable code. The two keys are mutually exclusive, and
expect-no-coverable-lines is package-scoped only.
A package can replace its base policy for a Cargo-style target selector:
[package.metadata.coverage-gate]
min-lines-percent = 100
[package.metadata.coverage-gate.target.'cfg(not(windows))']
expect-no-coverable-lines = true
[package.metadata.coverage-gate.target.x86_64-unknown-linux-gnu]
min-lines-percent = 100A target-specific no-coverable-lines assertion uses the same nesting:
[package.metadata.coverage-gate.target.thumbv7em-none-eabihf]
expect-no-coverable-lines = trueTarget tables are package-scoped; they are invalid in workspace metadata.
Their keys accept exact Rust target triples or quoted cfg(...) expressions
using the target-derived subset of Cargo’s target grammar. Target
configuration options such as windows, unix, target_os, and
target_arch are supported. Build-context options such as feature, test,
debug_assertions, and proc_macro are rejected because a standalone target
query cannot evaluate them. A selected target table sets either
min-lines-percent or expect-no-coverable-lines = true, completely
replacing the package’s base policy to produce its effective policy. Exact
triples take precedence over matching cfg(...) expressions. Multiple
matching cfg policies are a configuration error rather than depending on
declaration order.
A zero target-specific threshold disables gating on the matching target,
but does not disable test execution or instrumentation. Those test binaries
remain instrumented because they may contribute coverage to other packages.
If cargo-llvm-cov reports that an instrumented run produced no coverage
data, automation can supply an empty lcov tracefile: zero-threshold and
expect-no-coverable-lines packages pass, while positively gated packages
report NO DATA.
cargo-llvm-cov exports the same instrumentation run in several
formats (JSON, lcov, cobertura, codecov-custom-JSON). The gate
consumes lcov because that is what every other coverage report fed by
the same data sees: Codecov ingests lcov uploads directly, ADO
consumes cobertura that cargo-llvm-cov derives from lcov, and the
lcov line semantics (“a line is covered if any region on it was
hit”) match the human reading of “did we hit this line”. The JSON
export uses a stricter “every region on the line must be hit”
interpretation that systematically reports a couple of
percentage-points lower, which makes calibrating thresholds against
Codecov / ADO numbers confusing.
cargo coverage-gate [--lcov <path>]... [-p|--package <spec>]...
[--target <triple>]
[--summary-file <path>] [--quiet]
--lcov may be repeated; the tracefiles are merged at the line level
(per-line counts summed) so multiple feature-config exports
(--all-features, --no-default-features) can be gated together
without a separate, platform-specific merge step.
Exit codes: 0 if every gated package meets its threshold, 1 if any
gated package falls below its threshold, and 2 for configuration
errors (unparseable lcov, missing data for a gated package, a --package
selector that matches no member, an out-of-range min-lines-percent
value, …).
When --summary-file is unset, the binary falls back to
$GITHUB_STEP_SUMMARY and then $COVERAGE_GATE_SUMMARY to decide
where to write the Markdown verdict table.
use std::io;
let lcov = std::fs::read_to_string("target/coverage/lcov.info")?;
let report = cargo_coverage_gate::evaluate(&lcov, None, &[])?;
report.render_text(&mut io::stdout())?;
let code = report.verdict().as_exit_code();evaluate gates one lcov tracefile for the rustc host target, while
evaluate_many merges multiple tracefiles at line level.
evaluate_many_for_target evaluates a selected Rust target, which may be
supplied explicitly or omitted to select the rustc host target.
Evaluation returns an EvaluatedReport, which renders as plain
text via EvaluatedReport::render_text or GitHub-flavored Markdown via
EvaluatedReport::render_markdown and reduces to a Verdict via
EvaluatedReport::verdict. The accompanying binary loads tracefiles from
disk and orchestrates rendering plus the appropriate exit code.
This crate was developed as part of The Oxidizer Project. Browse this crate's source code.
