Skip to content

Commit

Permalink
Expand test coverage for compatibility changes related to annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
guillermocalvo committed Apr 24, 2024
1 parent 599da82 commit 46f77ae
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 18 deletions.
108 changes: 106 additions & 2 deletions japicmp/src/test/java/japicmp/compat/CompatibilityChangesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2615,18 +2615,22 @@ public void testAnnotationOnClassModified() throws Exception {
public List<CtClass> createOldClasses(ClassPool classPool) throws Exception {
CtClass anAnnotation = CtAnnotationBuilder.create().name("japicmp.MyAnnotation").addToClassPool(classPool);
CtMethodBuilder.create().name("foo").returnType(CtClass.intType).publicAccess().addToClass(anAnnotation);
return Collections.singletonList(anAnnotation);
CtClass aClass = CtClassBuilder.create().name("japicmp.Test").withAnnotation("japicmp.MyAnnotation", new CtElement("foo", 1000)).addToClassPool(classPool);
return Arrays.asList(aClass, anAnnotation);
}

@Override
public List<CtClass> createNewClasses(ClassPool classPool) {
CtClass anAnnotation = CtAnnotationBuilder.create().name("japicmp.MyAnnotation").addToClassPool(classPool);
return Arrays.asList(anAnnotation);
CtClass aClass = CtClassBuilder.create().name("japicmp.Test").withAnnotation("japicmp.MyAnnotation", new CtElement("foo", 999)).addToClassPool(classPool);
return Arrays.asList(aClass, anAnnotation);
}
});
JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.MyAnnotation");
JApiMethod jApiMethod = getJApiMethod(jApiClass.getMethods(), "foo");
assertThat(jApiMethod.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.METHOD_REMOVED)));
JApiClass jApiClassWithAnnotation = getJApiClass(jApiClasses, "japicmp.Test");
assertThat(jApiClassWithAnnotation.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_MODIFIED)));
}

@Test
Expand Down Expand Up @@ -2676,4 +2680,104 @@ public List<CtClass> createNewClasses(ClassPool classPool) throws Exception {
JApiMethod jApiMethod = getJApiMethod(jApiClass.getMethods(), "method");
assertThat(jApiMethod.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_REMOVED)));
}

@Test
public void testAnnotationOnMethodModified() throws Exception {
JarArchiveComparatorOptions options = new JarArchiveComparatorOptions();
List<JApiClass> jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() {
@Override
public List<CtClass> createOldClasses(ClassPool classPool) throws Exception {
CtClass anAnnotation = CtAnnotationBuilder.create().name("japicmp.MyAnnotation").addToClassPool(classPool);
CtMethodBuilder.create().name("foo").returnType(CtClass.intType).publicAccess().addToClass(anAnnotation);
CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool);
CtMethodBuilder.create().publicAccess().name("method").withAnnotation("japicmp.MyAnnotation", new CtElement("foo", 1000)).addToClass(aClass);
return Arrays.asList(aClass, anAnnotation);
}

@Override
public List<CtClass> createNewClasses(ClassPool classPool) throws Exception {
CtClass anAnnotation = CtAnnotationBuilder.create().name("japicmp.MyAnnotation").addToClassPool(classPool);
CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool);
CtMethodBuilder.create().publicAccess().name("method").withAnnotation("japicmp.MyAnnotation", new CtElement("foo", 999)).addToClass(aClass);
return Arrays.asList(aClass, anAnnotation);
}
});
JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.Test");
JApiMethod jApiMethod = getJApiMethod(jApiClass.getMethods(), "method");
assertThat(jApiMethod.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_MODIFIED)));
}

@Test
public void testAnnotationAddedToField() throws Exception {
JarArchiveComparatorOptions options = new JarArchiveComparatorOptions();
List<JApiClass> jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() {
@Override
public List<CtClass> createOldClasses(ClassPool classPool) throws Exception {
CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool);
CtFieldBuilder.create().name("field").addToClass(aClass);
return Collections.singletonList(aClass);
}

@Override
public List<CtClass> createNewClasses(ClassPool classPool) throws Exception {
CtClass anAnnotation = CtAnnotationBuilder.create().name("japicmp.MyAnnotation").addToClassPool(classPool);
CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool);
CtFieldBuilder.create().withAnnotation("japicmp.MyAnnotation").name("field").addToClass(aClass);
return Arrays.asList(aClass, anAnnotation);
}
});
JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.Test");
JApiField jApiField = getJApiField(jApiClass.getFields(), "field");
assertThat(jApiField.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_ADDED)));
}

@Test
public void testAnnotationRemovedFromField() throws Exception {
JarArchiveComparatorOptions options = new JarArchiveComparatorOptions();
List<JApiClass> jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() {
@Override
public List<CtClass> createOldClasses(ClassPool classPool) throws Exception {
CtClass anAnnotation = CtAnnotationBuilder.create().name("japicmp.MyAnnotation").addToClassPool(classPool);
CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool);
CtFieldBuilder.create().name("field").withAnnotation("japicmp.MyAnnotation").addToClass(aClass);
return Arrays.asList(aClass, anAnnotation);
}

@Override
public List<CtClass> createNewClasses(ClassPool classPool) throws Exception {
CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool);
CtFieldBuilder.create().name("field").addToClass(aClass);
return Collections.singletonList(aClass);
}
});
JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.Test");
JApiField jApiField = getJApiField(jApiClass.getFields(), "field");
assertThat(jApiField.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_REMOVED)));
}

@Test
public void testAnnotationOnFieldModified() throws Exception {
JarArchiveComparatorOptions options = new JarArchiveComparatorOptions();
List<JApiClass> jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() {
@Override
public List<CtClass> createOldClasses(ClassPool classPool) throws Exception {
CtClass anAnnotation = CtAnnotationBuilder.create().name("japicmp.MyAnnotation").addToClassPool(classPool);
CtMethodBuilder.create().name("foo").returnType(CtClass.intType).publicAccess().addToClass(anAnnotation);
CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool);
CtFieldBuilder.create().name("field").withAnnotation("japicmp.MyAnnotation", new CtElement("foo", 1000)).addToClass(aClass);
return Arrays.asList(aClass, anAnnotation);
}

@Override
public List<CtClass> createNewClasses(ClassPool classPool) throws Exception {
CtClass anAnnotation = CtAnnotationBuilder.create().name("japicmp.MyAnnotation").addToClassPool(classPool);
CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool);
CtFieldBuilder.create().name("field").withAnnotation("japicmp.MyAnnotation", new CtElement("foo", 999)).addToClass(aClass);
return Arrays.asList(aClass, anAnnotation);
}
});
JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.Test");
JApiField jApiField = getJApiField(jApiClass.getFields(), "field");
assertThat(jApiField.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_MODIFIED)));
}
}
13 changes: 9 additions & 4 deletions japicmp/src/test/java/japicmp/util/CtClassBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
import javassist.bytecode.annotation.Annotation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CtClassBuilder {
public static final String DEFAULT_CLASS_NAME = "japicmp.Test";
private String name = DEFAULT_CLASS_NAME;
private int modifier = Modifier.PUBLIC;
private final List<String> annotations = new ArrayList<>();
private final Map<String, CtElement[]> annotations = new HashMap<>();
private Optional<CtClass> superclass = Optional.absent();
private final List<CtClass> interfaces = new ArrayList<>();

Expand All @@ -30,8 +32,8 @@ public CtClassBuilder syntheticModifier() {
return this;
}

public CtClassBuilder withAnnotation(String annotation) {
this.annotations.add(annotation);
public CtClassBuilder withAnnotation(String annotation, CtElement... elements) {
this.annotations.put(annotation, elements);
return this;
}

Expand Down Expand Up @@ -69,11 +71,14 @@ public CtClass addToClassPool(ClassPool classPool) {
ctClass = classPool.makeClass(this.name);
}
ctClass.setModifiers(this.modifier);
for (String annotation : annotations) {
for (String annotation : annotations.keySet()) {
ClassFile classFile = ctClass.getClassFile();
ConstPool constPool = classFile.getConstPool();
AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
Annotation annot = new Annotation(annotation, constPool);
for (CtElement element : annotations.get(annotation)) {
annot.addMemberValue(element.name, element.value.apply(constPool));
}
attr.setAnnotation(annot);
ctClass.getClassFile2().addAttribute(attr);
}
Expand Down
66 changes: 66 additions & 0 deletions japicmp/src/test/java/japicmp/util/CtElement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package japicmp.util;

import java.util.function.Function;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.BooleanMemberValue;
import javassist.bytecode.annotation.ByteMemberValue;
import javassist.bytecode.annotation.CharMemberValue;
import javassist.bytecode.annotation.ClassMemberValue;
import javassist.bytecode.annotation.DoubleMemberValue;
import javassist.bytecode.annotation.FloatMemberValue;
import javassist.bytecode.annotation.IntegerMemberValue;
import javassist.bytecode.annotation.LongMemberValue;
import javassist.bytecode.annotation.MemberValue;
import javassist.bytecode.annotation.ShortMemberValue;
import javassist.bytecode.annotation.StringMemberValue;

public class CtElement {

public final String name;
public final Function<ConstPool, MemberValue> value;

private CtElement(String name, Function<ConstPool, MemberValue> value) {
this.name = name;
this.value = value;
}

public CtElement(String name, boolean value) {
this(name, cp -> new BooleanMemberValue(value, cp));
}

public CtElement(String name, byte value) {
this(name, cp -> new ByteMemberValue(value, cp));
}

public CtElement(String name, char value) {
this(name, cp -> new CharMemberValue(value, cp));
}

public CtElement(String name, short value) {
this(name, cp -> new ShortMemberValue(value, cp));
}

public CtElement(String name, int value) {
this(name, cp -> new IntegerMemberValue(cp, value));
}

public CtElement(String name, long value) {
this(name, cp -> new LongMemberValue(value, cp));
}

public CtElement(String name, float value) {
this(name, cp -> new FloatMemberValue(value, cp));
}

public CtElement(String name, double value) {
this(name, cp -> new DoubleMemberValue(value, cp));
}

public CtElement(String name, Class<?> value) {
this(name, cp -> new ClassMemberValue(value.getName(), cp));
}

public CtElement(String name, String value) {
this(name, cp -> new StringMemberValue(value, cp));
}
}
15 changes: 9 additions & 6 deletions japicmp/src/test/java/japicmp/util/CtFieldBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;

import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;

public class CtFieldBuilder {
public static final String DEFAULT_FIELD_NAME = "field";
private CtClass type = CtClass.intType;
private String name = DEFAULT_FIELD_NAME;
private int modifier = Modifier.PUBLIC;
private final List<String> annotations = new ArrayList<>();
private final Map<String, CtElement[]> annotations = new HashMap<>();
private Object constantValue = null;

public CtFieldBuilder type(CtClass ctClass) {
Expand Down Expand Up @@ -53,11 +53,14 @@ public CtField addToClass(CtClass ctClass) throws CannotCompileException {
} else {
ctClass.addField(ctField);
}
for (String annotation : annotations) {
for (String annotation : annotations.keySet()) {
ClassFile classFile = ctClass.getClassFile();
ConstPool constPool = classFile.getConstPool();
AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
Annotation annot = new Annotation(annotation, constPool);
for (CtElement element : annotations.get(annotation)) {
annot.addMemberValue(element.name, element.value.apply(constPool));
}
attr.setAnnotation(annot);
ctField.getFieldInfo().addAttribute(attr);
}
Expand All @@ -68,8 +71,8 @@ public static CtFieldBuilder create() {
return new CtFieldBuilder();
}

public CtFieldBuilder withAnnotation(String annotation) {
this.annotations.add(annotation);
public CtFieldBuilder withAnnotation(String annotation, CtElement... elements) {
this.annotations.put(annotation, elements);
return this;
}

Expand Down
15 changes: 9 additions & 6 deletions japicmp/src/test/java/japicmp/util/CtMethodBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
import javassist.bytecode.SignatureAttribute;
import javassist.bytecode.annotation.Annotation;

import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;

public class CtMethodBuilder extends CtBehaviorBuilder {
private static final String DEFAULT_METHOD_NAME = "method";
private String body = "return null;";
private String name = DEFAULT_METHOD_NAME;
private CtClass returnType;
private final List<String> annotations = new ArrayList<>();
private final Map<String, CtElement[]> annotations = new HashMap<>();

public CtMethodBuilder name(String name) {
this.name = name;
Expand Down Expand Up @@ -80,8 +80,8 @@ public CtMethodBuilder finalMethod() {
return (CtMethodBuilder) super.finalMethod();
}

public CtMethodBuilder withAnnotation(String annotation) {
this.annotations.add(annotation);
public CtMethodBuilder withAnnotation(String annotation, CtElement... elements) {
this.annotations.put(annotation, elements);
return this;
}

Expand All @@ -97,10 +97,13 @@ public CtMethod addToClass(CtClass declaringClass) throws CannotCompileException
CtMethod ctMethod = CtNewMethod.make(this.modifier, this.returnType, this.name, this.parameters, this.exceptions, this.body, declaringClass);
ctMethod.setModifiers(this.modifier);
declaringClass.addMethod(ctMethod);
for (String annotation : annotations) {
for (String annotation : annotations.keySet()) {
ConstPool constPool = declaringClass.getClassFile().getConstPool();
AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
Annotation annot = new Annotation(annotation, constPool);
for (CtElement element : annotations.get(annotation)) {
annot.addMemberValue(element.name, element.value.apply(constPool));
}
attr.setAnnotation(annot);
ctMethod.getMethodInfo().addAttribute(attr);
}
Expand Down

0 comments on commit 46f77ae

Please sign in to comment.