From 34200ce171aabd73a4d8c71fb1c87b3c3eab7a66 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 30 Jun 2020 16:00:55 +0300 Subject: [PATCH] Allow the use of QuarkusTestResourceLifecycleManager with @TestProfile --- .../it/main/GreetingProfileTestCase.java | 45 +++++++++ .../test/common/TestResourceManager.java | 91 +++++++++++-------- .../test/junit/QuarkusTestExtension.java | 41 ++++++++- .../test/junit/QuarkusTestProfile.java | 29 ++++++ 4 files changed, 167 insertions(+), 39 deletions(-) diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/GreetingProfileTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/GreetingProfileTestCase.java index ed4115bd2740d..af7b379fbfb42 100644 --- a/integration-tests/main/src/test/java/io/quarkus/it/main/GreetingProfileTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/GreetingProfileTestCase.java @@ -3,12 +3,15 @@ import static org.hamcrest.Matchers.is; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.QuarkusTestProfile; import io.quarkus.test.junit.TestProfile; @@ -35,6 +38,12 @@ public void testPortTakesEffect() { Assertions.assertEquals(7777, RestAssured.port); } + @Test + public void testTestResourceState() { + // 155 means that the TestResource was started but hasn't yet stopped + Assertions.assertEquals(155, DummyTestResource.state.get()); + } + public static class MyProfile implements QuarkusTestProfile { @Override @@ -46,5 +55,41 @@ public Map getConfigOverrides() { public Set> getEnabledAlternatives() { return Collections.singleton(BonjourService.class); } + + @Override + public List testResources() { + return Collections + .singletonList(new TestResourceEntry(DummyTestResource.class, Collections.singletonMap("num", "100"))); + } + } + + /** + * This only used to ensure that the TestResource has been handled correctly by the QuarkusTestExtension + */ + public static class DummyTestResource implements QuarkusTestResourceLifecycleManager { + + public static final AtomicInteger state = new AtomicInteger(0); + public static final int START_DELTA = 55; + + private Integer numArg; + + @Override + public void init(Map initArgs) { + numArg = Integer.parseInt(initArgs.get("num")); + state.set(numArg); + } + + @Override + public Map start() { + state.addAndGet(START_DELTA); + return Collections.emptyMap(); + } + + @Override + public void stop() { + if (state.get() != (numArg + START_DELTA)) { + throw new IllegalStateException("TestResource state was not properly handled"); + } + } } } diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/TestResourceManager.java b/test-framework/common/src/main/java/io/quarkus/test/common/TestResourceManager.java index 8051002d82e80..1145cecc39e9d 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/TestResourceManager.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/TestResourceManager.java @@ -27,7 +27,11 @@ public class TestResourceManager implements Closeable { private boolean started = false; public TestResourceManager(Class testClass) { - testResourceEntries = getTestResources(testClass); + this(testClass, Collections.emptyList()); + } + + public TestResourceManager(Class testClass, List additionalTestResources) { + testResourceEntries = getTestResources(testClass, additionalTestResources); } public void init() { @@ -101,19 +105,18 @@ public void close() { } } - @SuppressWarnings("unchecked") - private List getTestResources(Class testClass) { + private List getTestResources(Class testClass, List additionalTestResources) { IndexView index = TestClassIndexer.readIndex(testClass); List testResourceEntries = new ArrayList<>(); // we need to keep track of duplicate entries to make sure we don't start the same resource // multiple times even if there are multiple same @QuarkusTestResource annotations - Set alreadyAddedEntries = new HashSet<>(); + Set uniqueEntries = new HashSet<>(); for (AnnotationInstance annotation : findQuarkusTestResourceInstances(index)) { try { - Class testResourceClass = (Class) Class - .forName(annotation.value().asString(), true, Thread.currentThread().getContextClassLoader()); + Class testResourceClass = loadTestResourceClassFromTCCL( + annotation.value().asString()); AnnotationValue argsAnnotationValue = annotation.value("initArgs"); Map args; @@ -127,16 +130,22 @@ private List getTestResources(Class testClass) { } } - TestResourceClassEntry testResourceClassEntry = new TestResourceClassEntry(testResourceClass, args); - if (alreadyAddedEntries.contains(testResourceClassEntry)) { - continue; - } - alreadyAddedEntries.add(testResourceClassEntry); + uniqueEntries.add(new TestResourceClassEntry(testResourceClass, args)); + } catch (IllegalArgumentException | SecurityException e) { + throw new RuntimeException("Unable to instantiate the test resource " + annotation.value().asString()); + } + } + + uniqueEntries.addAll(additionalTestResources); + for (TestResourceClassEntry entry : uniqueEntries) { + Class testResourceClass = entry.clazz; + Map args = entry.args; + try { testResourceEntries.add(new TestResourceEntry(testResourceClass.getConstructor().newInstance(), args)); - } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { - throw new RuntimeException("Unable to instantiate the test resource " + annotation.value().asString()); + throw new RuntimeException("Unable to instantiate the test resource " + testResourceClass.getName()); } } @@ -158,6 +167,16 @@ public int compare(TestResourceEntry o1, TestResourceEntry o2) { return testResourceEntries; } + @SuppressWarnings("unchecked") + private Class loadTestResourceClassFromTCCL(String className) { + try { + return (Class) Class + .forName(className, true, Thread.currentThread().getContextClassLoader()); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + private Collection findQuarkusTestResourceInstances(IndexView index) { Set testResourceAnnotations = new HashSet<>(index .getAnnotations(DotName.createSimple(QuarkusTestResource.class.getName()))); @@ -168,29 +187,7 @@ private Collection findQuarkusTestResourceInstances(IndexVie return testResourceAnnotations; } - private static class TestResourceEntry { - private final QuarkusTestResourceLifecycleManager testResource; - private final Map args; - - public TestResourceEntry(QuarkusTestResourceLifecycleManager testResource) { - this(testResource, Collections.emptyMap()); - } - - public TestResourceEntry(QuarkusTestResourceLifecycleManager testResource, Map args) { - this.testResource = testResource; - this.args = args; - } - - public QuarkusTestResourceLifecycleManager getTestResource() { - return testResource; - } - - public Map getArgs() { - return args; - } - } - - private static class TestResourceClassEntry { + public static class TestResourceClassEntry { private Class clazz; private Map args; @@ -216,4 +213,26 @@ public int hashCode() { } } + private static class TestResourceEntry { + private final QuarkusTestResourceLifecycleManager testResource; + private final Map args; + + public TestResourceEntry(QuarkusTestResourceLifecycleManager testResource) { + this(testResource, Collections.emptyMap()); + } + + public TestResourceEntry(QuarkusTestResourceLifecycleManager testResource, Map args) { + this.testResource = testResource; + this.args = args; + } + + public QuarkusTestResourceLifecycleManager getTestResource() { + return testResource; + } + + public Map getArgs() { + return args; + } + } + } 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 3f1d334eb1099..6ea98dcdf8653 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 @@ -12,6 +12,7 @@ import java.nio.file.Paths; import java.util.AbstractMap; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -132,8 +133,9 @@ private ExtensionState doJavaStart(ExtensionContext context, Class additional = new HashMap<>(profileInstance.getConfigOverrides()); if (!profileInstance.getEnabledAlternatives().isEmpty()) { additional.put("quarkus.arc.selected-alternatives", profileInstance.getEnabledAlternatives().stream() @@ -188,8 +190,9 @@ private ExtensionState doJavaStart(ExtensionContext context, Class getAdditionalTestResources( + QuarkusTestProfile profileInstance, ClassLoader classLoader) { + if ((profileInstance == null) || profileInstance.testResources().isEmpty()) { + return Collections.emptyList(); + } + + try { + Constructor testResourceClassEntryConstructor = Class + .forName(TestResourceManager.TestResourceClassEntry.class.getName(), true, classLoader) + .getConstructor(Class.class, Map.class); + + List testResources = profileInstance.testResources(); + List result = new ArrayList<>(testResources.size()); + for (QuarkusTestProfile.TestResourceEntry testResource : testResources) { + Object instance = testResourceClassEntryConstructor.newInstance( + Class.forName(testResource.getClazz().getName(), true, classLoader), testResource.getArgs()); + result.add(instance); + } + + return result; + } catch (Exception e) { + throw new IllegalStateException("Unable to handle profile " + profileInstance.getClass()); + } + } + // keep it super simple for now, but we might need multiple strategies in the future private void populateDeepCloneField(StartupAction startupAction) { deepClone = new XStreamDeepClone(startupAction.getClassLoader()); @@ -538,6 +572,7 @@ public void interceptTestTemplateMethod(Invocation invocation, ReflectiveI invocation.skip(); } + @SuppressWarnings("unchecked") @Override public T interceptTestFactoryMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestProfile.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestProfile.java index f370cc0f929f6..ff74ec63f7290 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestProfile.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestProfile.java @@ -1,9 +1,12 @@ package io.quarkus.test.junit; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Set; +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; + /** * Defines a 'test profile'. Tests run under a test profile * will have different configuration options to other tests. @@ -39,4 +42,30 @@ default Set> getEnabledAlternatives() { default String getConfigProfile() { return null; } + + /** + * {@link QuarkusTestResourceLifecycleManager} classes (along with their init params) to be used from this + * specific test profile + */ + default List testResources() { + return Collections.emptyList(); + } + + final class TestResourceEntry { + private Class clazz; + private Map args; + + public TestResourceEntry(Class clazz, Map args) { + this.clazz = clazz; + this.args = args; + } + + public Class getClazz() { + return clazz; + } + + public Map getArgs() { + return args; + } + } }