Skip to content

Commit

Permalink
Allow the use of QuarkusTestResourceLifecycleManager with @testprofile
Browse files Browse the repository at this point in the history
  • Loading branch information
geoand committed Jul 2, 2020
1 parent 9663f0c commit 34200ce
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -46,5 +55,41 @@ public Map<String, String> getConfigOverrides() {
public Set<Class<?>> getEnabledAlternatives() {
return Collections.singleton(BonjourService.class);
}

@Override
public List<TestResourceEntry> 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<String, String> initArgs) {
numArg = Integer.parseInt(initArgs.get("num"));
state.set(numArg);
}

@Override
public Map<String, String> 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");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<TestResourceClassEntry> additionalTestResources) {
testResourceEntries = getTestResources(testClass, additionalTestResources);
}

public void init() {
Expand Down Expand Up @@ -101,19 +105,18 @@ public void close() {
}
}

@SuppressWarnings("unchecked")
private List<TestResourceEntry> getTestResources(Class<?> testClass) {
private List<TestResourceEntry> getTestResources(Class<?> testClass, List<TestResourceClassEntry> additionalTestResources) {
IndexView index = TestClassIndexer.readIndex(testClass);

List<TestResourceEntry> 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<TestResourceClassEntry> alreadyAddedEntries = new HashSet<>();
Set<TestResourceClassEntry> uniqueEntries = new HashSet<>();
for (AnnotationInstance annotation : findQuarkusTestResourceInstances(index)) {
try {
Class<? extends QuarkusTestResourceLifecycleManager> testResourceClass = (Class<? extends QuarkusTestResourceLifecycleManager>) Class
.forName(annotation.value().asString(), true, Thread.currentThread().getContextClassLoader());
Class<? extends QuarkusTestResourceLifecycleManager> testResourceClass = loadTestResourceClassFromTCCL(
annotation.value().asString());

AnnotationValue argsAnnotationValue = annotation.value("initArgs");
Map<String, String> args;
Expand All @@ -127,16 +130,22 @@ private List<TestResourceEntry> 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<? extends QuarkusTestResourceLifecycleManager> testResourceClass = entry.clazz;
Map<String, String> 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());
}
}

Expand All @@ -158,6 +167,16 @@ public int compare(TestResourceEntry o1, TestResourceEntry o2) {
return testResourceEntries;
}

@SuppressWarnings("unchecked")
private Class<? extends QuarkusTestResourceLifecycleManager> loadTestResourceClassFromTCCL(String className) {
try {
return (Class<? extends QuarkusTestResourceLifecycleManager>) Class
.forName(className, true, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}

private Collection<AnnotationInstance> findQuarkusTestResourceInstances(IndexView index) {
Set<AnnotationInstance> testResourceAnnotations = new HashSet<>(index
.getAnnotations(DotName.createSimple(QuarkusTestResource.class.getName())));
Expand All @@ -168,29 +187,7 @@ private Collection<AnnotationInstance> findQuarkusTestResourceInstances(IndexVie
return testResourceAnnotations;
}

private static class TestResourceEntry {
private final QuarkusTestResourceLifecycleManager testResource;
private final Map<String, String> args;

public TestResourceEntry(QuarkusTestResourceLifecycleManager testResource) {
this(testResource, Collections.emptyMap());
}

public TestResourceEntry(QuarkusTestResourceLifecycleManager testResource, Map<String, String> args) {
this.testResource = testResource;
this.args = args;
}

public QuarkusTestResourceLifecycleManager getTestResource() {
return testResource;
}

public Map<String, String> getArgs() {
return args;
}
}

private static class TestResourceClassEntry {
public static class TestResourceClassEntry {
private Class<? extends QuarkusTestResourceLifecycleManager> clazz;
private Map<String, String> args;

Expand All @@ -216,4 +213,26 @@ public int hashCode() {
}
}

private static class TestResourceEntry {
private final QuarkusTestResourceLifecycleManager testResource;
private final Map<String, String> args;

public TestResourceEntry(QuarkusTestResourceLifecycleManager testResource) {
this(testResource, Collections.emptyMap());
}

public TestResourceEntry(QuarkusTestResourceLifecycleManager testResource, Map<String, String> args) {
this.testResource = testResource;
this.args = args;
}

public QuarkusTestResourceLifecycleManager getTestResource() {
return testResource;
}

public Map<String, String> getArgs() {
return args;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -132,8 +133,9 @@ private ExtensionState doJavaStart(ExtensionContext context, Class<? extends Qua
final QuarkusBootstrap.Builder runnerBuilder = QuarkusBootstrap.builder()
.setIsolateDeployment(true)
.setMode(QuarkusBootstrap.Mode.TEST);
QuarkusTestProfile profileInstance = null;
if (profile != null) {
QuarkusTestProfile profileInstance = profile.newInstance();
profileInstance = profile.newInstance();
Map<String, String> additional = new HashMap<>(profileInstance.getConfigOverrides());
if (!profileInstance.getEnabledAlternatives().isEmpty()) {
additional.put("quarkus.arc.selected-alternatives", profileInstance.getEnabledAlternatives().stream()
Expand Down Expand Up @@ -188,8 +190,9 @@ private ExtensionState doJavaStart(ExtensionContext context, Class<? extends Qua

//must be done after the TCCL has been set
testResourceManager = (Closeable) startupAction.getClassLoader().loadClass(TestResourceManager.class.getName())
.getConstructor(Class.class)
.newInstance(requiredTestClass);
.getConstructor(Class.class, List.class)
.newInstance(requiredTestClass,
getAdditionalTestResources(profileInstance, startupAction.getClassLoader()));
testResourceManager.getClass().getMethod("init").invoke(testResourceManager);
testResourceManager.getClass().getMethod("start").invoke(testResourceManager);

Expand Down Expand Up @@ -253,6 +256,37 @@ public void run() {
}
}

/**
* Since {@link TestResourceManager} is loaded from the ClassLoader passed in as an argument,
* we need to convert the user input {@link QuarkusTestProfile.TestResourceEntry} into instances of
* {@link TestResourceManager.TestResourceClassEntry}
* that are loaded from that ClassLoader
*/
private List<Object> 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<QuarkusTestProfile.TestResourceEntry> testResources = profileInstance.testResources();
List<Object> 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());
Expand Down Expand Up @@ -538,6 +572,7 @@ public void interceptTestTemplateMethod(Invocation<Void> invocation, ReflectiveI
invocation.skip();
}

@SuppressWarnings("unchecked")
@Override
public <T> T interceptTestFactoryMethod(Invocation<T> invocation,
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -39,4 +42,30 @@ default Set<Class<?>> getEnabledAlternatives() {
default String getConfigProfile() {
return null;
}

/**
* {@link QuarkusTestResourceLifecycleManager} classes (along with their init params) to be used from this
* specific test profile
*/
default List<TestResourceEntry> testResources() {
return Collections.emptyList();
}

final class TestResourceEntry {
private Class<? extends QuarkusTestResourceLifecycleManager> clazz;
private Map<String, String> args;

public TestResourceEntry(Class<? extends QuarkusTestResourceLifecycleManager> clazz, Map<String, String> args) {
this.clazz = clazz;
this.args = args;
}

public Class<? extends QuarkusTestResourceLifecycleManager> getClazz() {
return clazz;
}

public Map<String, String> getArgs() {
return args;
}
}
}

0 comments on commit 34200ce

Please sign in to comment.