From 4fb3f73bc5a4c3146bac2850af7dc72435a32daf Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Wed, 11 Dec 2024 15:50:44 +0100 Subject: [PATCH] Add full support to result cache (#919) --- bin/phpunit-wrapper.php | 9 ++-- composer.json | 6 +-- phpstan-baseline.neon | 30 +++++------- .../ApplicationForWrapperWorker.php | 31 ++++++------- src/WrapperRunner/SuiteLoader.php | 9 +++- src/WrapperRunner/WrapperRunner.php | 28 +++++++++-- src/WrapperRunner/WrapperWorker.php | 20 ++++++-- test/Unit/WrapperRunner/WrapperRunnerTest.php | 46 +++++++++++++++++++ test/fixtures/order_by/ASuccessfulTest.php | 16 +++++++ test/fixtures/order_by/BFailingTest.php | 16 +++++++ test/fixtures/order_by/phpunit.xml | 11 +++++ 11 files changed, 171 insertions(+), 51 deletions(-) create mode 100644 test/fixtures/order_by/ASuccessfulTest.php create mode 100644 test/fixtures/order_by/BFailingTest.php create mode 100644 test/fixtures/order_by/phpunit.xml diff --git a/bin/phpunit-wrapper.php b/bin/phpunit-wrapper.php index cdee6b0e..3c5d654e 100644 --- a/bin/phpunit-wrapper.php +++ b/bin/phpunit-wrapper.php @@ -10,7 +10,8 @@ 'status-file:', 'progress-file:', 'unexpected-output-file:', - 'testresult-file:', + 'test-result-file:', + 'result-cache-file:', 'teamcity-file:', 'testdox-file:', 'testdox-color', @@ -40,7 +41,8 @@ assert(isset($getopt['progress-file']) && is_string($getopt['progress-file'])); assert(isset($getopt['unexpected-output-file']) && is_string($getopt['unexpected-output-file'])); - assert(isset($getopt['testresult-file']) && is_string($getopt['testresult-file'])); + assert(isset($getopt['test-result-file']) && is_string($getopt['test-result-file'])); + assert(!isset($getopt['result-cache-file']) || is_string($getopt['result-cache-file'])); assert(!isset($getopt['teamcity-file']) || is_string($getopt['teamcity-file'])); assert(!isset($getopt['testdox-file']) || is_string($getopt['testdox-file'])); assert(!isset($getopt['testdox-columns']) || $getopt['testdox-columns'] === (string) (int) $getopt['testdox-columns']); @@ -53,7 +55,8 @@ $phpunitArgv, $getopt['progress-file'], $getopt['unexpected-output-file'], - $getopt['testresult-file'], + $getopt['test-result-file'], + $getopt['result-cache-file'] ?? null, $getopt['teamcity-file'] ?? null, $getopt['testdox-file'] ?? null, isset($getopt['testdox-color']), diff --git a/composer.json b/composer.json index 5b7153ab..3226d328 100644 --- a/composer.json +++ b/composer.json @@ -40,12 +40,12 @@ "ext-simplexml": "*", "fidry/cpu-core-counter": "^1.2.0", "jean85/pretty-package-versions": "^2.1.0", - "phpunit/php-code-coverage": "^11.0.7", + "phpunit/php-code-coverage": "^11.0.8", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-timer": "^7.0.1", - "phpunit/phpunit": "^11.5.0", + "phpunit/phpunit": "^11.5.1", "sebastian/environment": "^7.2.0", - "symfony/console": "^6.4.14 || ^7.2.0", + "symfony/console": "^6.4.14 || ^7.2.1", "symfony/process": "^6.4.14 || ^7.2.0" }, "require-dev": { diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 16235c95..2d462358 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -30,24 +30,6 @@ parameters: count: 1 path: src/Options.php - - - message: '#^Call to an undefined method PHPUnit\\Metadata\\Api\\CodeCoverage\:\:linesToBeIgnored\(\)\.$#' - identifier: method.notFound - count: 1 - path: src/WrapperRunner/ApplicationForWrapperWorker.php - - - - message: '#^Call to an undefined method PHPUnit\\Runner\\Filter\\Factory\:\:addNameFilter\(\)\.$#' - identifier: method.notFound - count: 1 - path: src/WrapperRunner/ApplicationForWrapperWorker.php - - - - message: '#^Parameter \#1 \$linesToBeIgnored of method PHPUnit\\Runner\\CodeCoverage\:\:ignoreLines\(\) expects array\\>, mixed given\.$#' - identifier: argument.type - count: 1 - path: src/WrapperRunner/ApplicationForWrapperWorker.php - - message: '#^Match expression does not handle remaining value\: string$#' identifier: match.unhandled @@ -186,6 +168,12 @@ parameters: count: 2 path: src/WrapperRunner/WrapperWorker.php + - + message: '#^Access to an uninitialized readonly property ParaTest\\WrapperRunner\\WrapperWorker\:\:\$resultCacheFile\.$#' + identifier: property.uninitializedReadonly + count: 2 + path: src/WrapperRunner/WrapperWorker.php + - message: '#^Access to an uninitialized readonly property ParaTest\\WrapperRunner\\WrapperWorker\:\:\$teamcityFile\.$#' identifier: property.uninitializedReadonly @@ -210,6 +198,12 @@ parameters: count: 1 path: src/WrapperRunner/WrapperWorker.php + - + message: '#^Class ParaTest\\WrapperRunner\\WrapperWorker has an uninitialized readonly property \$resultCacheFile\. Assign it in the constructor\.$#' + identifier: property.uninitializedReadonly + count: 1 + path: src/WrapperRunner/WrapperWorker.php + - message: '#^Class ParaTest\\WrapperRunner\\WrapperWorker has an uninitialized readonly property \$teamcityFile\. Assign it in the constructor\.$#' identifier: property.uninitializedReadonly diff --git a/src/WrapperRunner/ApplicationForWrapperWorker.php b/src/WrapperRunner/ApplicationForWrapperWorker.php index cacf210b..93eb07ce 100644 --- a/src/WrapperRunner/ApplicationForWrapperWorker.php +++ b/src/WrapperRunner/ApplicationForWrapperWorker.php @@ -11,7 +11,6 @@ use PHPUnit\Logging\JUnit\JunitXmlLogger; use PHPUnit\Logging\TeamCity\TeamCityLogger; use PHPUnit\Logging\TestDox\TestResultCollector; -use PHPUnit\Metadata\Api\CodeCoverage as CodeCoverageMetadataApi; use PHPUnit\Runner\Baseline\CannotLoadBaselineException; use PHPUnit\Runner\Baseline\Reader; use PHPUnit\Runner\CodeCoverage; @@ -21,9 +20,10 @@ use PHPUnit\Runner\Extension\Facade as ExtensionFacade; use PHPUnit\Runner\Extension\PharLoader; use PHPUnit\Runner\Filter\Factory; +use PHPUnit\Runner\ResultCache\DefaultResultCache; +use PHPUnit\Runner\ResultCache\ResultCacheHandler; use PHPUnit\Runner\TestSuiteLoader; use PHPUnit\Runner\TestSuiteSorter; -use PHPUnit\Runner\Version; use PHPUnit\TestRunner\IssueFilter; use PHPUnit\TestRunner\TestResult\Facade as TestResultFacade; use PHPUnit\TextUI\Configuration\Builder; @@ -46,7 +46,6 @@ use function str_ends_with; use function strpos; use function substr; -use function version_compare; /** * @internal @@ -64,7 +63,8 @@ public function __construct( private readonly array $argv, private readonly string $progressFile, private readonly string $unexpectedOutputFile, - private readonly string $testresultFile, + private readonly string $testResultFile, + private readonly ?string $resultCacheFile, private readonly ?string $teamcityFile, private readonly ?string $testdoxFile, private readonly bool $testdoxColor, @@ -80,11 +80,7 @@ public function runTest(string $testPath): int $filter = new Factory(); $name = substr($testPath, $null + 1); assert($name !== ''); - if (version_compare(Version::id(), '11.0.0') >= 0) { - $filter->addIncludeNameFilter($name); - } else { - $filter->addNameFilter($name); - } + $filter->addIncludeNameFilter($name); $testPath = substr($testPath, 0, $null); } @@ -99,14 +95,6 @@ public function runTest(string $testPath): int $testSuite = TestSuite::fromClassReflector($testSuiteRefl); } - if (version_compare(Version::id(), '11.0.0') < 0) { - if (CodeCoverage::instance()->isActive()) { - CodeCoverage::instance()->ignoreLines( - (new CodeCoverageMetadataApi())->linesToBeIgnored($testSuite), - ); - } - } - (new TestSuiteFilterProcessor())->process($this->configuration, $testSuite); if ($filter !== null) { @@ -209,6 +197,13 @@ private function bootstrap(): void TestResultFacade::init(); DeprecationCollector::init(); + if (isset($this->resultCacheFile)) { + new ResultCacheHandler( + new DefaultResultCache($this->resultCacheFile), + EventFacade::instance(), + ); + } + if ($this->configuration->source()->useBaseline()) { $baselineFile = $this->configuration->source()->baseline(); $baseline = null; @@ -262,7 +257,7 @@ public function end(): void ); } - file_put_contents($this->testresultFile, serialize($result)); + file_put_contents($this->testResultFile, serialize($result)); EventFacade::emitter()->applicationFinished(0); } diff --git a/src/WrapperRunner/SuiteLoader.php b/src/WrapperRunner/SuiteLoader.php index 03b966f9..454981e4 100644 --- a/src/WrapperRunner/SuiteLoader.php +++ b/src/WrapperRunner/SuiteLoader.php @@ -13,6 +13,7 @@ use PHPUnit\Runner\Extension\Facade as ExtensionFacade; use PHPUnit\Runner\Extension\PharLoader; use PHPUnit\Runner\PhptTestCase; +use PHPUnit\Runner\ResultCache\DefaultResultCache; use PHPUnit\Runner\ResultCache\NullResultCache; use PHPUnit\Runner\TestSuiteSorter; use PHPUnit\TestRunner\TestResult\Facade as TestResultFacade; @@ -95,7 +96,13 @@ public function __construct( $this->options->configuration->executionOrderDefects() !== TestSuiteSorter::ORDER_DEFAULT || $this->options->configuration->resolveDependencies() ) { - (new TestSuiteSorter(new NullResultCache()))->reorderTestsInSuite( + $resultCache = new NullResultCache(); + if ($this->options->configuration->cacheResult()) { + $resultCache = new DefaultResultCache($this->options->configuration->testResultCacheFile()); + $resultCache->load(); + } + + (new TestSuiteSorter($resultCache))->reorderTestsInSuite( $testSuite, $this->options->configuration->executionOrder(), $this->options->configuration->resolveDependencies(), diff --git a/src/WrapperRunner/WrapperRunner.php b/src/WrapperRunner/WrapperRunner.php index b7672eb5..e92e825f 100644 --- a/src/WrapperRunner/WrapperRunner.php +++ b/src/WrapperRunner/WrapperRunner.php @@ -10,6 +10,7 @@ use ParaTest\Options; use ParaTest\RunnerInterface; use PHPUnit\Runner\CodeCoverage; +use PHPUnit\Runner\ResultCache\DefaultResultCache; use PHPUnit\TestRunner\TestResult\Facade as TestResultFacade; use PHPUnit\TestRunner\TestResult\TestResult; use PHPUnit\TextUI\Configuration\CodeCoverageFilterRegistry; @@ -55,7 +56,9 @@ final class WrapperRunner implements RunnerInterface /** @var list */ private array $unexpectedOutputFiles = []; /** @var list */ - private array $testresultFiles = []; + private array $resultCacheFiles = []; + /** @var list */ + private array $testResultFiles = []; /** @var list */ private array $coverageFiles = []; /** @var list */ @@ -211,7 +214,11 @@ private function startWorker(int $token): WrapperWorker $this->statusFiles[] = $worker->statusFile; $this->progressFiles[] = $worker->progressFile; $this->unexpectedOutputFiles[] = $worker->unexpectedOutputFile; - $this->testresultFiles[] = $worker->testresultFile; + $this->testResultFiles[] = $worker->testResultFile; + + if (isset($worker->resultCacheFile)) { + $this->resultCacheFiles[] = $worker->resultCacheFile; + } if (isset($worker->junitFile)) { $this->junitFiles[] = $worker->junitFile; @@ -245,7 +252,7 @@ private function destroyWorker(int $token): void private function complete(TestResult $testResultSum): int { - foreach ($this->testresultFiles as $testresultFile) { + foreach ($this->testResultFiles as $testresultFile) { if (! $testresultFile->isFile()) { continue; } @@ -281,6 +288,18 @@ private function complete(TestResult $testResultSum): int ); } + if ($this->options->configuration->cacheResult()) { + $resultCacheSum = new DefaultResultCache($this->options->configuration->testResultCacheFile()); + foreach ($this->resultCacheFiles as $resultCacheFile) { + $resultCache = new DefaultResultCache($resultCacheFile->getPathname()); + $resultCache->load(); + + $resultCacheSum->mergeWith($resultCache); + } + + $resultCacheSum->persist(); + } + $this->printer->printResults( $testResultSum, $this->teamcityFiles, @@ -304,7 +323,8 @@ private function complete(TestResult $testResultSum): int $this->clearFiles($this->statusFiles); $this->clearFiles($this->progressFiles); $this->clearFiles($this->unexpectedOutputFiles); - $this->clearFiles($this->testresultFiles); + $this->clearFiles($this->testResultFiles); + $this->clearFiles($this->resultCacheFiles); $this->clearFiles($this->coverageFiles); $this->clearFiles($this->junitFiles); $this->clearFiles($this->teamcityFiles); diff --git a/src/WrapperRunner/WrapperWorker.php b/src/WrapperRunner/WrapperWorker.php index 2674ae0f..ce1b039f 100644 --- a/src/WrapperRunner/WrapperWorker.php +++ b/src/WrapperRunner/WrapperWorker.php @@ -34,7 +34,8 @@ final class WrapperWorker public readonly SplFileInfo $statusFile; public readonly SplFileInfo $progressFile; public readonly SplFileInfo $unexpectedOutputFile; - public readonly SplFileInfo $testresultFile; + public readonly SplFileInfo $testResultFile; + public readonly SplFileInfo $resultCacheFile; public readonly SplFileInfo $junitFile; public readonly SplFileInfo $coverageFile; public readonly SplFileInfo $teamcityFile; @@ -66,7 +67,12 @@ public function __construct( touch($this->progressFile->getPathname()); $this->unexpectedOutputFile = new SplFileInfo($commonTmpFilePath . 'unexpected_output'); touch($this->unexpectedOutputFile->getPathname()); - $this->testresultFile = new SplFileInfo($commonTmpFilePath . 'testresult'); + $this->testResultFile = new SplFileInfo($commonTmpFilePath . 'test_result'); + + if ($this->options->configuration->cacheResult()) { + $this->resultCacheFile = new SplFileInfo($commonTmpFilePath . 'result_cache'); + } + if ($options->configuration->hasLogfileJunit()) { $this->junitFile = new SplFileInfo($commonTmpFilePath . 'junit'); } @@ -89,8 +95,14 @@ public function __construct( $parameters[] = $this->progressFile->getPathname(); $parameters[] = '--unexpected-output-file'; $parameters[] = $this->unexpectedOutputFile->getPathname(); - $parameters[] = '--testresult-file'; - $parameters[] = $this->testresultFile->getPathname(); + $parameters[] = '--test-result-file'; + $parameters[] = $this->testResultFile->getPathname(); + + if (isset($this->resultCacheFile)) { + $parameters[] = '--result-cache-file'; + $parameters[] = $this->resultCacheFile->getPathname(); + } + if (isset($this->teamcityFile)) { $parameters[] = '--teamcity-file'; $parameters[] = $this->teamcityFile->getPathname(); diff --git a/test/Unit/WrapperRunner/WrapperRunnerTest.php b/test/Unit/WrapperRunner/WrapperRunnerTest.php index d3fdb048..acf31c43 100644 --- a/test/Unit/WrapperRunner/WrapperRunnerTest.php +++ b/test/Unit/WrapperRunner/WrapperRunnerTest.php @@ -30,6 +30,7 @@ use function file_put_contents; use function glob; use function implode; +use function is_file; use function min; use function posix_mkfifo; use function preg_match; @@ -805,6 +806,51 @@ public function testExtensionMustRunBeforeDataProvider(): void self::assertEquals(RunnerInterface::SUCCESS_EXIT, $runnerResult->exitCode); } + public function testOrderByCache(): void + { + $resultCacheFile = $this->fixture('order_by') . DIRECTORY_SEPARATOR . '.phpunit.result.cache'; + if (is_file($resultCacheFile)) { + unlink($resultCacheFile); + } + + $this->bareOptions['--configuration'] = $this->fixture('order_by' . DIRECTORY_SEPARATOR . 'phpunit.xml'); + $this->bareOptions['--processes'] = '1'; + $this->bareOptions['--order-by'] = 'defects'; + + $expectedOutput = static function (string $order): string { + return 'Processes: %s +Runtime: PHP %s +Configuration: %s + +' . $order . ' 2 / 2 (100%) + +Time: %s, Memory: %s MB + +There was 1 failure: + +1) ParaTest\Tests\fixtures\order_by\BFailingTest::testFailure +Failed asserting that false is true. + +%s/test/fixtures/order_by/BFailingTest.php:14 + +FAILURES! +%s +'; + }; + + $runnerResult = $this->runRunner(); + + self::assertStringMatchesFormat($expectedOutput('.F'), $runnerResult->output); + self::assertEquals(RunnerInterface::FAILURE_EXIT, $runnerResult->exitCode); + + self::assertFileExists($resultCacheFile); + + $runnerResult = $this->runRunner(); + + self::assertStringMatchesFormat($expectedOutput('F.'), $runnerResult->output); + self::assertEquals(RunnerInterface::FAILURE_EXIT, $runnerResult->exitCode); + } + /** * ### WARNING ### * diff --git a/test/fixtures/order_by/ASuccessfulTest.php b/test/fixtures/order_by/ASuccessfulTest.php new file mode 100644 index 00000000..55b41d68 --- /dev/null +++ b/test/fixtures/order_by/ASuccessfulTest.php @@ -0,0 +1,16 @@ +assertTrue(true); + } +} diff --git a/test/fixtures/order_by/BFailingTest.php b/test/fixtures/order_by/BFailingTest.php new file mode 100644 index 00000000..2ae84ea2 --- /dev/null +++ b/test/fixtures/order_by/BFailingTest.php @@ -0,0 +1,16 @@ +assertTrue(false); + } +} diff --git a/test/fixtures/order_by/phpunit.xml b/test/fixtures/order_by/phpunit.xml new file mode 100644 index 00000000..9de5cb8f --- /dev/null +++ b/test/fixtures/order_by/phpunit.xml @@ -0,0 +1,11 @@ + + + + + . + + +