diff --git a/src/Bootstrappers/BootPhpUnitConfiguration.php b/src/Bootstrappers/BootPhpUnitConfiguration.php new file mode 100644 index 00000000..c9f80812 --- /dev/null +++ b/src/Bootstrappers/BootPhpUnitConfiguration.php @@ -0,0 +1,19 @@ +build(['pest']); + } +} diff --git a/src/Kernel.php b/src/Kernel.php index 49693690..28232891 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -41,6 +41,7 @@ final class Kernel */ private const array BOOTSTRAPPERS = [ Bootstrappers\BootOverrides::class, + Bootstrappers\BootPhpUnitConfiguration::class, Plugins\Tia\Bootstrapper::class, Bootstrappers\BootSubscribers::class, Bootstrappers\BootFiles::class, diff --git a/src/Plugins/Tia.php b/src/Plugins/Tia.php index 1332bbdb..70453cd4 100644 --- a/src/Plugins/Tia.php +++ b/src/Plugins/Tia.php @@ -584,7 +584,7 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable if (! Fingerprint::structuralMatches($stored, $current)) { $drift = Fingerprint::structuralDrift($stored, $current); - $this->renderBadge('INFO', sprintf( + $this->renderChild(sprintf( 'Graph structure outdated (%s).', $this->formatStructuralDrift($drift), )); @@ -1446,7 +1446,7 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable ]; $projectRoot = TestSuite::getInstance()->rootPath; - $testPaths = SourceScope::testPaths($projectRoot); + $testPaths = SourceScope::testPaths(); if ($testPaths === []) { return false; diff --git a/src/Plugins/Tia/Graph.php b/src/Plugins/Tia/Graph.php index 93274aa8..0327658d 100644 --- a/src/Plugins/Tia/Graph.php +++ b/src/Plugins/Tia/Graph.php @@ -10,6 +10,7 @@ use Pest\Support\View; use Pest\TestSuite; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestStatus\TestStatus; +use PHPUnit\TextUI\Configuration\Registry; /** * @internal @@ -566,17 +567,58 @@ final class Graph private function shouldRerun(int $status): bool { $testStatus = TestStatus::from($status); - if ($testStatus->isFailure()) { - return true; - } - if ($testStatus->isError()) { - return true; - } - if ($testStatus->isIncomplete()) { + + if ($testStatus->isFailure() || $testStatus->isError()) { return true; } - return $testStatus->isRisky(); + $configuration = Registry::get(); + + if ($testStatus->isRisky()) { + return $configuration->failOnRisky(); + } + + if ($testStatus->isWarning()) { + if ($configuration->failOnWarning()) { + return true; + } + + return $configuration->displayDetailsOnTestsThatTriggerWarnings(); + } + + if ($testStatus->isNotice()) { + if ($configuration->failOnNotice()) { + return true; + } + + return $configuration->displayDetailsOnTestsThatTriggerNotices(); + } + + if ($testStatus->isDeprecation()) { + if ($configuration->failOnDeprecation()) { + return true; + } + + return $configuration->displayDetailsOnTestsThatTriggerDeprecations(); + } + + if ($testStatus->isIncomplete()) { + if ($configuration->failOnIncomplete()) { + return true; + } + + return $configuration->displayDetailsOnIncompleteTests(); + } + + if ($testStatus->isSkipped()) { + if ($configuration->failOnSkipped()) { + return true; + } + + return $configuration->displayDetailsOnSkippedTests(); + } + + return false; } /** diff --git a/src/Plugins/Tia/SourceScope.php b/src/Plugins/Tia/SourceScope.php index 6d26b631..a9c078ca 100644 --- a/src/Plugins/Tia/SourceScope.php +++ b/src/Plugins/Tia/SourceScope.php @@ -4,6 +4,9 @@ declare(strict_types=1); namespace Pest\Plugins\Tia; +use PHPUnit\TextUI\Configuration\Registry; +use Throwable; + /** * @internal */ @@ -38,19 +41,21 @@ final readonly class SourceScope public static function fromProjectRoot(string $projectRoot): self { - $configPath = self::configPath($projectRoot); - $phpunitIncludes = []; $phpunitExcludes = []; - if ($configPath !== null) { - $xml = @simplexml_load_file($configPath); + try { + $source = Registry::get()->source(); - if ($xml !== false) { - $configDir = dirname($configPath); - $phpunitIncludes = self::extractDirectories($xml, 'source/include/directory', $configDir); - $phpunitExcludes = self::extractDirectories($xml, 'source/exclude/directory', $configDir); + foreach ($source->includeDirectories() as $dir) { + $phpunitIncludes[] = self::normalise($dir->path()); } + + foreach ($source->excludeDirectories() as $dir) { + $phpunitExcludes[] = self::normalise($dir->path()); + } + } catch (Throwable) { + // Registry not initialized — fall back to project-root scanning. } $rootIncludes = self::topLevelProjectDirs($projectRoot); @@ -71,26 +76,25 @@ final readonly class SourceScope /** * @return list Absolute, normalised paths to testsuite directories and files declared in phpunit.xml. */ - public static function testPaths(string $projectRoot): array + public static function testPaths(): array { - $configPath = self::configPath($projectRoot); - - if ($configPath === null) { + try { + $suites = Registry::get()->testSuite(); + } catch (Throwable) { return []; } + $out = []; + foreach ($suites as $suite) { + foreach ($suite->directories() as $directory) { + $out[] = self::normalise($directory->path()); + } - $xml = @simplexml_load_file($configPath); - - if ($xml === false) { - return []; + foreach ($suite->files() as $file) { + $out[] = self::normalise($file->path()); + } } - $configDir = dirname($configPath); - - return array_values(array_unique([ - ...self::extractDirectories($xml, 'testsuites/testsuite/directory', $configDir), - ...self::extractDirectories($xml, 'testsuites/testsuite/file', $configDir), - ])); + return array_values(array_unique($out)); } public function contains(string $absoluteFile): bool @@ -122,45 +126,6 @@ final readonly class SourceScope return $this->includes; } - private static function configPath(string $projectRoot): ?string - { - foreach (['phpunit.xml', 'phpunit.xml.dist'] as $name) { - $candidate = $projectRoot.DIRECTORY_SEPARATOR.$name; - - if (is_file($candidate)) { - return $candidate; - } - } - - return null; - } - - /** - * @return list - */ - private static function extractDirectories(\SimpleXMLElement $xml, string $xpath, string $configDir): array - { - $nodes = $xml->xpath($xpath); - - if (! is_array($nodes)) { - return []; - } - - $out = []; - - foreach ($nodes as $node) { - $value = trim((string) $node); - - if ($value === '') { - continue; - } - - $out[] = self::resolveRelative($value, $configDir); - } - - return array_values(array_unique($out)); - } - /** * @return list */ @@ -216,22 +181,6 @@ final readonly class SourceScope return $out; } - private static function resolveRelative(string $path, string $configDir): string - { - $isAbsolute = $path !== '' && ($path[0] === DIRECTORY_SEPARATOR || $path[0] === '/' - || (strlen($path) >= 2 && $path[1] === ':')); - - $combined = $isAbsolute ? $path : $configDir.DIRECTORY_SEPARATOR.$path; - - $real = @realpath($combined); - - if ($real === false) { - return self::normalise($combined); - } - - return self::normalise($real); - } - private static function normalise(string $path): string { return rtrim($path, '/\\'); diff --git a/src/Plugins/Tia/TestPaths.php b/src/Plugins/Tia/TestPaths.php index f82954a5..23ecd7bd 100644 --- a/src/Plugins/Tia/TestPaths.php +++ b/src/Plugins/Tia/TestPaths.php @@ -5,6 +5,8 @@ declare(strict_types=1); namespace Pest\Plugins\Tia; use Pest\TestSuite; +use PHPUnit\TextUI\Configuration\Registry; +use Throwable; /** * Resolves the set of project-relative paths that are considered test files, @@ -28,39 +30,48 @@ final readonly class TestPaths public static function fromProjectRoot(string $projectRoot): self { - $configPath = self::configPath($projectRoot); - $directories = []; $files = []; - $suffixes = ['.php']; + $suffixes = []; - if ($configPath !== null) { - $xml = @simplexml_load_file($configPath); + try { + $configuration = Registry::get(); - if ($xml !== false) { - $configDir = dirname($configPath); - - foreach ($xml->xpath('testsuites/testsuite/directory') ?: [] as $node) { - $rel = self::toRelative((string) $node, $configDir, $projectRoot); + foreach ($configuration->testSuite() as $suite) { + foreach ($suite->directories() as $directory) { + $rel = self::toRelative($directory->path(), $projectRoot); if ($rel !== null) { $directories[] = $rel; } - $suffix = (string) ($node['suffix'] ?? ''); - if ($suffix !== '' && ! in_array($suffix, $suffixes, true)) { + $suffix = $directory->suffix(); + + if ($suffix !== '') { $suffixes[] = str_starts_with($suffix, '.') ? $suffix : '.'.$suffix; } } - foreach ($xml->xpath('testsuites/testsuite/file') ?: [] as $node) { - $rel = self::toRelative((string) $node, $configDir, $projectRoot); + foreach ($suite->files() as $file) { + $rel = self::toRelative($file->path(), $projectRoot); if ($rel !== null) { $files[] = $rel; } } } + + if ($suffixes === []) { + foreach ($configuration->testSuffixes() as $suffix) { + $suffixes[] = str_starts_with($suffix, '.') ? $suffix : '.'.$suffix; + } + } + } catch (Throwable) { + // Registry not initialized — fall through to defaults. + } + + if ($suffixes === []) { + $suffixes = ['.php']; } if ($directories === [] && $files === []) { @@ -109,20 +120,7 @@ final readonly class TestPaths return false; } - private static function configPath(string $projectRoot): ?string - { - foreach (['phpunit.xml', 'phpunit.xml.dist'] as $name) { - $candidate = $projectRoot.DIRECTORY_SEPARATOR.$name; - - if (is_file($candidate)) { - return $candidate; - } - } - - return null; - } - - private static function toRelative(string $value, string $configDir, string $projectRoot): ?string + private static function toRelative(string $value, string $projectRoot): ?string { $value = trim($value); @@ -130,13 +128,8 @@ final readonly class TestPaths return null; } - $isAbsolute = $value[0] === '/' || $value[0] === DIRECTORY_SEPARATOR - || (strlen($value) >= 2 && $value[1] === ':'); - - $combined = $isAbsolute ? $value : $configDir.DIRECTORY_SEPARATOR.$value; - - $real = @realpath($combined); - $resolved = $real === false ? $combined : $real; + $real = @realpath($value); + $resolved = $real === false ? $value : $real; $resolved = str_replace(DIRECTORY_SEPARATOR, '/', $resolved); $root = str_replace(DIRECTORY_SEPARATOR, '/', rtrim($projectRoot, '/\\')).'/'; @@ -152,7 +145,7 @@ final readonly class TestPaths { try { $testPath = TestSuite::getInstance()->testPath; - } catch (\Throwable) { + } catch (Throwable) { return null; }