diff --git a/overrides/TextUI/TestSuiteFilterProcessor.php b/overrides/TextUI/TestSuiteFilterProcessor.php index 64440ce2..536ab208 100644 --- a/overrides/TextUI/TestSuiteFilterProcessor.php +++ b/overrides/TextUI/TestSuiteFilterProcessor.php @@ -83,7 +83,7 @@ final readonly class TestSuiteFilterProcessor } if (Only::isEnabled()) { - $factory->addIncludeGroupFilter(['__pest_only']); + $factory->addIncludeGroupFilter([Only::group()]); } elseif ($configuration->hasGroups()) { $factory->addIncludeGroupFilter( $configuration->groups(), diff --git a/src/Functions.php b/src/Functions.php index 77a078d7..401e1e3e 100644 --- a/src/Functions.php +++ b/src/Functions.php @@ -7,6 +7,7 @@ use Pest\Configuration; use Pest\Exceptions\AfterAllWithinDescribe; use Pest\Exceptions\BeforeAllWithinDescribe; use Pest\Expectation; +use Pest\Mutate\Contracts\MutationTestRunner; use Pest\PendingCalls\AfterEachCall; use Pest\PendingCalls\BeforeEachCall; use Pest\PendingCalls\DescribeCall; @@ -14,6 +15,7 @@ use Pest\PendingCalls\TestCall; use Pest\PendingCalls\UsesCall; use Pest\Repositories\DatasetsRepository; use Pest\Support\Backtrace; +use Pest\Support\Container; use Pest\Support\DatasetInfo; use Pest\Support\HigherOrderTapProxy; use Pest\TestSuite; @@ -222,7 +224,15 @@ if (! function_exists('covers')) { { $filename = Backtrace::file(); - (new BeforeEachCall(TestSuite::getInstance(), $filename)) - ->covers(...$classesOrFunctions); + $beforeEachCall = (new BeforeEachCall(TestSuite::getInstance(), $filename)); + + $beforeEachCall->covers(...$classesOrFunctions); + + /** @var MutationTestRunner $runner */ + $runner = Container::getInstance()->get(MutationTestRunner::class); + + if ($runner->isEnabled()) { + $beforeEachCall->only('__pest_mutate_only'); + } } } diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index a9befed1..6e461dc2 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -225,7 +225,7 @@ final class TestCall */ public function only(): self { - Only::enable($this); + Only::enable($this, ...func_get_args()); // @phpstan-ignore-line return $this; } @@ -523,7 +523,8 @@ final class TestCall $classesOrFunctions = array_reduce($classesOrFunctions, fn ($carry, $item): array => is_array($item) ? array_merge($carry, $item) : array_merge($carry, [$item]), []); // @pest-ignore-type foreach ($classesOrFunctions as $classOrFunction) { - $isClass = class_exists($classOrFunction) || trait_exists($classOrFunction) || interface_exists($classOrFunction) || enum_exists($classOrFunction); + $isClass = class_exists($classOrFunction) || interface_exists($classOrFunction) || enum_exists($classOrFunction); + $isTrait = trait_exists($classOrFunction); $isFunction = function_exists($classOrFunction); if (! $isClass && ! $isFunction) { @@ -532,6 +533,8 @@ final class TestCall if ($isClass) { $this->coversClass($classOrFunction); + } elseif ($isTrait) { + $this->coversTrait($classOrFunction); } else { $this->coversFunction($classOrFunction); } @@ -559,6 +562,25 @@ final class TestCall return $this; } + /** + * Sets the covered classes. + */ + public function coversTrait(string ...$traits): self + { + foreach ($traits as $trait) { + $this->testCaseFactoryAttributes[] = new Attribute( + \PHPUnit\Framework\Attributes\CoversTrait::class, + [$trait], + ); + } + + /** @var Configuration $configuration */ + $configuration = Container::getInstance()->get(ConfigurationRepository::class)->globalConfiguration('default'); // @phpstan-ignore-line + $configuration->class(...$traits); // @phpstan-ignore-line + + return $this; + } + /** * Sets the covered functions. */ diff --git a/src/Plugins/Only.php b/src/Plugins/Only.php index 5ffce81e..977723cc 100644 --- a/src/Plugins/Only.php +++ b/src/Plugins/Only.php @@ -38,18 +38,26 @@ final class Only implements Terminable /** * Creates the lock file. */ - public static function enable(TestCall $testCall): void + public static function enable(TestCall $testCall, string $group = '__pest_only'): void { if (Environment::name() === Environment::CI) { return; } - $testCall->group('__pest_only'); + $testCall->group($group); $lockFile = self::TEMPORARY_FOLDER.DIRECTORY_SEPARATOR.'only.lock'; + if (file_exists($lockFile) && $group === '__pest_only') { + file_put_contents($lockFile, $group); + + return; + } + if (! file_exists($lockFile)) { touch($lockFile); + + file_put_contents($lockFile, $group); } } @@ -62,4 +70,18 @@ final class Only implements Terminable return file_exists($lockFile); } + + /** + * Returns the group name. + */ + public static function group(): string + { + $lockFile = self::TEMPORARY_FOLDER.DIRECTORY_SEPARATOR.'only.lock'; + + if (! file_exists($lockFile)) { + return '__pest_only'; + } + + return file_get_contents($lockFile) ?: '__pest_only'; // @phpstan-ignore-line + } } diff --git a/tests/Features/Covers.php b/tests/Features/Covers.php index 719329a4..c7721aa8 100644 --- a/tests/Features/Covers.php +++ b/tests/Features/Covers.php @@ -41,9 +41,9 @@ it('guesses if the given argument is a class or function', function () { it('uses the correct PHPUnit attribute for trait', function () { $attributes = (new ReflectionClass($this))->getAttributes(); - expect($attributes[8]->getName())->toBe('PHPUnit\Framework\Attributes\CoversClass'); + expect($attributes[8]->getName())->toBe('PHPUnit\Framework\Attributes\CoversTrait'); expect($attributes[8]->getArguments()[0])->toBe('Tests\Fixtures\Covers\CoversTrait'); -})->coversClass(CoversTrait::class); +})->coversTrait(CoversTrait::class); it('uses the correct PHPUnit attribute for covers nothing', function () { $attributes = (new ReflectionMethod($this, $this->name()))->getAttributes(); diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php index 3ca89d4e..6d1fc4c5 100644 --- a/tests/Visual/Parallel.php +++ b/tests/Visual/Parallel.php @@ -16,7 +16,7 @@ $run = function () { test('parallel', function () use ($run) { expect($run('--exclude-group=integration')) - ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 19 skipped, 1081 passed (2600 assertions)') + ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 28 skipped, 1091 passed (2624 assertions)') ->toContain('Parallel: 3 processes'); })->skipOnWindows();