From f7708dbd35fd39498e25193a88e694549dc8f46c Mon Sep 17 00:00:00 2001 From: Gert de Pagter Date: Sat, 4 May 2019 16:43:37 +0200 Subject: [PATCH] Add test cases for all mutators (#685) * Add the all mutator tests that mutators can handle 'exotic' code * Add two files to parse for the parser * Fix an issue found in DecrementInteger * Fix order of Visitors in abstract mutator testcase * Add a parent return type --- composer.json | 3 +- src/Mutator/Number/DecrementInteger.php | 4 +- tests/Fixtures/CodeSamples/ReturnTypes.php | 56 ++++++++ .../CodeSamples/VariableFunctionNames.php | 77 +++++++++++ tests/Fixtures/NullMutationVisitor.php | 35 +++++ tests/Mutator/AbstractMutatorTestCase.php | 2 +- tests/Mutator/AllMutatorTest.php | 125 ++++++++++++++++++ 7 files changed, 298 insertions(+), 4 deletions(-) create mode 100644 tests/Fixtures/CodeSamples/ReturnTypes.php create mode 100644 tests/Fixtures/CodeSamples/VariableFunctionNames.php create mode 100644 tests/Fixtures/NullMutationVisitor.php create mode 100644 tests/Mutator/AllMutatorTest.php diff --git a/composer.json b/composer.json index badab3e6b..8d413247a 100644 --- a/composer.json +++ b/composer.json @@ -78,7 +78,8 @@ "Infection\\Tests\\": "tests/" }, "classmap": [ - "tests/Fixtures/Autoloaded" + "tests/Fixtures/Autoloaded", + "tests/Fixtures/CodeSamples" ], "files": [ "tests/Helpers.php", diff --git a/src/Mutator/Number/DecrementInteger.php b/src/Mutator/Number/DecrementInteger.php index 86dcaec80..8598bfbbe 100644 --- a/src/Mutator/Number/DecrementInteger.php +++ b/src/Mutator/Number/DecrementInteger.php @@ -88,7 +88,7 @@ private function isAllowedComparison(Node\Scalar\LNumber $node): bool return true; } - if ($parentNode->left instanceof Node\Expr\FuncCall + if ($parentNode->left instanceof Node\Expr\FuncCall && $parentNode->left->name instanceof Node\Name && \in_array( $parentNode->left->name->toLowerString(), self::COUNT_NAMES, @@ -97,7 +97,7 @@ private function isAllowedComparison(Node\Scalar\LNumber $node): bool return false; } - if ($parentNode->right instanceof Node\Expr\FuncCall + if ($parentNode->right instanceof Node\Expr\FuncCall && $parentNode->right->name instanceof Node\Name && \in_array( $parentNode->right->name->toLowerString(), self::COUNT_NAMES, diff --git a/tests/Fixtures/CodeSamples/ReturnTypes.php b/tests/Fixtures/CodeSamples/ReturnTypes.php new file mode 100644 index 000000000..57436edd7 --- /dev/null +++ b/tests/Fixtures/CodeSamples/ReturnTypes.php @@ -0,0 +1,56 @@ +$b(); + $$a->$b(); + $a->$$b(); + $$a->$$b(); + 'Class'->$c(); + //'Class'->'function'(); is invalid syntax + + // As static method calls in assignments + $a = $a::$b(); + $b = $$a::$b(); + $a = $a::$$b(); + $b = $$a::$$b(); + $c = 'Class'::$c(); + + // As method calls in assignments + $a = $a->$b(); + $b = $$a->$b(); + $a = $a->$$b(); + $b = $$a->$$b(); + $c = 'Class'->$c(); + + //With an array + $$a = $a[$b->$a($a->$$$b::$a)]; + + // With comparisons + if ($$a() < 3 || 3 > $$a() || 3 < 'func'()) {} + if ($b() > 12 || 12 < $b() || 'func'() > 3) {} + if ($$a() == 12 || 12 == $$a() || 'func'() == 3) {} + if ($b() === 12 || 12 === $b() || 'func'() === 12) {} + // With 0 + if ($$b() < 0 || $$b > 0 || 'func'() == 0) {} + if ($a() > 0 || 0 < $b() || 0 == 'func'()) {} + if ($$a() == 0 || 0 == $$b() || 'func'() === 0 ) {} + if ($a() === 0 || 0 === $b() || 0 === 'func'()) {} + // With 1 + if ($$b() < 1 || $$b > 1 || 'func'() == 1) {} + if ($a() > 1 || 1 < $b() || 1 == 'func'()) {} + if ($$a() == 1 || 1 == $$b() || 'func'() === 1) {} + if ($a() === 1 || 1 === $b() || 1 === 'func'()) {} + + } +} diff --git a/tests/Fixtures/NullMutationVisitor.php b/tests/Fixtures/NullMutationVisitor.php new file mode 100644 index 000000000..dd85d0375 --- /dev/null +++ b/tests/Fixtures/NullMutationVisitor.php @@ -0,0 +1,35 @@ +mutator = $mutator; + } + + /** + * Runs the mutator, but does mutate the node + */ + public function leaveNode(Node $node) + { + $clonedNode = clone $node; + if (!$this->mutator->shouldMutate($clonedNode)) { + return; + } + $this->mutator->mutate($clonedNode); + + } +} diff --git a/tests/Mutator/AbstractMutatorTestCase.php b/tests/Mutator/AbstractMutatorTestCase.php index 4b53fb02a..e1aba1588 100644 --- a/tests/Mutator/AbstractMutatorTestCase.php +++ b/tests/Mutator/AbstractMutatorTestCase.php @@ -154,10 +154,10 @@ private function getMutationsFromCode(string $code, Parser $parser, array $setti $mutationsCollectorVisitor = new SimpleMutationsCollectorVisitor($this->getMutator($settings), $initialStatements); $traverser->addVisitor(new NotMutableIgnoreVisitor()); - $traverser->addVisitor($mutationsCollectorVisitor); $traverser->addVisitor(new ParentConnectorVisitor()); $traverser->addVisitor(new FullyQualifiedClassNameVisitor()); $traverser->addVisitor(new ReflectionVisitor()); + $traverser->addVisitor($mutationsCollectorVisitor); $traverser->traverse($initialStatements); diff --git a/tests/Mutator/AllMutatorTest.php b/tests/Mutator/AllMutatorTest.php new file mode 100644 index 000000000..8f29258f4 --- /dev/null +++ b/tests/Mutator/AllMutatorTest.php @@ -0,0 +1,125 @@ +create(ParserFactory::PREFER_PHP7); + } + + /** + * This test only proves that the mutators do not crash on more 'exotic' code. + * It does not care whether or not the code is actually mutated, only if it does not error. + * + * @dataProvider provideMutatorAndCodeCases + */ + public function test_the_mutator_does_not_crash_during_parsing(string $code, Mutator $mutator, string $fileName): void + { + try { + $this->getMutationsFromCode($code, $mutator); + } catch (Throwable $t) { + $this->fail(sprintf( + 'Ran into an error on the "%s" mutator, while parsing the file "%s". The original error was "%s"', + $mutator::getName(), + $fileName, + $t->getMessage() + )); + } + $this->addToAssertionCount(1); + } + + public function provideMutatorAndCodeCases(): Generator + { + foreach ($this->getCodeSamples() as $codeSample) { + foreach (MutatorProfile::FULL_MUTATOR_LIST as $mutator) { + yield [$codeSample->getContents(), new $mutator(new MutatorConfig([])), $codeSample->getFilename()]; + } + } + } + + /** + * @return SplFileInfo[]|Finder + */ + public function getCodeSamples() + { + return Finder::create() + ->in(__DIR__ . '/../Fixtures/CodeSamples') + ->name('*.php') + ->files(); + } + + private function getMutationsFromCode(string $code, Mutator $mutator): void + { + $initialStatements = self::$parser->parse($code); + + $traverser = new NodeTraverser(); + + $traverser->addVisitor(new NotMutableIgnoreVisitor()); + $traverser->addVisitor(new ParentConnectorVisitor()); + $traverser->addVisitor(new FullyQualifiedClassNameVisitor()); + $traverser->addVisitor(new ReflectionVisitor()); + $traverser->addVisitor(new NullMutationVisitor($mutator)); + + $traverser->traverse($initialStatements); + } +}