Skip to content

Commit

Permalink
Support passing parameters to a QuarkusTestResource
Browse files Browse the repository at this point in the history
Fixes: #9066
  • Loading branch information
geoand committed May 11, 2020
1 parent 35ed807 commit f10e752
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;

import io.quarkus.test.common.QuarkusTestResource.List;

/**
* Used to define a test resource.
*
* <b>All</b> {@code QuarkusTestResource} annotations in the test module
* are discovered (regardless of the test which contains the annotation)
* and their corresponding {@code QuarkusTestResourceLifecycleManager}
* started <b>before</b> <b>any</b> test is run.
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
Expand All @@ -23,6 +30,13 @@
*/
Class<? extends QuarkusTestResourceLifecycleManager> value();

/**
* @return The arguments to be passed to the {@code QuarkusTestResourceLifecycleManager}
*
* @see QuarkusTestResourceLifecycleManager#init(Map)
*/
ResourceArg[] initArgs() default {};

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ public interface QuarkusTestResourceLifecycleManager {
*/
void stop();

/**
* Arguments passed to the lifecycle manager before it starts
* These arguments are taken from {@code QuarkusTestResource#initArgs()}
*
* The {@code args} is never null
*
* @see QuarkusTestResource#initArgs()
*/
default void init(Map<String, String> initArgs) {

}

/**
* Allow each resource to provide custom injection of fields of the test class
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.test.common;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Uses to define arguments of for {@code QuarkusTestResource}
*
* see {@link QuarkusTestResource#initArgs()}
*/
@Target({})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResourceArg {

String name();

String value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,52 @@
import java.io.Closeable;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.Set;

import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;

public class TestResourceManager implements Closeable {

private final List<QuarkusTestResourceLifecycleManager> testResources;
private final List<TestResourceEntry> testResourceEntries;
private Map<String, String> oldSystemProps;

public TestResourceManager(Class<?> testClass) {
testResources = getTestResources(testClass);
testResourceEntries = getTestResources(testClass);
}

public void init() {
for (TestResourceEntry entry : testResourceEntries) {
try {
entry.getTestResource().init(entry.getArgs());
} catch (Exception e) {
throw new RuntimeException("Unable initialize test resource " + entry.getTestResource(), e);
}
}
}

public Map<String, String> start() {
Map<String, String> ret = new HashMap<>();
for (QuarkusTestResourceLifecycleManager testResource : testResources) {
for (TestResourceEntry entry : testResourceEntries) {
try {
Map<String, String> start = testResource.start();
Map<String, String> start = entry.getTestResource().start();
if (start != null) {
ret.putAll(start);
}
} catch (Exception e) {
throw new RuntimeException("Unable to start Quarkus test resource " + testResource, e);
throw new RuntimeException("Unable to start Quarkus test resource " + entry.getTestResource(), e);
}
}
oldSystemProps = new HashMap<>();
Expand All @@ -51,8 +64,8 @@ public Map<String, String> start() {
}

public void inject(Object testInstance) {
for (QuarkusTestResourceLifecycleManager testResource : testResources) {
testResource.inject(testInstance);
for (TestResourceEntry entry : testResourceEntries) {
entry.getTestResource().inject(testInstance);
}
}

Expand All @@ -68,11 +81,11 @@ public void close() {
}
}
oldSystemProps = null;
for (QuarkusTestResourceLifecycleManager testResource : testResources) {
for (TestResourceEntry entry : testResourceEntries) {
try {
testResource.stop();
entry.getTestResource().stop();
} catch (Exception e) {
throw new RuntimeException("Unable to stop Quarkus test resource " + testResource, e);
throw new RuntimeException("Unable to stop Quarkus test resource " + entry.getTestResource(), e);
}
}
try {
Expand All @@ -83,45 +96,118 @@ public void close() {
}

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

Set<Class<? extends QuarkusTestResourceLifecycleManager>> testResourceRunnerClasses = new LinkedHashSet<>();
List<TestResourceEntry> testResourceEntries = new ArrayList<>();

Set<AnnotationInstance> testResourceAnnotations = new HashSet<>();
testResourceAnnotations.addAll(index.getAnnotations(DotName.createSimple(QuarkusTestResource.class.getName())));
for (AnnotationInstance annotation : index
.getAnnotations(DotName.createSimple(QuarkusTestResource.List.class.getName()))) {
Collections.addAll(testResourceAnnotations, annotation.value().asNestedArray());
}
for (AnnotationInstance annotation : testResourceAnnotations) {
// 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<>();
for (AnnotationInstance annotation : findQuarkusTestResourceInstances(index)) {
try {
testResourceRunnerClasses.add((Class<? extends QuarkusTestResourceLifecycleManager>) Class
.forName(annotation.value().asString(), true, Thread.currentThread().getContextClassLoader()));
} catch (ClassNotFoundException e) {
throw new RuntimeException("Unable to find the class for the test resource " + annotation.value().asString());
}
}
Class<? extends QuarkusTestResourceLifecycleManager> testResourceClass = (Class<? extends QuarkusTestResourceLifecycleManager>) Class
.forName(annotation.value().asString(), true, Thread.currentThread().getContextClassLoader());

AnnotationValue argsAnnotationValue = annotation.value("initArgs");
Map<String, String> args;
if (argsAnnotationValue == null) {
args = Collections.emptyMap();
} else {
args = new HashMap<>();
AnnotationInstance[] resourceArgsInstances = argsAnnotationValue.asNestedArray();
for (AnnotationInstance resourceArgsInstance : resourceArgsInstances) {
args.put(resourceArgsInstance.value("name").asString(), resourceArgsInstance.value().asString());
}
}

List<QuarkusTestResourceLifecycleManager> testResourceRunners = new ArrayList<>();
TestResourceClassEntry testResourceClassEntry = new TestResourceClassEntry(testResourceClass, args);
if (alreadyAddedEntries.contains(testResourceClassEntry)) {
continue;
}
alreadyAddedEntries.add(testResourceClassEntry);

for (Class<? extends QuarkusTestResourceLifecycleManager> testResourceRunnerClass : testResourceRunnerClasses) {
try {
testResourceRunners.add(testResourceRunnerClass.getConstructor().newInstance());
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
testResourceEntries.add(new TestResourceEntry(testResourceClass.getConstructor().newInstance(), args));
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
throw new RuntimeException("Unable to instantiate the test resource " + testResourceRunnerClass);
throw new RuntimeException("Unable to instantiate the test resource " + annotation.value().asString());
}
}

for (QuarkusTestResourceLifecycleManager quarkusTestResourceLifecycleManager : ServiceLoader
.load(QuarkusTestResourceLifecycleManager.class, Thread.currentThread().getContextClassLoader())) {
testResourceRunners.add(quarkusTestResourceLifecycleManager);
testResourceEntries.add(new TestResourceEntry(quarkusTestResourceLifecycleManager));
}

testResourceEntries.sort(new Comparator<TestResourceEntry>() {

private final QuarkusTestResourceLifecycleManagerComparator lifecycleManagerComparator = new QuarkusTestResourceLifecycleManagerComparator();

@Override
public int compare(TestResourceEntry o1, TestResourceEntry o2) {
return lifecycleManagerComparator.compare(o1.getTestResource(), o2.getTestResource());
}
});

return testResourceEntries;
}

private Collection<AnnotationInstance> findQuarkusTestResourceInstances(IndexView index) {
Set<AnnotationInstance> testResourceAnnotations = new HashSet<>(index
.getAnnotations(DotName.createSimple(QuarkusTestResource.class.getName())));
for (AnnotationInstance annotation : index
.getAnnotations(DotName.createSimple(QuarkusTestResource.List.class.getName()))) {
Collections.addAll(testResourceAnnotations, annotation.value().asNestedArray());
}
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;
}

Collections.sort(testResourceRunners, new QuarkusTestResourceLifecycleManagerComparator());
public Map<String, String> getArgs() {
return args;
}
}

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

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

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
TestResourceClassEntry that = (TestResourceClassEntry) o;
return clazz.equals(that.clazz) &&
args.equals(that.args);
}

@Override
public int hashCode() {
return Objects.hash(clazz, args);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ public void beforeEach(ExtensionContext extensionContext) throws Exception {
ExtensionContext.Store store = extensionContext.getRoot().getStore(ExtensionContext.Namespace.GLOBAL);
if (store.get(TestResourceManager.class.getName()) == null) {
TestResourceManager manager = new TestResourceManager(extensionContext.getRequiredTestClass());
manager.init();
manager.start();
store.put(TestResourceManager.class.getName(), new ExtensionContext.Store.CloseableResource() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ public void run() {
ExtensionContext.Store store = extensionContext.getRoot().getStore(ExtensionContext.Namespace.GLOBAL);
if (store.get(TestResourceManager.class.getName()) == null) {
TestResourceManager manager = new TestResourceManager(extensionContext.getRequiredTestClass());
manager.init();
manager.start();
store.put(TestResourceManager.class.getName(), new ExtensionContext.Store.CloseableResource() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ public void run() {
ExtensionContext.Store store = extensionContext.getRoot().getStore(ExtensionContext.Namespace.GLOBAL);
if (store.get(TestResourceManager.class.getName()) == null) {
TestResourceManager manager = new TestResourceManager(extensionContext.getRequiredTestClass());
manager.init();
manager.start();
store.put(TestResourceManager.class.getName(), new ExtensionContext.Store.CloseableResource() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public void beforeAll(ExtensionContext extensionContext) throws Exception {
if (state == null) {
TestResourceManager testResourceManager = new TestResourceManager(extensionContext.getRequiredTestClass());
try {
testResourceManager.init();
Map<String, String> systemProps = testResourceManager.start();
NativeImageLauncher launcher = new NativeImageLauncher(extensionContext.getRequiredTestClass());
launcher.addSystemProperties(systemProps);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ private ExtensionState doJavaStart(ExtensionContext context) throws Throwable {
testResourceManager = (Closeable) startupAction.getClassLoader().loadClass(TestResourceManager.class.getName())
.getConstructor(Class.class)
.newInstance(requiredTestClass);
testResourceManager.getClass().getMethod("init").invoke(testResourceManager);
testResourceManager.getClass().getMethod("start").invoke(testResourceManager);

populateCallbacks(startupAction.getClassLoader());
Expand Down

0 comments on commit f10e752

Please sign in to comment.