From 9b9e2d8bc0a16c3ba15f69fa7cdc6eb9389ab9ab Mon Sep 17 00:00:00 2001 From: SirYwell Date: Wed, 30 Oct 2024 08:52:46 +0100 Subject: [PATCH] test: easy type access for ModelTests --- src/test/java/spoon/test/type/TypeTest.java | 11 ++++---- .../java/spoon/testing/utils/ByClass.java | 22 ++++++++++++++++ .../spoon/testing/utils/BySimpleName.java | 22 ++++++++++++++++ .../utils/ModelTestParameterResolver.java | 26 +++++++++++++++++-- 4 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 src/test/java/spoon/testing/utils/ByClass.java create mode 100644 src/test/java/spoon/testing/utils/BySimpleName.java diff --git a/src/test/java/spoon/test/type/TypeTest.java b/src/test/java/spoon/test/type/TypeTest.java index 8625becf176..4a4a146deb8 100644 --- a/src/test/java/spoon/test/type/TypeTest.java +++ b/src/test/java/spoon/test/type/TypeTest.java @@ -52,6 +52,8 @@ import spoon.test.type.testclasses.Mole; import spoon.test.type.testclasses.Pozole; import spoon.test.type.testclasses.TypeMembersOrder; +import spoon.testing.utils.ByClass; +import spoon.testing.utils.BySimpleName; import spoon.testing.utils.ModelTest; import spoon.testing.utils.ModelUtils; @@ -116,9 +118,8 @@ public void testTypeAccessOnPrimitive() { } @ModelTest("./src/test/java/spoon/test/type/testclasses") - public void testTypeAccessForTypeAccessInInstanceOf(Launcher launcher) { + public void testTypeAccessForTypeAccessInInstanceOf(@ByClass(Pozole.class) CtClass aPozole) { // contract: the right hand operator must be a CtTypeAccess. - final CtClass aPozole = launcher.getFactory().Class().get(Pozole.class); final CtMethod eat = aPozole.getMethodsByName("eat").get(0); final List> typeAccesses = eat.getElements(new TypeFilter<>(CtTypeAccess.class)); @@ -389,9 +390,8 @@ public void testShadowType() { } @ModelTest("./src/test/java/spoon/test/type/testclasses/TypeMembersOrder.java") - public void testTypeMemberOrder(Factory f) { + public void testTypeMemberOrder(Factory f, @ByClass(TypeMembersOrder.class) CtClass aTypeMembersOrder) { // contract: The TypeMembers keeps order of members same like in source file - final CtClass aTypeMembersOrder = f.Class().get(TypeMembersOrder.class); { List typeMemberNames = new ArrayList<>(); for (CtTypeMember typeMember : aTypeMembersOrder.getTypeMembers()) { @@ -424,9 +424,8 @@ public void testBinaryOpStringsType() { value = {"./src/test/resources/noclasspath/issue5208/"}, noClasspath = true ) - void testClassNotReplacedInNoClasspathMode(Factory factory) { + void testClassNotReplacedInNoClasspathMode(@BySimpleName("ClassT1") CtType type) { // contract: ClassT1 is not replaced once present when looking up the ClassT1#classT3 field from ClassT2 - CtType type = factory.Type().get("p20.ClassT1"); assertNotNull(type); assertNotEquals(SourcePosition.NOPOSITION, type.getPosition()); } diff --git a/src/test/java/spoon/testing/utils/ByClass.java b/src/test/java/spoon/testing/utils/ByClass.java new file mode 100644 index 00000000000..a57ea88a3bf --- /dev/null +++ b/src/test/java/spoon/testing/utils/ByClass.java @@ -0,0 +1,22 @@ +package spoon.testing.utils; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * If a parameter of a test method is annotated with this annotation, + * and the parameter type is {@link spoon.reflect.declaration.CtType} or a subtype, + * the parameter will be filled with the type with the fully qualified name of the class + * given by {@link #value()}. + *

+ * If no matching type exists, the test will fail with a + * {@link org.junit.jupiter.api.extension.ParameterResolutionException} + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface ByClass { + + Class value(); +} diff --git a/src/test/java/spoon/testing/utils/BySimpleName.java b/src/test/java/spoon/testing/utils/BySimpleName.java new file mode 100644 index 00000000000..883fb31ec94 --- /dev/null +++ b/src/test/java/spoon/testing/utils/BySimpleName.java @@ -0,0 +1,22 @@ +package spoon.testing.utils; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * If a parameter of a test method is annotated with this annotation, + * and the parameter type is {@link spoon.reflect.declaration.CtType} or a subtype, + * the parameter will be filled with the first type in the model with the simple name + * given by {@link #value()}. + *

+ * If no matching type exists, the test will fail with a + * {@link org.junit.jupiter.api.extension.ParameterResolutionException} + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface BySimpleName { + + String value(); +} diff --git a/src/test/java/spoon/testing/utils/ModelTestParameterResolver.java b/src/test/java/spoon/testing/utils/ModelTestParameterResolver.java index 1d4c3531095..10d657f5849 100644 --- a/src/test/java/spoon/testing/utils/ModelTestParameterResolver.java +++ b/src/test/java/spoon/testing/utils/ModelTestParameterResolver.java @@ -6,6 +6,8 @@ import org.junit.jupiter.api.extension.ParameterResolver; import spoon.Launcher; import spoon.reflect.CtModel; +import spoon.reflect.declaration.CtClass; +import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import java.lang.reflect.Executable; @@ -23,7 +25,8 @@ public boolean supportsParameter( return false; } Class type = parameterContext.getParameter().getType(); - return type == Launcher.class || type == CtModel.class || type == Factory.class; + return type == Launcher.class || type == CtModel.class || type == Factory.class + || CtType.class.isAssignableFrom(type); } @Override @@ -42,9 +45,28 @@ public Object resolveParameter( return launcher.getModel(); } else if (parameterContext.getParameter().getType() == Factory.class) { return launcher.getFactory(); + } else if (parameterContext.isAnnotated(BySimpleName.class) + && CtType.class.isAssignableFrom(parameterContext.getParameter().getType())) { + String name = parameterContext.findAnnotation(BySimpleName.class) + .map(BySimpleName::value) + .orElseThrow(); + return launcher.getModel().getAllTypes().stream() + .filter(type -> type.getSimpleName().equals(name)) + .findFirst() + .orElseThrow(() -> new ParameterResolutionException("no type with simple name " + name + " found")); + } else if (parameterContext.isAnnotated(ByClass.class) + && CtType.class.isAssignableFrom(parameterContext.getParameter().getType())) { + Class clazz = parameterContext.findAnnotation(ByClass.class) + .map(ByClass::value) + .orElseThrow(); + CtClass ctClass = launcher.getFactory().Class().get(clazz.getName()); + if (ctClass == null) { + throw new ParameterResolutionException("no type with name " + clazz.getName() + " found"); + } + return ctClass; } - throw new AssertionError("supportsParameter is not exhaustive"); + throw new ParameterResolutionException("supportsParameter is not exhaustive (" + parameterContext + ")"); } private Launcher createLauncher(Executable method) {