PIT currently provides some built-in mutators, of which most are activated by default. The default set can be overridden, and different operators selected, by passing the names of the required operators to the mutators parameter. To make configuration easier, some mutators are put together in groups. Passing the name of a group in the mutators parameter will activate all mutators of the group.
Mutations are performed on the byte code generated by the compiler rather than on the source files. This approach has the advantage of being generally much faster and easier to incorporate into a build, but it can sometimes be difficult to simply describe how the mutation operators map to equivalent changes to a Java source file.
The operators are largely designed to be stable (i.e not be too easy to detect) and minimise the number of equivalent mutations that they generate. Those operators that do not meet these requirements are not enabled by default.
Additional operators are available from arcmutate.
The following table list available mutators and whether or not they are part of a group :
Mutators | “OLD_DEFAULTS” group | “DEFAULTS” group | “STRONGER” group | “ALL” group |
---|---|---|---|---|
Conditionals Boundary | yes | yes | yes | yes |
Increments | yes | yes | yes | yes |
Invert Negatives | yes | yes | yes | yes |
Math | yes | yes | yes | yes |
Negate Conditionals | yes | yes | yes | yes |
Return Values | yes | yes | ||
Void Method Calls | yes | yes | yes | yes |
Empty returns | yes | yes | yes | |
False Returns | yes | yes | yes | |
True returns | yes | yes | yes | |
Null returns | yes | yes | yes | |
Primitive returns | yes | yes | yes | |
Remove Conditionals | EQUAL_ELSE case | yes | ||
Experimental Switch | yes | yes | ||
Inline Constant | yes | |||
Constructor Calls | yes | |||
Non Void Method Calls | yes | |||
Remove Increments | yes | |||
Experimental Argument Propagation | yes | |||
Experimental Big Integer | yes | |||
Experimental Member Variable | yes | |||
Experimental Naked Receiver | yes | |||
Negation | yes | |||
Arithmetic Operator Replacement | yes | |||
Arithmetic Operator Deletion | yes | |||
Constant Replacement | yes | |||
Bitwise Operator | yes | |||
Relational Operator Replacement | yes | |||
Unary Operator Insertion | yes |
See the current code for current list (latest development version).
Active by default
The conditionals boundary mutator replaces the relational operators <, <=, >, >=
with their boundary counterpart as per the table below.
Original conditional | Mutated conditional |
---|---|
< | <= |
<= | < |
> | >= |
>= | > |
For example
if (a < b) {
// do something
}
will be mutated to
if (a <= b) {
// do something
}
Active by default
The increments mutator will mutate increments, decrements and assignment increments and decrements of local variables (stack variables). It will replace increments with decrements and vice versa.
For example
public int method(int i) {
i++;
return i;
}
will be mutated to
public int method(int i) {
i--;
return i;
}
Please note that the increments mutator will be applied to increments of local variables only. Increments and decrements of member variables will be covered by the Math Mutator.
Active by default
The invert negatives mutator inverts negation of integer and floating point variables. For example
public float negate(final float i) {
return -i;
}
will be mutated to
public float negate(final float i) {
return i;
}
Please note, this mutator does not mutate negative constants, only variables.
Active by default
The math mutator replaces binary arithmetic operations for either integer or floating-point arithmetic with another operation. The replacements will be selected according to the table below.
Original conditional | Mutated conditional |
---|---|
+ | - |
- | + |
* | / |
/ | * |
% | * |
& | | |
| | & |
^ | & |
<< | >> |
>> | << |
>>> | << |
For example
int a = b + c;
will be mutated to
int a = b - c;
Keep in mind that the +
operator on String
s as in
String a = "foo" + "bar";
is not a mathematical operator but a string concatenation and will be replaced by the compiler with something like
String a = new StringBuilder("foo").append("bar").toString();
Please note that the compiler will also use binary arithmetic operations for
increments, decrements and assignment increments and decrements of non-local
variables (member variables) although a special iinc
opcode for increments
exists. This special opcode is restricted to local variables (also called stack
variables) and cannot be used for member variables. That means the math mutator
will also mutate
public class A {
private int i;
public void foo() {
this.i++;
}
}
to
public class A {
private int i;
public void foo() {
this.i = this.i - 1;
}
}
See the Increments Mutator for details.
Active by default
The negate conditionals mutator will mutate all conditionals found according to the replacement table below.
Original conditional | Mutated conditional |
---|---|
== | != |
!= | == |
<= | > |
>= | < |
< | >= |
> | <= |
For example
if (a == b) {
// do something
}
will be mutated to
if (a != b) {
// do something
}
This mutator overlaps to a degree with the conditionals boundary mutator, but is less stable i.e these mutations are generally easier for a test suite to detect.
This mutator has been superseded by the new returns mutator set. See Empty returns, False returns, True returns, Null returns and Primitive returns.
The return values mutator mutates the return values of method calls. Depending on the return type of the method another mutation is used.4
Return Type | Mutation |
---|---|
boolean |
replace the unmutated return value true with false and replace the unmutated return value false with true |
int byte short |
if the unmutated return value is 0 return 1 , otherwise mutate to return value 0 |
long |
replace the unmutated return value x with the result of x+1 |
float double |
replace the unmutated return value x with the result of -(x+1.0) if x is not NAN and replace NAN with 0 |
Object |
replace non-null return values with null and throw a java.lang.RuntimeException if the unmutated method would return null |
For example
public Object foo() {
return new Object();
}
will be mutated to
public Object foo() {
new Object();
return null;
}
Please note that constructor calls are not considered void method calls. See the Constructor Call Mutator for mutations of constructors or the Non Void Method Call Mutator for mutations of non void methods.
Active by default
The void method call mutator removes method calls to void methods. For example
public void someVoidMethod(int i) {
// does something
}
public int foo() {
int i = 5;
someVoidMethod(i);
return i;
}
will be mutated to
public void someVoidMethod(int i) {
// does something
}
public int foo() {
int i = 5;
return i;
}
Active by default
Replaces return values with an ‘empty’ value for that type as follows
Pitest will filter out equivalent mutations to methods that are already hard coded to return the empty value.
Active by default
Replaces primitive and boxed boolean return values with false.
Pitest will filter out equivalent mutations to methods that are already hard coded to return false.
Active by default
Replaces primitive and boxed boolean return values with true.
Pitest will filter out equivalent mutations to methods that are already hard coded to return true.
Active by default
Replaces return values with null. Methods that can be mutated by the EMPTY_RETURNS mutator or that are directly annotated with NotNull will not be mutated.
Pitest will filter out equivalent mutations to methods that are already hard coded to return null.
Active by default
Replaces int, short, long, char, float and double return values with 0.
Pitest will filter out equivalent mutations to methods that are already hard coded to return 0.
Optional mutator that replaces constructor calls with null
values. For example
public Object foo() {
Object o = new Object();
return o;
}
will be mutated to
public Object foo() {
Object o = null;
return o;
}
Please note that this mutation is fairly unstable and likely to cause NullPointerException
s even
with weak test suites.
This mutator does not affect non constructor method calls. See Void Method Call Mutator for mutations of void methods and Non Void Method Call Mutator for mutations of non void methods.
The inline constant mutator mutates inline constants. An inline constant is a literal value assigned to a non-final variable, for example
public void foo() {
int i = 3;
// do something with i
}
Depending on the type of the inline constant another mutation is used. The rules are a little complex due to the different ways that apparently similar Java statements are converted to byte code.
Constant Type | Mutation |
---|---|
boolean |
replace the unmutated value true with false and replace the unmutated value false with true |
integer byte short |
replace the unmutated value 1 with 0 , -1 with 1 , 5 with -1 or otherwise increment the unmutated value by one. 1 |
long |
replace the unmutated value 1 with 0 , otherwise increment the unmutated value by one. |
float |
replace the unmutated values 1.0 and 2.0 with 0.0 and replace any other value with 1.0 2 |
double |
replace the unmutated value 1.0 with 0.0 and replace any other value with 1.0 3 |
For example
public int foo() {
int i = 42;
return i;
}
will be mutated to
public int foo() {
int i = 43;
return i;
}
Please note that the compiler might optimize the use of final variables (regardless whether those are stack variables or member variables). For example the following code
public class A {
private static final int VAR = 13;
public String foo() {
final int i = 42;
return "" + VAR + ":" + i;
}
}
will be changed/optimized by the compiler to
public class A {
public String foo() {
return "13:42";
}
}
In such situations the mutation engine can not mutate any variable.
The non void method call mutator removes method calls to non void methods. Their return value is replaced by the Java Default Value for that specific type. See the table below.
Table: Java Default Values for Primitives and Reference Types
Type | Default value |
---|---|
boolean |
false |
int byte short long |
0 |
float double |
0.0 |
char |
'\u0000' |
Object |
null |
For example
public int someNonVoidMethod() {
return 5;
}
public void foo() {
int i = someNonVoidMethod();
// do more stuff with i
}
will be mutated to
public int someNonVoidMethod() {
return 5;
}
public void foo() {
int i = 0;
// do more stuff with i
}
and for method calls returning an object type the call
public Object someNonVoidMethod() {
return new Object();
}
public void foo() {
Object o = someNonVoidMethod();
// do more stuff with o
}
will be mutated to
public Object someNonVoidMethod() {
return new Object();
}
public void foo() {
Object o = null;
// do more stuff with o
}
Please note that this mutation is fairly unstable for some types (especially Objects where
NullPointerException
s are likely) and may also create equivalent mutations if
it replaces a method that already returns one of the default values without also having
a side effect.
This mutator does not affect void methods or constructor calls. See Void Method Call Mutator for mutations of void methods and Constructor Call Mutator for mutations of constructors.
The remove conditionals mutator will remove all conditionals statements such that the guarded statements always execute
For example
if (a == b) {
// do something
}
will be mutated to
if (true) {
// do something
}
Although not currently enabled by default it is highly recommended that you enable it if you wish to ensure your test suite has full coverage of conditional statements.
As shown above the basic remove conditionals mutator ensures that the statements following the conditional always execute. It will also only mutate only equality checks (e.g. ==, !=).
Additional specialised versions of the mutator exist that will ensure the block never executes so
if (a == b) {
// do something
}
will be mutated to
if (false) {
// do something
}
If an else block is present it will always execute
if (a == b) {
// do something
} else {
// do something else
}
will be mutated to
if (false) {
// do something
} else {
// do something else
}
Specialisations also exist that will mutate the bytecode instructions for order checks (e.g. <=, >).
The available specialisations are:
The names reflect which branch will be forced to execute (the “if” or the “else”) and the type of checks that will be mutated.
The reason these are not enabled by default is that there is a large degree of overlap in the tests required to kill these mutations and those required to kill mutations from other default operators such as the conditional boundaries mutator.
Optional mutator that removes local variable increments.
Experimental mutator that replaces method call with one of its parameters of matching type.
Experimental mutator that swaps big integer methods.
Experimental mutator that replaces method call with a naked receiver.
The experimental member variable mutator mutates classes by removing assignments to member variables. The mutator can even remove assignments to final members. The members will be initialized with their Java Default Value for the specific type. See the table below.
Table: Java Default Values for Primitives and Reference Types
Type | Default value |
---|---|
boolean |
false |
int byte short long |
0 |
float double |
0.0 |
char |
'\u0000' |
Object |
null |
For example
public class MutateMe {
private final int x = 5;
//...
}
will be mutated to
public class MutateMe {
private final int x = 0;
...
}
Please Note: This mutator is likely to create equivalent mutations if a member variable is explicitly initialized with the Java default value for the specific type of the member variable as in
public class EquivalentMutant {
private int x = 0;
}
The switch mutator finds the first label within a switch statement that differs from the default label. It mutates the switch statement by replacing the default label (wherever it is used) with this label. All the other labels are replaced by the default one.
This mutator replace any use of a numeric variable (local variable, field, array cell) with its negation. For example:
public float get(final float i) {
return i;
}
will be mutated to
public float get(final float i) {
return -i;
}
Like the math mutator, this mutator replaces binary arithmetic operations for either integer or floating-point arithmetic with another operation. The mutator is composed of 4 sub-mutators (AOR_1 to AOR_4) that mutate operators according to the table below.
Original operator | AOR_1 | AOR_2 | AOR_3 | AOR_4 |
---|---|---|---|---|
+ | - | * | / | % |
- | + | * | / | % |
* | / | % | + | - |
/ | * | % | + | - |
% | * | / | + | - |
This mutator replaces an arithmetic operation with one of its members. The mutator is composed of 2 sub-mutators, AOD_1 and AOD_2, that mutate the operation to its first and second member respectively. For example
int a = b + c;
will be mutated to
int a = b;
and to
int a = c;
Like the inline constant mutator, this mutator mutates inline constant. The mutator is composed of 6 sub-mutators (CRCR1 to CRCR6) that mutate constants according to the table below.
Constant | CRCR1 | CRCR2 | CRCR3 | CRCR4 | CRCR5 | CRCR6 |
---|---|---|---|---|---|---|
c | 1 | 0 | -1 | -c | c+1 | c-1 |
This mutator mutates bitwise and (&) and or (|). It is composed of three sub-mutators, OBBN1, OBBN2 and OBBN3 that respectively reverse the operators, replace a bitwise operation by its first member, and by its second member. For example
a & b;
will be mutated to
a | b;
by OBBN1, to
a;
by OBBN2 and to
b;
by OBBN3.
This mutator replaces a relational operator with another one. The mutator is composed of 5 sub-mutators (ROR1 to ROR5) that mutate the operators according to the table below.
Original operator | ROR1 | ROR2 | ROR3 | ROR4 | ROR5 |
---|---|---|---|---|---|
< | <= | > | >= | == | != |
<= | < | > | >= | == | != |
> | < | <= | >= | == | != |
>= | < | <= | > | == | != |
== | < | <= | > | >= | != |
!= | < | <= | > | >= | == |
This mutator inserts a unary operator (increment or decrement) to a variable call. It affects local variables, array variables, fields, and parameters. It is composed of 4 sub-mutators, UOI1 to UOI4 that insert operators according to the table below.
Variable | UOI1 | UOI2 | UOI3 | UOI4 |
---|---|---|---|---|
a | a++ | a– | ++a | –a |
Thanks to Stefan Penndorf who contributed this documentation.