Skip to content

ci(coverage): permanent — per-symbol uncovered list + HTML artifact on heavy-gate failure #184

Description

@TaprootFreak

Background

The heavy CI gate runs cargo llvm-cov nextest --fail-under-lines 100 --fail-under-functions 100. When it fails, the existing Show missing coverage on gate failure step emits:

  1. cargo llvm-cov report --show-missing-lines (text)
  2. cargo llvm-cov report --json | jq (per-file summary of files below 100%)

That output tells you which file dropped below 100% — but not which symbol or which line.

Problem (concrete example from #161)

The gate failed on feat/jobs-api-core with the per-file summary:

node/src/router.rs   1232 regions  7 missed  99.43%
                     84 functions  1 missed  98.81%
                     1105 lines    1 missed  99.91%

The --show-missing-lines block listed only program-plonky2/... filesrouter.rs was absent from the per-file uncovered listing despite being the file that failed the gate. This is a quirk of --show-missing-lines interacting with --ignore-filename-regex: the listing surface and the gate surface use different filter semantics, and partial-file failures get elided.

To find the actual gap, the only path forward was:

  • Either re-run cargo llvm-cov locally (≈50 min on an M3 Ultra; ≈hours on developer hardware)
  • Or guess based on the diff and add speculative tests, push, wait another 50 min, repeat

Both burn shared self-hosted runner time and operator attention; the second one is unreliable (we tried it: three of the four guesses were wrong).

What this issue tracks

The fix landed alongside #161 (commit 45836ac). Filing as a separate issue so the change is documented as a stand-alone diagnostic improvement — useful in every future coverage failure, not just the one that motivated it.

The failure-only step now also emits:

1. Per-symbol uncovered function list

cargo llvm-cov report --release --json --ignore-filename-regex "$IGNORE" \
  | jq -r '.data[0].functions[]
      | select(.count == 0)
      | "\(.filenames[0]):\(.regions[0][0])\t\(.name)"' \
  | sort -u

Emits one line per uncovered function as file:line\tmangled_symbol_name. The mangled symbol is recoverable via rustfilt (or by reading the source at the line). For the #161 failure the relevant line was unambiguous:

/Users/dfxai/.../node/src/router.rs:1465  <closure>

2. Full HTML coverage report uploaded as a CI artifact

cargo llvm-cov report --release --html \
  --output-dir target/llvm-cov-html \
  --ignore-filename-regex "$IGNORE"

Plus a follow-up step:

- name: Upload coverage HTML report on gate failure
  if: failure()
  uses: actions/upload-artifact@v4
  with:
    name: llvm-cov-html-${{ github.run_id }}-${{ github.run_attempt }}
    path: target/llvm-cov-html
    retention-days: 14

The operator downloads the artifact, opens index.html, clicks through to the failing file, and sees red regions / red lines / red functions at a glance. Same UX as a local cargo llvm-cov --open without paying the reproduction cost.

Result

Before After
Time to locate a missed line 50 min (local reproduce) or several push-iterations 30 seconds (download artifact, scroll to red)
Operator info on failure Per-file percent Per-file percent + per-symbol list + clickable HTML
Shared runner load on failure-iteration 1 heavy gate per guess 1 heavy gate total

How to use (for the next coverage failure)

  1. Open the failing Tests + Coverage Gate job
  2. Scroll to --- llvm-cov report: uncovered functions (per-symbol) --- for the symbol list, OR
  3. Open the run summary page → Artifactsllvm-cov-html-{run_id}-{attempt} → unzip → html/coverage/.../<file>.rs.html
  4. The red regions/lines/functions are the missing coverage

Cost

The two added steps run only on if: failure() — green CI pays nothing. HTML generation + artifact upload add ≈10–30 s to the post-failure path, dwarfed by the gate's existing 44–50 min runtime.

Why this took until now

The 100% line + function coverage gate has been in place for months; the failure diagnostics were originally good enough because gate failures were rare (full file uncovered → obvious). Once we started hitting partial-file failures (closures, single uncovered branches), --show-missing-lines proved insufficient and the absence of an HTML artifact forced expensive local re-runs. The new diagnostics close that gap permanently.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestobservabilityMetrics / logging / alertingtestingTest coverage / fuzz / property testing

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions