feat(mutate): only

This commit is contained in:
Nuno Maduro
2024-09-05 02:49:52 +01:00
parent c82f77ea75
commit 2d80ff19ec
6 changed files with 64 additions and 10 deletions

View File

@ -83,7 +83,7 @@ final readonly class TestSuiteFilterProcessor
} }
if (Only::isEnabled()) { if (Only::isEnabled()) {
$factory->addIncludeGroupFilter(['__pest_only']); $factory->addIncludeGroupFilter([Only::group()]);
} elseif ($configuration->hasGroups()) { } elseif ($configuration->hasGroups()) {
$factory->addIncludeGroupFilter( $factory->addIncludeGroupFilter(
$configuration->groups(), $configuration->groups(),

View File

@ -7,6 +7,7 @@ use Pest\Configuration;
use Pest\Exceptions\AfterAllWithinDescribe; use Pest\Exceptions\AfterAllWithinDescribe;
use Pest\Exceptions\BeforeAllWithinDescribe; use Pest\Exceptions\BeforeAllWithinDescribe;
use Pest\Expectation; use Pest\Expectation;
use Pest\Mutate\Contracts\MutationTestRunner;
use Pest\PendingCalls\AfterEachCall; use Pest\PendingCalls\AfterEachCall;
use Pest\PendingCalls\BeforeEachCall; use Pest\PendingCalls\BeforeEachCall;
use Pest\PendingCalls\DescribeCall; use Pest\PendingCalls\DescribeCall;
@ -14,6 +15,7 @@ use Pest\PendingCalls\TestCall;
use Pest\PendingCalls\UsesCall; use Pest\PendingCalls\UsesCall;
use Pest\Repositories\DatasetsRepository; use Pest\Repositories\DatasetsRepository;
use Pest\Support\Backtrace; use Pest\Support\Backtrace;
use Pest\Support\Container;
use Pest\Support\DatasetInfo; use Pest\Support\DatasetInfo;
use Pest\Support\HigherOrderTapProxy; use Pest\Support\HigherOrderTapProxy;
use Pest\TestSuite; use Pest\TestSuite;
@ -222,7 +224,15 @@ if (! function_exists('covers')) {
{ {
$filename = Backtrace::file(); $filename = Backtrace::file();
(new BeforeEachCall(TestSuite::getInstance(), $filename)) $beforeEachCall = (new BeforeEachCall(TestSuite::getInstance(), $filename));
->covers(...$classesOrFunctions);
$beforeEachCall->covers(...$classesOrFunctions);
/** @var MutationTestRunner $runner */
$runner = Container::getInstance()->get(MutationTestRunner::class);
if ($runner->isEnabled()) {
$beforeEachCall->only('__pest_mutate_only');
}
} }
} }

View File

@ -225,7 +225,7 @@ final class TestCall
*/ */
public function only(): self public function only(): self
{ {
Only::enable($this); Only::enable($this, ...func_get_args()); // @phpstan-ignore-line
return $this; 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 $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) { 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); $isFunction = function_exists($classOrFunction);
if (! $isClass && ! $isFunction) { if (! $isClass && ! $isFunction) {
@ -532,6 +533,8 @@ final class TestCall
if ($isClass) { if ($isClass) {
$this->coversClass($classOrFunction); $this->coversClass($classOrFunction);
} elseif ($isTrait) {
$this->coversTrait($classOrFunction);
} else { } else {
$this->coversFunction($classOrFunction); $this->coversFunction($classOrFunction);
} }
@ -559,6 +562,25 @@ final class TestCall
return $this; 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. * Sets the covered functions.
*/ */

View File

@ -38,18 +38,26 @@ final class Only implements Terminable
/** /**
* Creates the lock file. * 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) { if (Environment::name() === Environment::CI) {
return; return;
} }
$testCall->group('__pest_only'); $testCall->group($group);
$lockFile = self::TEMPORARY_FOLDER.DIRECTORY_SEPARATOR.'only.lock'; $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)) { if (! file_exists($lockFile)) {
touch($lockFile); touch($lockFile);
file_put_contents($lockFile, $group);
} }
} }
@ -62,4 +70,18 @@ final class Only implements Terminable
return file_exists($lockFile); 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
}
} }

View File

@ -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 () { it('uses the correct PHPUnit attribute for trait', function () {
$attributes = (new ReflectionClass($this))->getAttributes(); $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'); 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 () { it('uses the correct PHPUnit attribute for covers nothing', function () {
$attributes = (new ReflectionMethod($this, $this->name()))->getAttributes(); $attributes = (new ReflectionMethod($this, $this->name()))->getAttributes();

View File

@ -16,7 +16,7 @@ $run = function () {
test('parallel', function () use ($run) { test('parallel', function () use ($run) {
expect($run('--exclude-group=integration')) 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'); ->toContain('Parallel: 3 processes');
})->skipOnWindows(); })->skipOnWindows();