diff --git a/src/Plugin.php b/src/Plugin.php index ac95c34..cb58d25 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -16,7 +16,6 @@ use Pest\TypeCoverage\Support\ConfigurationSourceDetector; use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Finder\Finder; use function Termwind\render; use function Termwind\renderUsing; @@ -119,10 +118,39 @@ public function handleOriginalArguments(array $arguments): void } } - $configArg = current(array_filter($arguments, fn ($arg) => str_starts_with($arg, '--configuration='))); - $source = ConfigurationSourceDetector::detect($configArg ? [$configArg] : []); + // Normalize configuration argument to support: --configuration=, --configuration , -c , -c= + $normalizedConfigArg = null; + foreach ($arguments as $index => $arg) { + if (str_starts_with($arg, '--configuration=')) { + $normalizedConfigArg = $arg; + break; + } + + if ($arg === '--configuration') { + $value = $arguments[$index + 1] ?? null; + if ($value !== null) { + $normalizedConfigArg = '--configuration='.$value; + break; + } + } + + if ($arg === '-c') { + $value = $arguments[$index + 1] ?? null; + if ($value !== null) { + $normalizedConfigArg = '--configuration='.$value; + break; + } + } - if ($source === []) { + if (str_starts_with($arg, '-c=')) { + $normalizedConfigArg = '--configuration='.substr($arg, 3); + break; + } + } + + $files = ConfigurationSourceDetector::detect($normalizedConfigArg ? [$normalizedConfigArg] : []); + + if ($files === []) { View::render('components.badge', [ 'type' => 'ERROR', 'content' => 'No source section found. Did you forget to add a `source` section to your `phpunit.xml` file?', @@ -131,16 +159,11 @@ public function handleOriginalArguments(array $arguments): void $this->exit(1); } - $files = Finder::create() - ->in($source) - ->name('*.php') - ->notName('*.blade.php') - ->files(); - - $files = array_filter( - iterator_to_array($files), - fn (string $file): bool => ! str_contains(file_get_contents($file), 'trait '), - ); + // Filter out traits + $files = array_values(array_filter( + $files, + static fn (string $file): bool => is_string($file) && is_file($file) && ! str_contains((string) file_get_contents($file), 'trait '), + )); $totals = []; @@ -158,13 +181,13 @@ public function handleOriginalArguments(array $arguments): void } if ($total > 1) { - $files = array_filter($files, static function ($file) use ($index, $total): bool { - return (crc32($file->getRealPath()) % $total) === ($index - 1); - }); + $files = array_values(array_filter($files, static function (string $file) use ($index, $total): bool { + return (crc32((string) realpath($file)) % $total) === ($index - 1); + })); } Analyser::analyse( - array_keys($files), + $files, function (Result $result) use (&$totals): void { $path = str_replace(TestSuite::getInstance()->rootPath.DIRECTORY_SEPARATOR, '', $result->file); $uncoveredLines = []; diff --git a/src/Support/ConfigurationSourceDetector.php b/src/Support/ConfigurationSourceDetector.php index aea3604..0a63969 100644 --- a/src/Support/ConfigurationSourceDetector.php +++ b/src/Support/ConfigurationSourceDetector.php @@ -6,9 +6,11 @@ use PHPUnit\TextUI\CliArguments\Builder; use PHPUnit\TextUI\CliArguments\XmlConfigurationFileFinder; +use PHPUnit\TextUI\Configuration\File; use PHPUnit\TextUI\Configuration\FilterDirectory; use PHPUnit\TextUI\XmlConfiguration\DefaultConfiguration; use PHPUnit\TextUI\XmlConfiguration\Loader; +use Symfony\Component\Finder\Finder; /** * @internal @@ -23,17 +25,91 @@ final class ConfigurationSourceDetector */ public static function detect(array $arguments = []): array { - $cliConfiguration = (new Builder)->fromParameters($arguments); - $configurationFile = (new XmlConfigurationFileFinder)->find($cliConfiguration); + // Try to resolve configuration file directly from provided arguments + $configurationFile = null; + foreach ($arguments as $index => $arg) { + if (str_starts_with($arg, '--configuration=')) { + $configurationFile = substr($arg, strlen('--configuration=')); + break; + } + if ($arg === '--configuration') { + $configurationFile = $arguments[$index + 1] ?? null; + break; + } + if ($arg === '-c') { + $configurationFile = $arguments[$index + 1] ?? null; + break; + } + } + + if (! is_string($configurationFile)) { + $cliConfiguration = (new Builder)->fromParameters($arguments); + $configurationFile = (new XmlConfigurationFileFinder)->find($cliConfiguration); + } $xmlConfiguration = DefaultConfiguration::create(); if (is_string($configurationFile)) { $xmlConfiguration = (new Loader)->load($configurationFile); } - return array_map( - fn (FilterDirectory $directory): string => (string) realpath($directory->path()), - $xmlConfiguration->source()->includeDirectories()->asArray(), - ); + $source = $xmlConfiguration->source(); + + $includeDirectories = array_values(array_filter(array_map( + fn (FilterDirectory $directory): string|false => realpath($directory->path()) ?: false, + $source->includeDirectories()->asArray(), + ))); + + $includeFiles = array_values(array_filter(array_map( + fn (File $file): string|false => realpath($file->path()) ?: false, + $source->includeFiles()->asArray(), + ))); + + $excludeDirectories = array_values(array_filter(array_map( + fn (FilterDirectory $directory): string|false => realpath($directory->path()) ?: false, + $source->excludeDirectories()->asArray(), + ))); + + $excludeFiles = array_values(array_filter(array_map( + fn (File $file): string|false => realpath($file->path()) ?: false, + $source->excludeFiles()->asArray(), + ))); + + // Build the list of PHP files from included directories (if any) + $filesFromDirectories = []; + if ($includeDirectories !== []) { + $finder = Finder::create() + ->in($includeDirectories) + ->name('*.php') + ->notName('*.blade.php') + ->files(); + + $filesFromDirectories = array_map( + static fn ($file): string => (string) realpath($file->getRealPath()), + iterator_to_array($finder) + ); + } + + // Merge with explicitly included files + $allIncluded = array_values(array_unique(array_merge($filesFromDirectories, $includeFiles))); + + // Apply excludes + $allIncluded = array_values(array_filter($allIncluded, static function (string $path) use ($excludeDirectories, $excludeFiles): bool { + // Exclude explicit files + if (in_array($path, $excludeFiles, true)) { + return false; + } + + // Exclude by directories + foreach ($excludeDirectories as $excludeDir) { + $prefix = rtrim($excludeDir, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; + if (str_starts_with($path, $prefix)) { + return false; + } + } + + return true; + })); + + return $allIncluded; } } diff --git a/tests/Fixtures/phpunit.exclude.file.xml b/tests/Fixtures/phpunit.exclude.file.xml new file mode 100644 index 0000000..1ce6e8b --- /dev/null +++ b/tests/Fixtures/phpunit.exclude.file.xml @@ -0,0 +1,20 @@ + + + + + ./tests + + + + + ./ + + + ./Parameters.php + + + + + diff --git a/tests/Fixtures/phpunit.include.file.xml b/tests/Fixtures/phpunit.include.file.xml new file mode 100644 index 0000000..47bcc7d --- /dev/null +++ b/tests/Fixtures/phpunit.include.file.xml @@ -0,0 +1,17 @@ + + + + + ./tests + + + + + ./All.php + + + + + diff --git a/tests/Support/ConfigurationSourceDetector.php b/tests/Support/ConfigurationSourceDetector.php index ac4e3cb..fda6eb3 100644 --- a/tests/Support/ConfigurationSourceDetector.php +++ b/tests/Support/ConfigurationSourceDetector.php @@ -5,8 +5,34 @@ it('detects the source of the application', function () { $sources = ConfigurationSourceDetector::detect(); - expect($sources)->toBe([ - realpath(__DIR__.'/../../src'), - realpath(__DIR__.'/../../tests/Fixtures'), - ]); + expect($sources)->toBeArray() + ->and($sources)->toContain( + realpath(__DIR__.'/../../src/Plugin.php'), + realpath(__DIR__.'/../../tests/Fixtures/All.php'), + ); +}); + +it('supports --configuration= and includes single file', function () { + $config = realpath(__DIR__.'/../Fixtures/phpunit.include.file.xml'); + $sources = ConfigurationSourceDetector::detect(['--configuration='.$config]); + + expect($sources)->toHaveCount(1) + ->and($sources)->toContain(realpath(__DIR__.'/../Fixtures/All.php')); +}); + +it('supports --configuration and excludes files', function () { + $config = realpath(__DIR__.'/../Fixtures/phpunit.exclude.file.xml'); + $sources = ConfigurationSourceDetector::detect(['--configuration', $config]); + + expect($sources)->not->toContain( + realpath(__DIR__.'/../Fixtures/Parameters.php') + )->and($sources)->not->toBeEmpty(); +}); + +it('supports -c ', function () { + $config = realpath(__DIR__.'/../Fixtures/phpunit.include.file.xml'); + $sourcesShort = ConfigurationSourceDetector::detect(['-c', $config]); + + expect($sourcesShort)->toHaveCount(1) + ->and($sourcesShort)->toContain(realpath(__DIR__.'/../Fixtures/All.php')); });