Skip to content

Commit

Permalink
Disabling mutating "true" -> "false" in TrueValue mutator for in_arra…
Browse files Browse the repository at this point in the history
…y/array_search (#599)

* Add settings to TrueValue mutator and disabling mutating "true" -> "false" in in_array and array_search functions.

Add json schema validation for TrueValue mutator in infection.json

* Set config as protected in Mutator to reuse in children, add more tests, fix review comments

* Make config property private again, add a final `getSettings()` method to Mutator class
  • Loading branch information
maks-rafalko committed Jan 21, 2019
1 parent 0c1f59f commit c1fa075
Show file tree
Hide file tree
Showing 7 changed files with 406 additions and 19 deletions.
28 changes: 27 additions & 1 deletion resources/schema.json
Expand Up @@ -90,7 +90,33 @@
},
"mutators": {
"type": "object",
"description": "Contains the settings for different mutations and profiles"
"description": "Contains the settings for different mutations and profiles",
"properties": {
"TrueValue": {
"type": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"ignore": {"type": "array", "items": {"type": "string"}},
"settings": {
"type": "object",
"additionalProperties": false,
"properties": {
"in_array": {"type": "boolean"},
"array_search": {"type": "boolean"}
}
}
}
}
]
}
}
}
},
"testFramework": {
"type": "string",
Expand Down
23 changes: 22 additions & 1 deletion src/Mutator/Boolean/TrueValue.php
Expand Up @@ -36,13 +36,19 @@
namespace Infection\Mutator\Boolean;

use Infection\Mutator\Util\Mutator;
use Infection\Visitor\ParentConnectorVisitor;
use PhpParser\Node;

/**
* @internal
*/
final class TrueValue extends Mutator
{
private const DEFAULT_SETTINGS = [
'array_search' => false,
'in_array' => false,
];

/**
* Replaces "true" with "false"
*
Expand All @@ -60,6 +66,21 @@ protected function mutatesNode(Node $node): bool
return false;
}

return $node->name->toLowerString() === 'true';
if ($node->name->toLowerString() !== 'true') {
return false;
}

$parentNode = $node->getAttribute(ParentConnectorVisitor::PARENT_KEY);
$grandParentNode = $parentNode !== null ? $parentNode->getAttribute(ParentConnectorVisitor::PARENT_KEY) : null;

if (!$grandParentNode instanceof Node\Expr\FuncCall || !$grandParentNode->name instanceof Node\Name) {
return true;
}

$resultSettings = array_merge(self::DEFAULT_SETTINGS, $this->getSettings());

$functionName = $grandParentNode->name->toLowerString();

return array_key_exists($functionName, $resultSettings) && $resultSettings[$functionName] !== false;
}
}
5 changes: 5 additions & 0 deletions src/Mutator/Util/Mutator.php
Expand Up @@ -77,5 +77,10 @@ final public static function getName(): string
return end($parts);
}

final protected function getSettings(): array
{
return $this->config->getMutatorSettings();
}

abstract protected function mutatesNode(Node $node): bool;
}
11 changes: 11 additions & 0 deletions src/Mutator/Util/MutatorConfig.php
Expand Up @@ -45,9 +45,15 @@ final class MutatorConfig
*/
private $ignoreConfig;

/**
* @var array
*/
private $mutatorSettings;

public function __construct(array $config)
{
$this->ignoreConfig = $config['ignore'] ?? [];
$this->mutatorSettings = $config['settings'] ?? [];
}

public function isIgnored(string $class, string $method): bool
Expand All @@ -68,4 +74,9 @@ public function isIgnored(string $class, string $method): bool

return false;
}

public function getMutatorSettings(): array
{
return $this->mutatorSettings;
}
}
151 changes: 151 additions & 0 deletions tests/Json/JsonFileTest.php
Expand Up @@ -113,4 +113,155 @@ public function test_it_throws_schema_validation_exception(): void

(new JsonFile($jsonPath))->decode();
}

/**
* @dataProvider validTrueValueProvider
*/
public function test_it_validates_true_value_mutator(string $jsonString): void
{
$jsonPath = $this->tmpDir . '/file.json';

$this->filesystem->dumpFile($jsonPath, $jsonString);

$content = (new JsonFile($jsonPath))->decode();

self::assertObjectHasAttribute('mutators', $content);
}

public function validTrueValueProvider(): \Generator
{
yield 'Boolean value' => [
<<<JSON
{
"timeout": 25,
"source": {"directories": ["src"]},
"mutators": {
"TrueValue": true
}
}
JSON
];

yield 'Object value' => [
<<<JSON
{
"timeout": 25,
"source": {
"directories": ["src"]
},
"mutators": {
"TrueValue": {
"ignore": [
"IgnoreClass"
],
"settings": {
"in_array": false,
"array_search": true
}
}
}
}
JSON
];
}

/**
* @dataProvider invalidTrueValueProvider
*/
public function test_it_throws_exception_for_invalid_true_value_mutator(string $jsonString, string $expectedMessageRegex): void
{
$jsonPath = $this->tmpDir . '/file.json';

$this->filesystem->dumpFile($jsonPath, $jsonString);

self::expectException(ValidationException::class);
self::expectExceptionMessageRegExp($expectedMessageRegex);

(new JsonFile($jsonPath))->decode();
}

public function invalidTrueValueProvider(): \Generator
{
yield 'Extra property for TrueValue mutator' => [
<<<'JSON'
{
"timeout": 25,
"source": {"directories": ["src"]},
"mutators": {
"TrueValue": {
"EXTRA_KEY": true,
"ignore": [
"IgnoreClass"
],
"settings": {
"in_array": false,
"array_search": true
}
}
}
}
JSON
,
'/mutators\.TrueValue : The property EXTRA_KEY is not defined and the definition does not allow additional properties/',
];

yield 'Extra property for TrueValue mutator, settings object' => [
<<<'JSON'
{
"timeout": 25,
"source": {"directories": ["src"]},
"mutators": {
"TrueValue": {
"ignore": [
"IgnoreClass"
],
"settings": {
"EXTRA_KEY": true,
"in_array": false,
"array_search": true
}
}
}
}
JSON
,
'/mutators\.TrueValue\.settings : The property EXTRA_KEY is not defined and the definition does not allow additional properties/',
];

yield 'Invalid type for "in_array" setting' => [
<<<'JSON'
{
"timeout": 25,
"source": {"directories": ["src"]},
"mutators": {
"TrueValue": {
"ignore": [
"IgnoreClass"
],
"settings": {
"in_array": 123,
"array_search": true
}
}
}
}
JSON
,
'/mutators\.TrueValue\.settings\.in_array : Integer value found, but a boolean is required/',
];

yield 'Invalid type TrueValue' => [
<<<'JSON'
{
"timeout": 25,
"source": {"directories": ["src"]},
"mutators": {
"TrueValue": 123
}
}
JSON
,
'/mutators\.TrueValue : Failed to match at least one schema/',
];
}
}
16 changes: 8 additions & 8 deletions tests/Mutator/AbstractMutatorTestCase.php
Expand Up @@ -66,7 +66,7 @@ protected function setUp(): void
$this->mutator = $this->getMutator();
}

public function doTest(string $inputCode, $expectedCode = null): void
public function doTest(string $inputCode, $expectedCode = null, array $settings = []): void
{
$expectedCodeSamples = (array) $expectedCode;

Expand All @@ -76,7 +76,7 @@ public function doTest(string $inputCode, $expectedCode = null): void
throw new \LogicException('Input code cant be the same as mutated code');
}

$mutants = $this->mutate($inputCode);
$mutants = $this->mutate($inputCode, $settings);

$this->assertSame(\count($mutants), \count($expectedCodeSamples), sprintf(
'Failed asserting that the number of code samples (%d) equals the number of mutants (%d) created by the mutator.',
Expand All @@ -98,12 +98,12 @@ public function doTest(string $inputCode, $expectedCode = null): void
}
}

protected function getMutator(): Mutator
protected function getMutator(array $settings = []): Mutator
{
$class = \get_class($this);
$mutator = substr(str_replace('\Tests', '', $class), 0, -4);

return new $mutator(new MutatorConfig([]));
return new $mutator(new MutatorConfig($settings));
}

protected function getNodes(string $code): array
Expand All @@ -114,12 +114,12 @@ protected function getNodes(string $code): array
return $parser->parse($code);
}

protected function mutate(string $code): array
protected function mutate(string $code, array $settings = []): array
{
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7, new Lexer\Emulative());
$prettyPrinter = new Standard();

$mutations = $this->getMutationsFromCode($code, $parser);
$mutations = $this->getMutationsFromCode($code, $parser, $settings);

$traverser = new NodeTraverser();
$traverser->addVisitor(new CloneVisitor());
Expand All @@ -144,13 +144,13 @@ protected function mutate(string $code): array
/**
* @return SimpleMutation[]
*/
private function getMutationsFromCode(string $code, Parser $parser): array
private function getMutationsFromCode(string $code, Parser $parser, array $settings): array
{
$initialStatements = $parser->parse($code);

$traverser = new NodeTraverser();

$mutationsCollectorVisitor = new SimpleMutationsCollectorVisitor($this->getMutator(), $initialStatements);
$mutationsCollectorVisitor = new SimpleMutationsCollectorVisitor($this->getMutator($settings), $initialStatements);

$traverser->addVisitor($mutationsCollectorVisitor);
$traverser->addVisitor(new ParentConnectorVisitor());
Expand Down

0 comments on commit c1fa075

Please sign in to comment.