From ec45cc2063d92b25e1c6a5b2e840b3027301580a Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 13 Jul 2020 13:51:12 +0300 Subject: [PATCH] Make sure that test callbacks always work on the proper test method Fixes: #10623 --- .../it/main/QuarkusTestCallbacksTestCase.java | 30 +++++++++++ ...leAnnotationCheckerBeforeEachCallback.java | 30 +++++++++++ ...nit.callback.QuarkusTestBeforeEachCallback | 1 + .../test/junit/QuarkusTestExtension.java | 50 ++++++++++++++++--- 4 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 integration-tests/main/src/test/java/io/quarkus/it/main/QuarkusTestCallbacksTestCase.java create mode 100644 integration-tests/main/src/test/java/io/quarkus/it/main/SimpleAnnotationCheckerBeforeEachCallback.java create mode 100644 integration-tests/main/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/QuarkusTestCallbacksTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/QuarkusTestCallbacksTestCase.java new file mode 100644 index 00000000000000..5f521553042ceb --- /dev/null +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/QuarkusTestCallbacksTestCase.java @@ -0,0 +1,30 @@ +package io.quarkus.it.main; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +/** + * The purpose of this test is simply to ensure that {@link SimpleAnnotationCheckerBeforeEachCallback} + * can read {@code @TestAnnotation} without issue + */ +@QuarkusTest +public class QuarkusTestCallbacksTestCase { + + @Test + @TestAnnotation + public void testTestMethodHasAnnotation() { + + } + + @Target({ METHOD }) + @Retention(RUNTIME) + public @interface TestAnnotation { + } +} diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/SimpleAnnotationCheckerBeforeEachCallback.java b/integration-tests/main/src/test/java/io/quarkus/it/main/SimpleAnnotationCheckerBeforeEachCallback.java new file mode 100644 index 00000000000000..85927763f10852 --- /dev/null +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/SimpleAnnotationCheckerBeforeEachCallback.java @@ -0,0 +1,30 @@ +package io.quarkus.it.main; + +import java.lang.reflect.Method; + +import io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback; +import io.quarkus.test.junit.callback.QuarkusTestMethodContext; + +public class SimpleAnnotationCheckerBeforeEachCallback implements QuarkusTestBeforeEachCallback { + + @Override + public void beforeEach(QuarkusTestMethodContext context) { + // make sure that this comes into play only for the test we care about + + Method testMethod = context.getTestMethod(); + if (!testMethod.getDeclaringClass().getName().endsWith("QuarkusTestCallbacksTestCase")) { + return; + } + + if (!testMethod.getName().equals("testTestMethodHasAnnotation")) { + return; + } + + QuarkusTestCallbacksTestCase.TestAnnotation annotation = testMethod + .getAnnotation(QuarkusTestCallbacksTestCase.TestAnnotation.class); + if (annotation == null) { + throw new IllegalStateException( + "Expected to find annotation @TestAnnotation on method test method testTestMethodHasAnnotation"); + } + } +} diff --git a/integration-tests/main/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback b/integration-tests/main/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback new file mode 100644 index 00000000000000..18e484433990e0 --- /dev/null +++ b/integration-tests/main/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback @@ -0,0 +1 @@ +io.quarkus.it.main.SimpleAnnotationCheckerBeforeEachCallback diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java index 6ea98dcdf86530..0fbff8b836e335 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java @@ -96,9 +96,9 @@ public class QuarkusTestExtension private static Path testClassLocation; private static Throwable firstException; //if this is set then it will be thrown from the very first test that is run, the rest are aborted - private static List beforeAllCallbacks = new ArrayList<>(); - private static List beforeEachCallbacks = new ArrayList<>(); - private static List afterEachCallbacks = new ArrayList<>(); + private static List beforeAllCallbacks; + private static List beforeEachCallbacks; + private static List afterEachCallbacks; private static Class quarkusTestMethodContextClass; private static Class quarkusTestProfile; @@ -293,6 +293,13 @@ private void populateDeepCloneField(StartupAction startupAction) { } private void populateCallbacks(ClassLoader classLoader) throws ClassNotFoundException { + // make sure that we start over everytime we populate the callbacks + // otherwise previous runs of QuarkusTest (with different TestProfile values can leak into the new run) + quarkusTestMethodContextClass = null; + beforeAllCallbacks = new ArrayList<>(); + beforeEachCallbacks = new ArrayList<>(); + afterEachCallbacks = new ArrayList<>(); + ServiceLoader quarkusTestBeforeAllLoader = ServiceLoader .load(Class.forName(QuarkusTestBeforeAllCallback.class.getName(), false, classLoader), classLoader); for (Object quarkusTestBeforeAllCallback : quarkusTestBeforeAllLoader) { @@ -363,14 +370,45 @@ public void afterEach(ExtensionContext context) throws Exception { } } + // We need the usual ClassLoader hacks in order to present the callbacks with the proper test object and context private Map.Entry, ?> createQuarkusTestMethodContextTuple(ExtensionContext context) throws Exception { + ClassLoader classLoader = runningQuarkusApplication.getClassLoader(); if (quarkusTestMethodContextClass == null) { - quarkusTestMethodContextClass = Class.forName(QuarkusTestMethodContext.class.getName(), true, - runningQuarkusApplication.getClassLoader()); + quarkusTestMethodContextClass = Class.forName(QuarkusTestMethodContext.class.getName(), true, classLoader); + } + + Method originalTestMethod = context.getRequiredTestMethod(); + Class[] originalParameterTypes = originalTestMethod.getParameterTypes(); + Method actualTestMethod = null; + + // go up the class hierarchy to fetch the proper test method + Class c = actualTestClass; + List> parameterTypesFromTccl = new ArrayList<>(originalParameterTypes.length); + for (Class type : originalParameterTypes) { + if (type.isPrimitive()) { + parameterTypesFromTccl.add(type); + } else { + parameterTypesFromTccl + .add(Class.forName(type.getName(), true, classLoader)); + } + } + Class[] parameterTypes = parameterTypesFromTccl.toArray(new Class[0]); + while (c != Object.class) { + try { + actualTestMethod = c.getDeclaredMethod(originalTestMethod.getName(), parameterTypes); + break; + } catch (NoSuchMethodException ignored) { + + } + c = c.getSuperclass(); } + if (actualTestMethod == null) { + throw new RuntimeException("Could not find method " + originalTestMethod + " on test class"); + } + Constructor constructor = quarkusTestMethodContextClass.getConstructor(Object.class, Method.class); return new AbstractMap.SimpleEntry<>(quarkusTestMethodContextClass, - constructor.newInstance(actualTestInstance, context.getRequiredTestMethod())); + constructor.newInstance(actualTestInstance, actualTestMethod)); } private boolean isNativeTest(ExtensionContext context) {