Overview

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.

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.

Available mutators

We’ll briefly describe the available mutators in the following sections. See the test cases for more detailed examples.

Here is the list of available mutators:

(activated by default) - Conditionals Boundary Mutator - Increments Mutator - Invert Negatives Mutator - Math Mutator - Negate Conditionals Mutator - Return Values Mutator - Void Method Calls Mutator

(deactivated by default) - Constructor Calls Mutator - Inline Constant Mutator - Non Void Method Calls Mutator - Remove Conditionals Mutator - Experimental Member Variable Mutator - Experimental Switch Mutator

See the current code for current list (latest development version).


Conditionals Boundary Mutator (CONDITIONALS_BOUNDARY)

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
}

Negate Conditionals Mutator (NEGATE_CONDITIONALS)

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.

Remove Conditionals Mutator (REMOVE_CONDITIONALS)

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 (eg ==, !=).

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 (eg <=, >).

The available specialisations are

  • REMOVE_CONDITIONALS_EQ_IF
  • REMOVE_CONDITIONALS_EQ_ELSE
  • REMOVE_CONDITIONALS_ORD_IF
  • REMOVE_CONDITIONALS_ORD_ELSE

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.

Math Mutator (MATH)

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 Strings 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.

Increments Mutator (INCREMENTS)

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.

Invert Negatives Mutator (INVERT_NEGS)

Active by default

The invert negatives mutator inverts negation of integer and floating point numbers. For example

public float negate(final float i) {
  return -i;
}

will be mutated to

public float negate(final float i) {
  return i;
}

Inline Constant Mutator (INLINE_CONSTS)

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.

Return Values Mutator (RETURN_VALS)

Active by default

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;
}

Void Method Call Mutator (VOID_METHOD_CALLS)

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;
  doSomething(i);
  return i;
}

will be mutated to

public void someVoidMethod(int i) {
  // does something
}

public int foo() {
  int i = 5;
  return i;
}

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.

Non Void Method Call Mutator (NON_VOID_METHOD_CALLS)

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 NullPointerExceptions 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.

Constructor Call Mutator (CONSTRUCTOR_CALLS)

The constructor call mutator 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 NullPointerExceptions 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.

Experimental Member Variable Mutator (EXPERIMENTAL_MEMBER_VARIABLE) ——————————————————————-

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 short byte 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;
}

Experimental Switch Mutator (EXPERIMENTAL_SWITCH)

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.

Thanks to Stefan Penndorf who contributed this documentation.


  1. Integer numbers and booleans are actually represented in the same way be the JVM, it is therefore never safe if change a 0 to anything but a 1 or a 1 to anything but a 0.
  2. Floating point numbers are always changed to 1 rather than adding 1 to the original value as this would result in equivalent mutations. Adding 1 to a large floating point number doesn’t necessarily change its value due to the imprecise way in which floats are represented.
  3. See note above which applies to both floats and doubles.
  4. The strategy used by this mutator was translated from code in the Jumble project