Skip to content

fix: stale cached coverage of changed files survives the Tia coverage merge as phantom uncovered lines - #1792

Open
AlessioGiacobbe wants to merge 1 commit into
pestphp:5.xfrom
AlessioGiacobbe:fix/tia-coverage-merge-stale-changed-files
Open

fix: stale cached coverage of changed files survives the Tia coverage merge as phantom uncovered lines#1792
AlessioGiacobbe wants to merge 1 commit into
pestphp:5.xfrom
AlessioGiacobbe:fix/tia-coverage-merge-stale-changed-files

Conversation

@AlessioGiacobbe

Copy link
Copy Markdown

The bug

When Tia piggybacks coverage (--tia --coverage), the merged report contains phantom "executable but uncovered" lines for every changed file, at the file's old line numbers, alongside the live data at the new line numbers. Coverage for changed files is deflated on every filtered run, and lines that are now comments/blank can show up as uncovered executable lines.

Root cause

Two behaviours compose into the bug:

  1. CoverageMerger::stripCurrentTestsFromCached() removes the re-run test IDs from the cached baseline's line entries, but keeps the line keys — an entry like [42 => ['testA']] becomes [42 => []], which in ProcessedCodeCoverageData semantics means executable but uncovered.
  2. ProcessedCodeCoverageData::merge() (phpunit/php-code-coverage) never removes lines that exist only in the destination side — any line missing from $newData is skipped via continue.

For a changed file, every test that covered it is in the affected set and has just been re-run, so the live report already carries the file's entire coverage at the new line numbers. The cached entries at the old line numbers are pure staleness — but nothing invalidates them, so the stripped-to-empty entries survive the merge as phantom uncovered lines. Cached functionCoverage of changed files (e.g. renamed functions) survives the same way, and so does the data of deleted files.

The fix

Invalidate the cached data of changed files before merging:

  • Tia::enterReplayMode() persists the changed-files list (the same one fed to Graph::affected(), as absolute paths) under a new state key, coverage.changed.json. The key is cleared alongside the marker write in handleParent() so a list from an aborted run can never leak into a later one.
  • CoverageMerger::applyIfMarked() consumes that key and drops those files from the cached baseline's line and function coverage before the strip + merge. Since the affected set re-runs every test that covered a changed file, the live report fully re-measures it — the merge then simply copies the live data over.

Unchanged files keep the existing behaviour: rerun test IDs are stripped from their cached entries and re-attributed from the live data, while tests outside the affected set keep their cached attribution.

Tests

tests/Unit/Plugins/Tia/CoverageMerger.php (new):

  • a changed file with shifted line numbers ends up with only the live data — no phantom entries at the old line numbers, in the report, the updated cache, or functionCoverage (fails on 5.x with 10 => [], 12 => [] surviving the merge);
  • rerun tests are still re-attributed on unchanged files, and non-rerun tests keep their cached attribution;
  • with no cache yet, the current run becomes the baseline and the changed list is consumed.

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.

1 participant