From 0feb65f4c82418c191c6f50085625b15326900c2 Mon Sep 17 00:00:00 2001 From: Sander Muller Date: Sun, 25 Oct 2026 17:33:07 +0100 Subject: [PATCH] [Testing] Lock the fixture directory while a test uses its temp file --- .gitignore | 1 + .../PHPUnit/AbstractRectorTestCase.php | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/.gitignore b/.gitignore index 9c96bd27896..a84aba2c87e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ composer.lock .phpunit.result.cache .phpunit.cache .fastunit-cache +.fixture.lock # scoped & downgraded version php-scoper.phar diff --git a/src/Testing/PHPUnit/AbstractRectorTestCase.php b/src/Testing/PHPUnit/AbstractRectorTestCase.php index bfb62fc8108..7605c006119 100644 --- a/src/Testing/PHPUnit/AbstractRectorTestCase.php +++ b/src/Testing/PHPUnit/AbstractRectorTestCase.php @@ -36,6 +36,11 @@ abstract class AbstractRectorTestCase extends AbstractLazyTestCase implements Re private ?string $inputFilePath = null; + /** + * @var resource|null + */ + private $fixtureDirectoryLock; + /** * @var array */ @@ -133,6 +138,8 @@ protected function tearDown(): void FileSystem::delete($this->inputFilePath); } + $this->unlockFixtureDirectory(); + // a next test that does not set its own paths would still locate the deleted file $this->dynamicSourceLocatorProvider->reset(); } @@ -177,6 +184,10 @@ protected function doTestFile(string $fixtureFilePath, bool $includeFixtureDirec throw new ShouldNotHappenException('Fixture file and input file cannot be the same: ' . $fixtureFilePath); } + // test classes that share a fixture directory write the same temp files, + // so in parallel processes one could delete the other's file + $this->lockFixtureDirectory(dirname($inputFilePath)); + // write temp file FileSystem::write($inputFilePath, $inputFileContents, null); @@ -282,6 +293,31 @@ private function processFilePath(string $filePath, bool $includeFixtureDirectory return new RectorTestResult($changedFileContents, $processResult); } + private function lockFixtureDirectory(string $directory): void + { + // a test can call doTestFile() more than once; a second lock from this process would wait for the first + $this->unlockFixtureDirectory(); + + $fixtureDirectoryLock = fopen($directory . '/.fixture.lock', 'c'); + if ($fixtureDirectoryLock === false) { + throw new ShouldNotHappenException('Cannot open the lock file in ' . $directory); + } + + flock($fixtureDirectoryLock, LOCK_EX); + $this->fixtureDirectoryLock = $fixtureDirectoryLock; + } + + private function unlockFixtureDirectory(): void + { + if (! is_resource($this->fixtureDirectoryLock)) { + return; + } + + flock($this->fixtureDirectoryLock, LOCK_UN); + fclose($this->fixtureDirectoryLock); + $this->fixtureDirectoryLock = null; + } + private function createInputFilePath(string $fixtureFilePath): string { $inputFileDirectory = dirname($fixtureFilePath);