Skip to content

TIA: a reverted file stays in lastRunTree forever when no coverage driver is present, so every subsequent run runs the full suite #1864

Description

@studioycm

Summary

With a TIA graph present and no coverage driver enabled, a file that was modified during
one run and then reverted is treated as changed by every run afterwards, indefinitely. Each
of those runs takes the no coverage driver branch, which runs the whole suite and returns
before the snapshot that would clear the stale entry. The result is a permanent
full-suite fallback with a WARN that reads like a one-off.

Observed (pest 5.1.0, PHP 8.4.23, Xdebug present but xdebug.mode empty)

  1. Record a graph with XDEBUG_MODE=coverage vendor/bin/pest --tia.
  2. Edit one broadly-covered file, run XDEBUG_MODE=coverage vendor/bin/pest --tia --filtered
    to completion, then git checkout -- that file. Tree is clean and HEAD is unchanged
    from recorded_at_sha.
  3. vendor/bin/pest --tia (no XDEBUG_MODE):
WARN  Detected PHP source changes but no coverage driver is available.
─ Running the full suite to avoid using a stale dependency graph.
Tests: 2032 passed        Duration: 361.80s
  1. Run it again, still a clean tree — identical WARN, full suite again, 353.89s.
  2. graph.json shows the cause: the baseline's tree holds exactly one entry, the
    pre-revert hash of the file edited in step 2.
  3. With a driver enabled, the same clean tree still reports
    164 affected test files (from 1 changed file).

Mechanism

  • ChangedFiles::filterUnchangedSinceLastRun() (src/Plugins/Tia/ChangedFiles.php:27-50)
    seeds its candidate set with every file in lastRunTree, not only the files git
    reports, and keeps any whose current hash differs from the snapshot. A reverted file
    differs, so it is reported as changed on a clean tree.
  • setLastRunTree() is called only from Tia.php:639, :727 and :1654 — all on paths
    that complete a record or a replay.
  • Tia::enterReplayMode() returns at :1051, inside the
    $hasProjectPhpSourceChanges && ! $coverageAvailable branch, before reaching any of them.

So the only branch that can clear the stale entry is unreachable from the state that the
stale entry itself forces you into, unless you install/enable a driver.

Impact

A contributor without pcov/Xdebug who has ever had a TIA graph gets full-suite runs forever,
with a warning that suggests a transient condition. --tia is silently a no-op plus
overhead for them.

Suggested fix

Snapshot the tree on the no-driver path too, or drop entries from lastRunTree once their
file matches its committed content. Either removes the trap without weakening the guard —
the guard's purpose (do not filter using a stale graph) is served by running the full suite,
which already happened.

Provenance note

The stuck state and both full-suite runs are directly observed. That a completed run
with a driver clears the entry is inferred from the source above and not measured — it
costs a 27-minute run on the reporter's suite and the verdict it fed did not turn on it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions