Skip to content

Match hyphen in multi-revision comment matchers#124137

Merged
bors merged 1 commit intorust-lang:masterfrom
tgross35:testsuite-multi-rev-regex
Apr 20, 2024
Merged

Match hyphen in multi-revision comment matchers#124137
bors merged 1 commit intorust-lang:masterfrom
tgross35:testsuite-multi-rev-regex

Conversation

@tgross35
Copy link
Copy Markdown
Contributor

@tgross35 tgross35 commented Apr 18, 2024

Currently, the matcher //[rev-foo,rev-bar]~ does not get selected by the regex. Change the matcher to include -.

@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Apr 18, 2024

r? @compiler-errors

rustbot has assigned @compiler-errors.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added A-testsuite Area: The testsuite used to check the correctness of rustc S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-bootstrap Relevant to the bootstrap subteam: Rust's build system (x.py and src/bootstrap) labels Apr 18, 2024
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Apr 18, 2024

Some changes occurred in src/tools/compiletest

cc @jieyouxu

@tgross35
Copy link
Copy Markdown
Contributor Author

Reasoning: https://github.com/rust-lang/rust/pull/123816/files#diff-0238262528c07a864b8aa49d6d3aa989c49b045a012b1a8e2c491c84e3ccac3b wasn't matching anything labeled //[legacy,verbose-legacy]~^, @michaelwoerister suggested fixing the regex.

@compiler-errors
Copy link
Copy Markdown
Contributor

r? @jieyouxu

@michaelwoerister
Copy link
Copy Markdown
Member

The new regex looks good, @tgross35. Would you mind adding a unit test for the parse_expected function that covers the basics? See this for examples of other unit tests in compiletest:
https://github.com/rust-lang/rust/blob/master/src/tools/compiletest/src/header/tests.rs

@jieyouxu
Copy link
Copy Markdown
Member

Making revision names accept - makes sense to me, but I don't remember if the dash messes with crate names generated by revisions, could you double check if something like

//@ revisions: hello-world
//@[hello-world] run-pass
fn main() {}

isn't borked?

@jieyouxu
Copy link
Copy Markdown
Member

IIRC revision names are used in crate names for artifact names and output, I don't immediately recall if we normalized it to become underscore.

Comment thread src/tools/compiletest/src/errors.rs Outdated
// //[rev1,rev2]~^^
static RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"//(?:\[(?P<revs>[\w,]+)])?~(?P<adjust>\||\^*)").unwrap());
Lazy::new(|| Regex::new(r"//(?:\[(?P<revs>.+)])?~(?P<adjust>\||\^*)").unwrap());
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.

Does this regex mean test.name is accepted, along other non-alphanumeric characters? If so, that will break anything using revision name as crate names unless you also perform "name mangling" to normalize the non-alphanumeric characters

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 another case of "silently failing" in compiletest where it really really needs to loudly report that your directives don't work and won't work as expected...

@michaelwoerister
Copy link
Copy Markdown
Member

michaelwoerister commented Apr 19, 2024

#123816 is modifying a test case that already uses - in a revision name. It seems to work except for when used in error annotations.

However, generally restricting revision names to valid ASCII-only identifiers seems reasonable to me (as long as we give a good error message when compiletest encounters an invalid revision name 🙂)

@jieyouxu
Copy link
Copy Markdown
Member

jieyouxu commented Apr 19, 2024

#123816 is modifying a test case that already uses - in a revision name. It seems to work except for when used in error annotations.

However, generally restricting revision names to valid ASCII-only identifiers seems reasonable to me (as long as we give a good error message when compiletest encounters an invalid revision name 🙂)

I think that only recently works because I added some explicit crate name mangling for revisioned run-rustfix: #123601, which modifying the accepted revision name regex luckily hits. But that only handles . and - I want to say? So if the revision name contains a $ it probably breaks?

@jieyouxu
Copy link
Copy Markdown
Member

However, generally restricting revision names to valid ASCII-only identifiers seems reasonable to me (as long as we give a good error message when compiletest encounters an invalid revision name 🙂)

I think we could just only accept revision names that are crate-name-like (that is, restrict revision names to only consist of alphanumeric + underscore characters), and report an error otherwise. I don't think we want to perform any weird crate name mangling, because e.g. if you naively mangle rev-name as rev_name, then the corresponding crate name will conflict with another revision rev_name. People usually don't write revision names like this, but yeah...

@michaelwoerister
Copy link
Copy Markdown
Member

I agree, the simpler the better.

@tgross35
Copy link
Copy Markdown
Contributor Author

Having - in revision names seems reasonably common, just based on a quick search https://github.com/search?q=repo%3Arust-lang%2Frust+%2F%40.*revisions%3A.*-%2F&type=code, so it seems like that should work without any special mangling. I just changed the regex to match everything so if all revision names get matched even if invalid, and let them fail elsewhere rather than silently be skipped (doesn’t help if the revision doesn’t exist, but that is a different problem…).

What would you prefer for this PR? I can leave it as-is and add a test, or change back to the original pattern and just add -.

@jieyouxu
Copy link
Copy Markdown
Member

Having - in revision names seems reasonably common, just based on a quick search https://github.com/search?q=repo%3Arust-lang%2Frust+%2F%40.*revisions%3A.*-%2F&type=code, so it seems like that should work without any special mangling. I just changed the regex to match everything so if all revision names get matched even if invalid, and let them fail elsewhere rather than silently be skipped (doesn’t help if the revision doesn’t exist, but that is a different problem…).

What would you prefer for this PR? I can leave it as-is and add a test, or change back to the original pattern and just add -.

Huh, I suppose then it does work with -s. In that case, let's respect the existing accepted revision names (alphanumeric + _ + -), but just update the error annotation regex to accept _ and - in addition to alphanumeric characters only (and not any character). Could you also enforce that revision names can only contain alphanumeric + _ + -, and report an error if revision names don't match the regex? A test is also desirable to verify our behavior here.

@jieyouxu
Copy link
Copy Markdown
Member

But otherwise it seems like an oversight that revisions accept - but not the error annotations (lol). What we really should do in compiletest (in the long term) is to have the revisions parsing be strict about what revision names it accept. Thanks for the fix, helping compiletest be more self-consistent is very helpful.

@tgross35 tgross35 force-pushed the testsuite-multi-rev-regex branch from 4b4673c to 23fea67 Compare April 19, 2024 18:57
@tgross35
Copy link
Copy Markdown
Contributor Author

I changed this PR to just do the simplest thing and match - in addition to what was there before. I plan on refactoring #123882, which I think would be a better place to check that the name is valid.

Since there is no new rejecting behavior, is this okay as-is or do you want a test still? (if so, what would that look like - just a dummy UI test?)

@tgross35 tgross35 changed the title Match any characters in multi-revision comment matchers Match hyphen in multi-revision comment matchers Apr 19, 2024
@jieyouxu
Copy link
Copy Markdown
Member

jieyouxu commented Apr 19, 2024

Since there is no new rejecting behavior, is this okay as-is or do you want a test still? (if so, what would that look like - just a dummy UI test?)

Could you still add a unit test for parse_expected checking that a revision with a name containing - is accepted? I realize that uhh the error reporting here is less than ideal, but for the time being having a regression test might remind future reworks in terms of what revision names can be accepted.

@tgross35
Copy link
Copy Markdown
Contributor Author

Alrightie I added a simple test, let me know if that looks fine.

@tgross35 tgross35 force-pushed the testsuite-multi-rev-regex branch from 23fea67 to feec066 Compare April 19, 2024 19:45
@tgross35
Copy link
Copy Markdown
Contributor Author

Tidy failed so it didn't push before, should be up to date now

@rust-log-analyzer

This comment has been minimized.

Currently, the matcher `//[rev-foo,rev-bar]~` does not get selected by
the regex. Change the matcher to also match strings that contain a `-`.h
@tgross35 tgross35 force-pushed the testsuite-multi-rev-regex branch from feec066 to 282488c Compare April 19, 2024 20:03
@jieyouxu
Copy link
Copy Markdown
Member

Looks good to me, thanks for the patch!

@bors r+ rollup

@bors
Copy link
Copy Markdown
Collaborator

bors commented Apr 19, 2024

📌 Commit 282488c has been approved by jieyouxu

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 19, 2024
@tgross35
Copy link
Copy Markdown
Contributor Author

Thanks!

bors added a commit to rust-lang-ci/rust that referenced this pull request Apr 20, 2024
…iaskrgr

Rollup of 3 pull requests

Successful merges:

 - rust-lang#123409 (Implement Modified Condition/Decision  Coverage)
 - rust-lang#124104 (Fix capturing duplicated lifetimes via parent in `precise_captures` (`impl use<'...>`))
 - rust-lang#124137 (Match hyphen in multi-revision comment matchers)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit f58ef08 into rust-lang:master Apr 20, 2024
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Apr 20, 2024
Rollup merge of rust-lang#124137 - tgross35:testsuite-multi-rev-regex, r=jieyouxu

Match hyphen in multi-revision comment matchers

Currently, the matcher `//[rev-foo,rev-bar]~` does not get selected by the regex. Change the matcher to include `-`.
@rustbot rustbot added this to the 1.79.0 milestone Apr 20, 2024
@michaelwoerister
Copy link
Copy Markdown
Member

Thanks for getting this fixed, everyone!

@tgross35 tgross35 deleted the testsuite-multi-rev-regex branch July 21, 2024 07:14
cmbays added a commit to breezy-bays-labs/mokumo that referenced this pull request May 4, 2026
Adds the CI job that runs the per-handler branch-coverage producer
introduced in earlier waves and feeds its artifact into
scorecard-aggregate.

Side-car design (data layer + workflow stay in lockstep):
- Pinned nightly channel from rust-nightly-coverage-toolchain.toml
  via scripts/nightly-coverage-channel.sh — the rest of the repo
  stays on stable. Removal path is one PR (toml + script + this job
  + the moon task) when rust-lang/rust#124137 stabilizes.
- continue-on-error: true mirrors quality.toml's
  coverage_handler.report_only = true: nightly outage surfaces in
  the CI UI and the scorecard pending-note, never blocks the merge
  queue. Promotion to a hard gate is two paired edits.
- scorecard-aggregate consumes the artifact via download-artifact
  with continue-on-error so a missing file falls through to the
  renderer's "producer pending" pathway.

Reuses existing tools.toml pins (cargo-nextest@0.9.133,
cargo-llvm-cov@0.8.5) — no new pins needed. coverage-handlers is
intentionally NOT in verdict's needs[]: matches the existing
convention for continue-on-error jobs (desktop-e2e, crap-delta).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cmbays added a commit to breezy-bays-labs/mokumo that referenced this pull request May 6, 2026
…tly side-car (#805)

* chore(coverage): #583 — pin nightly-only side car for branch-coverage producer

Branch-level coverage instrumentation (-Zcoverage-options=branch surfaced via
cargo llvm-cov --branch) is nightly-only as of rustc 1.97 (rust-lang/rust
#124137). The repo's stable 1.95.0 pin makes per-handler branch coverage —
the signal Row::CoverageDelta.breakouts.handlers[].branch_coverage_pct names
in the V4 schema — unobtainable without a nightly side channel.

Rather than override rust-toolchain.toml (which would bleed nightly into
every other task), this commit lands the date-pinned channel in a separate
file consumed only by:

  - the new shop:coverage-branches moon task (--branch on nightly, output
    coverage-branches.json — separate artifact from coverage.json so the
    CRAP gate path stays on stable),
  - the coverage-handlers CI job that will follow.

Single source of truth: rust-nightly-coverage-toolchain.toml. Both consumers
read it through scripts/nightly-coverage-channel.sh, so a pin bump is a
single-file edit. The rationale and the four-step removal path (delete file
+ task + script + CI job; add --branch to the existing coverage task) are
documented in the toolchain file's prologue.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(coverage): #583 — coverage-breakouts producer (route walker + LLVM JSON join)

Lifts per-handler branch coverage out of "(producer pending — see #583)"
sentinel territory into a real artifact the scorecard aggregator can
consume. New `coverage-breakouts` bin under `tools/docs-gen/`:

- syn-based route walker (tools/docs-gen/src/coverage/route_walker.rs)
  resolves `(method, url_path, handler_rust_path)` from `.route()` and
  `.nest()` calls. Handles fully-qualified handler refs (`crate::x::y`),
  bare idents resolved via in-file `use` items (with alias support), and
  inline-anonymous nested routers. Records unresolvable findings rather
  than silently dropping them.

- LLVM coverage JSON parser (tools/docs-gen/src/coverage/llvm_cov.rs)
  decodes the `cargo llvm-cov --branch --json` payload (3.x schema as
  emitted on nightly), demangles function names via `rustc-demangle`,
  and computes branch coverage per LLVM convention: count = 2 ×
  branches.len(), covered = sum of (true>0) + (false>0). Partially-
  covered branches (one side hit) register as 1-of-2 — exactly the
  negative-path signal Quinn's blind-spot 3 wants surfaced.

- Producer (tools/docs-gen/src/coverage/producer.rs) discovers crates
  from crates/*/Cargo.toml + apps/*/Cargo.toml, applies the same
  exclusion ledger as crap4rs.toml, joins routes with coverage on Rust
  path equality, and emits CoverageBreakoutArtifact JSON. Diagnostics
  surface every unresolved-handler and unresolvable-route with file +
  line so the operator can fix root cause; the bin exits with code 2
  when any are present (artifact still written for inspection).

- Bin shim (tools/docs-gen/src/bin/coverage-breakouts.rs) is CC=1 by
  the same logic as lcov-dedup and adr-validate: coverage credit lands
  on the library, the bin is a thin arg parser.

Wire schema unchanged. The producer-internal HandlerArtifactEntry
carries rust_path, branch counts, and function_count for diagnostics;
the aggregator (next commit) translates these into the already-defined
HandlerBreakout wire shape (handler + branch_coverage_pct).

Smoke-tested end-to-end against a partial coverage payload (scorecard
crate only): walker discovers 55 routes across kikan + mokumo-shop,
all resolve to coherent Rust paths, and the producer correctly reports
all 55 as unresolved (no kikan/mokumo-shop coverage in the partial
payload) — the exact failure mode CI will hit if a handler is renamed
without the coverage rerun.

Tests: 34 unit tests across artifact, llvm_cov, route_walker,
crap_exclusions, producer (epoch math + crate discovery). Real-fixture
test in llvm_cov is opt-in (skipped when fixture absent so a fresh
checkout still passes).

Also tightens .gitignore — re-includes tools/docs-gen/src/coverage/
since the global `coverage/` rule (for runtime artifacts) was hiding
the producer source.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(scorecard): #583 — wire per-handler breakouts producer artifact into aggregator + threshold gate

Closes the second leg of #583: the aggregator now reads the
`coverage-breakouts.json` producer artifact, populates
`Row::CoverageDelta.breakouts.by_crate[].handlers[]` with `(route,
branch_coverage_pct)` pairs, and runs them through a configurable
threshold gate.

Producer artifact reader: new `crates/scorecard/src/coverage_breakouts.rs`
mirrors the wire types from the producer (under `tools/docs-gen/src/
coverage/artifact.rs`) so the aggregator doesn't take a build-time dep
on docs-gen. Versioned (`ARTIFACT_VERSION_MAX = 1`); a higher-version
artifact is a hard parse error so a producer/consumer skew surfaces
loudly. The wire translation drops producer-internal fields
(`rust_path`, branch counts, `function_count`) — the renderer only
needs route + percentage. Uses `Result<T, String>` rather than `anyhow`
so the scorecard lib's deps-zero-by-default invariant holds.

CLI: `scorecard aggregate` gains `--coverage-breakouts-json <PATH>`.
Optional — when omitted (or the file is missing), the drill-down stays
empty and the existing `(per-handler producer pending — see #583)`
note path still applies.

Threshold gate: new `[rows.coverage_handler]` table in `quality.toml`.
- `warn_pct_below` (default 60.0): handler at-or-below resolves Yellow.
- `fail_pct_below` (default 40.0): handler at-or-below resolves Red.
- `report_only` (default true): when true, gate verdict is informational
  only — does not escalate row status. Defaults to true so a nightly-
  toolchain outage on the producer side cannot block the merge queue.
  Operators flip to false in `quality.toml` once the side-channel's
  reliability is established.

Verdict logic: handler-floor verdict = worst-of across all handlers
(one Red handler drives the row Red). Final row status = max(delta-
driven status, handler verdict) when `report_only = false`. When Red
fires from the handler path, `failure_detail_md` names up to 5
offending handlers + their pcts so the operator sees the actionable
signal without expanding the drill-down.

Wire schema: unchanged (the existing `Breakouts.by_crate[].handlers[]`
shape is what we populate). Operator-config schema bumps the
`additionalProperties:false` count from 7 to 8 (new
`CoverageHandlerThresholds`). Regenerated
`.config/scorecard/quality.config.schema.json` via
`scorecard emit-schema`.

Tests: 5 new aggregate-level tests pin breakouts population, the
report_only-true default no-escalation path, the enforce-mode Red,
the warn-floor Yellow, and the missing-input empty-drill-down path.
6 new coverage_breakouts unit tests pin parse/round-trip, version
rejection, wire translation, iter_handler_pcts, and diagnostics
classification. 256 lib tests pass total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(scorecard): #583 — render per-handler branch coverage drill-down

Closes the third leg of #583. The sticky-comment renderer now surfaces
the breakouts the aggregator populates (Wave C) inside a collapsible
`<details>` block beneath the `Row::CoverageDelta` row.

Layout:

- Handlers grouped by crate (matches the wire shape's `by_crate[]`).
- Within each crate, sorted ascending by `branch_coverage_pct` so the
  most-uncovered handlers float to the top — operator's eye is drawn
  to the actionable end first.
- Each handler row carries a small icon (🔴 ≤40%, 🟡 ≤60%, 🟢 above)
  matching the `[rows.coverage_handler]` quality.toml defaults. The
  numbers live in named constants (`HANDLER_FAIL_PCT`,
  `HANDLER_WARN_PCT`) so a future "render thresholds from artifact"
  upgrade has one grep target. Operators tuning the gate threshold
  will see slight cosmetic drift between icons and verdict — that's
  acceptable; the row's status is the authoritative signal.

Producer-pending fallback: when the artifact carries no handlers (no
`--coverage-breakouts-json` supplied to the aggregator, or producer
artifact is empty), the renderer emits an inline italic note linking
to issue #583 — wires the existing `(per-handler producer pending —
see #583)` documented behavior that types.d.ts already promised but
the renderer hadn't implemented yet.

Tests: 13 new vitest cases pin (a) the threshold constants matching
quality.toml defaults, (b) the empty/undefined breakouts → pending
note path, (c) the populated → details block path, (d) ascending
sort by pct, (e) icon mapping at the inclusive boundaries (40.0 →
🔴, 60.0 → 🟡), (f) per-crate grouping with multiple crates, (g)
singular nouns at count=1, (h) integration through
renderScorecardMarkdown. 78 vitest cases pass total across the
scorecard renderer suite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci(scorecard): #583 — coverage-handlers nightly side-car job

Adds the CI job that runs the per-handler branch-coverage producer
introduced in earlier waves and feeds its artifact into
scorecard-aggregate.

Side-car design (data layer + workflow stay in lockstep):
- Pinned nightly channel from rust-nightly-coverage-toolchain.toml
  via scripts/nightly-coverage-channel.sh — the rest of the repo
  stays on stable. Removal path is one PR (toml + script + this job
  + the moon task) when rust-lang/rust#124137 stabilizes.
- continue-on-error: true mirrors quality.toml's
  coverage_handler.report_only = true: nightly outage surfaces in
  the CI UI and the scorecard pending-note, never blocks the merge
  queue. Promotion to a hard gate is two paired edits.
- scorecard-aggregate consumes the artifact via download-artifact
  with continue-on-error so a missing file falls through to the
  renderer's "producer pending" pathway.

Reuses existing tools.toml pins (cargo-nextest@0.9.133,
cargo-llvm-cov@0.8.5) — no new pins needed. coverage-handlers is
intentionally NOT in verdict's needs[]: matches the existing
convention for continue-on-error jobs (desktop-e2e, crap-delta).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(scorecard): #583 — handler-coverage BDD specs + nightly side-car notes

Wave 6 (BDD): four @future scenarios under a new "Per-handler
drill-down" rule in scorecard_display.feature documenting the V4.1
contract — populated breakouts render a per-crate <details> block
sorted ascending, empty breakouts render a producer-pending note,
worst-of-handler thresholds escalate the row only when report_only
is disabled, and the report-only default keeps the gate purely
informational. @future-tagged so bdd-lint --exclude-tags treats them
as documented placeholders ahead of step-def implementation.

Wave 7 (docs):
- COVERAGE.md gains a "Per-handler branch coverage (mokumo#583)"
  section walking the three pipeline stages (capture → producer →
  aggregator + threshold gate), the non-blocking design, the two
  paired edits to promote to a hard gate, and the one-PR removal
  path when branch coverage stabilizes on rustc.
- CHANGELOG.md "Unreleased / Added" entry for V4.1 — schema
  additive (no schema_version bump), new operator surface
  ([rows.coverage_handler] in quality.toml), new CI job
  (coverage-handlers consuming pinned nightly).
- AGENTS.md §Dep-graph and verdict assertions gains a "Nightly
  toolchain side-cars" callout codifying the side-car convention
  used by mokumo#583 so future nightly-only features follow the
  same registry pattern.
- quality.toml gains a documented [rows.coverage_handler] section
  with the M00 60% / 40% defaults so operators can see and tune the
  handler thresholds without diving into Rust source.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(coverage-breakouts): #583 — resolve sibling-module handler refs

The route walker mis-resolved multi-segment handler refs whose head
named a sibling module declared in the same file. Smoke-running the
producer against the real workspace surfaced six kikan auth handlers
collapsing to literal `login::login` / `me::me` / `logout::logout` /
`recover::recover_*` instead of the fully-qualified
`kikan::platform::v1::auth::<sub>::<fn>` paths the LLVM coverage
artifact carries — the join in producer::run() then logged them as
`unresolved_handlers` and dropped them from the wire breakouts.

Root cause: `FileVisitor::resolve_path()` had three branches —
`use_map` hit, single-ident module-local, and multi-segment "external
crate". The third branch fired for `pub mod login; ... .post(login::login)`
patterns because `login` matched neither a `use` import nor was a
single-ident.

Fix: track sibling module declarations via a new `module_decls:
HashSet<String>` field populated from `visit_item_mod` (handles both
`mod foo;` and inline `mod foo { ... }`). In `resolve_path()`, a
multi-segment head whose value is in `module_decls` now resolves under
`<file_module_path>::<head>::<tail>` — the same shape as the
single-ident module-local branch, just with a tail.

Pinning test: `resolves_sibling_module_handler_via_mod_decl` writes a
two-file fixture mimicking the real auth/mod.rs + auth/login.rs layout
and asserts the resolved path is `demo::auth::login::login`. Re-running
the producer against the workspace post-fix shows the auth handlers
correctly resolved (still on the unresolved list because the synthetic
LLVM JSON only has one function — the path is now correct, the join
key just has nothing to join against until real coverage data lands).

The "external-crate qualified" fallback branch is preserved for
genuinely external multi-segment references (e.g. `axum::Router`); no
existing walker test relied on the previous behavior for sibling-mod
patterns.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* quality(coverage): #583 — bring crap-rust gate green for coverage-breakouts surface

CI surfaced 7 functions over CRAP threshold 15 (worst 272.0) — all in
the new producer surface added by this PR. Two compounding causes:

1. `bin/coverage-breakouts::main` and `coverage::producer::run` reported
   0% coverage because cargo-llvm-cov nextest doesn't credit `src/bin/`
   code, and `run()` had no end-to-end test exercising it through the
   library — the CRAP formula `CC + CC²·(1-cov)³` exploded.

2. Five route-walker / producer functions had high CC (22..31) from
   tightly-packed match arms and let-else early returns.

Fixes:

- New `docs_gen::coverage_breakouts` lib module mirroring `validate.rs`:
  `parse_args` / `Args` / `execute` with 13 unit tests covering CLI
  parse + happy/error execute paths. Bin shrinks to a 4-line shim.
- Three end-to-end `run()` tests in `producer.rs` building tempdir
  workspaces with synthetic LLVM JSON: happy join, unresolved-handler
  diagnostic, and crap4rs.toml exclusion path. Coverage on `run` jumps
  0% to 93%, dropping CRAP 272 to 16.
- `route_walker::collect_method_router` (CC 31) split into
  `head_call_method_handler` + `chain_call_method_handler` per chain
  position.
- `FileVisitor::resolve_path` (CC 22) dispatches keyword heads
  (`crate` / `self` / `super`) to a `join_path(prefix, tail)` helper
  and routes the non-keyword case to `resolve_other_head`.
- `route_walker::walk` (CC 29) decomposes into `parse_crate_sources` /
  `scan_src_tree` / `visit_one_file` / `emit_routes` over a
  `ParsedSources` accumulator.
- `route_walker::file_module_path` (CC 15) delegates per-component
  classification to `file_path_segment`.
- `producer::discover_crates` (CC 23) splits into
  `scan_subdir_for_packages` + `read_package_name`.
- `producer::run` (CC 16) decomposes into `gather_walker_inputs` /
  `join_routes_with_coverage` / `join_one_crate` / `lift_unresolvable`.

Local crap4rs against tools/docs-gen drops 7 to 0 above threshold; worst
14.4 (PASS). All 143 docs-gen lib tests pass.

The coverage-handlers job's separate failure (LLVM SIGSEGV in
`getInstantiationGroups` on nightly-2026-04-15) is upstream nightly LLVM
and already correctly soft-gated (`continue-on-error: true` + not in
verdict's `needs:`), so it doesn't block the rollup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci(coverage): #583 — narrow coverage-branches scope + bump nightly to dodge llvm-cov SIGSEGV

The `coverage-handlers` job's previous run crashed during the post-test
merge step: `llvm-cov export` SIGSEGV in
`llvm::coverage::CoverageMapping::getInstantiationGroups` (LLVM 22.1
shipped with `nightly-2026-04-15`). All 968 tests passed; the bug is
upstream in the instrumentation pipeline, not in our code.

Two cheap mitigations:

1. Bump the pinned nightly to `nightly-2026-05-01`. Rust's bundled LLVM
   rolls weekly and the bug may be already fixed in a newer rev.

2. Replace `--workspace --exclude {mokumo-desktop,kikan-tauri,kikan-admin-ui}`
   with `-p kikan -p kikan-cli -p kikan-spa-sveltekit -p mokumo-shop` —
   only crates that register `Router::route(...)` / `.nest(...)` need
   instrumentation for the producer's per-handler join. Drops the count
   of `-object` files llvm-cov merges from 11 to 4, narrowing the surface
   for the buggy code path. The route walker still walks every crate
   (it's a source-level scan, independent of coverage); coverage-less
   crates simply contribute zero handler lookups, which the producer
   already handles cleanly.

If both fail to clear the bug, `coverage-handlers` stays soft-gated
(`continue-on-error: true`, not in verdict's `needs:`) and the rollup
remains green — same containment we already have.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review(coverage): #583 — address CodeRabbit feedback on PR #805

Addresses 10 of the 12 review comments. Skips:
- moon.yml --exclude annotation request: stale, the prior commit
  (33d71a3) replaced --exclude flags with positive -p selection.
- route_walker multi-mount prefix fan-out: real bug but heavy lift
  (HashMap value to Vec, DFS over parent_of, multi-prefix emit). Mokumo's
  current Axum topology mounts each builder fn at most once so the bug
  is latent; deferred to a follow-up issue rather than expanding this
  PR's scope further.

Fixes:

- Branchless handlers report 100.0% (coverage::llvm_cov,
  coverage::artifact). A handler with no conditionals is vacuously
  fully covered; encoding the empty case as 0.0 would trip the
  worst-of [rows.coverage_handler] gate against handlers with
  nothing to cover.
- Producer exit_code follows the documented 0/2 contract
  (coverage::producer::run). Previously returned 0/1 internally and
  coverage_breakouts::execute re-coerced 1->2; library callers now get
  the right value directly without the translation layer.
- Cargo package names preserved in artifact (coverage::producer,
  coverage::route_walker). The walker now carries both the package
  name (e.g. mokumo-shop, used in RouteEntry.crate_name and the
  artifact's by_crate[].crate_name) and the Rust ident form
  (mokumo_shop, used for path resolution against demangled symbols).
- Inline .nest() targets walked once
  (coverage::route_walker::FileVisitor::visit_expr_method_call).
  handle_nest_call already recurses into target_expr with the prefix
  on the inline-prefix stack; falling through to the default visitor
  walked it a second time without the prefix and emitted duplicate
  routes. Now we descend into the receiver chain explicitly and skip
  the default args walk for the nest case.
- Yellow handler downgrade carries actionable detail
  (Row::coverage_delta_yellow, aggregate::handler_yellow_failure_detail).
  Yellow constructor now takes Option<String>; when the per-handler
  gate causes the downgrade (!report_only and handler_status == Yellow),
  the row carries a detail naming the offending handlers in the
  (warn, fail] band. Plain delta-driven Yellow stays detail-less.
- Offender count corrected after take(5)
  (aggregate::coverage_red_failure_detail). Previously counted
  post-take so any breakout >5 reported "5 handler(s)"; now totals
  before truncation and appends "and N more" when applicable.
- coverage_handler thresholds validated at config-load
  (aggregate::validate_threshold_config). NaN/Inf, out-of-range
  ([0, 100]), and inverted (fail > warn, making Yellow unreachable)
  configs now fail loud with structured error messages — same
  treatment the other threshold rows already get.
- JSON schema constrains pct_below thresholds to [0, 100]
  (threshold::CoverageHandlerThresholds schemars annotation +
  regenerated quality.config.schema.json). The config validator above
  enforces it at load; the JSON schema enforces it for ajv at the
  schema-drift gate.
- Nightly side-car continue-on-error: true carries the required
  tracked: annotation (.github/workflows/quality.yml). Per the
  exclusion-tracking contract added in #740, deliberate exclusion
  sites need an inline tracked: <repo>#<n> — <reason> marker.
- awk field separator includes single quotes
  (scripts/nightly-coverage-channel.sh). Inline doc claimed
  quote-tolerance for both ' and " but the awk -F only included ";
  a single-quoted channel would have leaked the surrounding quotes.

Local crap4rs against tools/docs-gen stays at 0 above threshold; all
404 docs-gen + scorecard tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* quality(scorecard): #583 — decompose validate_threshold_config

Extracted four per-section helpers — validate_coverage_section,
validate_count_sections, validate_ci_wall_clock_section,
validate_coverage_handler_section — so the entry function is a
linear orchestrator (CC=5, CRAP=5.0) instead of carrying every
section's branch fan-out (CC=15, CRAP=17.15 on the previous
revision after the coverage_handler validation block was added).

Largest helper post-split is CC=6 / CRAP=13.15, comfortably under
the threshold of 15. Behaviour is identical; the same error
strings are returned for the same inputs, validated by 256 lib
tests still passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review(coverage): #583 — address CodeRabbit second-pass feedback

Six fixes on revision e387ad9 — five correctness-leaning rustdoc /
behaviour gaps and one boundary test set that pins the new resolver:

1. route_walker.rs: visit_one_file no longer silently skips files whose
   `syn::parse_file` errors. Every `.rs` under a crate's `src/` is
   expected to be valid Rust; a silent skip would let the producer
   emit a partial artifact under exit-0 cover — exactly the drift the
   producer's loud-by-design diagnostics exist to surface.

2. llvm_cov.rs: module docs updated to (a) note `parse_str` accepts
   schema 2.x and 3.x, not just 2.x, and (b) document the 100%
   branchless rule on `branch_coverage_pct` so callers reading the
   rustdoc don't trip on the old "0.0% when total == 0" claim.

3. artifact.rs: doc on `CrateHandlerSet::crate_name` now states the
   field carries the Cargo package name (`mokumo-shop`) — matching
   the producer's pass-through behaviour after the previous round —
   instead of the stale Rust-ident form (`mokumo_shop`).

4. nightly-coverage-channel.sh: `awk` regex now tolerates indented
   `  channel = "..."` keys and matches the value by `nightly-`
   prefix rather than fixed field index, so a TOML formatter that
   indents under `[toolchain]` doesn't silently fail the side-car.

5. aggregate.rs: `read_coverage_breakouts` no longer returns
   `Ok(None)` for an explicit `--coverage-breakouts-json <missing>`.
   Operator-supplied paths now fail loud, matching the contract of
   `read_pr_meta`, `read_ci_wall_clock_json`, and the rest of the
   reader family. A path typo on a CI invocation will surface
   instead of silently degrading the verdict to a pending stub.
   Pinned by two new regression tests.

6. threshold.rs: 12 boundary tests for `resolve_coverage_handler`,
   the only resolver here that lacked them. Covers empty input,
   each side of warn / fail (using `f64::next_up()` for the
   smallest representable step at the boundary magnitude), the
   worst-of fold's promotion of one-Red over Greens, and the
   short-circuit-on-Red contract via a NaN-after-Red probe.

Test deltas: scorecard lib 256 → 270 passing; clippy clean across
both crates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Christopher Bays <cmbays@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-testsuite Area: The testsuite used to check the correctness of rustc S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-bootstrap Relevant to the bootstrap subteam: Rust's build system (x.py and src/bootstrap)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants