Extended Mutation Operators
Requires pitest 1.16.0 or above.
Provided by the arcmutate-base plugin.
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 specialisationsreactor.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 specialisationsreactor.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 specialisationsreactor.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 specialisationsreactor.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.
REACTIVE_CONCATMAP_TO_FLATMAP
Swaps calls to concatMap
for flatMap
for
reactor.core.publisher.Flux
io.reactivex.rxjava3.core.Observable
io.reactivex.rxjava3.core.Flowable
io.reactivex.Observable
io.reactivex.Flowable
REACTIVE_CONCATMAP_TO_SWITCHMAP
Swaps calls to concatMap
for switchMap
for
reactor.core.publisher.Flux
io.reactivex.rxjava3.core.Observable
io.reactivex.rxjava3.core.Flowable
io.reactivex.Observable
io.reactivex.Flowable
REACTIVE_FLATMAP_TO_CONCATMAP
Swaps calls to flatMap
for concatMap
for
reactor.core.publisher.Flux
io.reactivex.rxjava3.core.Observable
io.reactivex.rxjava3.core.Flowable
io.reactivex.Observable
io.reactivex.Flowable
REACTIVE_FLATMAP_TO_SWITCHMAP
Swaps calls to flatMap
for switchMap
for
reactor.core.publisher.Flux
io.reactivex.rxjava3.core.Observable
io.reactivex.rxjava3.core.Flowable
io.reactivex.Observable
io.reactivex.Flowable
REACTIVE_SWITCHMAP_TO_CONCATMAP
Swaps calls to switchMap
for concatMap
for
reactor.core.publisher.Flux
io.reactivex.rxjava3.core.Observable
io.reactivex.rxjava3.core.Flowable
io.reactivex.Observable
io.reactivex.Flowable
REACTIVE_SWITCHMAP_TO_FLATMAP
Swaps calls to switchMap
for flatMap
for
reactor.core.publisher.Flux
io.reactivex.rxjava3.core.Observable
io.reactivex.rxjava3.core.Flowable
io.reactivex.Observable
io.reactivex.Flowable
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.1</version>
<dependencies>
<dependency>
<groupId>com.arcmutate</groupId>
<artifactId>base</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
</plugin>
Or for gradle
dependencies {
pitest 'com.arcmutate:base:1.3.1'
}
Individual operators or groups should then be enabled using the standard pitest configuration options.