Skip to content

Commit

Permalink
Add test cases for all mutators (#685)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
BackEndTea authored and maks-rafalko committed May 4, 2019
1 parent 5264654 commit f7708db
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 4 deletions.
3 changes: 2 additions & 1 deletion composer.json
Expand Up @@ -78,7 +78,8 @@
"Infection\\Tests\\": "tests/"
},
"classmap": [
"tests/Fixtures/Autoloaded"
"tests/Fixtures/Autoloaded",
"tests/Fixtures/CodeSamples"
],
"files": [
"tests/Helpers.php",
Expand Down
4 changes: 2 additions & 2 deletions src/Mutator/Number/DecrementInteger.php
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
56 changes: 56 additions & 0 deletions tests/Fixtures/CodeSamples/ReturnTypes.php
@@ -0,0 +1,56 @@
<?php

namespace Infection\CodeSamples;

class ReturnTypes
{
public function withReturnType(): int
{
return 3;
}

public function nullableReturnType(): ?int
{
return null;
}

public function withoutReturnType()
{
return 3;
}

public function selfReturn(): self
{
return $this;
}

public function nullableSelfReturn(): ?self
{
return $this;
}

public function selfReturnWithoutReturnType()
{
return $this;
}

public function parentReturn(): parent
{
return $this;
}

public function nullableParentReturn(): ?parent
{
return $this;
}

public function withNewerReturnType(): object
{
return $this;
}

public function withVoidReturnType(): void
{
return;
}
}
77 changes: 77 additions & 0 deletions tests/Fixtures/CodeSamples/VariableFunctionNames.php
@@ -0,0 +1,77 @@
<?php

namespace Infection\CodeSamples;

class VariableFunctionNames
{
public function variableFunctionNames(): void
{
$a = 'fake_function';
$b = 'somethign';

$a('do the thing');

'a string can be a function as well'();
('it can also be in between brackets')();

//In assignments
$a = $$b();
$b = $a();
$c = 'function'();

//Outside of assignments
$$b();
$a();
'function'();

// As static method calls
$a::$b();
$$a::$b();
$a::$$b();
$$a::$$b();
'Class'::$a();
//'Class'::'function'(); is invalid syntax

// As method calls
$a->$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'()) {}

}
}
35 changes: 35 additions & 0 deletions tests/Fixtures/NullMutationVisitor.php
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Infection\Tests\Fixtures;

use Infection\Mutator\Util\Mutator;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;

final class NullMutationVisitor extends NodeVisitorAbstract
{
/**
* @var Mutator[]
*/
private $mutator;

public function __construct(Mutator $mutator)
{
$this->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);

}
}
2 changes: 1 addition & 1 deletion tests/Mutator/AbstractMutatorTestCase.php
Expand Up @@ -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);

Expand Down
125 changes: 125 additions & 0 deletions tests/Mutator/AllMutatorTest.php
@@ -0,0 +1,125 @@
<?php
/**
* This code is licensed under the BSD 3-Clause License.
*
* Copyright (c) 2017-2019, Maks Rafalko
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

declare(strict_types=1);

namespace Infection\Tests\Mutator;

use Generator;
use Infection\Mutator\Util\Mutator;
use Infection\Mutator\Util\MutatorConfig;
use Infection\Mutator\Util\MutatorProfile;
use Infection\Tests\Fixtures\NullMutationVisitor;
use Infection\Visitor\FullyQualifiedClassNameVisitor;
use Infection\Visitor\NotMutableIgnoreVisitor;
use Infection\Visitor\ParentConnectorVisitor;
use Infection\Visitor\ReflectionVisitor;
use PhpParser\NodeTraverser;
use PhpParser\Parser;
use PhpParser\ParserFactory;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use Throwable;

/**
* @internal
*/
final class AllMutatorTest extends TestCase
{
/**
* @var Parser
*/
private static $parser;

public static function setUpBeforeClass(): void
{
self::$parser = (new ParserFactory())->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);
}
}

0 comments on commit f7708db

Please sign in to comment.