Skip to content

fix(tia): fingerprint the configuration file the run uses - #1924

Open
cyppe wants to merge 2 commits into
pestphp:5.xfrom
cyppe:tia-fingerprint-active-configuration
Open

cyppe wants to merge 2 commits into
pestphp:5.xfrom
cyppe:tia-fingerprint-active-configuration

Conversation

@cyppe

@cyppe cyppe commented Sep 18, 2026 •

Copy link
Copy Markdown

What:

  • Bug Fix
  • New Feature

Description:

The Tia fingerprint hashed phpunit.xml and phpunit.xml.dist from the project root, and only when git tracks them. So:

  • A graph recorded with pest --tia is replayed by pest --tia -c phpunit.integration.xml, even though the two files can select different suites, groups, bootstrap and source.
  • Editing the file passed with -c never invalidates its graph.
  • Editing a phpunit.xml that git ignores never invalidates the graph.
  • phpunit.dist.xml, which PHPUnit also looks for, is not considered.

This is a problem for projects that run several stages with their own configuration files and pest()->tia()->directory() per stage. Today they need a watch() pattern per configuration file, and even that only re-runs tests, it does not invalidate the graph.

This PR resolves the configuration file the run actually uses and fingerprints its content, tracked or not:

  • Tia\ConfigurationFile::fromArguments() passes only the configuration arguments (-c, --configuration, --configuration=, --no-configuration) to PHPUnit's own CliArguments\Builder and XmlConfigurationFileFinder. That gives the same lookup PHPUnit uses: a file, or a directory containing phpunit.xml, phpunit.dist.xml or phpunit.xml.dist. The Configuration plugin runs before Tia, so the arguments always name the file in use, including the generated .pest.xml.
  • Fingerprint::compute() accepts that ConfigurationFile. When none is given it uses the same default lookup in the project root. The two phpunit_xml* entries become one phpunit_configuration entry, and SCHEMA_VERSION goes to 19, so existing graphs rebuild once.
  • The entry is <project-relative path>:<content hash>, so an identical copy of the configuration under another name or in another directory is a different configuration. Relative paths inside a configuration file resolve against its own directory. A run with --no-configuration stores none, so it can never share a fingerprint with a run that loads a file.
  • XdebugRestarter::tiaWillReplay() resolves the file from the same arguments, so it compares like with like.

Tests, in tests/Features/Tia/ActiveConfiguration.php (sequential and parallel where it applies):

  • -c phpunit.xml, -c short form, --configuration=phpunit.xml and -c . still replay a graph recorded under phpunit.xml.
  • A graph recorded under one configuration file is not replayed under another.
  • Editing the configuration file in use stops the replay.
  • Editing a phpunit.xml that git ignores stops the replay.
  • An identical copy of the configuration under another name does not replay the graph.
  • tests/Unit/Plugins/Tia/ConfigurationFile.php covers the path-plus-hash value, a directory resolving like PHPUnit, and --no-configuration giving none.

The last three fail on 5.x (all 6 tests replayed) and pass with this change. The tests assert what holds without a coverage driver, as on CI. With PCOV enabled I also checked that the edited run re-records under the new fingerprint and that the next run replays all 6 tests again.

The existing replay-hooks test in tests/Features/Tia.php runs under tests/Fixtures/Suites/TiaReplayHooks.xml, so its seeded graph now fingerprints that file.

composer test (lint, type check, type coverage, unit, parallel, integration) passes on PHP 8.4 and 8.5.

Behaviour change to be aware of: projects that point two configuration files at one Tia directory used to share a graph silently, which is the replay this fixes. They now rebuild whenever they switch configuration. pest()->tia()->directory() per configuration, the workaround in #1877, keeps one graph per configuration and replays in each.

#1861 addressed the same issue by parsing -c by hand; its author closed it for inactivity. This version defers to PHPUnit's resolver instead.

Related:

Fixes #1858

🤖 Generated with Claude Code

@jfatr

jfatr commented Sep 18, 2026

Copy link
Copy Markdown

Heads up on an overlap, and two cases I hit while solving the same thing.

#1915 also fingerprints the configuration file the run uses. There, Fingerprint::compute() takes the run arguments and adds a configuration entry to the structural fingerprint, resolved in Tia\ExternalSources, which already parses -c to find source roots outside the project root. So the two PRs change the same block of compute() and both move SCHEMA_VERSION.
@cyppe
I prefer your version for this concern:

  • It hashes the configuration file in every case. Mine returns null when the selected file is the one in the project root with a fingerprinted name, and leaves that case to phpunit_xml and phpunit_xml_dist, which use trackedHash(). A phpunit.xml that git ignores therefore still does not invalidate the graph in mine, and does in yours.
  • One phpunit_configuration entry is the right shape. Mine adds a third key next to two that describe the same fact, and needs a name list to decide between them.
  • XmlConfigurationFileFinder cannot drift from PHPUnit's own lookup order. Mine repeats that order in a constant.

Two cases that mine covers and this PR does not:

  1. --no-configuration does not get its own value. fromArguments() returns null both when the flag suppresses a file that exists and when no file exists at all, so phpunit_configuration is the same in both runs and a run with --no-configuration replays a graph recorded without it. Mine stores 'none' for the suppressed case.
  2. The entry holds the content hash alone. Two configuration files with identical content in different paths produce the same fingerprint, so switching between them replays. Mine prefixes the repository-relative path, <relative>:<hash>, so the path is part of the value. phpunit.xml and a copy of it under another name are a real case here.

Both PRs are open, so neither of us knows which one the maintainers take, or whether they want the configuration fingerprint in one place or the other. I am not rebasing either branch on the other. I am flagging the overlap so whoever merges the second one knows that Fingerprint::compute() and SCHEMA_VERSION conflict, and I will adapt #1915 to whatever lands first.

@cyppe

cyppe commented Sep 18, 2026

Copy link
Copy Markdown
Author

Thanks, both cases were real. The fingerprint is now <project-relative path>:<content hash>, and --no-configuration stores none. There's a feature test for an identical copy under another name and unit tests for the value itself (e297d12). Agreed on the overlap: whichever of #1915 / #1924 lands second rebases on the other's compute() and SCHEMA_VERSION.

@nunomaduro

Copy link
Copy Markdown
Member

thank you! can you rebase this?

@cyppe
cyppe force-pushed the tia-fingerprint-active-configuration branch from e297d12 to 427749c Compare October 12, 2026 21:55
@cyppe

cyppe commented Oct 12, 2026

Copy link
Copy Markdown
Author

Rebased on 5.x, CI green locally. ConfigurationFile now passes EventFacade::emitter() to CliArguments\Builder, as PHPUnit 13.4 requires (same as Cache and Configuration). SCHEMA_VERSION stays 19 (5.x is still on 18).

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.

TIA: the fingerprint ignores the config actually in use (-c / --configuration)

3 participants