Do not search directories that are excluded from the source - #6915
Do not search directories that are excluded from the source#6915nicolas-grekas wants to merge 1 commit into
Conversation
feadc4e to
9b6eff9
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## 12.5 #6915 +/- ##
=========================================
Coverage 96.03% 96.04%
- Complexity 7576 7599 +23
=========================================
Files 798 798
Lines 23233 23273 +40
=========================================
+ Hits 22312 22352 +40
Misses 921 921 ☔ View full report in Codecov by Harness. |
The source map is built by searching every included directory. The excluded directories are searched afterwards, only to remove the files that were just collected from them. Both searches enter directories that cannot contribute a file to the map, and a directory that cannot be read aborts the whole test run. Pass the excluded directories to the file iterator of the first search, when their filters remove everything the included directory collects, and skip the second search for directories that hold no collected file.
9b6eff9 to
c2da8f5
Compare
|
Thank you, Nicolas, for raising this issue. The failure is real, I reproduced it, and it is worth fixing. I do not think the fix belongs in PHPUnit, though. Why not hereThe crash does not come from That is also the practical problem with this pull request: it removes the crash from the fixture it ships, and leaves it in place for the case you describe. I ran your branch against three trees:
The second one is the general case, and it is the one your description is actually about: a Composer temporary directory that appears and disappears during the walk can be anywhere below an included directory, not only below an excluded one. The third is the bamarni/composer-bin-plugin layout, which is common in exactly the monorepos you have in mind — there, Problems in the change itselfBeyond not covering the case it is meant to cover, the change introduces regressions in the source map:
Both are silent, and this map drives
More generally, I would rather not have Where it does belongPlease have a look at sebastianbergmann/php-file-iterator#162. It passes With that in The boundary fix for Not searching excluded directories remains a reasonable optimisation, but it is a separate concern from this bug, and I would like to treat it as one. |
|
Thanks I'll have a look in this direction! |
The bug
SourceMapper::map()searches every directory of<source><include>, and then searches every directory of<source><exclude>to remove the files it has just collected from them. Both searches enter directories that cannot contribute a file to the map.RecursiveDirectoryIteratorthrows when it cannot open a directory, so a single unreadable or disappearing directory below an excluded path aborts the whole test run.Steps to reproduce
phpunit.xml:ExampleTestcarries#[RunTestsInSeparateProcesses], so the source map is written for the child process.What happens
What I expected
buildis excluded from the source, so what it contains should not affect the run.Where this comes from
In a monorepo, several
composer updateruns work in parallel below the directory that one test run walks. Composer creates and removes a temporary extraction directory, and PHPUnit walks it in the moment between the two. The directory is gone whenopendir()is called, the test run of that package dies with the message above, and the job is red for a reason that has nothing to do with the tests.The change
ExcludeIteratormatches on the path prefix.TestSuiteMapperalready hands the excluded directories to the file iterator for<testsuites>, so this makes<source>behave the same way.Tests
testDoesNotSearchExcludedDirectoryfails on12.5with the exception above and passes with this change.testDoesNotExcludeDirectoryWhoseNameBeginsWithTheNameOfAnExcludedDirectorypasses with and without this change. It protects the existing behaviour: without the prefix guard, excludingtestswould also droptests-integrationfrom the map.testIgnoresExcludedDirectoryThatDoesNotExistcovers an excluded directory that does not exist on disk, which is where bothrealpath()results are false.sys_get_temp_dir(). The first test skips on Windows and when the unreadable directory cannot be created.Checks
./phpunit --testsuite unit: OK, 4056 tests, 12127 assertions../phpunit --testsuite end-to-end: 813 tests, one failure,defaulttestsuite-using-testsuite-without-name.phpt, which fails the same way on12.5without this change in my checkout becausetests/_files/testsdoes not exist there../tools/php-cs-fixer fix: 0 of 2226 files fixed../tools/phpstan analyse: no errors.Note
The prefix guard would not be needed if
ExcludeIterator::accept()compared on the directory boundary, that is$path === $exclude || str_starts_with($path, $exclude . DIRECTORY_SEPARATOR). I can send that tophp-file-iteratorseparately if you prefer that route.Use of an LLM-based coding assistant
I used Claude Code for this change: it reproduced the failure, wrote the patch and the tests, and ran the checks listed above. I reviewed the result, and I am able to explain and defend it.