Skip to content

Commit

Permalink
Add --only-covering-test-cases option to enable/disable filtering b…
Browse files Browse the repository at this point in the history
…y covering test cases

By default, Infection runs all tests cases from **files** that contain at least 1 test case that cover mutated line

Using `--only-covering-test-cases`, we can enable filtering and run only those test cases from files, that cover mutated line.

For very fast test suites this decreases performance (see #1539).

For slow test suites (like functional / integration tests) - this can dramatically improve performance of Mutation Testing / Infection
  • Loading branch information
maks-rafalko committed Jul 25, 2021
1 parent 0fa6025 commit 50f972a
Show file tree
Hide file tree
Showing 13 changed files with 116 additions and 34 deletions.
11 changes: 10 additions & 1 deletion src/Command/RunCommand.php
Expand Up @@ -116,6 +116,8 @@ final class RunCommand extends BaseCommand

private const OPTION_USE_NOOP_MUTATORS = 'noop';

private const OPTION_EXECUTE_ONLY_COVERING_TEST_CASES = 'only-covering-test-cases';

/** @var string */
private const OPTION_MIN_MSI = 'min-msi';

Expand Down Expand Up @@ -257,6 +259,12 @@ protected function configure(): void
InputOption::VALUE_NONE,
'Use noop mutators that do not change AST. For debugging purposes.',
)
->addOption(
self::OPTION_EXECUTE_ONLY_COVERING_TEST_CASES,
null,
InputOption::VALUE_NONE,
'Execute only those test cases that cover mutated line, not the whole file with covering test cases. Can dramatically speed up Mutation Testing for slow test suites. For PHPUnit / Pest it uses `--filter` option',
)
->addOption(
self::OPTION_MIN_MSI,
null,
Expand Down Expand Up @@ -444,7 +452,8 @@ private function createContainer(IO $io, LoggerInterface $logger): Container
$gitDiffFilter,
$gitDiffBase,
(bool) $input->getOption(self::OPTION_LOGGER_GITHUB),
(bool) $input->getOption(self::OPTION_USE_NOOP_MUTATORS)
(bool) $input->getOption(self::OPTION_USE_NOOP_MUTATORS),
(bool) $input->getOption(self::OPTION_EXECUTE_ONLY_COVERING_TEST_CASES)
);
}

Expand Down
10 changes: 9 additions & 1 deletion src/Configuration/Configuration.php
Expand Up @@ -87,6 +87,7 @@ class Configuration
private bool $dryRun;
/** @var array<string, array<int, string>> */
private array $ignoreSourceCodeMutatorsMap;
private bool $executeOnlyCoveringTestCases;

/**
* @param string[] $sourceDirectories
Expand Down Expand Up @@ -123,7 +124,8 @@ public function __construct(
int $msiPrecision,
int $threadCount,
bool $dryRun,
array $ignoreSourceCodeMutatorsMap
array $ignoreSourceCodeMutatorsMap,
bool $executeOnlyCoveringTestCases
) {
Assert::nullOrGreaterThanEq($timeout, 0);
Assert::allString($sourceDirectories);
Expand Down Expand Up @@ -161,6 +163,7 @@ public function __construct(
$this->threadCount = $threadCount;
$this->dryRun = $dryRun;
$this->ignoreSourceCodeMutatorsMap = $ignoreSourceCodeMutatorsMap;
$this->executeOnlyCoveringTestCases = $executeOnlyCoveringTestCases;
}

public function getProcessTimeout(): float
Expand Down Expand Up @@ -317,4 +320,9 @@ public function getIgnoreSourceCodeMutatorsMap(): array
{
return $this->ignoreSourceCodeMutatorsMap;
}

public function getExecuteOnlyCoveringTestCases(): bool
{
return $this->executeOnlyCoveringTestCases;
}
}
6 changes: 4 additions & 2 deletions src/Configuration/ConfigurationFactory.php
Expand Up @@ -118,7 +118,8 @@ public function create(
?string $gitDiffFilter,
?string $gitDiffBase,
bool $useGitHubLogger,
bool $useNoopMutators
bool $useNoopMutators,
bool $executeOnlyCoveringTestCases
): Configuration {
$configDir = dirname($schema->getFile());

Expand Down Expand Up @@ -170,7 +171,8 @@ public function create(
$msiPrecision,
$threadCount,
$dryRun,
$ignoreSourceCodeMutatorsMap
$ignoreSourceCodeMutatorsMap,
$executeOnlyCoveringTestCases
);
}

Expand Down
13 changes: 9 additions & 4 deletions src/Container.php
Expand Up @@ -166,6 +166,7 @@ final class Container
public const DEFAULT_GIT_DIFF_BASE = null;
public const DEFAULT_USE_GITHUB_LOGGER = false;
public const DEFAULT_USE_NOOP_MUTATORS = false;
public const DEFAULT_EXECUTE_ONLY_COVERING_TEST_CASES = false;
public const DEFAULT_NO_PROGRESS = false;
public const DEFAULT_FORCE_PROGRESS = false;
public const DEFAULT_EXISTING_COVERAGE_PATH = null;
Expand Down Expand Up @@ -684,7 +685,8 @@ public static function create(): self
self::DEFAULT_GIT_DIFF_FILTER,
self::DEFAULT_GIT_DIFF_BASE,
self::DEFAULT_USE_GITHUB_LOGGER,
self::DEFAULT_USE_NOOP_MUTATORS
self::DEFAULT_USE_NOOP_MUTATORS,
self::DEFAULT_EXECUTE_ONLY_COVERING_TEST_CASES
);
}

Expand Down Expand Up @@ -715,7 +717,8 @@ public function withValues(
?string $gitDiffFilter,
?string $gitDiffBase,
bool $useGitHubLogger,
bool $useNoopMutators
bool $useNoopMutators,
bool $executeOnlyCoveringTestCases
): self {
$clone = clone $this;

Expand Down Expand Up @@ -790,7 +793,8 @@ static function (self $container) use (
$gitDiffFilter,
$gitDiffBase,
$useGitHubLogger,
$useNoopMutators
$useNoopMutators,
$executeOnlyCoveringTestCases
): Configuration {
return $container->getConfigurationFactory()->create(
$container->getSchemaConfiguration(),
Expand All @@ -815,7 +819,8 @@ static function (self $container) use (
$gitDiffFilter,
$gitDiffBase,
$useGitHubLogger,
$useNoopMutators
$useNoopMutators,
$executeOnlyCoveringTestCases
);
}
);
Expand Down
6 changes: 4 additions & 2 deletions src/TestFramework/Factory.php
Expand Up @@ -102,7 +102,8 @@ public function create(string $adapterName, bool $skipCoverage): TestFrameworkAd
$this->jUnitFilePath,
$this->projectDir,
$this->infectionConfig->getSourceDirectories(),
$skipCoverage
$skipCoverage,
$this->infectionConfig->getExecuteOnlyCoveringTestCases()
);
}

Expand All @@ -120,7 +121,8 @@ public function create(string $adapterName, bool $skipCoverage): TestFrameworkAd
$this->jUnitFilePath,
$this->projectDir,
$this->infectionConfig->getSourceDirectories(),
$skipCoverage
$skipCoverage,
$this->infectionConfig->getExecuteOnlyCoveringTestCases()
);
}

Expand Down
5 changes: 3 additions & 2 deletions src/TestFramework/PhpUnit/Adapter/PestAdapterFactory.php
Expand Up @@ -64,7 +64,8 @@ public static function create(
string $jUnitFilePath,
string $projectDir,
array $sourceDirectories,
bool $skipCoverage
bool $skipCoverage,
bool $executeOnlyCoveringTestCases = false
): TestFrameworkAdapter {
Assert::string($testFrameworkConfigDir, 'Config dir is not allowed to be `null` for the Pest adapter');

Expand Down Expand Up @@ -97,7 +98,7 @@ public static function create(
$projectDir,
new JUnitTestCaseSorter()
),
new ArgumentsAndOptionsBuilder(),
new ArgumentsAndOptionsBuilder($executeOnlyCoveringTestCases),
new VersionParser(),
new CommandLineBuilder()
);
Expand Down
5 changes: 3 additions & 2 deletions src/TestFramework/PhpUnit/Adapter/PhpUnitAdapterFactory.php
Expand Up @@ -67,7 +67,8 @@ public static function create(
string $jUnitFilePath,
string $projectDir,
array $sourceDirectories,
bool $skipCoverage
bool $skipCoverage,
bool $executeOnlyCoveringTestCases = false
): TestFrameworkAdapter {
Assert::string($testFrameworkConfigDir, 'Config dir is not allowed to be `null` for the Pest adapter');

Expand Down Expand Up @@ -100,7 +101,7 @@ public static function create(
$projectDir,
new JUnitTestCaseSorter()
),
new ArgumentsAndOptionsBuilder(),
new ArgumentsAndOptionsBuilder($executeOnlyCoveringTestCases),
new VersionParser(),
new CommandLineBuilder()
);
Expand Down
Expand Up @@ -51,6 +51,13 @@
*/
final class ArgumentsAndOptionsBuilder implements CommandLineArgumentsAndOptionsBuilder
{
private bool $executeOnlyCoveringTestCases;

public function __construct(bool $executeOnlyCoveringTestCases)
{
$this->executeOnlyCoveringTestCases = $executeOnlyCoveringTestCases;
}

public function buildForInitialTestsRun(string $configPath, string $extraOptions): array
{
$options = [
Expand All @@ -77,7 +84,7 @@ public function buildForMutant(string $configPath, string $extraOptions, array $
{
$options = $this->buildForInitialTestsRun($configPath, $extraOptions);

if (count($tests) > 0) {
if ($this->executeOnlyCoveringTestCases && count($tests) > 0) {
$filterString = '/';
$usedTestCases = [];

Expand Down
4 changes: 3 additions & 1 deletion tests/phpunit/Configuration/ConfigurationAssertions.php
Expand Up @@ -83,7 +83,8 @@ private function assertConfigurationStateIs(
int $expectedMsiPrecision,
int $expectedThreadCount,
bool $expectedDryRyn,
array $expectedIgnoreSourceCodeMutatorsMap
array $expectedIgnoreSourceCodeMutatorsMap,
bool $expectedExecuteOnlyCoveringTestCases
): void {
$this->assertSame($expectedTimeout, $configuration->getProcessTimeout());
$this->assertSame($expectedSourceDirectories, $configuration->getSourceDirectories());
Expand Down Expand Up @@ -132,6 +133,7 @@ private function assertConfigurationStateIs(
$this->assertSame($expectedThreadCount, $configuration->getThreadCount());
$this->assertSame($expectedDryRyn, $configuration->isDryRun());
$this->assertSame($expectedIgnoreSourceCodeMutatorsMap, $configuration->getIgnoreSourceCodeMutatorsMap());
$this->assertSame($expectedExecuteOnlyCoveringTestCases, $configuration->getExecuteOnlyCoveringTestCases());
}

/**
Expand Down

0 comments on commit 50f972a

Please sign in to comment.