Merge pull request #1655 from stsepelin/fix/parallel-empty-suite-reporting

fix: nested dataset discovery and parallel invalid-dataset reporting
This commit is contained in:
nuno maduro
2026-04-10 11:59:48 +01:00
committed by GitHub
13 changed files with 248 additions and 20 deletions

View File

@ -170,6 +170,14 @@ final class ResultPrinter
$state = (new StateGenerator)->fromPhpUnitTestResult($this->passedTests, $testResult);
if ($testResult->numberOfTestsRun() === 0 && $state->testSuiteTestsCount() === 0) {
$this->output->writeln([
'',
' <fg=white;options=bold;bg=blue> INFO </> No tests found.',
'',
]);
}
$this->compactPrinter->errors($state);
$this->compactPrinter->recap($state, $testResult, $duration, $this->options);
}

View File

@ -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;
@ -492,14 +493,52 @@ final class WrapperRunner implements RunnerInterface
private function getTestFiles(SuiteLoader $suiteLoader): array
{
/** @var array<string, non-empty-string> $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;
}
}

View File

@ -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<string>
*/
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);
}
}

View File

@ -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<string, list<PhpunitDeprecationTriggered|PhpunitErrorTriggered|PhpunitNoticeTriggered|PhpunitWarningTriggered>> $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()))
));
}
}
}
}