Skip to content

ci: run coverage via nextest to fix false tracing coverage gaps#557

Closed
martintmk wants to merge 3 commits into
mainfrom
user/martintmk/20260709-coverage-nextest
Closed

ci: run coverage via nextest to fix false tracing coverage gaps#557
martintmk wants to merge 3 commits into
mainfrom
user/martintmk/20260709-coverage-nextest

Conversation

@martintmk

@martintmk martintmk commented Jul 9, 2026

Copy link
Copy Markdown
Member

Problem

Codecov intermittently reports missing coverage on tracing::event! / log macro
lines (for example the field-value expressions in
crates/fetch_hyper/src/connection/client_connector.rs), even though those paths
are exercised by tests. Those expressions only execute on the macro's
"subscriber is interested" branch. tracing caches callsite interest
process-globally, so under the default single-process cargo test harness a
no-subscriber test can register a callsite as never-interested before a
subscriber-installing test runs. The enabled branch is then recorded as
uncovered, nondeterministically and differently per platform, producing spurious
Codecov gaps.

Change

Generate coverage with cargo llvm-cov nextest (one process per test) instead of
the default cargo test harness. Per-test process isolation removes the
cross-test contamination of tracing's callsite-interest cache, so the macro's
enabled branch is measured deterministically. This also aligns CI with the
existing just coverage-measure recipe. cargo-nextest is added to the coverage
job's tool installs.

nextest also compiles the non-test library of leaf crates that the old harness
never built, so a few pre-existing latent issues are fixed to keep the coverage
build clean under -D warnings and at 100%:

  • automation / fetch_hyper enable feature(coverage_attribute) whenever
    coverage_nightly is set (they apply #[coverage(off)] to non-test items).
  • Remove redundant test-only imports in fetch_hyper's hyper_handler.
  • Allow the unused_features lint (the coverage-attribute gate is legitimately
    unused in feature/target combinations where a crate's coverage(off) is fully
    cfg-gated out).
  • Drop cachet's uncoverable no-op logging fallbacks (let _ = (..) blocks that
    only exist in non-test, no-logs builds and can never be executed by a test).

Validated locally: llvm-cov nextest passes clean under -D warnings for both
--all-features (4752 tests) and --no-default-features (3882 tests), with no
uncovered lines.

The coverage job used the default cargo test/libtest harness (all tests in
one process). tracing caches callsite interest process-globally, so a
no-subscriber test can register a 	racing::event! callsite as never-interested
before a subscriber-installing test runs, leaving the macro's enabled branch
(field-value expressions) recorded as uncovered nondeterministically across
platforms. This surfaced as spurious Codecov misses on log macro lines.

Switch to cargo llvm-cov nextest (one process per test), matching the
just coverage-measure recipe, so each test runs isolated and the enabled
branch is measured deterministically. Installs cargo-nextest in the job.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a49a8937-3f70-47a4-aaad-a24a82a7639b
@martintmk martintmk added the road-to-gas-town Touched by a Road to Gas Town skill label Jul 9, 2026
Switching the coverage job to `cargo llvm-cov nextest` makes it compile the
non-test library of leaf crates that the previous `cargo test` harness never
built. Under the coverage build's `-D warnings` this surfaced pre-existing
latent issues:

- `automation` and `fetch_hyper` apply `#[coverage(off)]` to non-test
  items (a crate-level attribute and the no-TLS `no_tls_backend_unreachable`
  fallback), but gated `feature(coverage_attribute)` on `test` only, so the
  non-test lib failed to compile (E0658). Enable the feature whenever
  `coverage_nightly` is set.
- `hyper_handler` test module redundantly re-imported `BodyExt`/`Service`
  (already in scope via `use super::*`); unused under no-default-features.
- Allow the `unused_features` lint: leaf crates whose `coverage(off)` is
  entirely feature/`cfg(test)`-gated leave the coverage-attribute gate unused
  in some target/feature combinations, which is a benign instrumentation
  artifact.

Verified locally: `llvm-cov nextest` all-features (4752 tests) and
no-default-features (3882 tests) both build and pass clean under -D warnings.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a49a8937-3f70-47a4-aaad-a24a82a7639b
@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.0%. Comparing base (76824e1) to head (193fc11).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #557   +/-   ##
=======================================
  Coverage   100.0%   100.0%           
=======================================
  Files         356      356           
  Lines       26966    26966           
=======================================
  Hits        26966    26966           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The record_{debug,info,error}_with_duration helpers had a
`#[cfg(not(any(feature = "logs", test)))] { let _ = (..); }` block whose sole
job was to consume the parameters when the logging path is compiled out. That
block is only present in non-test, no-`logs` builds, so no test can ever execute
it (any test build sets `cfg(test)`). The nextest coverage job compiles cachet's
non-test library under `--no-default-features`, which made those lines count as
permanently-uncovered and dropped project coverage below 100%.

Replace the no-op blocks with a cfg-gated `allow(unused_variables)` so the
parameters may go unused without emitting a coverage region. Behaviour is
unchanged (both were no-ops); the logging path is still covered via the
all-features report.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a49a8937-3f70-47a4-aaad-a24a82a7639b
@geeknoid geeknoid marked this pull request as ready for review July 9, 2026 17:35
Copilot AI review requested due to automatic review settings July 9, 2026 17:35
@geeknoid geeknoid enabled auto-merge (squash) July 9, 2026 17:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the CI coverage workflow to generate deterministic coverage results by running tests under cargo llvm-cov nextest (process-per-test), addressing intermittent Codecov “misses” on tracing::event! / log macro field-expression lines caused by cross-test callsite-interest caching.

Changes:

  • Switch the CI coverage job to cargo llvm-cov nextest and install cargo-nextest in that job.
  • Ensure coverage_attribute is enabled under coverage_nightly for non-test builds in crates that use #[coverage(off)] outside cfg(test).
  • Clean up warnings uncovered by the new coverage build mode (remove redundant test imports; replace “uncoverable” no-op logging fallbacks with targeted lint allowances).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
crates/fetch_hyper/src/lib.rs Enable feature(coverage_attribute) whenever coverage_nightly is set so non-test coverage builds can compile #[coverage(off)].
crates/fetch_hyper/src/connection/hyper_handler.rs Remove redundant test-only imports to keep coverage builds clean under -D warnings.
crates/cachet/src/telemetry/cache.rs Remove non-executable no-op logging fallbacks and replace with cfg-gated allow(unused_variables) for non-logs/non-test builds.
crates/automation/src/lib.rs Enable feature(coverage_attribute) under coverage_nightly (not just tests) to support coverage instrumentation builds.
Cargo.toml Allow the unused_features lint workspace-wide to avoid coverage-instrumentation-only noise.
.github/workflows/main.yml Run coverage via cargo llvm-cov nextest and install cargo-nextest in the coverage job.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Cargo.toml
Comment on lines +218 to +221
# gate (enabled by the `coverage_nightly` cfg) is technically unused. That is a
# benign artifact of instrumentation, not a real problem, so allow it rather
# than encoding brittle per-crate feature conditions on every coverage gate.
unused_features = "allow"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is smelly but I suppose there is no clean solution that avoids a cfg-conditional mess. We should file an issue in rust-lang/clippy to request possiblity for an allowlist in clippy.toml, though, so we can specifically say we are fine with "coverage_nightly" looking unused.

@psandana psandana left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving, although I dislike the unused_features = "allow". We should fix this soon.

@sandersaares sandersaares left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fixing the symptoms rather than the cause. If some test installs a global tracing listener (especially a no-op one) it needs to be isolated into its own test process via putting it into a separate test binary, thereby ensuring it does not contaminate state of other tests (alternatively, by modifying it to not install a global listener).

Hacking around this by using a different test runner is just that - a hack. This is not addressing the root cause.

This can cause other problems than just coverage gaps - it is a leak between tests, which is not something we should tolerate.

@ralfbiedert

Copy link
Copy Markdown
Collaborator

Would most of these go away once we switch to observed with individual sinks for tests?

@psandana

Copy link
Copy Markdown
Contributor

Hacking around this by using a different test runner is just that - a hack. This is not addressing the root cause.

Shall we convert those from unit to integration tests ?

@sandersaares

Copy link
Copy Markdown
Member

As I am on-call this week, I'll take over this topic as part of https://o365exchange.visualstudio.com/O365%20Core/_workitems/edit/7601362. Will try do the root-cause-addressing change in a separate PR, one that does not depend on tooling-imposed isolation.

auto-merge was automatically disabled July 14, 2026 13:00

Pull request was closed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

road-to-gas-town Touched by a Road to Gas Town skill

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants