Skip to content

Commit

Permalink
Merge pull request #249 from azjezz/fix/infection-and-psalm-config
Browse files Browse the repository at this point in the history
allow passing infection arguments and configuring psalm config file/directory
  • Loading branch information
Ocramius authored Nov 28, 2021
2 parents ed40c03 + 901087a commit fc85a68
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 11 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ composer require --dev roave/infection-static-analysis-plugin
vendor/bin/roave-infection-static-analysis-plugin
```

### Configuration

The `roave-infection-static-analysis-plugin` binary accepts all of `infection` flags and arguments, and an additional `--psalm-config` argument.

Using `--psalm-config`, you can specify the psalm configuration file to use when analysing the generated mutations:

```sh
vendor/bin/roave-infection-static-analysis-plugin --psalm-config config/psalm.xml
```

## Background

If you come from a statically typed language with AoT compilers, you may be
Expand Down
19 changes: 15 additions & 4 deletions bin/roave-infection-static-analysis-plugin
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ use Psalm\Internal\Provider\Providers;
use Psalm\Internal\RuntimeCaches;
use Psalm\Report\ReportOptions;
use Roave\InfectionStaticAnalysis\Bootstrapper;
use Roave\InfectionStaticAnalysis\CliUtility;
use Roave\InfectionStaticAnalysis\Psalm\RunStaticAnalysisAgainstMutant;
use Symfony\Component\Console\Input\ArgvInput;
use UnexpectedValueException;
use function define;
use function defined;
use function getcwd;
use function is_file;
use function sprintf;
use function var_export;

Expand Down Expand Up @@ -56,10 +59,19 @@ use function var_export;
define('PHP_PARSER_VERSION', Versions::getVersion('nikic/php-parser'));
}

$makeAnalyzer = static function () use ($projectPath): ProjectAnalyzer {
/** @var list<non-empty-string> */
$arguments = $_SERVER['argv'] ?? [];
[$arguments, $configuration] = CliUtility::extractArgument($arguments, 'psalm-config');

$makeAnalyzer = static function () use ($configuration, $projectPath): ProjectAnalyzer {
RuntimeCaches::clearAll();

$config = Config::getConfigForPath($projectPath, $projectPath);
$configuration = $configuration ?? $projectPath;
if (is_file($configuration)) {
$config = Config::loadFromXMLFile($configuration, $projectPath);
} else {
$config = Config::getConfigForPath($configuration, $projectPath);
}

$config->setIncludeCollector(new IncludeCollector());

Expand All @@ -73,6 +85,5 @@ use function var_export;
(new Application(Bootstrapper::bootstrap(
Container::create(),
new RunStaticAnalysisAgainstMutant($makeAnalyzer)
)))
->run();
)))->run(new ArgvInput($arguments));
})();
13 changes: 6 additions & 7 deletions src/Roave/InfectionStaticAnalysis/Bootstrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,17 @@ public static function bootstrap(
): Container {
$reflectionOffsetSet = (new ReflectionMethod(Container::class, 'offsetSet'));

// @TODO we may want to make this one lazy, but for now this is OK
$replacedService = $container->getMutantExecutionResultFactory();
$factory = static function () use ($replacedService, $runStaticAnalysis): MutantExecutionResultFactory {
$new_container = clone $container;
$factory = static function () use ($container, $runStaticAnalysis): MutantExecutionResultFactory {
return new RunStaticAnalysisAgainstEscapedMutant(
$replacedService,
$runStaticAnalysis
$container->getMutantExecutionResultFactory(),
$runStaticAnalysis,
);
};

$reflectionOffsetSet->setAccessible(true);
$reflectionOffsetSet->invokeArgs($container, [MutantExecutionResultFactory::class, $factory]);
$reflectionOffsetSet->invokeArgs($new_container, [MutantExecutionResultFactory::class, $factory]);

return $container;
return $new_container;
}
}
90 changes: 90 additions & 0 deletions src/Roave/InfectionStaticAnalysis/CliUtility.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

declare(strict_types=1);

namespace Roave\InfectionStaticAnalysis;

use RuntimeException;

use function array_values;
use function sprintf;
use function strpos;
use function substr;

/**
* @internal
*/
final class CliUtility
{
private function __construct()
{
}

/**
* @param list<non-empty-string> $arguments
* @param non-empty-string $argument
*
* @return array{0: list<non-empty-string>, 1: (non-empty-string|null)}
*/
public static function extractArgument(array $arguments, string $argument): array
{
$lookup = '--' . $argument;

$result = null;
$present = false;
foreach ($arguments as $index => $arg) {
if ($arg === $lookup) {
$present = true;
unset($arguments[$index]);
// grab the next argument in the list
$value = $arguments[$index + 1] ?? null;
// if the argument is not a flag/argument name ( starts with - )
if ($value !== null && strpos($value, '-') !== 0) {
// consider it the value, and remove it from the list.
$result = $value;
unset($arguments[$index + 1]);
}

break;
}

// if the argument starts with `--argument-name=`
// we consider anything after '=' to be the value
if (strpos($arg, $lookup . '=') === 0) {
$present = true;
unset($arguments[$index]);

$result = substr($arg, 15);
break;
}
}

$arguments = array_values($arguments);
$value = self::removeSurroundingQuites($result);
if ($present && $value === null) {
throw new RuntimeException(sprintf('Please provide a value for "%s" argument.', $argument));
}

return [$arguments, $value];
}

/**
* @return non-empty-string|null
*/
private static function removeSurroundingQuites(?string $argument): ?string
{
if ($argument === null || $argument === '') {
return null;
}

if ($argument[0] === '"') {
$argument = substr($argument, 1);
}

if (substr($argument, -1) === '"') {
$argument = substr($argument, 0, -1);
}

return $argument === '' ? null : $argument;
}
}
170 changes: 170 additions & 0 deletions test/unit/Roave/InfectionStaticAnalysisTest/CliUtilityTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
<?php

declare(strict_types=1);

namespace Roave\InfectionStaticAnalysisTest;

use PHPUnit\Framework\TestCase;
use Roave\InfectionStaticAnalysis\CliUtility;
use RuntimeException;

use function sprintf;

/**
* @covers \Roave\InfectionStaticAnalysis\CliUtility
*/
final class CliUtilityTest extends TestCase
{
/**
* @param list<non-empty-string> $expectedNewArguments
* @param non-empty-string|null $expectedArgumentValue
* @param list<non-empty-string> $arguments
* @param non-empty-string $argument
*
* @dataProvider provideExtractionData
*/
public function testExtractArgument(
array $expectedNewArguments,
?string $expectedArgumentValue,
array $arguments,
string $argument
): void {
$result = CliUtility::extractArgument($arguments, $argument);

self::assertSame($expectedNewArguments, $result[0]);
self::assertSame($expectedArgumentValue, $result[1]);
}

/**
* @return list<array{
* 0: list<non-empty-string>,
* 1: (non-empty-string|null),
* 2: list<non-empty-string>,
* 3: non-empty-string
* }>
*/
public function provideExtractionData(): array
{
return [
[
['vendor/bin/roave-infection-static-analysis-plugin'],
'configuration/psalm.xml',
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config', 'configuration/psalm.xml'],
'psalm-config',
],
[
['vendor/bin/roave-infection-static-analysis-plugin'],
'configuration/psalm.xml',
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config', '"configuration/psalm.xml"'],
'psalm-config',
],
[
['vendor/bin/roave-infection-static-analysis-plugin'],
'configuration/psalm.xml',
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config="configuration/psalm.xml"'],
'psalm-config',
],
[
['vendor/bin/roave-infection-static-analysis-plugin'],
'configuration/psalm.xml',
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config=configuration/psalm.xml"'],
'psalm-config',
],
[
['vendor/bin/roave-infection-static-analysis-plugin'],
'configuration/psalm.xml',
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config="configuration/psalm.xml'],
'psalm-config',
],
[
['vendor/bin/roave-infection-static-analysis-plugin'],
'configuration/psalm.xml',
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config=configuration/psalm.xml'],
'psalm-config',
],
[
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config=foo'],
'configuration/psalm.xml',
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config=configuration/psalm.xml', '--psalm-config=foo'],
'psalm-config',
],
[
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config=foo'],
'configuration/psalm.xml',
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config', 'configuration/psalm.xml', '--psalm-config=foo'],
'psalm-config',
],
[
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config', 'foo'],
'configuration/psalm.xml',
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config=configuration/psalm.xml', '--psalm-config', 'foo'],
'psalm-config',
],
[
['vendor/bin/roave-infection-static-analysis-plugin'],
null,
['vendor/bin/roave-infection-static-analysis-plugin'],
'psalm-config',
],
[
[],
null,
[],
'psalm-config',
],
[
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config', 'configuration/psalm.xml'],
null,
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config', 'configuration/psalm.xml'],
'psalm',
],
[
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config', 'configuration/psalm.xml'],
null,
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config', 'configuration/psalm.xml'],
'psalm',
],
];
}

/**
* @param list<non-empty-string> $arguments
* @param non-empty-string $argument
*
* @dataProvider provideExtractionMissingValueData
*/
public function testExtractArgumentThrowsForMissingValue(
array $arguments,
string $argument
): void {
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage(sprintf('Please provide a value for "%s" argument.', $argument));

CliUtility::extractArgument($arguments, $argument);
}

/**
* @return list<array{0: list<non-empty-string>, 1: non-empty-string}>
*/
public function provideExtractionMissingValueData(): array
{
return [
[
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config'],
'psalm-config',
],
[
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config='],
'psalm-config',
],
[
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config=""'],
'psalm-config',
],
[
['vendor/bin/roave-infection-static-analysis-plugin', '--psalm-config="'],
'psalm-config',
],
];
}
}

0 comments on commit fc85a68

Please sign in to comment.