- Guide
- Playground
- GitHub
-
Ecosystem
Help
Resource Lists
Documentation
- Introduction
- Installation
- Usage
- Command Line Options
- Mutators
- Custom Mutators
- Profiles
- Using with CI
- Mutation Badge, cloud HTML report
- How-to Guides
- Caveats
- Debugging Issues
- Infection Playground
- GitHub Sponsors ♥️
- Supported Test Frameworks
Miscellaneous
- Compare with competitors
Posts
- What's new in Infection 0.26.0
- What's new in Infection 0.25.0
- What's new in Infection 0.24.0
- What's new in Infection 0.23.0
- What's new in Infection 0.21.0
Mutators
Infection supports a set of Mutators which are based on AST and PHP-Parser project.
Read more about how to execute only particular set of mutators using
Name
column
Run the
infection describe
command to get information about mutators, right from the command line.
Function Signature
Name | Original | Mutated |
---|---|---|
PublicVisibility | public function ... |
protected function ... |
ProtectedVisibility | protected function ... |
private function ... |
To verify that the visibility of a method is necessary. If the visibility of a method can be reduced from public
to protected
or private
, this may be an indication that the publicly accessible part of API of a class is larger than what’s strictly necessary. This mutator will drive the source code towards classes with smaller publicly accessible APIs and thus better encapsulation.
Unwrap Function
The Unwrap* mutator family will unwrap function parameters.
Name | Original | Mutated |
---|---|---|
UnwrapArrayChangeKeyCase | $a = array_change_key_case(['foo' => 'bar']); |
$a = ['foo' => 'bar']; |
UnwrapArrayChunk | $a = array_chunk(['A', 'B', 'C'], 2); |
$a = ['A', 'B', 'C']; |
UnwrapArrayColumn | $a = array_column([['foo' => 'bar]], 'foo'); |
$a = [['foo' => 'bar]]; |
UnwrapArrayCombine | $a = array_combine(['A', 'B', 'C'], ['foo', 'bar', 'baz']); |
$a = ['A', 'B', 'C']; |
UnwrapArrayDiff | $a = array_diff(['A', 'B', 'C'], ['D']); |
$a = ['A', 'B', 'C']; |
UnwrapArrayDiffAssoc | $a = array_diff_assoc(['foo' => 'bar'], ['baz' => 'bar]); |
$a = ['foo' => 'bar']; |
UnwrapArrayDiffKey | $a = array_diff_key(['foo' => 'bar'], ['baz' => 'bar]); |
$a = ['foo' => 'bar']; |
UnwrapArrayDiffUassoc | $a = array_diff_assoc(['foo' => 'bar'], ['baz' => 'bar], $keyCompareFunc); |
$a = ['foo' => 'bar']; |
UnwrapArrayDiffUkey | $a = array_diff_ukey(['foo' => 'bar'], ['baz' => 'bar], $keyCompareFunc); |
$a = ['foo' => 'bar']; |
UnwrapArrayFilter | $a = array_filter(['A', 1, 'C'], 'is_int'); |
$a = ['A', 1, 'C']; |
UnwrapArrayFlip | $a = array_flip(['A', 'B', 'C']); |
$a = ['A', 'B', 'C']; |
UnwrapArrayIntersect | $a = array_intersect(['A', 'B', 'C'], ['D']); |
$a = ['A', 'B', 'C']; |
UnwrapArrayIntersectAssoc | $a = array_intersect_assoc(['A', 'B', 'C'], ['D']); |
$a = ['A', 'B', 'C']; |
UnwrapArrayIntersectKey | $a = array_intersect_key(['foo' => 'bar'], ['bar' => 'baz']); |
$a = ['foo' => 'bar']; |
UnwrapArrayIntersectUassoc | $a = array_intersect_uassoc(['foo' => 'bar'], ['bar' => 'baz'], $keyCompareFunc); |
$a = ['foo' => 'bar']; |
UnwrapArrayIntersectUkey | $a = array_intersect_ukey(['foo' => 'bar'], ['bar' => 'baz'], $keyCompareFunc); |
$a = ['foo' => 'bar']; |
UnwrapArrayKeys | $a = array_keys(['foo' => 'bar']); |
$a = ['foo' => 'bar']; |
UnwrapArrayMap | $a = array_map('strtolower', ['A', 'B', 'C']); |
$a = ['A', 'B', 'C']; |
UnwrapArrayMerge | $a = array_merge(['A', 'B', 'C'], ['D']); |
$a = ['A', 'B', 'C']; |
UnwrapArrayMergeRecursive | $a = array_merge_recursive(['A', 'B', 'C'], ['D']); |
$a = ['A', 'B', 'C']; |
UnwrapArrayPad | $a = array_pad(['A'], 2, 'B'); |
$a = ['A']; |
UnwrapArrayReduce | $a = array_reduce(['A', 'B', 'C'], $callback, ['D']); |
$a = ['D']; |
UnwrapArrayReplace | $a = array_replace(['A', 'B', 'C'], ['D']); |
$a = ['A', 'B', 'C']; |
UnwrapArrayReplaceRecursive | $a = array_replace_recursive(['A', 'B', 'C'], ['D']); |
$a = ['A', 'B', 'C']; |
UnwrapArrayReverse | $a = array_reverse(['A', 'B', 'C']); |
$a = ['A', 'B', 'C']; |
UnwrapArraySlice | $a = array_slice(['A', 'B', 'C'], 1); |
$a = ['A', 'B', 'C']; |
UnwrapArraySplice | $a = array_splice(['A', 'B', 'C'], 1); |
$a = ['A', 'B', 'C']; |
UnwrapArrayUdiff | $a = array_udiff(['foo' => 'bar'], ['baz' => 'bar], $valueCompareFunc); |
$a = ['foo' => 'bar']; |
UnwrapArrayUdiffAssoc | $a = array_udiff_assoc(['foo' => 'bar'], ['baz' => 'bar], $valueCompareFunc); |
$a = ['foo' => 'bar']; |
UnwrapArrayUdiffUassoc | $a = array_udiff_uassoc(['foo' => 'bar'], ['baz' => 'bar], $valueCompareFunc, $keyCompareFunc); |
$a = ['foo' => 'bar']; |
UnwrapArrayUintersect | $a = array_uintersect(['foo' => 'bar'], ['baz' => 'bar], $valueCompareFunc); |
$a = ['foo' => 'bar']; |
UnwrapArrayUintersectAssoc | $a = array_uintersect_assoc(['foo' => 'bar'], ['baz' => 'bar], $valueCompareFunc); |
$a = ['foo' => 'bar']; |
UnwrapArrayUintersectUassoc | $a = array_uintersect_uassoc(['foo' => 'bar'], ['baz' => 'bar], $valueCompareFunc, $keyCompareFunc); |
$a = ['foo' => 'bar']; |
UnwrapArrayUnique | $a = array_unique(['foo', 'bar', 'bar']); |
$a = ['foo', 'bar', 'bar']; |
UnwrapArrayValues | $a = array_values(['foo' => 'bar']); |
$a = ['foo' => 'bar']; |
UnwrapLcFirst | $a = lcfirst('Hello, world!'); |
$a = 'Hello, world!'; |
UnwrapLtrim | $a = ltrim(' Hello, world!'); |
$a = ' Hello, world!'; |
UnwrapRtrim | $a = rtrim('Hello, world! '); |
$a = 'Hello, world! '; |
UnwrapStrIreplace | $a = str_ireplace('Afternoon', 'Evening' ,'Good Afternoon!'); |
$a = 'Good Afternoon!'; |
UnwrapStrRepeat | $a = str_repeat('A', 3); |
$a = 'A'; |
UnwrapStrReplace | $a = str_replace('Afternoon', 'Evening' ,'Good Afternoon!'); |
$a = 'Good Afternoon!'; |
UnwrapStrRev | $a = strrev('Hello!'); |
`$a = ‘Hello!’; |
UnwrapStrShuffle | $a = str_shuffle('Hello!'); |
$a = 'Hello!'; |
UnwrapSubstr | $a = substr('Hello!', 0, -1); |
$a = 'Hello!'; |
UnwrapStrToLower | $a = strtolower('Hello!'); |
$a = 'Hello!'; |
UnwrapStrToUpper | $a = strtoupper('Hello, world!'); |
$a = 'Hello, world!'; |
UnwrapTrim | $a = trim(' Hello, world! '); |
$a = 'Hello, world!'; |
UnwrapUcFirst | $a = ucfirst('hello, world!'); |
$a = 'hello, world!'; |
UnwrapUcWords | $a = ucwords('hello, world!'); |
$a = 'hello, world!'; |
Binary Arithmetic
Name | Original | Mutated |
---|---|---|
Plus | + | - |
Minus | - | + |
Multiplication | * | / |
Division | / | * |
Modulus | % | * |
Exponentiation | ** | / |
MulEqual | *= | /= |
PlusEqual | += | -= |
MinusEqual | -= | += |
DivEqual | /= | *= |
ModEqual | %= | *= |
PowEqual | **= | /= |
BitwiseAnd | & | | |
BitwiseOr | | | & |
BitwiseXor | ^ | & |
BitwiseNot | ~ | |
ShiftRight | >> | << |
ShiftLeft | << | >> |
AssignmentEqual | == | = |
Assignment | += | = |
Assignment | -= | = |
Assignment | *= | = |
Assignment | **= | = |
Assignment | /= | = |
Assignment | %= | = |
Assignment | .= | = |
Assignment | &= | = |
Assignment | |= | = |
Assignment | ^= | = |
Assignment | <<= | = |
Assignment | >>= | = |
Assignment Coalesce | ??= | = |
Round Family
The Round Family mutator will make sure that there’s enough tests to cover the rounding possibilities.
Name | Original | Mutated |
---|---|---|
RoundingFamily | round() |
floor() |
RoundingFamily | round() |
ceil() |
RoundingFamily | ceil() |
floor() |
RoundingFamily | ceil() |
round() |
RoundingFamily | floor() |
round() |
RoundingFamily | floor() |
ceil() |
Boolean
Name | Original | Mutated |
---|---|---|
ArrayItem | [$a->foo => $b->bar] |
[$a->foo > $b->bar] |
TrueValue | true | false |
FalseValue | false | true |
InstanceOf_ | $a instanceof B |
true / false |
LogicalAnd | && | || |
LogicalOr | || | && |
LogicalLowerAnd | and | or |
LogicalLowerOr | or | and |
LogicalNot | ! | |
Yield_ | yield $a => $b; |
yield $a > $b; |
LogicalAndAllSubExprNegation | $a = $b && $c; |
$a = !$b && !$c; |
LogicalAndNegation | $a = $b && $c; |
$a = !($b && $c); |
LogicalAndSingleSubExprNegation | $a = $b && $c; |
$a = !$b && $c; , $a = $b && !$c; |
LogicalOrAllSubExprNegation | $a = $b || $c; | $a = !$b || !$c; |
LogicalOrNegation | $a = $b || $c; | $a = !($b || $c); |
LogicalOrSingleSubExprNegation | $a = $b || $c; | $a = !$b ||, $c; $a = $b || !$c; |
TrueValue
Default settings:
in_array: false
: whether to mutate 3rd argumenttrue
in function callarray_search: false
: whether to mutate 3rd argumenttrue
in function call
infection.json5:
{ |
Conditional Boundaries
Name | Original | Mutated |
---|---|---|
GreaterThan | > | >= |
LessThan | < | <= |
GreaterThanOrEqualTo | >= | > |
LessThanOrEqualTo | <= | < |
Equal or Identical Checks
Name | Original | Mutated |
---|---|---|
EqualIdentical | == |
=== |
NotEqualNotIdentical | != |
!== |
IdenticalEqual | === |
== |
NotIdenticalNotEqual | !== |
!= |
These mutators are disabled by default, you can use the
@equal
or@identical
profiles to enable the ones you prefer.
Negated Conditionals
Name | Original | Mutated |
---|---|---|
Equal | == | != |
NotEqual | != | == |
Identical | === | !== |
NotIdentical | !== | === |
GreaterThanNegotiation | > | <= |
LessThanNegotiation | < | >= |
GreaterThanOrEqualToNegotiation | >= | < |
LessThanOrEqualToNegotiation | <= | > |
Operator
Name | Original | Mutated |
---|---|---|
Catch_ | catch (\Throwable|\DomainException $e) |
catch (\DomainException $e) catch (\DomainException $e) |
Coalesce | $foo ?? $bar |
$bar ?? $foo |
Concat | $foo . $bar |
$bar . $foo |
NullSafeMethodCall | $object?->getObject() |
$object->getObject() |
NullSafePropertyCall | $object?->property |
$object->property |
SpreadAssignment | $array = [...$collection] |
$array = $collection |
SpreadOneItem | [...$collection, 2, 3] |
[[...$collection][0], 2, 3] |
SpreadRemoval | [...$collection, 2, 3] |
[$collection, 2, 3] |
Ternary | isset($b) ? 'B' : 'C' |
isset($b) ? 'C' : 'B' |
ElseIfNegation | } elseif ($this->bar()) { |
} elseif (!$this->bar()) { |
IfNegation | if ($this->fooBar()) { |
if (!$this->fooBar()) { |
Increments
Name | Original | Mutated |
---|---|---|
Increment | ++ | -- |
Decrement | -- | ++ |
Return Values
Name | Original | Mutated |
---|---|---|
ArrayOneItem | return $collection; | return count($collection) > 1 ? array_slice($collection, 0, 1, true) : $collection; |
TrueValue | return true; | return false; |
FalseValue | return false; | return true; |
IntegerNegation | return (Any Integer) ; |
return -(Any Integer) ; |
OneZeroFloat | return 0.0; | return 1.0; |
OneZeroFloat | return 1.0; | return 0.0; |
FloatNegation | return (Any Float) ; |
return -(Any Float) ; |
This | return $this; | return null; |
FunctionCall | return function(); | function(); return null; |
NewObject | return new Class(); | new Class(); return null; |
YieldValue | yield $key => $value; | yield $value; |
Removal Mutators
Name | Original | Mutated |
---|---|---|
ArrayItemRemoval | [1, $a, '3'] |
[$a, '3'] depending on configuration |
FunctionCallRemoval | foo_bar($a) | - |
MethodCallRemoval | $this->method($var) | - |
CatchBlockRemoval | try {} catch (\DomainException $e) {} catch (\Throwable $e) {} |
try {} catch (\DomainException $e) {} try {} catch (\Throwable $e) {} |
MatchArmRemoval | match ($x) { 1 => null, default => throw new \Exception()}; |
match ($x) { default => throw new \Exception()}; match ($x) { 1 => null}; |
CloneRemoval | clone (new stdClass()) | new stdClass() |
ConcatOperandRemoval | 'foo' . 'bar' |
'bar' and 'foo' |
SharedCaseRemoval | switch($a) { case 'a': case 'b': break; } |
switch($a) { case 'b': break; } switch($a) { case 'a': break; } |
ArrayItemRemoval
Configuration options:
remove: first
: defines the way the mutator operates. Could be:first
- remove only first element from each arraylast
- remove only last element from each arrayall
- remove every element one by one from each array resulting in as many mutations as total number of items in arrays.
limit: PHP_INT_MAX
: whenremove = all
specifies maximum number of elements that will be removed form array. Only elements at the beginning will be mutated.
When using
all
option we advise to set the limit as well
You should remember to exclude files containing large arrays (like configuration)
when usingArrayItemRemoval
mutator inall
mode
infection.json5:
{ |
Loop
Name | Original | Mutated |
---|---|---|
Break_ | break; | continue; |
Continue_ | continue; | break; |
Foreach_ | foreach ($someVar as …); | foreach ([] as …); |
For_ | for ($i=0; $i < 10; $i++); | for ($i=0; false; $i++); |
While_ | while ($condition); | while (false); |
DoWhile | do {} while ($condition); | do {} while (false); |
Sorting
Name | Original | Mutated |
---|---|---|
Spaceship | $a <=> $b | $b <=> $a |
Literal Numbers
Name | Original | Mutated |
---|---|---|
OneZeroFloat | 0.0 | 1.0 |
OneZeroFloat | 1.0 | 0.0 |
DecrementInteger | 7 | 6 |
IncrementInteger | 7 | 8 |
Exceptions
Name | Original | Mutated |
---|---|---|
Throw_ | throw new NotFoundException(); |
new NotFoundException(); |
Finally_ | try {} catch (\Exception $e) {} finally {} |
try {} catch (\Exception $e) {} |
UnwrapFinally | try { $callback(); } finally { $cleanUpCallbak(); } |
$callback(); $cleanUpCallbak(); |
Type Casting
Name | Original | Mutated |
---|---|---|
CastArray | (array) $value; |
$value |
CastBool | (bool) $value; |
$value |
CastFloat | (float) $value; |
$value |
CastInt | (int) $value; |
$value |
CastObject | (object) $value; |
$value |
CastString | (string) $value; |
$value |
Regex
Name | Original | Mutated |
---|---|---|
PregQuote | $a = preg_quote('text'); |
$a = 'text'; |
PregMatchMatches | preg_match('/pattern/', $value, $matches); |
(int) $matches = array(); |
PregMatchRemoveCaret | preg_match('/^test/', $string); |
preg_match('/test/', $string); |
PregMatchRemoveDollar | preg_match('/test$/', $string); |
preg_match('/test/', $string); |
PregMatchRemoveFlags | preg_match('/test$/iu', $string); |
preg_match('/test$/u', $string); , preg_match('/test$/i', $string); |
Extensions
BCMath
Name | Original | Mutated |
---|---|---|
BCMath | bcadd($a, $b, $mod); |
(string) ($a + $b); |
bcdiv($a, $b, $mod); |
(string) ($a / $b); |
|
bcmod($a, $b, $mod); |
(string) ($a % $b); |
|
bcmul($a, $b, $mod); |
(string) ($a * $b); |
|
bcpow($a, $b, $mod); |
(string) ($a ** $b); |
|
bcsub($a, $b, $mod); |
(string) ($a - $b); |
|
bcsqrt($a, $mod); |
(string) \sqrt($a); |
|
bcpowmod($a, $b, $c, $mod); |
(string) (\pow($a, $b) % $c); |
|
bccomp($a, $b, $mod); |
$a <=> $b; |
"bcpowmod": true
: You are able to disable any of the supported bcmath functions. All supported functions are enabled by default.
infection.json5:
{ |
MBString
Name | Original | Mutated |
---|---|---|
MBString | mb_chr($code); |
chr($code); |
mb_ord($character); |
ord($character); |
|
mb_parse_str('text', $results); |
parse_str('text', $results); |
|
mb_send_mail($to, $subject, $message, $headers, $parameters); |
mail($to, $subject, $message, $headers, $parameters); |
|
mb_strcut('text', 0, 123, 'utf-8'); |
substr('text', 0, 123); |
|
mb_stripos('text', 't', 0, 'utf-8'); |
stripos('text', 't', 0); |
|
mb_stristr('text', 't', true, 'utf-8'); |
stristr('text', 't', true); |
|
mb_strlen('text', 'utf-8'); |
strlen('text'); |
|
mb_strpos('text', 't', 0, 'utf-8'); |
strpos('text', 't', 0); |
|
mb_strrchr('text', 't', true, 'utf-8'); |
strrchr('text', 't', true); |
|
mb_strripos('text', 't', 0, 'utf-8'); |
strripos('text', 't', 0); |
|
mb_strrpos('text', 't', 0, 'utf-8'); |
strrpos('text', 't', 0); |
|
mb_strstr('text', 't', true, 'utf-8'); |
strstr('text', 't', true); |
|
mb_strtolower('text', 'utf-8'); |
strtolower('text'); |
|
mb_strtoupper('text', 'utf-8'); |
strtoupper('text'); |
|
mb_substr_count('text', 't', 'utf-8'); |
substr_count('text', 't'); |
|
mb_substr('text', 0, 123, 'utf-8'); |
substr('text', 0, 123); |
|
mb_str_split('text', 2, 'utf-8'); |
str_split('text', 2); |
|
mb_convert_case('text', $mode); |
strtoupper('text'); , strtolower('text'); or ucwords('text'); depending on mode |
"mb_parse_str": true
: You are able to disable any of the supported mb string functions. All supported functions are enabled by default.
Some of the functions are not supported due to complexity to convert them to standard string manipulation functions.
Implementing them either does not make sense or creates too many false positive mutations.
Not supported functions aremb_ereg*
,mb_split
,mb_strrichr
,mb_get_info
and similar.
infection.json5:
{ |