Overview

PIT currently provides ten built-in mutators, of which seven 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:


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.

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 (VOIDMETHODCALLS)

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 (NONVOIDMETHOD_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 Inline Constant Mutator (EXPERIMENTALINLINECONSTS)

The experimental inline constant mutator is similar to the inline constant mutator but attempts to provide a more consistent behaviour.

As much as possible the mutator simply adds 1 to the value. This is not done in some cases for due to esoteric JVM details. In the case of floating point types simply adding 1 would result in equivalent mutants (for large enough values, (a + 1) == a).

Constant Type Mutation
boolean replace the unmutated value `true` with `false` and replace the unmutated value `false` with `true`
int byte short replace the unmutated value `1` with `0`, `-1` with `0`, otherwise increment the unmutated value by one. Values of Byte.MAX, Short.MAX and Integer.MAX will be rolled to the corresponding min value. This happens even if the declared type would not overflow.
long replace the unmutated value `1` with `0`, otherwise increment the unmutated value by one.
float replace the `0.0` with `1.0`, `1.0` with `2.0` and replace any other value with `1.0`
double replace the `0.0` with `1.0`, `1.0` with `2.0` any replace other value with `1.0`

The rolling behaviour is a little surprising for short and int as the example below shows

short s = Byte.MAX_VALUE;
int i = Short.MAX_VALUE;
int j = Byte.MAX_VALUE;

will be mutated to

short s = Byte.MIN_VALUE;
int i = Short.MIN_VALUE;
int j = Byte.MIN_VALUE

Experimental Member Variable Mutator (EXPERIMENTALMEMBERVARIABLE)

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