mirror of
https://github.com/pestphp/pest.git
synced 2026-06-05 10:52:14 +02:00
wip
This commit is contained in:
19
src/Bootstrappers/BootPhpUnitConfiguration.php
Normal file
19
src/Bootstrappers/BootPhpUnitConfiguration.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Bootstrappers;
|
||||||
|
|
||||||
|
use Pest\Contracts\Bootstrapper;
|
||||||
|
use PHPUnit\TextUI\Configuration\Builder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class BootPhpUnitConfiguration implements Bootstrapper
|
||||||
|
{
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
(new Builder)->build(['pest']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -41,6 +41,7 @@ final class Kernel
|
|||||||
*/
|
*/
|
||||||
private const array BOOTSTRAPPERS = [
|
private const array BOOTSTRAPPERS = [
|
||||||
Bootstrappers\BootOverrides::class,
|
Bootstrappers\BootOverrides::class,
|
||||||
|
Bootstrappers\BootPhpUnitConfiguration::class,
|
||||||
Plugins\Tia\Bootstrapper::class,
|
Plugins\Tia\Bootstrapper::class,
|
||||||
Bootstrappers\BootSubscribers::class,
|
Bootstrappers\BootSubscribers::class,
|
||||||
Bootstrappers\BootFiles::class,
|
Bootstrappers\BootFiles::class,
|
||||||
|
|||||||
@ -584,7 +584,7 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
if (! Fingerprint::structuralMatches($stored, $current)) {
|
if (! Fingerprint::structuralMatches($stored, $current)) {
|
||||||
$drift = Fingerprint::structuralDrift($stored, $current);
|
$drift = Fingerprint::structuralDrift($stored, $current);
|
||||||
|
|
||||||
$this->renderBadge('INFO', sprintf(
|
$this->renderChild(sprintf(
|
||||||
'Graph structure outdated (%s).',
|
'Graph structure outdated (%s).',
|
||||||
$this->formatStructuralDrift($drift),
|
$this->formatStructuralDrift($drift),
|
||||||
));
|
));
|
||||||
@ -1446,7 +1446,7 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
];
|
];
|
||||||
|
|
||||||
$projectRoot = TestSuite::getInstance()->rootPath;
|
$projectRoot = TestSuite::getInstance()->rootPath;
|
||||||
$testPaths = SourceScope::testPaths($projectRoot);
|
$testPaths = SourceScope::testPaths();
|
||||||
|
|
||||||
if ($testPaths === []) {
|
if ($testPaths === []) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -10,6 +10,7 @@ use Pest\Support\View;
|
|||||||
use Pest\TestSuite;
|
use Pest\TestSuite;
|
||||||
use PHPUnit\Framework\Attributes\Group;
|
use PHPUnit\Framework\Attributes\Group;
|
||||||
use PHPUnit\Framework\TestStatus\TestStatus;
|
use PHPUnit\Framework\TestStatus\TestStatus;
|
||||||
|
use PHPUnit\TextUI\Configuration\Registry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -566,17 +567,58 @@ final class Graph
|
|||||||
private function shouldRerun(int $status): bool
|
private function shouldRerun(int $status): bool
|
||||||
{
|
{
|
||||||
$testStatus = TestStatus::from($status);
|
$testStatus = TestStatus::from($status);
|
||||||
if ($testStatus->isFailure()) {
|
|
||||||
return true;
|
if ($testStatus->isFailure() || $testStatus->isError()) {
|
||||||
}
|
|
||||||
if ($testStatus->isError()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if ($testStatus->isIncomplete()) {
|
|
||||||
return true;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -4,6 +4,9 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Pest\Plugins\Tia;
|
namespace Pest\Plugins\Tia;
|
||||||
|
|
||||||
|
use PHPUnit\TextUI\Configuration\Registry;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -38,19 +41,21 @@ final readonly class SourceScope
|
|||||||
|
|
||||||
public static function fromProjectRoot(string $projectRoot): self
|
public static function fromProjectRoot(string $projectRoot): self
|
||||||
{
|
{
|
||||||
$configPath = self::configPath($projectRoot);
|
|
||||||
|
|
||||||
$phpunitIncludes = [];
|
$phpunitIncludes = [];
|
||||||
$phpunitExcludes = [];
|
$phpunitExcludes = [];
|
||||||
|
|
||||||
if ($configPath !== null) {
|
try {
|
||||||
$xml = @simplexml_load_file($configPath);
|
$source = Registry::get()->source();
|
||||||
|
|
||||||
if ($xml !== false) {
|
foreach ($source->includeDirectories() as $dir) {
|
||||||
$configDir = dirname($configPath);
|
$phpunitIncludes[] = self::normalise($dir->path());
|
||||||
$phpunitIncludes = self::extractDirectories($xml, 'source/include/directory', $configDir);
|
|
||||||
$phpunitExcludes = self::extractDirectories($xml, 'source/exclude/directory', $configDir);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
$rootIncludes = self::topLevelProjectDirs($projectRoot);
|
||||||
@ -71,26 +76,25 @@ final readonly class SourceScope
|
|||||||
/**
|
/**
|
||||||
* @return list<string> Absolute, normalised paths to testsuite directories and files declared in phpunit.xml.
|
* @return list<string> 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);
|
try {
|
||||||
|
$suites = Registry::get()->testSuite();
|
||||||
if ($configPath === null) {
|
} catch (Throwable) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
$out = [];
|
||||||
|
foreach ($suites as $suite) {
|
||||||
|
foreach ($suite->directories() as $directory) {
|
||||||
|
$out[] = self::normalise($directory->path());
|
||||||
|
}
|
||||||
|
|
||||||
$xml = @simplexml_load_file($configPath);
|
foreach ($suite->files() as $file) {
|
||||||
|
$out[] = self::normalise($file->path());
|
||||||
if ($xml === false) {
|
}
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$configDir = dirname($configPath);
|
return array_values(array_unique($out));
|
||||||
|
|
||||||
return array_values(array_unique([
|
|
||||||
...self::extractDirectories($xml, 'testsuites/testsuite/directory', $configDir),
|
|
||||||
...self::extractDirectories($xml, 'testsuites/testsuite/file', $configDir),
|
|
||||||
]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function contains(string $absoluteFile): bool
|
public function contains(string $absoluteFile): bool
|
||||||
@ -122,45 +126,6 @@ final readonly class SourceScope
|
|||||||
return $this->includes;
|
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<string>
|
|
||||||
*/
|
|
||||||
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<string>
|
* @return list<string>
|
||||||
*/
|
*/
|
||||||
@ -216,22 +181,6 @@ final readonly class SourceScope
|
|||||||
return $out;
|
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
|
private static function normalise(string $path): string
|
||||||
{
|
{
|
||||||
return rtrim($path, '/\\');
|
return rtrim($path, '/\\');
|
||||||
|
|||||||
@ -5,6 +5,8 @@ declare(strict_types=1);
|
|||||||
namespace Pest\Plugins\Tia;
|
namespace Pest\Plugins\Tia;
|
||||||
|
|
||||||
use Pest\TestSuite;
|
use Pest\TestSuite;
|
||||||
|
use PHPUnit\TextUI\Configuration\Registry;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the set of project-relative paths that are considered test files,
|
* 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
|
public static function fromProjectRoot(string $projectRoot): self
|
||||||
{
|
{
|
||||||
$configPath = self::configPath($projectRoot);
|
|
||||||
|
|
||||||
$directories = [];
|
$directories = [];
|
||||||
$files = [];
|
$files = [];
|
||||||
$suffixes = ['.php'];
|
$suffixes = [];
|
||||||
|
|
||||||
if ($configPath !== null) {
|
try {
|
||||||
$xml = @simplexml_load_file($configPath);
|
$configuration = Registry::get();
|
||||||
|
|
||||||
if ($xml !== false) {
|
foreach ($configuration->testSuite() as $suite) {
|
||||||
$configDir = dirname($configPath);
|
foreach ($suite->directories() as $directory) {
|
||||||
|
$rel = self::toRelative($directory->path(), $projectRoot);
|
||||||
foreach ($xml->xpath('testsuites/testsuite/directory') ?: [] as $node) {
|
|
||||||
$rel = self::toRelative((string) $node, $configDir, $projectRoot);
|
|
||||||
|
|
||||||
if ($rel !== null) {
|
if ($rel !== null) {
|
||||||
$directories[] = $rel;
|
$directories[] = $rel;
|
||||||
}
|
}
|
||||||
|
|
||||||
$suffix = (string) ($node['suffix'] ?? '');
|
$suffix = $directory->suffix();
|
||||||
if ($suffix !== '' && ! in_array($suffix, $suffixes, true)) {
|
|
||||||
|
if ($suffix !== '') {
|
||||||
$suffixes[] = str_starts_with($suffix, '.') ? $suffix : '.'.$suffix;
|
$suffixes[] = str_starts_with($suffix, '.') ? $suffix : '.'.$suffix;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($xml->xpath('testsuites/testsuite/file') ?: [] as $node) {
|
foreach ($suite->files() as $file) {
|
||||||
$rel = self::toRelative((string) $node, $configDir, $projectRoot);
|
$rel = self::toRelative($file->path(), $projectRoot);
|
||||||
|
|
||||||
if ($rel !== null) {
|
if ($rel !== null) {
|
||||||
$files[] = $rel;
|
$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 === []) {
|
if ($directories === [] && $files === []) {
|
||||||
@ -109,20 +120,7 @@ final readonly class TestPaths
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function configPath(string $projectRoot): ?string
|
private static function toRelative(string $value, 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
|
|
||||||
{
|
{
|
||||||
$value = trim($value);
|
$value = trim($value);
|
||||||
|
|
||||||
@ -130,13 +128,8 @@ final readonly class TestPaths
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$isAbsolute = $value[0] === '/' || $value[0] === DIRECTORY_SEPARATOR
|
$real = @realpath($value);
|
||||||
|| (strlen($value) >= 2 && $value[1] === ':');
|
$resolved = $real === false ? $value : $real;
|
||||||
|
|
||||||
$combined = $isAbsolute ? $value : $configDir.DIRECTORY_SEPARATOR.$value;
|
|
||||||
|
|
||||||
$real = @realpath($combined);
|
|
||||||
$resolved = $real === false ? $combined : $real;
|
|
||||||
|
|
||||||
$resolved = str_replace(DIRECTORY_SEPARATOR, '/', $resolved);
|
$resolved = str_replace(DIRECTORY_SEPARATOR, '/', $resolved);
|
||||||
$root = str_replace(DIRECTORY_SEPARATOR, '/', rtrim($projectRoot, '/\\')).'/';
|
$root = str_replace(DIRECTORY_SEPARATOR, '/', rtrim($projectRoot, '/\\')).'/';
|
||||||
@ -152,7 +145,7 @@ final readonly class TestPaths
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$testPath = TestSuite::getInstance()->testPath;
|
$testPath = TestSuite::getInstance()->testPath;
|
||||||
} catch (\Throwable) {
|
} catch (Throwable) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user