- 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
Custom Mutators
Starting from Infection 0.29.0, it’s possible to create custom mutators that can be used by Infection.
Before creating mutator, make sure it’s not already supported by Infection itself by looking into our built-in mutators. Don’t forget to share results, probably your new mutator can be added to Infection core!
How to create a custom mutator
Imaging we want to create a mutator that replaces any string with Infected!
, like this:
- throw new RuntimeException('File not found'); |
1. Install infection/mutator
and create a class that implements Mutator
interface
If you want to use our mutator generator, additionally install
infection/infection
as a dev dependency and runvendor/bin/infection make:mutator
. Otherwise, create it manually as shown below.
Never install Infection as a production dependency
Custom Mutator must implement Mutator
interface which is located in infection/mutator
package. Install it by:
composer require infection/mutator |
Then, create a class that implements this interface:
<?php |
canMutate()
method is used to determine if the current mutator can mutate the node during traversing AST. What we are interested in here isNode\Scalar\String_
class that represents strings innikic/php-parser
lib.How to know which AST node you need to use in
Mutator::canMutate()
? You can play wih AST Explorer, by writing simple source code and clicking to AST nodes.mutate()
method is the code where our mutation happens. This method is only executed ifcanMutate()
returnstrue
for aNode
, so we always know that an instance ofNode\Scalar\String_
is passed tomutate()
. What we do here is just creating a newNode\Scalar\String_
instance passingInfected!
string, and preserve original node attributes (internal things like position of the node and others). Make sure to always create new nodes with keeping original attributes.getDefinition()
is a method that returns information about new mutator: its description, category and diff between original and mutated code. Make sure to always add details here. This will be used for documentation andbin/infection describe
command.
2. Register mutator
Now, it’s time to add mutator to Infection’s config and enable it:
// infecion.json5 |
3. Check documentation
And let’s check if Infection sees it by running
bin/infection describe App\\Mutator\\AnyStringToInfectedMutator |
This should display information from AnyStringToInfectedMutator::getDefinition()
method.
4. Mutate the code!
It’s time to mutate the code with our own cool mutator. You can run Infection as you usually do, or just with this new mutator to quickly get the feedback:
bin/infection --mutators="App\\Mutator\\AnyStringToInfectedMutator" --show-mutations |
5. Test your mutator
In order to write quality tests for your mutator, we highly recommend to use our generator and BaseMutatorTestCase
from infection/infection
:
composer require infection/infection --dev |
This will create mutator and a test file that you need to move to tests folders and complete by adding test cases. In our example, the test file would like this:
<?php |
6. Produce N
mutants for each Node
If you want to produce several mutants for a specific Node
, just yield
more nodes from mutate()
:
public function mutate(Node $node): iterable |
This will create 2 versions of the original source code (two mutants) for each string.
7. Infection Playground
You can even use our Playground to create custom mutators. See an example of AnyStringToInfectedMutator
: https://infection-php.dev/r/3edo
Understanding Node
attributes
PHP-Parser lib adds for each Node
instance different attributes, that can be accessed like this:
$attributes = $node->getAttributes(); |
Infection also adds additional attributes that can be very useful for custom mutators:
parent
attribute
Sometimes, in order to decide whether mutator needs to (not) mutate a Node
, we need to look to parent nodes.
For example, built-in TrueValue
mutator replaces true
with false
. But we don’t want to do a replacement if it’s the 3rd argument of in_array()
function. But in order to check where true
is used, we need to travers up to the AST.
This is where ‘parent’ attribute can help you;
// Mutator::caMutate() method |
Look here to see how it’s used in a real TrueValue
mutator.
Reflection attributes
reflectionClass
All nodes inside a class has reflectionClass
attribute, which contains \ReflectionClass
instance of the class where the node is located.
This can be useful in many places. Infection, for example, uses it to determine if a class has parents with the same method as being mutated in PublicVisibility
mutator.
/** @var \ClassReflection $reflection */ |
isInsideFunction
This boolean attribute is set to true
if the node is inside a function/method. For example, Infection uses it to not mutate the code outside of methods of classes.
$isInsideFunction = $node->getAttribute('isInsideFunction'); |
isOnFunctionSignature
This boolean attribute is set to true
if the node is on a function/method signature line.
public function foo(bool $someBoolean = true): void |
In this example, we can determine if true
is on a function signature line thanks to this attribute.
$isOnFunctionSignature = $node->getAttribute('isOnFunctionSignature'); |
functionScope
This attribute contains an instance of Node\Stmt\Function_
or null
. Node\Stmt\Function_
can be used to get the return type of the currently mutated function. See how it’s done in ArrayOneItem
mutator.
/** @var Node\Stmt\Function_|null $functionScope */ |
functionName
This attribute contains the name of the function/method where the node is located. It can be useful to determine if the node is inside a specific function that, for example, needs to be ignored in mutation logic.
$methodName = $node->getAttribute('functionName'); |
NameResolver
attributes
Infection uses php-parser’s NameResolver
visitor that adds namespacedName
property to a node, resolvedName
or namespacedName
attributes.
This visitor is configured in Infection with:
[ |
Refer to its documentation to understand how it works. In a nutshell, thanks to this visitor, you can get fully qualified class names or function names in the code.