Skip to content

Commit

Permalink
filter singleton constructors as per #903
Browse files Browse the repository at this point in the history
Filter mutations in constructors called from the same class' static initializer.

A check is not made that the constructor is private. Some killable mutants may therefore be filtered,
but this needs to be weighed against the confusion caused by mutants appearing/disappearing based on code visibility.
  • Loading branch information
Henry Coles committed Jun 13, 2022
1 parent 3b0fe10 commit e717f8c
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 85 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Read all about it at http://pitest.org

* #1025 - Rework String Switch filtering
* #1027 - Rework assert filtering and remove legacy filter mechanism
* #903 - Filter mutants in singleton constructors

### 1.8.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
/**
* Filters out mutations in Enum constructors, these are called only once
* per instance so are effectively static initializers.
*
* This filter overlaps with the StaticInitializerInterceptor, and could
* probably be removed. Left in place for now as it is computationally less
* expensive.
*/
public class EnumConstructorFilter implements MutationInterceptor {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
* for static initialisation if it is
*
* 1. In a static initializer (i.e <clinit>)
* 2. In a private static method called directly from <clinit>
* 2. In a private static method or constructor called directly from <clinit>
*
* TODO A better analysis would include private static methods called indirectly from <clinit>
* and would exclude methods called from location other than <clinit>.
Expand Down Expand Up @@ -74,7 +74,7 @@ private void analyseClass(ClassTree tree) {
final Predicate<MethodTree> matchingCalls = Prelude.or(selfCalls);

final Predicate<MutationDetails> initOnlyMethods = Prelude.or(tree.methods().stream()
.filter(isPrivateStatic())
.filter(isPrivateStatic().or(isConstructor()))
.filter(matchingCalls)
.map(AnalysisFunctions.matchMutationsInMethod())
.collect(Collectors.toList())
Expand All @@ -84,7 +84,6 @@ private void analyseClass(ClassTree tree) {
}
}


private static Predicate<MutationDetails> isInStaticInitializer() {
return a -> a.getId().getLocation().getMethodName().equals("<clinit>");
}
Expand All @@ -94,6 +93,10 @@ private static Predicate<MethodTree> isPrivateStatic() {
&& ((a.rawNode().access & Opcodes.ACC_PRIVATE) != 0);
}

private Predicate<MethodTree> isConstructor() {
return a -> a.rawNode().name.equals("<init>");
}

private static Predicate<MethodTree> matchesCall(final MethodInsnNode call) {
return a -> a.rawNode().name.equals(call.name)
&& a.rawNode().desc.equals(call.desc);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.example.staticinitializers;

public class SingletonWithWorkInInitializer {
int num = 6;

private static final SingletonWithWorkInInitializer INSTANCE = new SingletonWithWorkInInitializer();

private SingletonWithWorkInInitializer() {
}

public static SingletonWithWorkInInitializer getInstance() {
return INSTANCE;
}

public boolean isMember6() {
return 6 == num;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.Collections;
import java.util.logging.Logger;

import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;

Expand Down Expand Up @@ -198,7 +199,9 @@ public void shouldFilterMutationsToForEachLoops() {
public void shouldFilterMutationsToEnumConstructors() {
final Collection<MutationDetails> actual = findMutants(AnEnum.class);

this.data.setFeatures(Collections.singletonList("-FENUM"));
// both the enum and static initializer filters pick up
// enum constructors. Both left in place for now, but enum one could perhaps be removed
this.data.setFeatures(asList("-FENUM", "-FSTATI"));
final Collection<MutationDetails> actualWithoutFilter = findMutants(AnEnum.class);

assertThat(actual.size()).isLessThan(actualWithoutFilter.size());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,129 +1,163 @@
package org.pitest.mutationtest.build.intercept.staticinitializers;

import com.example.staticinitializers.SingletonWithWorkInInitializer;
import org.junit.Test;
import org.pitest.mutationtest.build.intercept.javafeatures.FilterTester;
import org.pitest.mutationtest.engine.MutationDetails;
import org.pitest.mutationtest.engine.gregor.mutators.NullMutateEverything;
import org.pitest.verifier.interceptors.InterceptorVerifier;
import org.pitest.verifier.interceptors.VerifierStart;

import java.util.function.Predicate;


public class StaticInitializerInterceptorTest {

StaticInitializerInterceptor testee = new StaticInitializerInterceptor();

FilterTester verifier = new FilterTester("", this.testee, NullMutateEverything.asList());


@Test
public void shouldNotFilterMutationsInClassWithoutStaticInitializer() {
verifier.assertFiltersNMutationFromClass(0, NoStaticInializer.class);
}

@Test
public void shouldFilterMutationsInStaticInitializer() {
verifier.assertFiltersMutationsMatching(inMethodNamed("<clinit>"), HasStaticInializer.class);
}

@Test
public void shouldMarkMutationsInPrivateMethodsCalledFromStaticInitializer() {
verifier.assertFiltersMutationsMatching(inMethodNamed("a"), HasPrivateCallsFromStaticInializer.class);
}
InterceptorVerifier v = VerifierStart.forInterceptorFactory(new StaticInitializerInterceptorFactory())
.usingMutator(new NullMutateEverything());


@Test
public void shouldNotFilterMutationsInClassWithoutStaticInitializer() {
v.forClass(NoStaticInializer.class)
.forAnyCode()
.mutantsAreGenerated()
.noMutantsAreFiltered()
.verify();
}

@Test
public void shouldFilterMutationsInStaticInitializer() {
v.forClass(HasStaticInializer.class)
.forMethod("<clinit>")
.forAnyCode()
.mutantsAreGenerated()
.allMutantsAreFiltered()
.verify();
}

@Test
public void shouldMarkMutationsInPrivateMethodsCalledFromStaticInitializer() {
v.forClass(HasPrivateCallsFromStaticInializer.class)
.forMethod("a")
.forAnyCode()
.mutantsAreGenerated()
.allMutantsAreFiltered()
.verify();
}

@Test
public void shouldNotMarkMutationsInPackageDefaultMethodsCalledFromStaticInitializer() {
v.forClass(HasDefaultCallsFromStaticInializer.class)
.forMethod("a")
.forAnyCode()
.mutantsAreGenerated()
.noMutantsAreFiltered()
.verify();
}


@Test
public void shouldNotMarkMutationsInPrivateStaticMethodsNotInvolvedInInit() {
v.forClass(HasOtherPrivateStaticMethods.class)
.forMethod("b")
.forAnyCode()
.mutantsAreGenerated()
.noMutantsAreFiltered()
.verify();
}

@Test
public void shouldNotMarkMutationsInOverriddenMethodsNotInvolvedInStaticInit() {
v.forClass(HasOverloadedMethodsThatAreNotUsedInStaticInitialization.class)
.forMutantsMatching(inMethod("a", "(I)V"))
.mutantsAreGenerated()
.noMutantsAreFiltered()
.verify();
}

@Test
public void shouldNotMarkMutationsInPackageDefaultMethodsCalledFromStaticInitializer() {
verifier.assertFiltersNoMutationsMatching(inMethodNamed("a"), HasDefaultCallsFromStaticInializer.class);
public void filtersMutantsInSingletonConstructor() {
v.forClass(SingletonWithWorkInInitializer.class)
.forMutantsMatching(inMethod("<init>", "()V"))
.mutantsAreGenerated()
.allMutantsAreFiltered()
.verify();
}


@Test
public void shouldNotMarkMutationsInPrivateStaticMethodsNotInvolvedInInit() {
verifier.assertFiltersNoMutationsMatching(inMethodNamed("b"), HasOtherPrivateStaticMethods.class);
}

@Test
public void shouldNotMarkMutationsInOverriddenMethodsNotInvolvedInStaticInit() {
verifier.assertFiltersNoMutationsMatching(inMethod("a", "(I)V"), HasOverloadedMethodsThatAreNotUsedInStaticInitialization.class);
}

private Predicate<MutationDetails> inMethodNamed(String name) {
return m -> m.getMethod().equals(name);
}

private Predicate<MutationDetails> inMethod(String name, String desc) {
return m -> m.getMethod().equals(name) && m.getId().getLocation().getMethodDesc().equals(desc);
}
private Predicate<MutationDetails> inMethod(String name, String desc) {
return m -> m.getMethod().equals(name) && m.getId().getLocation().getMethodDesc().equals(desc);
}

}

class NoStaticInializer {
{
System.out.println("NOT static code");
}
{
System.out.println("NOT static code");
}
}

class HasStaticInializer {
static {
System.out.println("static code");
}
static {
System.out.println("static code");
}
}

class HasPrivateCallsFromStaticInializer {
static {
a();
}
static {
a();
}

private static void a() {
System.out.println("static code");
}
private static void a() {
System.out.println("static code");
}
}

class HasDefaultCallsFromStaticInializer {
static {
a();
}
static {
a();
}

static void a() {
System.out.println("NOT guaranteed to be static code");
}
static void a() {
System.out.println("NOT guaranteed to be static code");
}
}

class HasOtherPrivateStaticMethods {
static {
a();
}
static {
a();
}

private static void a() {
private static void a() {

}
}

public static void entryPoint(int i) {
b(i);
}
public static void entryPoint(int i) {
b(i);
}


private static void b(int i) {
System.out.println("NOT static code");
}
private static void b(int i) {
System.out.println("NOT static code");
}
}


class HasOverloadedMethodsThatAreNotUsedInStaticInitialization {
static {
a();
}
static {
a();
}

private static void a() {
private static void a() {

}
}

public static void entryPoint(int i) {
a(i);
}
public static void entryPoint(int i) {
a(i);
}

// same name, different sig
private static void a(int i) {
System.out.println("NOT static code");
}
// same name, different sig
private static void a(int i) {
System.out.println("NOT static code");
}
}

0 comments on commit e717f8c

Please sign in to comment.