Skip to content

Commit

Permalink
Add more test cases + adjust logic to also catch overriden methods
Browse files Browse the repository at this point in the history
Signed-off-by: see-quick <maros.orsak159@gmail.com>
  • Loading branch information
see-quick committed Nov 8, 2024
1 parent 726c2cf commit 66a4bb1
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.pitest.bytecode.analysis.AnalysisFunctions;
import org.pitest.bytecode.analysis.ClassTree;
import org.pitest.bytecode.analysis.MethodTree;
import org.pitest.classpath.ClassloaderByteArraySource;
import org.pitest.functional.FCollection;
import org.pitest.functional.prelude.Prelude;
import org.pitest.mutationtest.build.InterceptorType;
Expand All @@ -14,6 +15,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
Expand All @@ -40,9 +42,9 @@ public void begin(ClassTree clazz) {
this.skipClass = clazz.annotations().stream()
.anyMatch(avoidedAnnotation());
if (!this.skipClass) {
// 1. Collect methods with avoided annotations
// 1. Collect methods with avoided annotations or that override such methods
final List<MethodTree> avoidedMethods = clazz.methods().stream()
.filter(hasAvoidedAnnotation())
.filter(hasAvoidedAnnotationOrOverridesMethodWithAvoidedAnnotation(clazz))
.collect(Collectors.toList());

final Set<String> avoidedMethodNames = avoidedMethods.stream()
Expand Down Expand Up @@ -87,9 +89,70 @@ private String extractEnclosingMethodName(String lambdaName) {
return lambdaName;
}

private Predicate<MethodTree> hasAvoidedAnnotation() {
/**
* Creates a predicate that checks if a method has an avoided annotation or overrides a method
* in its superclass hierarchy that has an avoided annotation.
*
* @param clazz The class tree of the current class.
* @return A predicate that returns true if the method should be avoided.
*/
private Predicate<MethodTree> hasAvoidedAnnotationOrOverridesMethodWithAvoidedAnnotation(ClassTree clazz) {
return methodTree ->
methodTree.annotations().stream().anyMatch(avoidedAnnotation());
methodTree.annotations().stream().anyMatch(avoidedAnnotation())
|| isOverridingMethodWithAvoidedAnnotation(methodTree, clazz);
}

/**
* Checks if the given method overrides a method in its superclass hierarchy that has an avoided annotation.
*
* @param method The method to check.
* @param clazz The class tree of the current class.
* @return True if the method overrides an annotated method; false otherwise.
*/
private boolean isOverridingMethodWithAvoidedAnnotation(MethodTree method, ClassTree clazz) {
String methodName = method.rawNode().name;
String methodDesc = method.rawNode().desc;
return isMethodInSuperClassWithAvoidedAnnotation(methodName, methodDesc, clazz);
}

/**
* Recursively checks if a method with the given name and descriptor exists in the superclass hierarchy
* and has an avoided annotation.
*
* @param methodName The name of the method to search for.
* @param methodDesc The descriptor of the method to search for.
* @param clazz The class tree of the current class or superclass.
* @return True if an annotated method is found in the superclass hierarchy; false otherwise.
*/
private boolean isMethodInSuperClassWithAvoidedAnnotation(String methodName, String methodDesc, ClassTree clazz) {
String superClassName = clazz.rawNode().superName;
if (superClassName == null || superClassName.equals("java/lang/Object")) {
return false;
}

ClassloaderByteArraySource source = ClassloaderByteArraySource.fromContext();
Optional<byte[]> superClassBytes = source.getBytes(superClassName.replace('/', '.'));
if (!superClassBytes.isPresent()) {
return false;
}

ClassTree superClassTree = ClassTree.fromBytes(superClassBytes.get());

Optional<MethodTree> superMethod = superClassTree.methods().stream()
.filter(m -> m.rawNode().name.equals(methodName) && m.rawNode().desc.equals(methodDesc))
.findFirst();

if (superMethod.isPresent()) {
if (superMethod.get().annotations().stream().anyMatch(avoidedAnnotation())) {
return true;
} else {
// continue recursion to check superclass chain
return isMethodInSuperClassWithAvoidedAnnotation(methodName, methodDesc, superClassTree);
}
} else {
// method not found in this superclass, continue searching up the hierarchy
return false;
}
}

private Predicate<AnnotationNode> avoidedAnnotation() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,62 @@ public void shouldFilterMethodsWithGeneratedAnnotation() {
assertThat(actual.iterator().next().getId().getLocation().getMethodName()).isEqualTo("bar");
}

@Test
public void shouldNotFilterMutationsInUnannotatedMethod() {
final Collection<MutationDetails> mutations = mutator.findMutations(ClassName.fromClass(UnannotatedMethodClass.class));
final Collection<MutationDetails> actual = runWithTestee(mutations, UnannotatedMethodClass.class);
assertThat(actual).containsExactlyElementsOf(mutations);
}

@Test
public void shouldFilterMutationsInAnnotatedMethod() {
final Collection<MutationDetails> mutations = mutator.findMutations(ClassName.fromClass(AnnotatedMethodClass.class));
final Collection<MutationDetails> actual = runWithTestee(mutations, AnnotatedMethodClass.class);
assertThat(actual).isEmpty();
}

@Test
public void shouldNotFilterMutationsInLambdaWithinUnannotatedMethod() {
final Collection<MutationDetails> mutations = mutator.findMutations(ClassName.fromClass(LambdaInUnannotatedMethodClass.class));
final Collection<MutationDetails> actual = runWithTestee(mutations, LambdaInUnannotatedMethodClass.class);
assertThat(actual).containsExactlyElementsOf(mutations);
}

@Test
public void shouldFilterMutationsInLambdaWithinAnnotatedMethod() {
final Collection<MutationDetails> mutations = mutator.findMutations(ClassName.fromClass(LambdaInAnnotatedMethodClass.class));
final Collection<MutationDetails> actual = runWithTestee(mutations, LambdaInAnnotatedMethodClass.class);
assertThat(actual).isEmpty();
}

@Test
public void shouldNotFilterMutationsInLambdaWithinUnannotatedOverriddenMethod() {
final Collection<MutationDetails> mutations = mutator.findMutations(ClassName.fromClass(SubclassWithLambdaInOverriddenUnannotatedMethod.class));
final Collection<MutationDetails> actual = runWithTestee(mutations, SubclassWithLambdaInOverriddenUnannotatedMethod.class);
assertThat(actual).containsExactlyElementsOf(mutations);
}

@Test
public void shouldFilterMutationsInLambdaWithinAnnotatedOverriddenMethod() {
final Collection<MutationDetails> mutations = mutator.findMutations(ClassName.fromClass(SubclassWithLambdaInOverriddenAnnotatedMethod.class));
final Collection<MutationDetails> actual = runWithTestee(mutations, SubclassWithLambdaInOverriddenAnnotatedMethod.class);
assertThat(actual).isEmpty();
}

@Test
public void shouldNotFilterMutationsInNestedLambdaWithinUnannotatedOverriddenMethod() {
final Collection<MutationDetails> mutations = mutator.findMutations(ClassName.fromClass(SubclassWithNestedLambdaInOverriddenUnannotatedMethod.class));
final Collection<MutationDetails> actual = runWithTestee(mutations, SubclassWithNestedLambdaInOverriddenUnannotatedMethod.class);
assertThat(actual).containsExactlyElementsOf(mutations);
}

@Test
public void shouldFilterMutationsInNestedLambdaWithinAnnotatedOverriddenMethod() {
final Collection<MutationDetails> mutations = mutator.findMutations(ClassName.fromClass(SubclassWithNestedLambdaInOverriddenAnnotatedMethod.class));
final Collection<MutationDetails> actual = runWithTestee(mutations, SubclassWithNestedLambdaInOverriddenAnnotatedMethod.class);
assertThat(actual).isEmpty();
}

@Test
public void shouldFilterMethodsWithGeneratedAnnotationAndLambdasInside() {
final List<MutationDetails> mutations = this.mutator.findMutations(ClassName.fromClass(ClassAnnotatedWithGeneratedWithLambdas.class));
Expand Down Expand Up @@ -138,6 +194,78 @@ public void bar() {

}

class UnannotatedMethodClass {
public void unannotatedMethod() {
System.out.println("This method is not annotated.");
}
}

class AnnotatedMethodClass {
@TestGeneratedAnnotation
public void annotatedMethod() {
System.out.println("This method is annotated.");
}
}

class LambdaInUnannotatedMethodClass {
public void methodWithLambda() {
Runnable runnable = () -> System.out.println("Lambda inside unannotated method.");
}
}

class LambdaInAnnotatedMethodClass {
@TestGeneratedAnnotation
public void methodWithLambda() {
Runnable runnable = () -> System.out.println("Lambda inside annotated method.");
}
}

class BaseUnannotatedClass {
public void overriddenMethod() {
System.out.println("Base unannotated method.");
}
}

class SubclassWithLambdaInOverriddenUnannotatedMethod extends BaseUnannotatedClass {
@Override
public void overriddenMethod() {
Runnable runnable = () -> System.out.println("Lambda inside overridden unannotated method.");
}
}

class BaseAnnotatedClass {
@TestGeneratedAnnotation
public void overriddenMethod() {
System.out.println("Base annotated method.");
}
}

class SubclassWithLambdaInOverriddenAnnotatedMethod extends BaseAnnotatedClass {
@Override
public void overriddenMethod() {
Runnable runnable = () -> System.out.println("Lambda inside overridden annotated method.");
}
}

class SubclassWithNestedLambdaInOverriddenUnannotatedMethod extends BaseUnannotatedClass {
@Override
public void overriddenMethod() {
Runnable outerLambda = () -> {
Runnable innerLambda = () -> System.out.println("Nested lambda inside overridden unannotated method.");
};
}
}


class SubclassWithNestedLambdaInOverriddenAnnotatedMethod extends BaseAnnotatedClass {
@Override
public void overriddenMethod() {
Runnable outerLambda = () -> {
Runnable innerLambda = () -> System.out.println("Nested lambda inside overridden annotated method.");
};
}
}

class OverloadedMethods {
public void foo(int x) {
System.out.println("mutate me");
Expand Down

0 comments on commit 66a4bb1

Please sign in to comment.