diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/EnumConstructorFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/EnumConstructorFilter.java new file mode 100755 index 000000000..e8d893e94 --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/EnumConstructorFilter.java @@ -0,0 +1,49 @@ +package org.pitest.mutationtest.build.intercept.javafeatures; + +import org.pitest.bytecode.analysis.ClassTree; +import org.pitest.mutationtest.build.InterceptorType; +import org.pitest.mutationtest.build.MutationInterceptor; +import org.pitest.mutationtest.engine.Mutater; +import org.pitest.mutationtest.engine.MutationDetails; + +import java.util.Collection; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * Filters out mutations in Enum constructors, these are called only once + * per instance so are effectively static initializers. + */ +public class EnumConstructorFilter implements MutationInterceptor { + + private boolean isEnum; + + @Override + public InterceptorType type() { + return InterceptorType.FILTER; + } + + @Override + public void begin(ClassTree clazz) { + this.isEnum = clazz.rawNode().superName.equals("java/lang/Enum"); + } + + @Override + public Collection intercept( + Collection mutations, Mutater m) { + return mutations.stream() + .filter(isInEnumConstructor().negate()) + .collect(Collectors.toList()); + } + + private Predicate isInEnumConstructor() { + return m -> isEnum && m.getMethod().name().equals(""); + } + + + @Override + public void end() { + } + +} + diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/EnumConstructorFilterFactory.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/EnumConstructorFilterFactory.java new file mode 100755 index 000000000..3c283abb0 --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/EnumConstructorFilterFactory.java @@ -0,0 +1,28 @@ +package org.pitest.mutationtest.build.intercept.javafeatures; + +import org.pitest.mutationtest.build.InterceptorParameters; +import org.pitest.mutationtest.build.MutationInterceptor; +import org.pitest.mutationtest.build.MutationInterceptorFactory; +import org.pitest.plugin.Feature; + +public class EnumConstructorFilterFactory implements MutationInterceptorFactory { + + @Override + public String description() { + return "Enum constructor filter"; + } + + @Override + public MutationInterceptor createInterceptor(InterceptorParameters params) { + return new EnumConstructorFilter(); + } + + @Override + public Feature provides() { + return Feature.named("FENUM") + .withOnByDefault(true) + .withDescription("Filters mutations in enum constructors"); + } + +} + diff --git a/pitest-entry/src/main/resources/META-INF/services/org.pitest.mutationtest.build.MutationInterceptorFactory b/pitest-entry/src/main/resources/META-INF/services/org.pitest.mutationtest.build.MutationInterceptorFactory old mode 100644 new mode 100755 index 448a2d7b3..f35ddbede --- a/pitest-entry/src/main/resources/META-INF/services/org.pitest.mutationtest.build.MutationInterceptorFactory +++ b/pitest-entry/src/main/resources/META-INF/services/org.pitest.mutationtest.build.MutationInterceptorFactory @@ -8,6 +8,7 @@ org.pitest.mutationtest.build.intercept.javafeatures.InlinedFinallyBlockFilterFa org.pitest.mutationtest.build.intercept.javafeatures.ImplicitNullCheckFilterFactory org.pitest.mutationtest.build.intercept.javafeatures.MethodReferenceNullCheckFilterFactory org.pitest.mutationtest.build.intercept.javafeatures.ForEachLoopFilterFactory +org.pitest.mutationtest.build.intercept.javafeatures.EnumConstructorFilterFactory org.pitest.mutationtest.build.intercept.logging.LoggingCallsFilterFactory org.pitest.mutationtest.build.intercept.timeout.InfiniteForLoopFilterFactory org.pitest.mutationtest.build.intercept.timeout.InfiniteIteratorLoopFilterFactory diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationDiscoveryTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationDiscoveryTest.java old mode 100644 new mode 100755 index 3c3cac21b..496054a63 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationDiscoveryTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationDiscoveryTest.java @@ -14,6 +14,7 @@ import org.pitest.classpath.ClassloaderByteArraySource; import org.pitest.mutationtest.EngineArguments; import org.pitest.mutationtest.MutationConfig; +import org.pitest.mutationtest.build.intercept.javafeatures.AnEnum; import org.pitest.mutationtest.build.intercept.javafeatures.ForEachFilterTest.HasForEachLoop; import org.pitest.mutationtest.config.PluginServices; import org.pitest.mutationtest.config.ReportOptions; @@ -193,6 +194,15 @@ public void shouldFilterMutationsToForEachLoops() { assertThat(actual.size()).isLessThan(actualWithoutFilter.size()); } + @Test + public void shouldFilterMutationsToEnumConstructors() { + final Collection actual = findMutants(AnEnum.class); + + this.data.setFeatures(Collections.singletonList("-FENUM")); + final Collection actualWithoutFilter = findMutants(AnEnum.class); + + assertThat(actual.size()).isLessThan(actualWithoutFilter.size()); + } @Test public void filtersEquivalentReturnValsMutants() { diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/AnEnum.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/AnEnum.java new file mode 100755 index 000000000..6349cd7af --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/AnEnum.java @@ -0,0 +1,13 @@ +package org.pitest.mutationtest.build.intercept.javafeatures; + +public enum AnEnum { + AN_INSTANCE("hello"); + + AnEnum(String s) { + System.out.println(s); + } + + public void aMethod() { + System.out.println("dont mutate me"); + } +} diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/EnumConstructorTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/EnumConstructorTest.java new file mode 100755 index 000000000..dd85e8a82 --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/EnumConstructorTest.java @@ -0,0 +1,39 @@ +package org.pitest.mutationtest.build.intercept.javafeatures; + +import org.junit.Test; +import org.pitest.mutationtest.engine.MutationDetails; +import java.util.function.Predicate; + +import static org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator.VOID_METHOD_CALL_MUTATOR; + +public class EnumConstructorTest { + + EnumConstructorFilter testee = new EnumConstructorFilter(); + + FilterTester verifier = new FilterTester("unused", this.testee, VOID_METHOD_CALL_MUTATOR); + + @Test + public void filtersMutantsFromEnumConstructor() { + this.verifier.assertFiltersMutationsMatching(inMethodNamed(""), AnEnum.class); + } + + @Test + public void doesNotFilterMutantsInCustomEnumMethods() { + this.verifier.assertFiltersNoMutationsMatching(inMethodNamed("aMethod"), AnEnum.class); + } + + @Test + public void doesNotFilterMutantsInNonEnumConstructors() { + this.verifier.assertFiltersNoMutationsMatching(inMethodNamed(""), AClass.class); + } + + private Predicate inMethodNamed(String name) { + return m -> m.getMethod().name().equals(name); + } +} + +class AClass { + AClass(String s) { + System.out.println(s); + } +} diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/FilterTester.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/FilterTester.java old mode 100644 new mode 100755 index 5c8c32783..c187d2167 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/FilterTester.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/FilterTester.java @@ -122,6 +122,63 @@ public void assertFiltersNMutationFromClass(int n, Class clazz) { softly.assertAll(); } + public void assertFiltersNoMutationsMatching(Predicate match, Class clazz) { + final Sample s = makeSampleForCurrentCompiler(clazz); + + final SoftAssertions softly = new SoftAssertions(); + GregorMutater mutator = mutateFromClassLoader(); + + assertFiltersNoMatchingMutants(match, mutator, s, softly); + + softly.assertAll(); + } + + public void assertFiltersMutationsMatching(Predicate match, Class clazz) { + final Sample s = makeSampleForCurrentCompiler(clazz); + + final SoftAssertions softly = new SoftAssertions(); + GregorMutater mutator = mutateFromClassLoader(); + + assertFiltersMatchingMutants(match, mutator, s, softly); + + softly.assertAll(); + } + + public void assertFiltersMutationsMatching(Predicate match, String sample) { + final GregorMutater mutator = mutateFromResourceDir(); + atLeastOneSampleExists(sample); + + final SoftAssertions softly = new SoftAssertions(); + + for (final Sample s : samples(sample)) { + assertFiltersMatchingMutants(match, mutator, s, softly); + } + + softly.assertAll(); + } + + private void assertFiltersMatchingMutants(Predicate match, GregorMutater mutator, Sample s, SoftAssertions softly) { + final List mutations = mutator.findMutations(s.className); + final Collection actual = filter(s.clazz, mutations, mutator); + + checkHasMutantsMatching(match, s, softly, mutations); + + softly.assertThat(actual) + .describedAs("Expected to filter out all matching mutants") + .noneMatch(match); + } + + private void assertFiltersNoMatchingMutants(Predicate match, GregorMutater mutator, Sample s, SoftAssertions softly) { + final List mutations = mutator.findMutations(s.className); + final Collection actual = filter(s.clazz, mutations, mutator); + + checkHasMutantsMatching(match, s, softly, mutations); + + softly.assertThat(actual) + .describedAs("Expected to filter no matching mutants") + .anyMatch(match); + } + private Sample makeSampleForCurrentCompiler(Class clazz) { final ClassloaderByteArraySource source = ClassloaderByteArraySource.fromContext(); final Sample s = new Sample(); @@ -163,6 +220,7 @@ private Predicate notIn( return a -> !actual.contains(a); } + private void assertFiltersNMutants(int n, GregorMutater mutator, Sample s, SoftAssertions softly) { final List mutations = mutator.findMutations(s.className); final Collection actual = filter(s.clazz, mutations, mutator); @@ -188,6 +246,12 @@ private void checkHasNMutants(int n, Sample s, SoftAssertions softly, .isGreaterThanOrEqualTo(n); } + private void checkHasMutantsMatching(Predicate match, Sample s, SoftAssertions softly, + List mutations) { + softly.assertThat(mutations) + .describedAs("No matching mutations produced with " + s.compiler + " compiler. This test has a bug in it.\n" + s.clazz) + .anyMatch(match); + } private GregorMutater mutateFromResourceDir() { return new GregorMutater(this.source, m -> true, this.mutators); }