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();