From 1752f037c6414bd3529a958e74ced7757b6c23b5 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Mon, 11 Nov 2024 21:19:32 +0000 Subject: [PATCH] tidy up filter and test --- .../ExcludedAnnotationInterceptor.java | 134 +++++-------- .../ExcludedAnnotationInterceptorTest.java | 189 ++++++++++-------- .../verifier/interceptors/Verifier.java | 4 + pom.xml | 2 +- 4 files changed, 169 insertions(+), 160 deletions(-) diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/annotations/ExcludedAnnotationInterceptor.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/annotations/ExcludedAnnotationInterceptor.java index 7046427d6..8d228c0b2 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/annotations/ExcludedAnnotationInterceptor.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/annotations/ExcludedAnnotationInterceptor.java @@ -6,10 +6,10 @@ import org.objectweb.asm.tree.InvokeDynamicInsnNode; import org.pitest.bytecode.analysis.ClassTree; import org.pitest.bytecode.analysis.MethodTree; -import org.pitest.functional.FCollection; -import org.pitest.functional.prelude.Prelude; +import org.pitest.classinfo.ClassName; import org.pitest.mutationtest.build.InterceptorType; import org.pitest.mutationtest.build.MutationInterceptor; +import org.pitest.mutationtest.engine.Location; import org.pitest.mutationtest.engine.Mutater; import org.pitest.mutationtest.engine.MutationDetails; @@ -17,11 +17,13 @@ import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; +import java.util.Optional; import java.util.List; import java.util.Queue; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.Stream; public class ExcludedAnnotationInterceptor implements MutationInterceptor { @@ -42,20 +44,21 @@ public InterceptorType type() { @Override public void begin(ClassTree clazz) { this.skipClass = clazz.annotations().stream() - .anyMatch(avoidedAnnotation()); + .anyMatch(avoidedAnnotation()); if (!this.skipClass) { - // 1. Collect methods with avoided annotations or that override such methods + // 1. Collect methods with avoided annotations final List avoidedMethods = clazz.methods().stream() - .filter(hasAvoidedAnnotation()) - .collect(Collectors.toList()); + .filter(hasAvoidedAnnotation()) + .collect(Collectors.toList()); // Collect method names along with descriptors to handle overloaded methods - final Set avoidedMethodSignatures = avoidedMethods.stream() - .map(method -> new MethodSignature(method.rawNode().name, method.rawNode().desc)) - .collect(Collectors.toSet()); + final Set avoidedMethodSignatures = avoidedMethods.stream() + .map(method -> new Location(clazz.name(), method.rawNode().name, method.rawNode().desc)) + .collect(Collectors.toSet()); - // Keep track of processed methods to avoid infinite loops - Set processedMethods = new HashSet<>(avoidedMethodSignatures); + // Keep track of processed methods to avoid infinite loops - TODO not clear + // that this is necessary + Set processedMethods = new HashSet<>(avoidedMethodSignatures); // 2. For each avoided method, collect lambda methods recursively for (MethodTree avoidedMethod : avoidedMethods) { @@ -64,8 +67,8 @@ public void begin(ClassTree clazz) { // 3. Create a predicate to match mutations in methods to avoid this.annotatedMethodMatcher = mutation -> { - MethodSignature mutationSignature = new MethodSignature( - mutation.getMethod(), mutation.getId().getLocation().getMethodDesc()); + Location mutationSignature = Location.location(clazz.name(), + mutation.getMethod(), mutation.getId().getLocation().getMethodDesc()); return avoidedMethodSignatures.contains(mutationSignature); }; } @@ -80,49 +83,50 @@ public void begin(ClassTree clazz) { * @param processedMethods The set of already processed methods to prevent infinite loops. */ private void collectLambdaMethods(MethodTree method, ClassTree clazz, - Set avoidedMethodSignatures, - Set processedMethods) { + Set avoidedMethodSignatures, + Set processedMethods) { Queue methodsToProcess = new LinkedList<>(); methodsToProcess.add(method); while (!methodsToProcess.isEmpty()) { MethodTree currentMethod = methodsToProcess.poll(); + Set lambdas = currentMethod.instructions().stream() + .flatMap(n -> lambdaCallsToClass(clazz.name(), n)) + .filter(l -> !avoidedMethodSignatures.contains(l) && !processedMethods.contains(l)) + .collect(Collectors.toSet()); - for (AbstractInsnNode insn : currentMethod.rawNode().instructions) { - if (insn instanceof InvokeDynamicInsnNode) { - InvokeDynamicInsnNode indy = (InvokeDynamicInsnNode) insn; - - for (Object bsmArg : indy.bsmArgs) { - if (bsmArg instanceof Handle) { - Handle handle = (Handle) bsmArg; - // Check if the method is in the same class and is a lambda method - if (handle.getOwner().equals(clazz.rawNode().name) && handle.getName().startsWith("lambda$")) { - MethodSignature lambdaMethodSignature = new MethodSignature(handle.getName(), handle.getDesc()); - if (!avoidedMethodSignatures.contains(lambdaMethodSignature) - && !processedMethods.contains(lambdaMethodSignature)) { - avoidedMethodSignatures.add(lambdaMethodSignature); - processedMethods.add(lambdaMethodSignature); - // Find the MethodTree for this lambda method - MethodTree lambdaMethod = findMethodTree(clazz, handle.getName(), handle.getDesc()); - if (lambdaMethod != null) { - methodsToProcess.add(lambdaMethod); - } - } - } - } - } - } - } + List recurse = lambdas.stream() + .map(clazz::method) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + + methodsToProcess.addAll(recurse); + + avoidedMethodSignatures.addAll(lambdas); + processedMethods.addAll(lambdas); } } - private MethodTree findMethodTree(ClassTree clazz, String methodName, String methodDesc) { - return clazz.methods().stream() - .filter(m -> m.rawNode().name.equals(methodName) && m.rawNode().desc.equals(methodDesc)) - .findFirst() - .orElse(null); - } + private Stream lambdaCallsToClass(ClassName clazz, AbstractInsnNode insn) { + if (!(insn instanceof InvokeDynamicInsnNode)) { + return Stream.empty(); + } + InvokeDynamicInsnNode indy = (InvokeDynamicInsnNode) insn; + + for (Object bsmArg : indy.bsmArgs) { + if (bsmArg instanceof Handle) { + Handle handle = (Handle) bsmArg; + // Check if the method is in the same class and is a lambda method + if (handle.getOwner().equals(clazz.asInternalName()) && handle.getName().startsWith("lambda$")) { + return Stream.of(Location.location(clazz,handle.getName(), handle.getDesc())); + } + } + } + return Stream.empty(); + } + /** * Creates a predicate that checks if a method has an avoided annotation. * @@ -130,7 +134,7 @@ private MethodTree findMethodTree(ClassTree clazz, String methodName, String met */ private Predicate hasAvoidedAnnotation() { return methodTree -> - methodTree.annotations().stream().anyMatch(avoidedAnnotation()); + methodTree.annotations().stream().anyMatch(avoidedAnnotation()); } private Predicate avoidedAnnotation() { @@ -139,12 +143,14 @@ private Predicate avoidedAnnotation() { @Override public Collection intercept( - Collection mutations, Mutater m) { + Collection mutations, Mutater m) { if (this.skipClass) { return Collections.emptyList(); } - return FCollection.filter(mutations, Prelude.not(this.annotatedMethodMatcher)); + return mutations.stream() + .filter(this.annotatedMethodMatcher.negate()) + .collect(Collectors.toList()); } @Override @@ -162,34 +168,4 @@ boolean shouldAvoid(String desc) { return false; } - /** - * Represents a method signature with its name and descriptor. - * Used to uniquely identify methods, especially overloaded ones. - */ - private static class MethodSignature { - private final String name; - private final String desc; - - MethodSignature(String name, String desc) { - this.name = name; - this.desc = desc; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - MethodSignature that = (MethodSignature) obj; - return name.equals(that.name) && desc.equals(that.desc); - } - - @Override - public int hashCode() { - return name.hashCode() * 31 + desc.hashCode(); - } - } } diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/annotations/ExcludedAnnotationInterceptorTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/annotations/ExcludedAnnotationInterceptorTest.java index 0879274ef..4dc48042d 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/annotations/ExcludedAnnotationInterceptorTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/annotations/ExcludedAnnotationInterceptorTest.java @@ -1,38 +1,24 @@ package org.pitest.mutationtest.build.intercept.annotations; -import org.junit.Before; import org.junit.Test; -import org.pitest.bytecode.analysis.ClassTree; -import org.pitest.classinfo.ClassName; -import org.pitest.classpath.ClassloaderByteArraySource; import org.pitest.mutationtest.build.InterceptorType; -import org.pitest.mutationtest.engine.Mutater; -import org.pitest.mutationtest.engine.MutationDetails; -import org.pitest.mutationtest.engine.gregor.GregorMutater; -import org.pitest.mutationtest.engine.gregor.MethodMutatorFactory; -import org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator; +import org.pitest.mutationtest.engine.gregor.mutators.NullMutateEverything; +import org.pitest.verifier.interceptors.InterceptorVerifier; +import org.pitest.verifier.interceptors.VerifierStart; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; -import static org.pitest.mutationtest.engine.MutationDetailsMother.aMutationDetail; public class ExcludedAnnotationInterceptorTest { ExcludedAnnotationInterceptor testee = new ExcludedAnnotationInterceptor(Arrays.asList("TestGeneratedAnnotation", "AnotherTestAnnotation")); - Mutater mutator; - @Before - public void setUp() { - final ClassloaderByteArraySource source = ClassloaderByteArraySource.fromContext(); - final Collection mutators = Collections.singleton((MethodMutatorFactory)VoidMethodCallMutator.VOID_METHOD_CALLS); - this.mutator = new GregorMutater(source, m -> true, mutators); - } + InterceptorVerifier v = VerifierStart.forInterceptor(testee) + .usingMutator(new NullMutateEverything()); @Test @@ -42,108 +28,147 @@ public void shouldDeclareSelfAsFilter() { @Test public void shouldNotFilterMutationsWhenNoAnnotations() { - final Collection input = someMutations(); - final Collection actual = runWithTestee(input, UnAnnotated.class); - assertThat(actual).containsExactlyElementsOf(input); + v.forClass(UnAnnotated.class) + .forAnyCode() + .noMutantsAreFiltered() + .verify(); } @Test public void shouldFilterAllMutationsForClassesWithGeneratedAnnotation() { - final Collection actual = runWithTestee(someMutations(), AnnotatedWithGenerated.class); - assertThat(actual).isEmpty(); + v.forClass(AnnotatedWithGenerated.class) + .forAnyCode() + .allMutantsAreFiltered() + .verify(); } @Test public void shouldFilterAllMutationsForClassesWithDoNoMutateAnnotation() { - final Collection actual = runWithTestee(someMutations(), AnnotatedWithDoNotMutate.class); - assertThat(actual).isEmpty(); + v.forClass(AnnotatedWithDoNotMutate.class) + .forAnyCode() + .allMutantsAreFiltered() + .verify(); } @Test public void shouldFilterMethodsWithGeneratedAnnotation() { - final List mutations = this.mutator.findMutations(ClassName.fromClass(MethodAnnotatedWithGenerated.class)); - final Collection actual = runWithTestee(mutations, MethodAnnotatedWithGenerated.class); - assertThat(actual).hasSize(1); - assertThat(actual.iterator().next().getId().getLocation().getMethodName()).isEqualTo("bar"); + v.forClass(MethodAnnotatedWithGenerated.class) + .forMethod("foo") + .forAnyCode() + .allMutantsAreFiltered() + .verify(); + + v.forClass(MethodAnnotatedWithGenerated.class) + .forMethod("bar") + .forAnyCode() + .noMutantsAreFiltered() + .verify(); + } @Test public void shouldNotFilterMutationsInUnannotatedMethod() { - final Collection mutations = mutator.findMutations(ClassName.fromClass(UnannotatedMethodClass.class)); - final Collection actual = runWithTestee(mutations, UnannotatedMethodClass.class); - assertThat(actual).containsExactlyElementsOf(mutations); + v.forClass(UnannotatedMethodClass.class) + .forAnyCode() + .noMutantsAreFiltered() + .verify(); } @Test public void shouldFilterMutationsInAnnotatedMethod() { - final Collection mutations = mutator.findMutations(ClassName.fromClass(AnnotatedMethodClass.class)); - final Collection actual = runWithTestee(mutations, AnnotatedMethodClass.class); - assertThat(actual).isEmpty(); + v.forClass(UnannotatedMethodClass.class) + .forMethod("annotatedMethod") + .forAnyCode() + .allMutantsAreFiltered() + .verify(); } @Test public void shouldNotFilterMutationsInLambdaWithinUnannotatedMethod() { - final Collection mutations = mutator.findMutations(ClassName.fromClass(LambdaInUnannotatedMethodClass.class)); - final Collection actual = runWithTestee(mutations, LambdaInUnannotatedMethodClass.class); - assertThat(actual).containsExactlyElementsOf(mutations); + v.forClass(LambdaInUnannotatedMethodClass.class) + .forAnyCode() + .noMutantsAreFiltered() + .verify(); } @Test public void shouldFilterMutationsInLambdaWithinAnnotatedMethod() { - final Collection mutations = mutator.findMutations(ClassName.fromClass(LambdaInAnnotatedMethodClass.class)); - final Collection actual = runWithTestee(mutations, LambdaInAnnotatedMethodClass.class); - assertThat(actual).isEmpty(); + v.forClass(LambdaInAnnotatedMethodClass.class) + .forMutantsMatching( m -> !m.getMethod().equals("")) + .allMutantsAreFiltered() + .verify(); } @Test public void shouldHandleOverloadedMethodsWithLambdas() { - final List mutations = this.mutator.findMutations(ClassName.fromClass(OverloadedMethods.class)); - final Collection actual = runWithTestee(mutations, OverloadedMethods.class); + v.forClass(OverloadedMethods.class) + .forMethod("foo", "(Ljava/lang/String;)V") + .forAnyCode() + .allMutantsAreFiltered() + .verify(); + + v.forClass(OverloadedMethods.class) + .forMethod("lambda$foo$1", "()V") + .forAnyCode() + .allMutantsAreFiltered() + .verify(); + + v.forClass(OverloadedMethods.class) + .forMethod("lambda$foo$0", "()V") + .forAnyCode() + .noMutantsAreFiltered() + .verify(); + + v.forClass(OverloadedMethods.class) + .forMethod("bar") + .forAnyCode() + .noMutantsAreFiltered() + .verify(); - // Expect mutations from unannotated methods and their lambdas - assertThat(actual).hasSize(3); // bar, foo(int x), and its lambda - for (MutationDetails mutationDetails : actual) { - assertThat(mutationDetails.getId().getLocation().getMethodName()) - .isIn("bar", "foo", "lambda$foo$0"); - } } @Test public void shouldNotFilterMutationsInNestedLambdaWithinUnannotatedOverloadedMethod() { - final Collection mutations = mutator.findMutations(ClassName.fromClass(NestedLambdaInOverloadedMethods.class)); - final Collection actual = runWithTestee(mutations, NestedLambdaInOverloadedMethods.class); + v.forClass(NestedLambdaInOverloadedMethods.class) + .forMethod("baz", "(Ljava/lang/String;)V") + .forAnyCode() + .allMutantsAreFiltered() + .verify(); + + v.forClass(NestedLambdaInOverloadedMethods.class) + .forMethod("lambda$baz$3") + .forAnyCode() + .allMutantsAreFiltered() + .verify(); + + v.forClass(NestedLambdaInOverloadedMethods.class) + .forMethod("lambda$baz$2") + .forAnyCode() + .allMutantsAreFiltered() + .verify(); + + v.forClass(NestedLambdaInOverloadedMethods.class) + .forMethod("lambda$baz$0") + .forAnyCode() + .noMutantsAreFiltered() + .verify(); + + v.forClass(NestedLambdaInOverloadedMethods.class) + .forMethod("lambda$baz$1") + .forAnyCode() + .noMutantsAreFiltered() + .verify(); - // Should include mutations from the unannotated method and its nested lambdas - assertThat(actual).anyMatch(mutation -> mutation.getId().getLocation().getMethodName().equals("baz")); - assertThat(actual).anyMatch(mutation -> { - String methodName = mutation.getId().getLocation().getMethodName(); - return methodName.startsWith("lambda$baz$") || methodName.startsWith("lambda$null$"); - }); } @Test public void shouldFilterMutationsInNestedLambdaWithinAnnotatedOverloadedMethod() { - final Collection mutations = mutator.findMutations(ClassName.fromClass(NestedLambdaInOverloadedMethods.class)); - final Collection actual = runWithTestee(mutations, NestedLambdaInOverloadedMethods.class); - - // Should not include mutations from the annotated method and its nested lambdas - assertThat(actual).noneMatch(mutation -> mutation.getId().getLocation().getMethodDesc().equals("(Ljava/lang/String;)V")); + v.forClass(NestedLambdaInOverloadedMethods.class) + .forMutantsMatching(mutation -> mutation.getId().getLocation().getMethodDesc().equals("(Ljava/lang/String;)V")) + .allMutantsAreFiltered() + .verify(); } - private Collection runWithTestee( - Collection input, Class clazz) { - this.testee.begin(treeFor(clazz)); - return this.testee.intercept(input, this.mutator); - } - - private Collection someMutations() { - return aMutationDetail().build(2); - } - - ClassTree treeFor(Class clazz) { - final ClassloaderByteArraySource source = ClassloaderByteArraySource.fromContext(); - return ClassTree.fromBytes(source.getBytes(clazz.getName()).get()); - } } @@ -153,12 +178,16 @@ class UnAnnotated { @TestGeneratedAnnotation class AnnotatedWithGenerated { - + public void foo() { + System.out.println("don't mutate me"); + } } @AnotherTestAnnotation class AnnotatedWithDoNotMutate { - + public void foo() { + System.out.println("don't mutate me"); + } } class MethodAnnotatedWithGenerated { diff --git a/pitest-entry/src/test/java/org/pitest/verifier/interceptors/Verifier.java b/pitest-entry/src/test/java/org/pitest/verifier/interceptors/Verifier.java index 0c53951cc..9d9a5e8b3 100644 --- a/pitest-entry/src/test/java/org/pitest/verifier/interceptors/Verifier.java +++ b/pitest-entry/src/test/java/org/pitest/verifier/interceptors/Verifier.java @@ -37,6 +37,10 @@ public Verifier forMethod(String name) { return new Verifier(sample, interceptor, mutator, match.and(m -> m.getMethod().equals(name))); } + public Verifier forMethod(String name, String desc) { + return new Verifier(sample, interceptor, mutator, match.and(m -> m.getMethod().equals(name)).and(m -> m.getId().getLocation().getMethodDesc().equals(desc))); + } + public MutantVerifier forAnyCode() { return forMutantsMatching(m -> true); } diff --git a/pom.xml b/pom.xml index 25ac02add..4ca4a2ab0 100644 --- a/pom.xml +++ b/pom.xml @@ -172,7 +172,7 @@ nl.jqno.equalsverifier equalsverifier - 3.15.2 + 3.17.3 test