From 07737bc0b29aaa5103e3a45d3c1275ea5fe3db82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksandr=20=C5=A0t=C5=A1epelin?= Date: Wed, 25 Mar 2026 23:59:28 +0200 Subject: [PATCH 1/3] Fix parallel file selection and empty-suite reporting --- .../Parallel/Paratest/ResultPrinter.php | 8 ++ .../Parallel/Paratest/WrapperRunner.php | 55 +++++++++-- tests/.snapshots/success.txt | 5 +- tests/Visual/Parallel.php | 2 +- tests/Visual/ParallelNestedDatasets.php | 91 +++++++++++++++++++ 5 files changed, 151 insertions(+), 10 deletions(-) create mode 100644 tests/Visual/ParallelNestedDatasets.php diff --git a/src/Plugins/Parallel/Paratest/ResultPrinter.php b/src/Plugins/Parallel/Paratest/ResultPrinter.php index e7a1c24d..85099a1b 100644 --- a/src/Plugins/Parallel/Paratest/ResultPrinter.php +++ b/src/Plugins/Parallel/Paratest/ResultPrinter.php @@ -171,6 +171,14 @@ final class ResultPrinter $state = (new StateGenerator)->fromPhpUnitTestResult($this->passedTests, $testResult); + if ($testResult->numberOfTestsRun() === 0 && $state->testSuiteTestsCount() === 0) { + $this->output->writeln([ + '', + ' INFO No tests found.', + '', + ]); + } + $this->compactPrinter->errors($state); $this->compactPrinter->recap($state, $testResult, $duration, $this->options); } diff --git a/src/Plugins/Parallel/Paratest/WrapperRunner.php b/src/Plugins/Parallel/Paratest/WrapperRunner.php index 064856bc..ea22f4c5 100644 --- a/src/Plugins/Parallel/Paratest/WrapperRunner.php +++ b/src/Plugins/Parallel/Paratest/WrapperRunner.php @@ -39,6 +39,7 @@ use function dirname; use function file_get_contents; use function max; use function realpath; +use function str_starts_with; use function unlink; use function unserialize; use function usleep; @@ -485,14 +486,52 @@ final class WrapperRunner implements RunnerInterface private function getTestFiles(SuiteLoader $suiteLoader): array { /** @var array $files */ - $files = [ - ...array_values(array_filter( - $suiteLoader->tests, - fn (string $filename): bool => ! str_ends_with($filename, "eval()'d code") - )), - ...TestSuite::getInstance()->tests->getFilenames(), - ]; + $files = array_fill_keys(array_values(array_filter( + $suiteLoader->tests, + fn (string $filename): bool => ! str_ends_with($filename, "eval()'d code") + )), null); - return $files; // @phpstan-ignore-line + foreach (TestSuite::getInstance()->tests->getFilenames() as $filename) { + if ($this->shouldIncludeBootstrappedTestFile($filename)) { + $files[$filename] = null; + } + } + + return array_keys($files); // @phpstan-ignore-line + } + + private function shouldIncludeBootstrappedTestFile(string $filename): bool + { + if (! $this->options->configuration->hasCliArguments()) { + return true; + } + + $resolvedFilename = realpath($filename); + + if ($resolvedFilename === false) { + $resolvedFilename = realpath($this->options->cwd.DIRECTORY_SEPARATOR.$filename); + } + + if ($resolvedFilename === false) { + return false; + } + + foreach ($this->options->configuration->cliArguments() as $path) { + $resolvedPath = realpath($path); + + if ($resolvedPath === false) { + continue; + } + + if ($resolvedFilename === $resolvedPath) { + return true; + } + + if (is_dir($resolvedPath) && str_starts_with($resolvedFilename, $resolvedPath.DIRECTORY_SEPARATOR)) { + return true; + } + } + + return false; } } diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 7b3d6f9b..d57d3687 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -1757,6 +1757,9 @@ ✓ parallel ✓ a parallel test can extend another test with same name + PASS Tests\Visual\ParallelNestedDatasets + ✓ parallel reports missing nested datasets without a passing summary + PASS Tests\Visual\SingleTestOrDirectory ✓ allows to run a single test ✓ allows to run a directory @@ -1782,4 +1785,4 @@ ✓ pass with dataset with ('my-datas-set-value') ✓ within describe → pass with dataset with ('my-datas-set-value') - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 39 todos, 35 skipped, 1188 passed (2813 assertions) \ No newline at end of file + Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 39 todos, 35 skipped, 1189 passed (2819 assertions) diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php index 1aced21d..cd5f62e1 100644 --- a/tests/Visual/Parallel.php +++ b/tests/Visual/Parallel.php @@ -21,5 +21,5 @@ test('parallel', function () use ($run) { })->skipOnWindows(); test('a parallel test can extend another test with same name', function () use ($run) { - expect($run('tests/Fixtures/Inheritance'))->toContain('Tests: 1 skipped, 2 passed (2 assertions)'); + expect($run('tests/Fixtures/Inheritance'))->toContain('Tests: 1 skipped, 1 passed (1 assertions)'); }); diff --git a/tests/Visual/ParallelNestedDatasets.php b/tests/Visual/ParallelNestedDatasets.php new file mode 100644 index 00000000..d2a7cbe6 --- /dev/null +++ b/tests/Visual/ParallelNestedDatasets.php @@ -0,0 +1,91 @@ +not->toBeEmpty(); +})->with('nested.users'); +PHP); + + return [$directory, 'tests/Features/ParallelNestedDatasetRepro/TestFileWithNestedDataset.php']; +}; + +$cleanup = function (string $directory): void { + if (! is_dir($directory)) { + return; + } + + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS), + RecursiveIteratorIterator::CHILD_FIRST, + ); + + foreach ($iterator as $item) { + if ($item->isDir()) { + rmdir($item->getPathname()); + } else { + unlink($item->getPathname()); + } + } + + rmdir($directory); +}; + +$run = function (string $target, bool $parallel = false): array { + $command = ['php', 'bin/pest', $target, '--colors=never']; + + if ($parallel) { + $command[] = '--parallel'; + $command[] = '--processes=2'; + } + + $process = new Process($command, dirname(__DIR__, 2), + ['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true'], + ); + + $process->run(); + + return [ + 'exitCode' => $process->getExitCode(), + 'output' => removeAnsiEscapeSequences($process->getOutput().$process->getErrorOutput()), + ]; +}; + +test('parallel reports missing nested datasets without a passing summary', function () use ($cleanup, $fixture, $run) { + [$directory, $target] = $fixture(); + + try { + $serial = $run($target); + $parallel = $run($target, true); + + expect($serial['exitCode'])->toBe(2) + ->and($parallel['exitCode'])->toBe(2) + ->and($serial['output'])->toContain('INFO No tests found.') + ->and($parallel['output'])->toContain('INFO No tests found.') + ->and($parallel['output'])->toContain('Parallel: 2 processes') + ->and($parallel['output'])->not->toContain('passed'); + } finally { + $cleanup($directory); + } +})->skipOnWindows(); From f7175ecfd711d0f5113ba1a31c2ac3ee34eaf7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksandr=20=C5=A0t=C5=A1epelin?= Date: Wed, 25 Mar 2026 23:59:29 +0200 Subject: [PATCH 2/3] Fix parallel dataset reporting and nested fixtures --- src/Support/DatasetInfo.php | 61 +++++++++++++- src/Support/StateGenerator.php | 28 +++++++ tests/.snapshots/success.txt | 69 +++++---------- .../MissingDatasetTest.php | 5 ++ .../ParallelInvalidDataset/PassingTest.php | 3 + .../Datasets/Nested/Users.php | 6 ++ .../TestFileWithNestedDataset.php | 5 ++ tests/Unit/Support/DatasetInfo.php | 11 +++ tests/Visual/Parallel.php | 11 ++- tests/Visual/ParallelNestedDatasets.php | 83 ++++--------------- tests/Visual/Success.php | 4 +- 11 files changed, 165 insertions(+), 121 deletions(-) create mode 100644 tests/.tests/ParallelInvalidDataset/MissingDatasetTest.php create mode 100644 tests/.tests/ParallelInvalidDataset/PassingTest.php create mode 100644 tests/Fixtures/ParallelNestedDatasets/Datasets/Nested/Users.php create mode 100644 tests/Fixtures/ParallelNestedDatasets/TestFileWithNestedDataset.php diff --git a/src/Support/DatasetInfo.php b/src/Support/DatasetInfo.php index c67f317c..70ca6df5 100644 --- a/src/Support/DatasetInfo.php +++ b/src/Support/DatasetInfo.php @@ -17,7 +17,7 @@ final class DatasetInfo public static function isInsideADatasetsDirectory(string $file): bool { - return basename(dirname($file)) === self::DATASETS_DIR_NAME; + return in_array(self::DATASETS_DIR_NAME, self::directorySegmentsInsideTestsDirectory($file), true); } public static function isADatasetsFile(string $file): bool @@ -32,7 +32,23 @@ final class DatasetInfo } if (self::isInsideADatasetsDirectory($file)) { - return dirname($file, 2); + $scope = []; + + foreach (self::directorySegmentsInsideTestsDirectory($file) as $segment) { + if ($segment === self::DATASETS_DIR_NAME) { + break; + } + + $scope[] = $segment; + } + + $testsDirectoryPath = self::testsDirectoryPath($file); + + if ($scope === []) { + return $testsDirectoryPath; + } + + return $testsDirectoryPath.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $scope); } if (self::isADatasetsFile($file)) { @@ -41,4 +57,45 @@ final class DatasetInfo return $file; } + + /** + * @return list + */ + private static function directorySegmentsInsideTestsDirectory(string $file): array + { + $directory = dirname(self::pathInsideTestsDirectory($file)); + + if ($directory === '.' || $directory === DIRECTORY_SEPARATOR) { + return []; + } + + return array_values(array_filter( + explode(DIRECTORY_SEPARATOR, trim($directory, DIRECTORY_SEPARATOR)), + static fn (string $segment): bool => $segment !== '', + )); + } + + private static function pathInsideTestsDirectory(string $file): string + { + $testsDirectory = DIRECTORY_SEPARATOR.trim(testDirectory(), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; + $position = strrpos($file, $testsDirectory); + + if ($position === false) { + return $file; + } + + return substr($file, $position + strlen($testsDirectory)); + } + + private static function testsDirectoryPath(string $file): string + { + $testsDirectory = DIRECTORY_SEPARATOR.trim(testDirectory(), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; + $position = strrpos($file, $testsDirectory); + + if ($position === false) { + return dirname($file); + } + + return substr($file, 0, $position + strlen($testsDirectory) - 1); + } } diff --git a/src/Support/StateGenerator.php b/src/Support/StateGenerator.php index a7ddba1a..3226343e 100644 --- a/src/Support/StateGenerator.php +++ b/src/Support/StateGenerator.php @@ -11,6 +11,10 @@ use PHPUnit\Event\Code\TestDoxBuilder; use PHPUnit\Event\Code\TestMethod; use PHPUnit\Event\Code\ThrowableBuilder; use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\PhpunitDeprecationTriggered; +use PHPUnit\Event\Test\PhpunitErrorTriggered; +use PHPUnit\Event\Test\PhpunitNoticeTriggered; +use PHPUnit\Event\Test\PhpunitWarningTriggered; use PHPUnit\Event\TestData\TestDataCollection; use PHPUnit\Framework\SkippedWithMessageException; use PHPUnit\Metadata\MetadataCollection; @@ -43,6 +47,8 @@ final class StateGenerator )); } + $this->addTriggeredPhpunitEvents($state, $testResult->testTriggeredPhpunitErrorEvents(), TestResult::FAIL); + foreach ($testResult->testMarkedIncompleteEvents() as $testResultEvent) { $state->add(TestResult::fromPestParallelTestCase( $testResultEvent->test(), @@ -99,6 +105,8 @@ final class StateGenerator } } + $this->addTriggeredPhpunitEvents($state, $testResult->testTriggeredPhpunitDeprecationEvents(), TestResult::DEPRECATED); + foreach ($testResult->notices() as $testResultEvent) { foreach ($testResultEvent->triggeringTests() as $triggeringTest) { ['test' => $test] = $triggeringTest; @@ -123,6 +131,8 @@ final class StateGenerator } } + $this->addTriggeredPhpunitEvents($state, $testResult->testTriggeredPhpunitNoticeEvents(), TestResult::NOTICE); + foreach ($testResult->warnings() as $testResultEvent) { foreach ($testResultEvent->triggeringTests() as $triggeringTest) { ['test' => $test] = $triggeringTest; @@ -135,6 +145,8 @@ final class StateGenerator } } + $this->addTriggeredPhpunitEvents($state, $testResult->testTriggeredPhpunitWarningEvents(), TestResult::WARN); + foreach ($testResult->phpWarnings() as $testResultEvent) { foreach ($testResultEvent->triggeringTests() as $triggeringTest) { ['test' => $test] = $triggeringTest; @@ -165,4 +177,20 @@ final class StateGenerator return $state; } + + /** + * @param array> $testResultEvents + */ + private function addTriggeredPhpunitEvents(State $state, array $testResultEvents, string $type): void + { + foreach ($testResultEvents as $events) { + foreach ($events as $event) { + $state->add(TestResult::fromPestParallelTestCase( + $event->test(), + $type, + ThrowableBuilder::from(new TestOutcome($event->message())) + )); + } + } + } } diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index d57d3687..58d988ac 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -1490,6 +1490,10 @@ PASS Tests\Fixtures\ExampleTest ✓ it example 2 + PASS Tests\Fixtures\ParallelNestedDatasets\TestFileWithNestedDataset + ✓ loads nested dataset with ('alice') + ✓ loads nested dataset with ('bob') + WARN Tests\Fixtures\UnexpectedOutput - output @@ -1640,9 +1644,14 @@ ✓ it cannot resolve a parameter without type PASS Tests\Unit\Support\DatasetInfo - ✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Datase…rs.php', true) + ✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/Datasets/project/tes…rs.php', true) #1 + ✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/Datasets/project/tes…rs.php', true) #2 + ✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Datase…rs.php', true) #1 + ✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Datase…rs.php', true) #2 ✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Datasets.php', false) - ✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Featur…rs.php', true) + ✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Featur…rs.php', true) #1 + ✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Featur…rs.php', true) #2 + ✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Featur…rs.php', true) #3 ✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Featur…rs.php', false) ✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Featur…ts.php', false) ✓ it can check if dataset is defined inside a Datasets.php file with ('/var/www/project/tests/Datase…rs.php', false) @@ -1650,12 +1659,18 @@ ✓ it can check if dataset is defined inside a Datasets.php file with ('/var/www/project/tests/Featur…rs.php', false) #1 ✓ it can check if dataset is defined inside a Datasets.php file with ('/var/www/project/tests/Featur…rs.php', false) #2 ✓ it can check if dataset is defined inside a Datasets.php file with ('/var/www/project/tests/Featur…ts.php', true) - ✓ it computes the dataset scope with ('/var/www/project/tests/Datase…rs.php', '/var/www/project/tests') + ✓ it computes the dataset scope with ('/var/www/Datasets/project/tes…rs.php', '/var/www/Datasets/project/tests') + ✓ it computes the dataset scope with ('/var/www/Datasets/project/tes…rs.php', '/var/www/Datasets/project/tes…atures') + ✓ it computes the dataset scope with ('/var/www/project/tests/Datase…rs.php', '/var/www/project/tests') #1 + ✓ it computes the dataset scope with ('/var/www/project/tests/Datase…rs.php', '/var/www/project/tests') #2 ✓ it computes the dataset scope with ('/var/www/project/tests/Datasets.php', '/var/www/project/tests') - ✓ it computes the dataset scope with ('/var/www/project/tests/Featur…rs.php', '/var/www/project/tests/Features') + ✓ it computes the dataset scope with ('/var/www/project/tests/Featur…rs.php', '/var/www/project/tests/Features') #1 + ✓ it computes the dataset scope with ('/var/www/project/tests/Featur…rs.php', '/var/www/project/tests/Features') #2 ✓ it computes the dataset scope with ('/var/www/project/tests/Featur…rs.php', '/var/www/project/tests/Featur…rs.php') #1 ✓ it computes the dataset scope with ('/var/www/project/tests/Featur…ts.php', '/var/www/project/tests/Features') - ✓ it computes the dataset scope with ('/var/www/project/tests/Featur…rs.php', '/var/www/project/tests/Featur…ollers') + ✓ it computes the dataset scope with ('/var/www/project/tests/Featur…rs.php', '/var/www/project/tests/Featur…ollers') #1 + ✓ it computes the dataset scope with ('/var/www/project/tests/Featur…rs.php', '/var/www/project/tests/Featur…ollers') #2 + ✓ it computes the dataset scope with ('/var/www/project/tests/Featur…rs.php', '/var/www/project/tests/Features') #3 ✓ it computes the dataset scope with ('/var/www/project/tests/Featur…rs.php', '/var/www/project/tests/Featur…rs.php') #2 ✓ it computes the dataset scope with ('/var/www/project/tests/Featur…ts.php', '/var/www/project/tests/Featur…ollers') @@ -1739,50 +1754,8 @@ ✓ it alerts users about tests with arguments but no input ✓ it can return an array of all test suite filenames - PASS Tests\Visual\BeforeEachTestName - ✓ description - ✓ latest description - - PASS Tests\Visual\Collision - ✓ collision with (['']) - - PASS Tests\Visual\Help - ✓ visual snapshot of help command output - - WARN Tests\Visual\JUnit - ✓ junit output - - junit with parallel → Not working yet - - PASS Tests\Visual\Parallel - ✓ parallel - ✓ a parallel test can extend another test with same name - - PASS Tests\Visual\ParallelNestedDatasets - ✓ parallel reports missing nested datasets without a passing summary - - PASS Tests\Visual\SingleTestOrDirectory - ✓ allows to run a single test - ✓ allows to run a directory - ✓ it disable decorating printer when colors is set to never - - WARN Tests\Visual\Success - - visual snapshot of test suite on success - - WARN Tests\Visual\TeamCity - - visual snapshot of team city with ('Failure.php') - - visual snapshot of team city with ('SuccessOnly.php') - - WARN Tests\Visual\Todo - - todos - - todos in parallel - - todo - - todo in parallel - - WARN Tests\Visual\Version - - visual snapshot of help command output - PASS Testsexternal\Features\Expect\toMatchSnapshot ✓ pass with dataset with ('my-datas-set-value') ✓ within describe → pass with dataset with ('my-datas-set-value') - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 39 todos, 35 skipped, 1189 passed (2819 assertions) + Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 39 todos, 26 skipped, 1191 passed (2802 assertions) \ No newline at end of file diff --git a/tests/.tests/ParallelInvalidDataset/MissingDatasetTest.php b/tests/.tests/ParallelInvalidDataset/MissingDatasetTest.php new file mode 100644 index 00000000..c7a1245f --- /dev/null +++ b/tests/.tests/ParallelInvalidDataset/MissingDatasetTest.php @@ -0,0 +1,5 @@ +toBe('x'); +})->with('missing.dataset'); diff --git a/tests/.tests/ParallelInvalidDataset/PassingTest.php b/tests/.tests/ParallelInvalidDataset/PassingTest.php new file mode 100644 index 00000000..915336ab --- /dev/null +++ b/tests/.tests/ParallelInvalidDataset/PassingTest.php @@ -0,0 +1,3 @@ +assertTrue(true); diff --git a/tests/Fixtures/ParallelNestedDatasets/Datasets/Nested/Users.php b/tests/Fixtures/ParallelNestedDatasets/Datasets/Nested/Users.php new file mode 100644 index 00000000..dbff16da --- /dev/null +++ b/tests/Fixtures/ParallelNestedDatasets/Datasets/Nested/Users.php @@ -0,0 +1,6 @@ +not->toBeEmpty(); +})->with('nested.users'); diff --git a/tests/Unit/Support/DatasetInfo.php b/tests/Unit/Support/DatasetInfo.php index b00c7b94..4feb6121 100644 --- a/tests/Unit/Support/DatasetInfo.php +++ b/tests/Unit/Support/DatasetInfo.php @@ -5,9 +5,14 @@ use Pest\Support\DatasetInfo; it('can check if dataset is defined inside a Datasets directory', function (string $file, bool $inside) { expect(DatasetInfo::isInsideADatasetsDirectory($file))->toBe($inside); })->with([ + ['file' => '/var/www/Datasets/project/tests/Datasets/Nested/Numbers.php', 'inside' => true], + ['file' => '/var/www/Datasets/project/tests/Features/Datasets/Nested/Numbers.php', 'inside' => true], ['file' => '/var/www/project/tests/Datasets/Numbers.php', 'inside' => true], + ['file' => '/var/www/project/tests/Datasets/Nested/Numbers.php', 'inside' => true], ['file' => '/var/www/project/tests/Datasets.php', 'inside' => false], ['file' => '/var/www/project/tests/Features/Datasets/Numbers.php', 'inside' => true], + ['file' => '/var/www/project/tests/Features/Datasets/Nested/Numbers.php', 'inside' => true], + ['file' => '/var/www/project/tests/Features/Datasets/Nested/Datasets/Numbers.php', 'inside' => true], ['file' => '/var/www/project/tests/Features/Numbers.php', 'inside' => false], ['file' => '/var/www/project/tests/Features/Datasets.php', 'inside' => false], ]); @@ -25,12 +30,18 @@ it('can check if dataset is defined inside a Datasets.php file', function (strin it('computes the dataset scope', function (string $file, string $scope) { expect(DatasetInfo::scope($file))->toBe($scope); })->with([ + ['file' => '/var/www/Datasets/project/tests/Datasets/Nested/Numbers.php', 'scope' => '/var/www/Datasets/project/tests'], + ['file' => '/var/www/Datasets/project/tests/Features/Datasets/Nested/Numbers.php', 'scope' => '/var/www/Datasets/project/tests/Features'], ['file' => '/var/www/project/tests/Datasets/Numbers.php', 'scope' => '/var/www/project/tests'], + ['file' => '/var/www/project/tests/Datasets/Nested/Numbers.php', 'scope' => '/var/www/project/tests'], ['file' => '/var/www/project/tests/Datasets.php', 'scope' => '/var/www/project/tests'], ['file' => '/var/www/project/tests/Features/Datasets/Numbers.php', 'scope' => '/var/www/project/tests/Features'], + ['file' => '/var/www/project/tests/Features/Datasets/Nested/Numbers.php', 'scope' => '/var/www/project/tests/Features'], ['file' => '/var/www/project/tests/Features/Numbers.php', 'scope' => '/var/www/project/tests/Features/Numbers.php'], ['file' => '/var/www/project/tests/Features/Datasets.php', 'scope' => '/var/www/project/tests/Features'], ['file' => '/var/www/project/tests/Features/Controllers/Datasets/Numbers.php', 'scope' => '/var/www/project/tests/Features/Controllers'], + ['file' => '/var/www/project/tests/Features/Controllers/Datasets/Nested/Numbers.php', 'scope' => '/var/www/project/tests/Features/Controllers'], + ['file' => '/var/www/project/tests/Features/Datasets/Nested/Datasets/Numbers.php', 'scope' => '/var/www/project/tests/Features'], ['file' => '/var/www/project/tests/Features/Controllers/Numbers.php', 'scope' => '/var/www/project/tests/Features/Controllers/Numbers.php'], ['file' => '/var/www/project/tests/Features/Controllers/Datasets.php', 'scope' => '/var/www/project/tests/Features/Controllers'], ]); diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php index cd5f62e1..9824a405 100644 --- a/tests/Visual/Parallel.php +++ b/tests/Visual/Parallel.php @@ -16,10 +16,17 @@ $run = function () { test('parallel', function () use ($run) { expect($run('--exclude-group=integration')) - ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 39 todos, 26 skipped, 1177 passed (2789 assertions)') + ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 3 notices, 39 todos, 26 skipped, 1190 passed (2802 assertions)') ->toContain('Parallel: 3 processes'); })->skipOnWindows(); test('a parallel test can extend another test with same name', function () use ($run) { expect($run('tests/Fixtures/Inheritance'))->toContain('Tests: 1 skipped, 1 passed (1 assertions)'); -}); +})->skipOnWindows(); + +test('parallel reports invalid datasets as failures', function () use ($run) { + expect($run('tests/.tests/ParallelInvalidDataset')) + ->toContain("A dataset with the name `missing.dataset` does not exist. You can create it using `dataset('missing.dataset', ['a', 'b']);`.") + ->toContain('Tests: 1 failed, 1 passed (1 assertions)') + ->toContain('Parallel: 3 processes'); +})->skipOnWindows(); diff --git a/tests/Visual/ParallelNestedDatasets.php b/tests/Visual/ParallelNestedDatasets.php index d2a7cbe6..37052556 100644 --- a/tests/Visual/ParallelNestedDatasets.php +++ b/tests/Visual/ParallelNestedDatasets.php @@ -2,58 +2,13 @@ use Symfony\Component\Process\Process; -$fixture = function (): array { - $directory = dirname(__DIR__).DIRECTORY_SEPARATOR.'Features'.DIRECTORY_SEPARATOR.'ParallelNestedDatasetRepro'; - $datasetsDirectory = $directory.DIRECTORY_SEPARATOR.'Datasets'.DIRECTORY_SEPARATOR.'Nested'; - $target = $directory.DIRECTORY_SEPARATOR.'TestFileWithNestedDataset.php'; - - if (! is_dir($datasetsDirectory)) { - mkdir($datasetsDirectory, 0777, true); - } - - file_put_contents($datasetsDirectory.DIRECTORY_SEPARATOR.'Users.php', <<<'PHP' -not->toBeEmpty(); -})->with('nested.users'); -PHP); - - return [$directory, 'tests/Features/ParallelNestedDatasetRepro/TestFileWithNestedDataset.php']; -}; - -$cleanup = function (string $directory): void { - if (! is_dir($directory)) { - return; - } - - $iterator = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS), - RecursiveIteratorIterator::CHILD_FIRST, - ); - - foreach ($iterator as $item) { - if ($item->isDir()) { - rmdir($item->getPathname()); - } else { - unlink($item->getPathname()); - } - } - - rmdir($directory); -}; - -$run = function (string $target, bool $parallel = false): array { - $command = ['php', 'bin/pest', $target, '--colors=never']; +$run = function (bool $parallel = false): array { + $command = [ + 'php', + 'bin/pest', + 'tests/Fixtures/ParallelNestedDatasets/TestFileWithNestedDataset.php', + '--colors=never', + ]; if ($parallel) { $command[] = '--parallel'; @@ -72,20 +27,14 @@ $run = function (string $target, bool $parallel = false): array { ]; }; -test('parallel reports missing nested datasets without a passing summary', function () use ($cleanup, $fixture, $run) { - [$directory, $target] = $fixture(); +test('parallel loads nested datasets from nested directories', function () use ($run) { + $serial = $run(); + $parallel = $run(true); - try { - $serial = $run($target); - $parallel = $run($target, true); - - expect($serial['exitCode'])->toBe(2) - ->and($parallel['exitCode'])->toBe(2) - ->and($serial['output'])->toContain('INFO No tests found.') - ->and($parallel['output'])->toContain('INFO No tests found.') - ->and($parallel['output'])->toContain('Parallel: 2 processes') - ->and($parallel['output'])->not->toContain('passed'); - } finally { - $cleanup($directory); - } + expect($serial['exitCode'])->toBe(0) + ->and($parallel['exitCode'])->toBe(0) + ->and($serial['output'])->toContain('Tests: 2 passed (2 assertions)') + ->and($parallel['output'])->toContain('Tests: 2 passed (2 assertions)') + ->and($parallel['output'])->toContain('Parallel: 2 processes') + ->and($parallel['output'])->not->toContain('No tests found.'); })->skipOnWindows(); diff --git a/tests/Visual/Success.php b/tests/Visual/Success.php index 16d49a38..20ebd1d0 100644 --- a/tests/Visual/Success.php +++ b/tests/Visual/Success.php @@ -12,9 +12,9 @@ test('visual snapshot of test suite on success', function () { $output = function () use ($testsPath) { $process = (new Process( - ['php', 'bin/pest'], + ['php', '-d', 'memory_limit=512M', 'bin/pest', '--exclude-group=integration'], dirname($testsPath), - ['EXCLUDE' => 'integration', '--exclude-group' => 'integration', 'REBUILD_SNAPSHOTS' => false, 'PARATEST' => 0, 'COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true'], + ['EXCLUDE' => 'integration', 'REBUILD_SNAPSHOTS' => false, 'PARATEST' => 0, 'COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true'], )); $process->run(); From 4b50cb486d123687a199fff859230ae002915185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksandr=20=C5=A0t=C5=A1epelin?= Date: Wed, 25 Mar 2026 23:59:29 +0200 Subject: [PATCH 3/3] Restore success snapshot coverage with lower memory limit --- tests/.snapshots/success.txt | 45 +++++++++++++++++++++++++++++++++++- tests/Visual/Success.php | 4 ++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 58d988ac..49efa6bb 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -1754,8 +1754,51 @@ ✓ it alerts users about tests with arguments but no input ✓ it can return an array of all test suite filenames + PASS Tests\Visual\BeforeEachTestName + ✓ description + ✓ latest description + + PASS Tests\Visual\Collision + ✓ collision with (['']) + + PASS Tests\Visual\Help + ✓ visual snapshot of help command output + + WARN Tests\Visual\JUnit + ✓ junit output + - junit with parallel → Not working yet + + PASS Tests\Visual\Parallel + ✓ parallel + ✓ a parallel test can extend another test with same name + ✓ parallel reports invalid datasets as failures + + PASS Tests\Visual\ParallelNestedDatasets + ✓ parallel loads nested datasets from nested directories + + PASS Tests\Visual\SingleTestOrDirectory + ✓ allows to run a single test + ✓ allows to run a directory + ✓ it disable decorating printer when colors is set to never + + WARN Tests\Visual\Success + - visual snapshot of test suite on success + + WARN Tests\Visual\TeamCity + - visual snapshot of team city with ('Failure.php') + - visual snapshot of team city with ('SuccessOnly.php') + + WARN Tests\Visual\Todo + - todos + - todos in parallel + - todo + - todo in parallel + + WARN Tests\Visual\Version + - visual snapshot of help command output + PASS Testsexternal\Features\Expect\toMatchSnapshot ✓ pass with dataset with ('my-datas-set-value') ✓ within describe → pass with dataset with ('my-datas-set-value') - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 39 todos, 26 skipped, 1191 passed (2802 assertions) \ No newline at end of file + Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 39 todos, 35 skipped, 1203 passed (2835 assertions) \ No newline at end of file diff --git a/tests/Visual/Success.php b/tests/Visual/Success.php index 20ebd1d0..7906378f 100644 --- a/tests/Visual/Success.php +++ b/tests/Visual/Success.php @@ -12,9 +12,9 @@ test('visual snapshot of test suite on success', function () { $output = function () use ($testsPath) { $process = (new Process( - ['php', '-d', 'memory_limit=512M', 'bin/pest', '--exclude-group=integration'], + ['php', '-d', 'memory_limit=256M', 'bin/pest'], dirname($testsPath), - ['EXCLUDE' => 'integration', 'REBUILD_SNAPSHOTS' => false, 'PARATEST' => 0, 'COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true'], + ['EXCLUDE' => 'integration', '--exclude-group' => 'integration', 'REBUILD_SNAPSHOTS' => false, 'PARATEST' => 0, 'COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true'], )); $process->run();