Skip to content

Commit

Permalink
Add ignore option
Browse files Browse the repository at this point in the history
  • Loading branch information
gehrisandro committed Nov 24, 2023
1 parent a7dbfb6 commit 0fab9b4
Show file tree
Hide file tree
Showing 15 changed files with 155 additions and 19 deletions.
35 changes: 30 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ The following options are available.
<div class="collection-method-list" markdown="1">

- [`path()`](#path)
- [`ignore()`](#ignore)
- [`class()`](#class)
- [`mutator()`](#mutator)
- [`except()`](#except)
Expand All @@ -152,12 +153,20 @@ Limit the directories or files to mutate by providing one or more paths to a dir
If no paths are provided, it defaults to the source directories configured in your `phpunit.xml` file.

```php
mutate()
->path('src');
```


You can also use patterns.
<a name="options-ignore"></a>
### `ignore()`
CLI: `--ignore`

Ignore one or more directory or file paths.

```php
mutate()
->path('src');
->ignore('src/Contracts');
```


Expand Down Expand Up @@ -350,8 +359,9 @@ vendor/bin/pest --mutate --covered-only
### Run tests in parallel

> Attention: This may slow down your tests if you have a small test suite or only a small part of your test suite is run for a certain mutation.
> Probably I am going to replace this in favor of running multiple mutations in parallel.
Run tests against multiple mutations in parallel. This can significantly reduce the time it takes to run mutation tests.

Against a single mutation the tests are not run in parallel, regardless of the parallel option.

```bash
vendor/bin/pest --mutate --parallel
Expand Down Expand Up @@ -624,8 +634,10 @@ This set consists of various mutators from different sets. Mutators included are

<div class="collection-method-list" markdown="1">

- [RemoveArrayItem](#removearrayitem-) (*)
- [RemoveFunctionCall](#removefunctioncall-) (*)
- [RemoveMethodCall](#removemethodcall-) (*)
- [RemoveNullSafeOperator](#removenullsafeoperator-) (*)

</div>

Expand Down Expand Up @@ -1671,6 +1683,19 @@ $a = (array) $b; // [tl! remove]
$a = $b; // [tl! add]
```

<a name="removearrayitem"></a>
### RemoveArrayItem (*)
Set: Removal

Removes an item from an array

```php
return [
'foo' => 1, // [tl! remove]
'bar' => 2,
];
```

<a name="removebooleancast"></a>
### RemoveBooleanCast (*)
Set: Casting
Expand Down Expand Up @@ -1738,7 +1763,7 @@ if ($a) { // [tl! add]
```

<a name="removenullsafeoperator"></a>
### RemoveNullSafeOperator
### RemoveNullSafeOperator (*)
Set: Removal

Converts nullsafe method and property calls to regular calls.
Expand Down
1 change: 0 additions & 1 deletion WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
# Backlog Prio 1
- [ ] Fix PHPStorm indexing Issues. Maybe do not end cached files with .php?
- [ ] Automatically empty cache when package version changes
- [ ] Exclude paths or files
- [ ] What should we do with interfaces? ignore them completely?
- [ ] Finish: Disable mutations by annotation
- [ ] Run mutations in a reasonable order: New, Survived, NotCovered, Skipped, Killed (Survived first, if --stop-on-survived or --bail; NotCovered first, if --stop-on-uncovered)
Expand Down
13 changes: 11 additions & 2 deletions docs/mutation-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ The following options are available.
<div class="collection-method-list" markdown="1">

- [`path()`](#path)
- [`ignore()`](#ignore)
- [`class()`](#class)
- [`mutator()`](#mutator)
- [`except()`](#except)
Expand All @@ -144,12 +145,20 @@ Limit the directories or files to mutate by providing one or more paths to a dir
If no paths are provided, it defaults to the source directories configured in your `phpunit.xml` file.

```php
mutate()
->path('src');
```

You can also use patterns.

<a name="options-ignore"></a>
### `ignore()`
CLI: `--ignore`

Ignore one or more directory or file paths.

```php
mutate()
->path('src');
->ignore('src/Contracts');
```


Expand Down
5 changes: 5 additions & 0 deletions src/Contracts/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ interface Configuration
*/
public function path(array|string ...$paths): self;

/**
* @param array<int, string>|string ...$paths
*/
public function ignore(array|string ...$paths): self;

/**
* @param array<int, class-string<Mutator|MutatorSet>>|class-string<Mutator|MutatorSet> ...$mutators
*/
Expand Down
10 changes: 10 additions & 0 deletions src/Decorators/TestCallDecorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@ public function path(array|string ...$paths): self
return $this;
}

/**
* @param array<int, string>|string ...$paths
*/
public function ignore(array|string ...$paths): self
{
$this->configuration->ignore(...$paths);

return $this;
}

/**
* {@inheritDoc}
*/
Expand Down
27 changes: 27 additions & 0 deletions src/Options/IgnoreOption.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Pest\Mutate\Options;

use Symfony\Component\Console\Input\InputOption;

class IgnoreOption
{
final public const ARGUMENT = 'ignore';

public static function remove(): bool
{
return true;
}

public static function match(string $argument): bool
{
return str_starts_with($argument, sprintf('--%s=', self::ARGUMENT));
}

public static function inputOption(): InputOption
{
return new InputOption(sprintf('--%s', self::ARGUMENT), null, InputOption::VALUE_REQUIRED, '', 0);
}
}
2 changes: 1 addition & 1 deletion src/Options/PathsOption.php → src/Options/PathOption.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use Symfony\Component\Console\Input\InputOption;

class PathsOption
class PathOption
{
final public const ARGUMENT = 'path';

Expand Down
1 change: 1 addition & 0 deletions src/Repositories/ConfigurationRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public function mergedConfiguration(): Configuration
return $this->mergedConfiguration = new Configuration(
coveredOnly: $config['covered_only'] ?? false,
paths: $config['paths'] ?? $this->pathsFromPhpunitConfiguration(),
pathsToIgnore: $config['paths_to_ignore'] ?? [],
mutators: array_diff($config['mutators'] ?? DefaultSet::mutators(), $config['excluded_mutators'] ?? []),
classes: $config['classes'] ?? [],
parallel: $parallel,
Expand Down
18 changes: 17 additions & 1 deletion src/Support/Configuration/AbstractConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ abstract class AbstractConfiguration implements ConfigurationContract
*/
private ?array $paths = null;

/**
* @var string[]|null
*/
private ?array $pathsToIgnore = null;

/**
* @var class-string<Mutator>[]|null
*/
Expand Down Expand Up @@ -57,6 +62,16 @@ public function path(array|string ...$paths): self
return $this;
}

/**
* {@inheritDoc}
*/
public function ignore(array|string ...$paths): self
{
$this->pathsToIgnore = array_merge(...array_map(fn (string|array $path): array => is_string($path) ? [$path] : $path, $paths));

return $this;
}

/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -152,12 +167,13 @@ public function changedOnly(?string $branch = 'main'): self
}

/**
* @return array{paths?: string[], mutators?: class-string<Mutator>[], excluded_mutators?: class-string<Mutator>[], classes?: string[], parallel?: bool, processes?: int, min_score?: float, covered_only?: bool, stop_on_survived?: bool, stop_on_not_covered?: bool, uncommitted_only?: bool, changed_only?: string}
* @return array{paths?: string[], paths_to_ignore?: string[], mutators?: class-string<Mutator>[], excluded_mutators?: class-string<Mutator>[], classes?: string[], parallel?: bool, processes?: int, min_score?: float, covered_only?: bool, stop_on_survived?: bool, stop_on_not_covered?: bool, uncommitted_only?: bool, changed_only?: string}
*/
public function toArray(): array
{
return array_filter([
'paths' => $this->paths,
'paths_to_ignore' => $this->pathsToIgnore,
'mutators' => $this->mutators !== null ? array_values(array_diff($this->mutators, $this->excludedMutators ?? [])) : null,
'excluded_mutators' => $this->excludedMutators,
'classes' => $this->classes,
Expand Down
14 changes: 10 additions & 4 deletions src/Support/Configuration/CliConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
use Pest\Mutate\Options\ClassOption;
use Pest\Mutate\Options\CoveredOnlyOption;
use Pest\Mutate\Options\ExceptOption;
use Pest\Mutate\Options\IgnoreOption;
use Pest\Mutate\Options\MinScoreOption;
use Pest\Mutate\Options\MutateOption;
use Pest\Mutate\Options\MutatorsOption;
use Pest\Mutate\Options\ParallelOption;
use Pest\Mutate\Options\PathsOption;
use Pest\Mutate\Options\PathOption;
use Pest\Mutate\Options\ProcessesOption;
use Pest\Mutate\Options\StopOnNotCoveredOption;
use Pest\Mutate\Options\StopOnSurvivedOption;
Expand All @@ -30,7 +31,8 @@ class CliConfiguration extends AbstractConfiguration
MinScoreOption::class,
MutatorsOption::class,
ExceptOption::class,
PathsOption::class,
PathOption::class,
IgnoreOption::class,
ParallelOption::class,
ProcessesOption::class,
StopOnSurvivedOption::class,
Expand Down Expand Up @@ -67,8 +69,12 @@ public function fromArguments(array $arguments): array
$this->coveredOnly($input->getOption(CoveredOnlyOption::ARGUMENT) !== 'false');
}

if ($input->hasOption(PathsOption::ARGUMENT)) {
$this->path(explode(',', (string) $input->getOption(PathsOption::ARGUMENT))); // @phpstan-ignore-line
if ($input->hasOption(PathOption::ARGUMENT)) {
$this->path(explode(',', (string) $input->getOption(PathOption::ARGUMENT))); // @phpstan-ignore-line
}

if ($input->hasOption(IgnoreOption::ARGUMENT)) {
$this->ignore(explode(',', (string) $input->getOption(IgnoreOption::ARGUMENT))); // @phpstan-ignore-line
}

if ($input->hasOption(MutatorsOption::ARGUMENT)) {
Expand Down
2 changes: 2 additions & 0 deletions src/Support/Configuration/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ class Configuration
{
/**
* @param array<int, string> $paths
* @param array<int, string> $pathsToIgnore
* @param array<int, class-string<Mutator>> $mutators
* @param array<int, string> $classes
*/
public function __construct(
public readonly bool $coveredOnly,
public readonly array $paths,
public readonly array $pathsToIgnore,
public readonly array $mutators,
public readonly array $classes,
public readonly bool $parallel,
Expand Down
11 changes: 7 additions & 4 deletions src/Tester/MutationTestRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public function run(): void
$coveredLines = array_map(fn (array $lines): array => array_filter($lines, fn (array $tests): bool => $tests !== []), $codeCoverage->getData()->lineCoverage());
$coveredLines = array_filter($coveredLines, fn (array $lines): bool => $lines !== []);

$files = $this->getFiles($this->getConfiguration()->paths);
$files = $this->getFiles($this->getConfiguration()->paths, $this->getConfiguration()->pathsToIgnore);

/** @var MutationGenerator $generator */
$generator = Container::getInstance()->get(MutationGenerator::class);
Expand Down Expand Up @@ -182,8 +182,9 @@ classesToMutate: $this->getConfiguration()->classes,

/**
* @param array<array-key, string> $paths
* @param array<array-key, string> $pathsToIgnore
*/
private function getFiles(array $paths): Finder
private function getFiles(array $paths, array $pathsToIgnore): Finder
{
$dirs = [];
$filePaths = [];
Expand All @@ -199,12 +200,14 @@ private function getFiles(array $paths): Finder
}
}

$pathsToIgnore = array_map(fn (string $path): string => str_starts_with($path, DIRECTORY_SEPARATOR) ? $path : getcwd().DIRECTORY_SEPARATOR.ltrim($path, '/'), $pathsToIgnore);

return Finder::create()
->in($dirs)
->name('*.php')
// ->notPath($options->ignoring)
->append($filePaths)
->files();
->files()
->filter(fn (SplFileInfo $file): bool => array_filter($pathsToIgnore, fn (string $pathToIgnore): bool => str_starts_with($file->getRealPath(), $pathToIgnore)) === []);
}

private function getConfiguration(): Configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@
})->mutate(ConfigurationRepository::FAKE.'_3')
->path('src/folder-1', 'src/folder-2');

it('sets the paths to ignore from test', function (): void {
$configuration = $this->repository->fakeTestConfiguration(ConfigurationRepository::FAKE.'_14');

expect($configuration->toArray()['paths_to_ignore'])
->toBe(['src/folder-1', 'src/folder-2']);
})->mutate(ConfigurationRepository::FAKE.'_14')
->ignore('src/folder-1', 'src/folder-2');

it('sets the mutators from test', function (): void {
$configuration = $this->repository->fakeTestConfiguration(ConfigurationRepository::FAKE.'_4');

Expand Down
13 changes: 12 additions & 1 deletion tests/Features/Profiles/HandlesCliProfileConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
$this->configuration = new CliConfiguration();
});

it('sets the paths if --paths argument is passed', function (): void {
it('sets the paths if --path argument is passed', function (): void {
$this->configuration->fromArguments(['--path=app']);

expect($this->configuration->toArray())
Expand All @@ -24,6 +24,17 @@
->paths->toEqual(['src/path-1', 'src/path-2']);
});

it('sets the paths to ignore if --ignore argument is passed', function (): void {
$this->configuration->fromArguments(['--ignore=src/path-1']);

expect($this->configuration->toArray())
->paths_to_ignore->toEqual(['src/path-1']);

$this->configuration->fromArguments(['--ignore=src/path-1,src/path-2']);
expect($this->configuration->toArray())
->paths_to_ignore->toEqual(['src/path-1', 'src/path-2']);
});

it('sets the mutators if --mutators argument is passed', function (): void {
$this->configuration->fromArguments(['--mutator=SetArithmetic']);
expect($this->configuration->toArray())
Expand Down
14 changes: 14 additions & 0 deletions tests/Features/Profiles/HandlesGlobalProfileConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@
->toEqual(['src/path-1', 'src/path-2', 'src/path-3']);
});

test('globally configure paths to ignore', function (): void {
mutate(ConfigurationRepository::FAKE)
->ignore(['src/path-1']);

expect($this->configuration->toArray()['paths_to_ignore'])
->toEqual(['src/path-1']);

mutate(ConfigurationRepository::FAKE)
->ignore(['src/path-1', 'src/path-2'], 'src/path-3');

expect($this->configuration->toArray()['paths_to_ignore'])
->toEqual(['src/path-1', 'src/path-2', 'src/path-3']);
});

test('globally configure mutators', function (): void {
mutate(ConfigurationRepository::FAKE)
->mutator(Mutators::SET_ARITHMETIC);
Expand Down

0 comments on commit 0fab9b4

Please sign in to comment.