Skip to content

Commit

Permalink
infection#658 added required tests, refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
majkel89 committed Mar 30, 2019
1 parent f5fab01 commit 91c8248
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 106 deletions.
97 changes: 49 additions & 48 deletions src/Mutator/Extensions/BCMath.php
Expand Up @@ -57,97 +57,98 @@ public function __construct(MutatorConfig $config)
}

/**
* @param Node|Node\Expr\FuncCall $node
*
* @return Node|Node[]|Generator
*/
public function mutate(Node $node)
{
yield from $this->converters[$this->getFunctionName($node)]($node);
yield from $this->converters[$node->name->toLowerString()]($node);
}

protected function mutatesNode(Node $node): bool
{
$functionName = $this->getFunctionName($node);

return $functionName !== null && isset($this->converters[$functionName]) && \function_exists($functionName);
}

private function getFunctionName(Node $node): ?string
{
if (!$node instanceof Node\Expr\FuncCall || !$node->name instanceof Node\Name) {
return null;
return false;
}

return \strtolower($node->name->toString());
$functionName = $node->name->toLowerString();

return isset($this->converters[$functionName]) && \function_exists($functionName);
}

private function setupConverters(array $functionsMap): void
{
$converters = [
'bcadd' => $this->minArgsCastString(2,
$this->binaryOp(Node\Expr\BinaryOp\Plus::class)
),
'bccomp' => $this->minArgsCastString(2,
$this->binaryOp(Node\Expr\BinaryOp\Spaceship::class)
),
'bcdiv' => $this->minArgsCastString(2,
$this->binaryOp(Node\Expr\BinaryOp\Div::class)
),
'bcmod' => $this->minArgsCastString(2,
$this->binaryOp(Node\Expr\BinaryOp\Mod::class)
),
'bcmul' => $this->minArgsCastString(2,
$this->binaryOp(Node\Expr\BinaryOp\Mul::class)
),
'bcpow' => $this->minArgsCastString(2,
$this->binaryOp(Node\Expr\BinaryOp\Pow::class)
),
'bcsub' => $this->minArgsCastString(2,
$this->binaryOp(Node\Expr\BinaryOp\Minus::class)
),
'bcsqrt' => $this->minArgsCastString(2,
$this->squareRoots()
),
'bcpowmod' => $this->minArgsCastString(3,
$this->powerModulo()
'bcadd' => $this->mapCheckingMinArgs(2, $this->mapCastToString(
$this->mapBinaryOperator(Node\Expr\BinaryOp\Plus::class)
)),
'bcdiv' => $this->mapCheckingMinArgs(2, $this->mapCastToString(
$this->mapBinaryOperator(Node\Expr\BinaryOp\Div::class)
)),
'bcmod' => $this->mapCheckingMinArgs(2, $this->mapCastToString(
$this->mapBinaryOperator(Node\Expr\BinaryOp\Mod::class)
)),
'bcmul' => $this->mapCheckingMinArgs(2, $this->mapCastToString(
$this->mapBinaryOperator(Node\Expr\BinaryOp\Mul::class)
)),
'bcpow' => $this->mapCheckingMinArgs(2, $this->mapCastToString(
$this->mapBinaryOperator(Node\Expr\BinaryOp\Pow::class)
)),
'bcsub' => $this->mapCheckingMinArgs(2, $this->mapCastToString(
$this->mapBinaryOperator(Node\Expr\BinaryOp\Minus::class)
)),
'bcsqrt' => $this->mapCheckingMinArgs(1, $this->mapCastToString(
$this->mapSquareRoots()
)),
'bcpowmod' => $this->mapCheckingMinArgs(3, $this->mapCastToString(
$this->mapPowerModulo()
)),
'bccomp' => $this->mapCheckingMinArgs(2,
$this->mapBinaryOperator(Node\Expr\BinaryOp\Spaceship::class)
),
];

$functionsToRemove = \array_filter($functionsMap, function ($isOn) {
$functionsToRemove = \array_filter($functionsMap, static function ($isOn) {
return !$isOn;
});

$this->converters = \array_diff_key($converters, $functionsToRemove);
}

private function minArgsCastString(int $minimumArgsCount, callable $converter): callable
private function mapCheckingMinArgs(int $minimumArgsCount, callable $converter)
{
return static function (Node\Expr\FuncCall $node) use ($minimumArgsCount, $converter): Generator {
if (\count($node->args) >= $minimumArgsCount) {
foreach ($converter($node) as $newNode) {
yield new Node\Expr\Cast\String_($newNode);
}
yield from $converter($node);
}
};
}

private function binaryOp(string $operator): callable
private function mapCastToString(callable $converter): callable
{
return static function (Node\Expr\FuncCall $node) use ($converter): Generator {
foreach ($converter($node) as $newNode) {
yield new Node\Expr\Cast\String_($newNode);
}
};
}

private function mapBinaryOperator(string $operator): callable
{
return static function (Node\Expr\FuncCall $node) use ($operator): Generator {
yield new $operator($node->args[0]->value, $node->args[1]->value);
};
}

private function squareRoots(): callable
private function mapSquareRoots(): callable
{
return static function (Node\Expr\FuncCall $node): Generator {
yield new Node\Expr\FuncCall(
new Node\Name('\sqrt'),
[$node->args[0], $node->args[1]]
);
yield new Node\Expr\FuncCall(new Node\Name('\sqrt'), [$node->args[0]]);
};
}

private function powerModulo(): callable
private function mapPowerModulo(): callable
{
return static function (Node\Expr\FuncCall $node): Generator {
yield new Node\Expr\BinaryOp\Mod(
Expand Down
144 changes: 86 additions & 58 deletions tests/Mutator/Extensions/BCMathTest.php
Expand Up @@ -32,39 +32,6 @@
*/

declare(strict_types=1);
/**
* 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\Extensions;

Expand All @@ -86,16 +53,8 @@ public function test_mutator(string $input, string $expected = null, array $sett

public function provideMutationCases(): Generator
{
yield 'It does not convert bcadd when disabled' => [
"<?php bcadd('1', '3');",
null,
['settings' => ['bcadd' => false]],
];

yield from $this->provideMutationCasesForBinaryOperator('bcadd', '+', 'summation');

yield from $this->provideMutationCasesForBinaryOperator('bccomp', '<=>', 'spaceship');

yield from $this->provideMutationCasesForBinaryOperator('bcdiv', '/', 'division');

yield from $this->provideMutationCasesForBinaryOperator('bcmod', '%', 'modulo');
Expand All @@ -109,6 +68,8 @@ public function provideMutationCases(): Generator
yield from $this->provideMutationCasesForSquareRoot();

yield from $this->provideMutationCasesForPowerModulo();

yield from $this->provideMutationCasesForComparision();
}

private function provideMutationCasesForBinaryOperator(string $bcFunc, string $op, string $expression): Generator
Expand All @@ -118,14 +79,17 @@ private function provideMutationCasesForBinaryOperator(string $bcFunc, string $o
"<?php\n\n(string) ('3' $op \$b);",
];

yield "It converts $bcFunc with scale to $expression expression" => [
"<?php $bcFunc(\$a, \$b, 2);",
"<?php\n\n(string) (\$a $op \$b);",
yield "It converts correctly when $bcFunc is wrongly capitalized" => [
"<?php \\{$this->randomizeCase($bcFunc)}(func(), \$b->test());",
"<?php\n\n(string) (func() $op \$b->test());",
];

yield "It does not convert $bcFunc when not enough arguments" => [
"<?php $bcFunc(\$a);",
yield "It converts $bcFunc with scale to $expression expression" => [
"<?php $bcFunc(CONSTANT, \$b, 2);",
"<?php\n\n(string) (CONSTANT $op \$b);",
];

yield from $this->provideCasesWhereMutatorShouldNotApply($bcFunc);
}

private function provideMutationCasesForPowerOperator(): Generator
Expand All @@ -135,31 +99,37 @@ private function provideMutationCasesForPowerOperator(): Generator
"<?php\n\n(string) 5 ** \$b;",
];

yield 'It converts correctly when bcpow is wrongly capitalized' => [
'<?php \\bCpOw(5, $b);',
"<?php\n\n(string) 5 ** \$b;",
];

yield 'It converts bcpow with scale to power expression' => [
'<?php bcpow($a, $b, 2);',
"<?php\n\n(string) \$a ** \$b;",
];

yield 'It does not convert bcpow when not enough arguments' => [
'<?php bcpow($a);',
];
yield from $this->provideCasesWhereMutatorShouldNotApply('bcpow');
}

private function provideMutationCasesForSquareRoot(): Generator
{
yield 'It converts bcsqrt to sqrt call' => [
'<?php \\bcsqrt(1, $b);',
"<?php\n\n(string) \sqrt(1, \$b);",
'<?php \\bcsqrt(2);',
"<?php\n\n(string) \sqrt(2);",
];

yield 'It converts bcsqrt with scale to sqrt call' => [
'<?php bcsqrt($a, $b, 2);',
"<?php\n\n(string) \sqrt(\$a, \$b);",
yield 'It converts correctly when bcsqrt is wrongly capitalized' => [
'<?php \\BCsqRt($a);',
"<?php\n\n(string) \sqrt(\$a);",
];

yield 'It does not convert bcsqrt when not enough arguments' => [
'<?php bcsqrt($a);',
yield 'It converts bcsqrt with scale to sqrt call' => [
'<?php bcsqrt($a, 2);',
"<?php\n\n(string) \sqrt(\$a);",
];

yield from $this->provideCasesWhereMutatorShouldNotApply('bcsqrt', 1);
}

private function provideMutationCasesForPowerModulo(): Generator
Expand All @@ -169,13 +139,71 @@ private function provideMutationCasesForPowerModulo(): Generator
"<?php\n\n(string) (\pow(\$a, \$b) % \$mod);",
];

yield 'It converts correctly when bcpowmod is wrongly capitalized' => [
'<?php \\BcPowMod($a, $b, $mod);',
"<?php\n\n(string) (\pow(\$a, \$b) % \$mod);",
];

yield 'It converts bcpowmod with scale to power modulo expression' => [
'<?php bcpowmod($a, $b, 2);',
"<?php\n\n(string) (\pow(\$a, \$b) % 2);",
];

yield 'It does not convert bcpowmod when not enough arguments' => [
'<?php bcpowmod($a, $b);',
yield from $this->provideCasesWhereMutatorShouldNotApply('bcpowmod', 3);
}

private function provideMutationCasesForComparision(): Generator
{
yield 'It converts bccomp to spaceship expression' => [
'<?php \\bccomp(\'3\', $b);',
"<?php\n\n'3' <=> \$b;",
];

yield 'It converts correctly when bccomp is wrongly capitalized' => [
'<?php \\bCCoMp(func(), $b->test());',
"<?php\n\nfunc() <=> \$b->test();",
];

yield 'It converts bccomp with scale to spaceship expression' => [
'<?php bccomp(CONSTANT, $b, 2);',
"<?php\n\nCONSTANT <=> \$b;",
];

yield from $this->provideCasesWhereMutatorShouldNotApply('bccomp', 2);
}

private function provideCasesWhereMutatorShouldNotApply(string $bcFunc, int $requiredArgumentsCount = 2): Generator
{
$invalidArgumentsExpression = $this->generateArgumentsExpression($requiredArgumentsCount - 1);
$validArgumentsExpression = $this->generateArgumentsExpression($requiredArgumentsCount);

yield "It does not convert $bcFunc when no enough arguments" => [
"<?php $bcFunc($invalidArgumentsExpression);",
];

yield "It does not mutate $bcFunc called via variable" => [
"<?php \$a = '$bcFunc'; \$a($validArgumentsExpression);",
];

yield "It does not convert $bcFunc when disabled" => [
"<?php $bcFunc($validArgumentsExpression);",
null,
['settings' => [$bcFunc => false]],
];
}

private function randomizeCase(string $bcFunc): string
{
$bcFunc[2] = strtoupper($bcFunc[2]);
$bcFunc[4] = strtoupper($bcFunc[4]);

return ucfirst($bcFunc);
}

private function generateArgumentsExpression(int $numberOfArguments): string
{
return \implode(', ', \array_map(static function (string $argument) {
return "'$argument'";
}, $numberOfArguments ? \range(1, $numberOfArguments) : []));
}
}

0 comments on commit 91c8248

Please sign in to comment.