Skip to content

fix(coverage): don't panic in summary reporter without a common root#35211

Merged
bartlomieju merged 2 commits into
mainfrom
claude/issue-relevance-check-bicav8
Jun 14, 2026
Merged

fix(coverage): don't panic in summary reporter without a common root#35211
bartlomieju merged 2 commits into
mainfrom
claude/issue-relevance-check-bicav8

Conversation

@lunadogbot

Copy link
Copy Markdown
Contributor

Summary

Fixes the panic reported in #30924, which still reproduces on Deno 2.7.7 (and on main):

thread 'main' panicked at cli\tools\coverage\reporter.rs:206:38:
called `Option::unwrap()` on a `None` value

SummaryCoverageReporter::done called summary.get("").unwrap() on the result of collect_summary. collect_summary returns an empty map (without the "" root entry) whenever the file reports can't be reduced to a common file-system root — i.e. when find_root(...).and_then(|r| r.to_file_path().ok()) yields None.

On Windows this happens when coverage spans multiple drive letters: the temp test files live under C:\Users\RUNNER~1\AppData\Local\Temp\ while the workspace is on D:\.... The character-wise common prefix collapses to file:///, which has no drive letter, so to_file_path() fails, collect_summary returns an empty map, and the reporter panics. This matches the panic URLs and D:\a\...\cov\... paths in the issue, and explains why it only reproduces on Windows.

(The simpler "all source files were skipped → empty file_reports" case is already handled gracefully upstream in cover_files, which returns No covered files included in the report.)

Changes

  • Bail out of the summary table when there is no "" root entry instead of unwrapping.
  • Guard the adjacent .max().unwrap() on the entries list (would panic on an empty list) with .unwrap_or(0).
  • Add a regression unit test that drives done with reports that have no common file-system root and asserts it doesn't panic. The test fails before this change and passes after.

Test plan

  • cargo test -p deno --lib summary_reporter_does_not_panic — passes with the fix, panics without it.
  • cargo clippy -p deno --lib — clean.
  • rustfmt — no changes.

Fixes #30924

https://claude.ai/code/session_01L957ioUbJpxDa8Sv9yCtEq


Generated by Claude Code

When coverage reports can't be reduced to a common file-system root
(e.g. on Windows when coverage spans multiple drive letters such as a
C:\…\Temp test file and a D:\…\workspace file), collect_summary
returns an empty map. The summary reporter then called .unwrap() on the
missing "" root entry and panicked.

Bail out of the summary table when there is no root entry, and avoid a
second panic on the empty .max() call.

Fixes #30924
@bartlomieju

Copy link
Copy Markdown
Member

Review

Traced the full path in reporter.rs / util.rs — solid, well-targeted fix. The diagnosis in the description is accurate: collect_summary explicitly return HashMap::new() when find_root(...).to_file_path() is None, and otherwise always inserts the "" entry, so summary.get("") is the exact and only failure mode. The fix targets the right line.

Correctness

  • Change 1 (let Some(root_stats) = summary.get("") else { return }) — correct and complete. This is the real fix and handles the only path that produces a missing "" entry. ✓
  • Change 2 (.max().unwrap().unwrap_or(0)) — harmless but effectively dead code. entries filters summary to file_text.is_some(); the "" root entry has file_text: None (excluded), while every leaf node inserted per report carries file_text: Some(..). We only reach this line when the map is non-empty, which requires find_root to have succeeded, which requires non-empty file_reports — so there's always ≥1 leaf entry and .max() never returns None. Reasonable defense-in-depth (and node_max = 0 then gets .max("All files".len()) so the table still renders), but it's unreachable given Change 1. Worth a one-line comment saying so, otherwise it reads as "what case is this protecting against?"
  • The unwrap()s left in collect_summary (report.url.to_file_path().unwrap(), strip_prefix(&root).unwrap()) are still fine — they only run after find_root produced a usable file-path root. Correctly untouched.

Test coverage

  • The regression test is hermetic and clever: a https://example.com/main.ts URL makes to_file_path() fail, driving collect_summary to the empty-map branch without a real multi-drive Windows setup. Traced it: find_rootSome("https://example.com/").to_file_path().ok()None → empty map → get("") is None → early return. Genuinely reproduces the panic on main and passes with the fix. ✓
  • Gap: because the test hits the early return, it exercises only Change 1. Change 2's .unwrap_or(0) is never reached (and per above likely can't be). Acceptable, just noting.

Suggestions (all minor, non-blocking)

  • UX: silently printing nothing means a user who ran deno coverage and expected a summary gets no output and no explanation. Consider a log::warn! like "Skipping coverage summary: reports span multiple file-system roots" (on stderr, so parsed stdout stays clean) so the missing table isn't mysterious.
  • Comment on Change 2 noting it's defensive/unreachable given the get("") guard, so future readers don't puzzle over which input triggers it.
  • Optional deeper fix (out of scope): the root cause is find_root's character-wise prefix in util.rs, which is fragile — it can slice a URL mid-path-segment, not just at drive boundaries. A path-segment-aware common root (or per-drive grouping) would make the summary work across drives rather than be skipped. Reasonable to defer, but worth a tracking note.

Verdict

LGTM. Small, correctly-targeted, well-explained panic fix with a genuine regression test. Only asks are a clarifying comment on the (unreachable) second guard and ideally a user-facing message instead of silent no-output — neither blocking.

Address review feedback:
- Emit a log::warn! explaining why no summary table is printed when the
  reports span multiple file-system roots, instead of silently producing
  no output.
- Document that the adjacent .unwrap_or(0) is defense-in-depth and that
  entries is always non-empty once the "" root entry is present.
@bartlomieju

Copy link
Copy Markdown
Member

Pushed 3644e5d addressing the two review nits:

  • Added a log::warn! ("Skipping coverage summary: reports span multiple file-system roots") so the missing summary table isn't silent and unexplained.
  • Documented that the adjacent .unwrap_or(0) is defense-in-depth — entries is always non-empty once the "" root entry is present.

cargo test -p deno --lib summary_reporter_does_not_panic passes, clippy and rustfmt clean. Should be ready to land.

@bartlomieju
bartlomieju enabled auto-merge (squash) June 14, 2026 10:31
@bartlomieju
bartlomieju merged commit 9cf4f7d into main Jun 14, 2026
136 checks passed
@bartlomieju
bartlomieju deleted the claude/issue-relevance-check-bicav8 branch June 14, 2026 11:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Deno 2.5.3 on Windows fails to generate coverage files intermittently

3 participants