Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1d17275
Design doc for cargo-coverage-gate
May 25, 2026
1a37df2
Rename crate
May 25, 2026
f1e0d94
docs(cargo-coverage-gate): drop bump, add workspace default, default …
Copilot May 25, 2026
2a11505
docs(cargo-coverage-gate): document cross-crate test attribution
Copilot May 26, 2026
606b96f
docs(cargo-coverage-gate): restructure docs, add implementation plan …
Copilot May 26, 2026
730371f
docs(cargo-coverage-gate): rename design/0000.md to design/main.md
Copilot May 26, 2026
2d6efb3
docs(cargo-coverage-gate): drop the init subcommand
Copilot May 26, 2026
cb1b807
docs(cargo-coverage-gate): drop the check subcommand
Copilot May 26, 2026
c3c3501
feat(cargo-coverage-gate): phase 1 — crate skeleton
Copilot May 26, 2026
15c45bd
feat(cargo-coverage-gate): phase 2 — JSON model
Copilot May 26, 2026
0c9be5b
feat(cargo-coverage-gate): phase 3 — workspace discovery
Copilot May 26, 2026
dc5737c
feat(cargo-coverage-gate): phase 4 — threshold resolution
Copilot May 26, 2026
eb84cbb
feat(cargo-coverage-gate): phase 5 — attribution, aggregation, verdict
Copilot May 26, 2026
c595a0f
feat(cargo-coverage-gate): phase 6 — renderers
Copilot May 26, 2026
b70fb26
feat(cargo-coverage-gate): phase 7 — end-to-end CLI tests
Copilot May 26, 2026
c8fde9e
feat(cargo-coverage-gate): phase 8 — docs and release prep
Copilot May 26, 2026
60f8c90
fix(cargo-coverage-gate): address PR check failures
Copilot May 26, 2026
1c3b2fc
fix(cargo-coverage-gate): address remaining check failures
Copilot May 26, 2026
d192520
fix(cargo-coverage-gate): final spell-check fixes
Copilot May 26, 2026
d97ebff
fix(cargo-coverage-gate): spell-check tokenization edge cases
Copilot May 26, 2026
84b0da2
refactor(cargo-coverage-gate): address PR review comments
Copilot May 27, 2026
4746b60
fix(cargo-coverage-gate): rustdoc + external-type-exposure follow-ups
Copilot May 27, 2026
b8a3163
refactor(cargo-coverage-gate): switch line counters from u64 to u32
Copilot May 27, 2026
084a4d5
feat(cargo-coverage-gate): accept LLVM coverage JSON v3 schema
Copilot May 27, 2026
dfae20e
fix(cargo-coverage-gate): honor min-lines = 0.0 as a true opt-out
Copilot May 27, 2026
b04cbee
refactor(cargo-coverage-gate): address PR review batch (package termi…
Copilot May 28, 2026
063f828
fix(cargo-coverage-gate): align doc / error / asset with renamed CLI …
Copilot May 28, 2026
496b7a3
docs(cargo-coverage-gate): finish the package terminology pass
Copilot May 28, 2026
072bf18
feat(cargo-coverage-gate): switch input format from llvm-cov JSON to …
May 28, 2026
bd5dc9d
feat(cargo-coverage-gate): accept cargo-style `-p` / `--package` with…
May 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .spelling
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,38 @@ workspace
xxH3
ADO
cargo-llvm-cov
cobertura
Codecov
deserializer
deserialization
destructured
emoji
freeform
GFM
JSON's
kebab
lcov
llvm
llvm-cov
llvm-tools
Markdown
monorepo
nextest
prefixed
ratchet
renderable
renderer
renderers
representable
subcommand
subcommands
unparseable
vs
workspace's
v2
§
tracefile
LCOV
deserializer
49 changes: 49 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ homepage = "https://github.com/microsoft/ox-tools"

[workspace.dependencies]
# local dependencies
cargo-coverage-gate = { path = "crates/cargo-coverage-gate", default-features = false, version = "0.1.0" }
cargo-heather = { path = "crates/cargo-heather", default-features = false, version = "0.2.1" }

# external dependencies
Expand All @@ -31,6 +32,7 @@ anyhow = { version = "1.0.100", default-features = false }
assert_cmd = { version = "2.2.0", default-features = false }
async-once-cell = { version = "0.5", default-features = false }
bytes = { version = "1.11.1", default-features = false }
cargo_metadata = { version = "0.23.1", default-features = false }
chrono = { version = "0.4.40", default-features = false }
chrono-tz = { version = "0.10.4", default-features = false }
clap = { version = "4.5.60", default-features = false }
Expand All @@ -48,6 +50,7 @@ http = { version = "1.2.0", default-features = false, features = ["std"] }
infinity_pool = { version = "0.8.1", default-features = false }
insta = { version = "1.44.1", default-features = false }
jiff = { version = "0.2.21", default-features = false }
lcov = { version = "0.8.2", default-features = false }
libc = { version = "0.2.178", default-features = false }
many_cpus = { version = "2.1.0", default-features = false }
mockall = { version = "0.14.0", default-features = false }
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ This repository contains a set of tools that help you build robust highly scala

These are the primary crates built out of this repo:

- [`cargo-coverage-gate`](./crates/cargo-coverage-gate/README.md) - A cargo sub-command that gates pull requests on per-crate line coverage measured by cargo-llvm-cov
- [`cargo-heather`](./crates/cargo-heather/README.md) - A cargo sub-command to validate license headers in Rust, TOML, PowerShell, Just, and env source files

## About this Repo
Expand Down
1 change: 1 addition & 0 deletions crates/cargo-coverage-gate/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Changelog
51 changes: 51 additions & 0 deletions crates/cargo-coverage-gate/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

[package]
name = "cargo-coverage-gate"
description = "A cargo subcommand that gates pull requests on per-package line coverage measured by cargo-llvm-cov"
version = "0.1.0"
readme = "README.md"
keywords = ["oxidizer", "cargo", "subcommand", "coverage", "ci"]
categories = ["command-line-utilities", "development-tools::cargo-plugins"]

edition.workspace = true
rust-version.workspace = true
authors.workspace = true
license.workspace = true
homepage.workspace = true
repository = "https://github.com/microsoft/ox-tools/tree/main/crates/cargo-coverage-gate"

[package.metadata.docs.rs]
all-features = true

[package.metadata.cargo_check_external_types]
allowed_external_types = [
# The `#[ohno::error]` attribute on `CoverageGateError` implements
# these traits, which transitively appear in the public API.
"ohno::enrichable::Enrichable",
"ohno::error_ext::ErrorExt",
]

[[bin]]
name = "cargo-coverage-gate"
path = "src/bin/cargo-coverage-gate/main.rs"

[dependencies]
cargo_metadata = { workspace = true }
clap = { workspace = true, features = ["derive", "std", "help", "usage", "error-context"] }
lcov = { workspace = true }
ohno = { workspace = true, features = ["app-err"] }
serde = { workspace = true, features = ["derive", "alloc"] }
serde_json = { workspace = true, features = ["std"] }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, features = ["fmt"] }

[dev-dependencies]
assert_cmd = { workspace = true }
predicates = { workspace = true }
pretty_assertions = { workspace = true, features = ["std"] }
tempfile = { workspace = true }

[lints]
workspace = true
109 changes: 109 additions & 0 deletions crates/cargo-coverage-gate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<div align="center">
<img src="./logo.png" alt="Cargo-Coverage-Gate Logo" width="96">
Comment thread
martin-kolinek marked this conversation as resolved.

# Cargo-Coverage-Gate

[![crate.io](https://img.shields.io/crates/v/cargo-coverage-gate.svg)](https://crates.io/crates/cargo-coverage-gate)
[![docs.rs](https://docs.rs/cargo-coverage-gate/badge.svg)](https://docs.rs/cargo-coverage-gate)
[![MSRV](https://img.shields.io/crates/msrv/cargo-coverage-gate)](https://crates.io/crates/cargo-coverage-gate)
[![CI](https://github.com/microsoft/ox-tools/actions/workflows/main.yml/badge.svg?event=push)](https://github.com/microsoft/ox-tools/actions/workflows/main.yml)
[![Coverage](https://codecov.io/gh/microsoft/ox-tools/graph/badge.svg?token=FCUG0EL5TI)](https://codecov.io/gh/microsoft/ox-tools)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](../../LICENSE)
<a href="../.."><img src="../../logo.svg" alt="This crate was developed as part of the Oxidizer project" width="20"></a>

</div>

## cargo-coverage-gate

A pull-request-time gate that compares per-package line coverage produced
by [`cargo-llvm-cov`][__link0] against per-package thresholds carried in
`Cargo.toml`. The accompanying `cargo-coverage-gate` binary reads the
coverage lcov tracefile, resolves each package’s threshold from a small
three-layer lookup, and emits a verdict table to stdout (and,
optionally, to a Markdown summary file for CI step summaries).

The full design is in [`docs/design/main.md`][__link1] in the source tree;
the implementation plan tracking the build is in
[`docs/implementation-plans/0000.md`][__link2].

### Threshold resolution

For each workspace member, the effective threshold is the first match
among:

1. `[package.metadata.coverage-gate] min-lines-percent = N` in the package’s
`Cargo.toml`,
1. `[workspace.metadata.coverage-gate] min-lines-percent = N` in the workspace
root `Cargo.toml`, or
1. The built-in default of `100.0` — full coverage required.

Setting `min-lines-percent = 0.0` explicitly opts a package out of gating.

### Why lcov, not the JSON?

`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 UI 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.

### Binary usage

```text
cargo coverage-gate [--lcov <path>] [-p <spec>]... [--package <spec>]...
[--summary-file <path>] [--quiet]
Comment thread
martin-kolinek marked this conversation as resolved.
```

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.

### Library usage

```rust
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().exit_code();
```

### Public API

The library exposes [`evaluate`][__link3], which returns an
[`EvaluatedReport`][__link4]. The report can be rendered as plain text via
[`EvaluatedReport::render_text`][__link5] or as GitHub-flavored Markdown
via [`EvaluatedReport::render_markdown`][__link6], and reduced to a single
[`Verdict`][__link7] via [`EvaluatedReport::verdict`][__link8]. The accompanying
binary loads the lcov tracefile from disk and orchestrates rendering
plus the appropriate exit code.


<hr/>
<sub>
This crate was developed as part of <a href="../..">The Oxidizer Project</a>. Browse this crate's <a href="https://github.com/microsoft/ox-tools/tree/main/crates/cargo-coverage-gate">source code</a>.
</sub>

[__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjJhdIQbYLuo4OFUWT8bvMCT2d1BCU8bCvLHCBSvMr0bKR38GpAvnJ5hYvRhcoQbeMd-OXFcBm0bmRUrCNlaa-Mbh5mmtfAjdb0bXEoRTJRFNyZhZIGDc2NhcmdvLWNvdmVyYWdlLWdhdGVlMC4xLjBzY2FyZ29fY292ZXJhZ2VfZ2F0ZQ
[__link0]: https://github.com/taiki-e/cargo-llvm-cov
[__link1]: https://github.com/microsoft/ox-tools/blob/main/crates/cargo-coverage-gate/docs/design/main.md
[__link2]: https://github.com/microsoft/ox-tools/blob/main/crates/cargo-coverage-gate/docs/implementation-plans/0000.md
[__link3]: https://docs.rs/cargo-coverage-gate/0.1.0/cargo_coverage_gate/fn.evaluate.html
[__link4]: https://docs.rs/cargo-coverage-gate/0.1.0/cargo_coverage_gate/struct.EvaluatedReport.html
[__link5]: https://docs.rs/cargo-coverage-gate/0.1.0/cargo_coverage_gate/?search=EvaluatedReport::render_text
[__link6]: https://docs.rs/cargo-coverage-gate/0.1.0/cargo_coverage_gate/?search=EvaluatedReport::render_markdown
[__link7]: https://docs.rs/cargo-coverage-gate/0.1.0/cargo_coverage_gate/enum.Verdict.html
[__link8]: https://docs.rs/cargo-coverage-gate/0.1.0/cargo_coverage_gate/?search=EvaluatedReport::verdict
Loading
Loading