Link Search Menu Expand Document

Extended Mutation Operators

Requires pitest 1.15.0 or above.

Provided by the arcmutate-base plugin.

Maven Central

Background

Pitest ships with a set of carefully selected default operators. They give good confidence in a test suite, while avoiding creating large numbers of equivalent or low quality mutants. Although pitest provides a number of other operators, they are not enabled by default as they may provide a poorer experience.

The default pitest operators were originally designed for use with Java 5. They are still useful, but modern Java code looks very different from what we used to write, with fewer loops and conditionals. Often the default mutator set is not able to mutate it usefully.

The extended operators have been designed to mutate code written in a modern style using apis introduced in Java 8 and later. Like the default set, they are low noise and work hard to avoid creating obviously equivalent mutants.

In addition, the plugin adds operators that support extreme mutation testing.

Subsumption

The plugin adds subsumption analysis to pitest. This reduces the number of mutations produced without reducing the strength of the analysis. This is achieved by identifying mutants that would always be killed by the same tests as other mutants generated for a class.

Mutator Groups

EXTENDED

The recommended set of extended operators. These should be used in combination with the built in DEFAULTS or STRONGER set.

e.g. for maven

<configuration>
  <mutators>
    <mutator>STRONGER</mutator>
    <mutator>EXTENDED</mutator>
  </mutators>
</configuration>

EXTREME

All extreme mutation operators. These are unstable mutations which remove entire method bodies, substituting a suitable hard coded return value. This can be a useful approach when introducing mutation testing to a project for the first time as fewer mutants are created, resulting in faster analysis times.

The extreme operators can be mixed with the standard operators, but as they are unstable they will be subsumed by any other mutation to the same method.

EXTENDED_ALL

The recommended extended operators, plus additional operators that we do not yet recommend enabling by default as they may not result in a smooth experience.

Mutators from this group may be moved to the EXTENDED group as we gather reports from more code bases and introduce additional static analysis to avoid issues.

The Operators

CHAINED_CALLS

Removes calls to methods with same return type as owner, as are commonly seen when using the Builder Pattern.

For example, the following code would be mutated at the indicated points, while standard pitest would only mutate the method return value to null.

  public Widget provides() {
    return Widget.named("foo") // change to return null (standard mutation)
      .withDescription("A widget") // call removed
      .withOnByDefault(true) // call removed
      .withChildren(asList("a", "b"); // call removed
  }

This mutator is similar to the experimental NAKED_RECEIVER operator in standard pitest, but is aware of Java generics. Analysis is also performed to remove low quality mutants from this operator.

VARARGS

Removes the last argument to a varargs call, so

  public List<String> someStrings() {
    return asList("a", "b", "c");
  }

becomes

  public List<String> someStrings() {
    return asList("a", "b");
  }

Empty varargs calls will not be mutated.

ONE_LESS_PARAM

introduced in 0.1.4

Swaps method calls for overloads which take one less parameter.

List.of(1,2);

Becomes

List.of(1);

To avoid creating low quality mutants, this mutator only targets the following methods:

  • List.of
  • Set.of
  • EnumSet.of
  • LocalDateTime.of
  • LocalTime.of
  • io.reactivex.rxjava3.core.Flowable.of
  • io.reactivex.rxjava3.core.Observable.of
  • io.reactivex.Flowable.of
  • io.reactivex.Observable.of

REMOVE_DISTINCT

Removes calls to distinct for

  • java.util.Stream and its primitive specialisations
  • reactor.core.publisher.Flux
  • io.reactivex.rxjava3.core.Observable
  • io.reactivex.rxjava3.core.Flowable
  • io.reactivex.Observable
  • io.reactivex.Flowable

REMOVE_FILTER

Removes calls to filter for

  • java.util.Stream and its primitive specialisations
  • reactor.core.publisher.Flux
  • reactor.core.publisher.Mono
  • io.reactivex.rxjava3.core.Observable
  • io.reactivex.rxjava3.core.Flowable
  • io.reactivex.rxjava3.core.Maybe
  • io.reactivex.Observable
  • io.reactivex.Flowable
  • io.reactivex.Maybe

Logic implemented in stream filters is often already mutated by the standard returns mutator when the predicate passed to the filter is a lambda or method reference within the same class.

This operator fills the gap when the filter is passed a method reference to a different class. For example, standard pitest would not mutate the following code

Stream<File> fileLogic(Stream<File> files) {
   return files
     .filter(File::exists) // call removed by extended operator
     .filter(File::isDirectory); // call removed by extended operator
} 

When using the extended operators a subsumption analysis is run to remove redundant mutants. return true mutants in lambdas will be suppressed when a remove filter mutant subsumes it, and remove filter mutants will be suppressed when subsumed by a return true mutant in a referenced method in the same class.

REMOVE_LIMIT

Removes calls to Stream.limit.

To avoid creating timeouts, this mutant is suppressed when the stream it acts on is clearly infinite.

REMOVE_SKIP

Removes calls to skip for

  • java.util.Stream and its primitive specialisations
  • reactor.core.publisher.Flux
  • io.reactivex.rxjava3.core.Observable
  • io.reactivex.rxjava3.core.Flowable
  • io.reactivex.Observable
  • io.reactivex.Flowable

REMOVE_SORTED

Removes calls to sorted and sort for

  • java.util.Stream and its primitive specialisations
  • reactor.core.publisher.Flux
  • io.reactivex.rxjava3.core.Observable
  • io.reactivex.rxjava3.core.Flowable
  • io.reactivex.Observable
  • io.reactivex.Flowable

REMOVE_PREDICATE_NEGATION

Removes calls to negate on predicates.

REMOVE_PREDICATE_AND

Removes calls to and on predicates.

REMOVE_PREDICATE_OR

Removes calls to or on predicates.

FIELD_WRITES

Removes writes to non static fields.

This operator is not included in the main EXTENDED group as it highlights issues on the setter methods of Java beans. Although these are valid and valuable mutants in most contexts, some teams choose to write them pre emptively, even though they are not used within the code. The mutator is therefore not enabled by default, but you may wish to enable it. If you have unused setter methods they can be excluded from the mutation analysis using pitest’s excludedMethods parameter.

SWAP_PARAMS

Swaps the last two parameters of a method call if they have the same type.

void foo(int enemy, int friend) {
   fireMissilesAvoidingAlly(enemy, friend); // mutates to fireMissilesAvoidingAlly(friend, enemy); 
}

Mutations are not created when the same parameter is passed at both positions, or where generic types are mismatched.

SWAP_ALL_MATCH

Swaps calls to Stream.allMatch for Stream.anyMatch

SWAP_PREDICATE_OR

Swaps calls to or for and on predicates.

SWAP_PREDICATE_AND

Swaps calls to and for or on predicates.

REACTIVE_RETURNS

Improves the stability of mutations to the return statement of methods returning Mono, Flux, Maybe, Flowable or Observable. Pitest will mutate these to null, this mutator subsumes these mutants with mutations that return the corresponding empty() method.

EXTREME_VOID

Removes the entire body of void methods.

EXTREME_NULL

Removes the entire body of methods returning an object type, replacing it with a null return. This operator is very unstable and will be subsumed by any other mutator capable of mutating the method.

EXTREME_BOOLEAN

Removes the entire body of methods returning primitive and boxed boolean, replacing it with a false return.

EXTREME_ZERO

Removes the entire body of methods returning primitive types and boxed type, replacing it with a 0 return.

EXTREME_EMPTY

Removes the entire body of methods, replacing it with an empty return for that type as follows

  • String -> “”
  • Optional -> Optional.empty()
  • Stream -> Stream.empty()
  • List -> Collections.emptyList()
  • Set -> Collections.emptySet()
  • Map -> Collections.emptyMap()
  • Collection -> Collections.emptyList()
  • Iterable -> Collections.emptyList()
  • array -> empty array
  • Mono -> Mono.empty()
  • Flux -> Flux.empty()
  • Maybe -> Maybe.empty()
  • Flowable -> Flowable.empty()

Installation

To you use the plugin you must first acquire a licence.

The licence file must be named arcmutate-licence.txt and placed at the root of the project.

The plugin itself should be placed on the classpath of the pitest build integration plugin.

E.g for maven

<plugin>
  <groupId>org.pitest</groupId>
  <artifactId>pitest-maven</artifactId>
  <version>1.16.0</version>
    <dependencies>
      <dependency>
        <groupId>com.arcmutate</groupId>
        <artifactId>base</artifactId>
        <version>1.3.0</version>
      </dependency>
    </dependencies>
</plugin>

Or for gradle

dependencies {
  pitest 'com.arcmutate:base:1.3.0'
}

Individual operators or groups should then be enabled using the standard pitest configuration options.