Skip to content

fix(codecov): report every source file and keep uncovered code in the totals - #393

Merged
roxblnfk merged 4 commits into
1.xfrom
fix/codecov-coverage-gaps
Oct 13, 2026
Merged

roxblnfk merged 4 commits into
1.xfrom
fix/codecov-coverage-gaps

Conversation

@roxblnfk

Copy link
Copy Markdown
Member

What was changed

  • Files under src that no test loaded now appear in the reports with their executable lines uncovered. Their lines are estimated from tokens, without loading the file.
  • #[Covers] narrows what a test is credited with instead of cutting the rest out of the report: lines outside the targets stay as uncovered.
  • Lines without code are dropped from driver data, so a file no longer shows a phantom uncovered line at its end.
  • @codeCoverageIgnore, @codeCoverageIgnoreStart/End and the docblock form leave code out of the reports, read the way php-code-coverage reads them.

Why?

Untested classes and narrow #[Covers] both inflated the total, and the last line of many files showed up as uncovered.

Review notes

  • A driver puts a line only where a token starts, except the file's implicit top-level return, which lands on the last line. Checked against Xdebug 3.4 and PCOV on 300+ files: no other line without a token is ever reported. The filter keeps a reported line only when a code token starts on it, so it keeps the blank line an interpolated heredoc starts on, which dropping every blank line (as PHPUnit does) loses.
  • The estimator for unloaded files matches Xdebug at 97.4% recall and 98.8% precision on core and plugin/*/src. Known driver disagreements are listed in ExecutableLinesTest.
  • Two deliberate differences from php-code-coverage for ignored code: an End closes its range, and a branch is dropped only when it lies wholly on ignored lines, with the exits into it. php-code-coverage drops every branch an ignored line touches, which took out whole functions' branch data.

Checklist

… uncovered

`#[Covers]` now narrows what a test is credited with instead of what reaches the report: a line or branch the test ran outside its targets turns unexecuted rather than disappearing, so code no other test touches still counts against the total.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
A driver reports only the files PHP compiled during the run, so a class without tests was missing from the report and the total came out higher than the share of covered source. Every PHP file under `src` that no test loaded is now listed with its executable lines uncovered. The lines are estimated from the file's tokens, since loading the file would run its top-level code; the estimate is checked against Xdebug and PCOV on fixtures covering the constructs whose line numbering they disagree on.

Closes #383

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
Both drivers report the implicit `return` of a file's top-level code on its last line: PCOV as unexecuted when the file was loaded outside a coverage window, Xdebug as executed when it was loaded inside one. Apart from that line, a driver reports a line only where a token starts, so a reported line on which no code token starts is dropped from the raw data, and so is a last line that only closes, such as `}` or `);`, unless it ends a function body that falls through. Unlike dropping every blank line, as PHPUnit does, this keeps the blank line an interpolated heredoc starts on and the closing brace of a function that ends the file. The lines are cached per file, since the cleanup runs on every collection.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
@roxblnfk
roxblnfk requested a review from a team as a code owner October 13, 2026 19:04
…verage

The PHPUnit annotations are read the way php-code-coverage reads them, so a migrated project gets the same lines: a comment holding only `@codeCoverageIgnore` ignores its line, `@codeCoverageIgnoreStart` and `@codeCoverageIgnoreEnd` ignore the lines between them, and a docblock mentioning the annotation ignores the class, trait, enum, function or method it documents. Ignored lines drop out of the report instead of counting as uncovered, and files no test loaded leave them out of their estimate.

Two differences from php-code-coverage. An `End` closes its range, so a stray second `End` ignores only its own line rather than reaching back to the earlier `Start`. And a branch is dropped only when it lies wholly on ignored lines, along with the paths through it and the exits into it: php-code-coverage drops every branch an ignored line touches, so a single ignored line inside a function took out its entry branch and every decision with it, while an ignored guard left its `if` half covered.

The annotations are found in the same token pass that drops the lines without code, cached per file for the run.

Closes #392

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
@roxblnfk
roxblnfk force-pushed the fix/codecov-coverage-gaps branch from b928540 to 0d9e0b8 Compare October 13, 2026 19:23
@roxblnfk
roxblnfk merged commit 2e97ad3 into 1.x Oct 13, 2026
27 of 29 checks passed
@roxblnfk
roxblnfk deleted the fix/codecov-coverage-gaps branch October 13, 2026 19:30
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.

Leave code marked with @codeCoverageIgnore out of coverage Coverage reports omit source files that no test loads, inflating the total

1 participant