ci: run coverage via nextest to fix false tracing coverage gaps#557
ci: run coverage via nextest to fix false tracing coverage gaps#557martintmk wants to merge 3 commits into
Conversation
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
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 Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
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
There was a problem hiding this comment.
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 nextestand installcargo-nextestin that job. - Ensure
coverage_attributeis enabled undercoverage_nightlyfor non-test builds in crates that use#[coverage(off)]outsidecfg(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.
| # 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" |
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
Approving, although I dislike the unused_features = "allow". We should fix this soon.
There was a problem hiding this comment.
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.
|
Would most of these go away once we switch to |
Shall we convert those from unit to integration tests ? |
|
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. |
Pull request was closed
Problem
Codecov intermittently reports missing coverage on
tracing::event!/ log macrolines (for example the field-value expressions in
crates/fetch_hyper/src/connection/client_connector.rs), even though those pathsare exercised by tests. Those expressions only execute on the macro's
"subscriber is interested" branch.
tracingcaches callsite interestprocess-globally, so under the default single-process
cargo testharness ano-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 ofthe default
cargo testharness. Per-test process isolation removes thecross-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-measurerecipe.cargo-nextestis added to the coveragejob'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 warningsand at 100%:automation/fetch_hyperenablefeature(coverage_attribute)whenevercoverage_nightlyis set (they apply#[coverage(off)]to non-test items).fetch_hyper'shyper_handler.unused_featureslint (the coverage-attribute gate is legitimatelyunused in feature/target combinations where a crate's
coverage(off)is fullycfg-gated out).cachet's uncoverable no-op logging fallbacks (let _ = (..)blocks thatonly exist in non-test, no-
logsbuilds and can never be executed by a test).Validated locally:
llvm-cov nextestpasses clean under-D warningsfor both--all-features(4752 tests) and--no-default-features(3882 tests), with nouncovered lines.