Skip to content

Commit

Permalink
Refactor TestResource handling code
Browse files Browse the repository at this point in the history
This is a prerequisite of fixing the behavior of
`@WithTestResource`
  • Loading branch information
geoand committed Sep 3, 2024
1 parent 70ab415 commit e7b2153
Show file tree
Hide file tree
Showing 9 changed files with 628 additions and 266 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.quarkus.test.common;

/**
* Defines how Quarkus behaves with regard to the application of the resource to this test and the testsuite in general
*/
public enum TestResourceScope {

/**
* The declaration order must be from the narrowest scope to the widest
*/
RESTRICTED_TO_CLASS, // means that Quarkus will run the test in complete isolation, i.e. it will restart every time it finds such a resource
MATCHING_RESOURCE, // means that Quarkus will not restart when running consecutive tests that use the same resource
GLOBAL, // means the resource applies to all tests in the testsuite
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import static io.quarkus.test.common.PathTestHelper.getTestClassesLocation;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand Down Expand Up @@ -39,10 +38,8 @@
import io.quarkus.paths.PathList;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.test.common.PathTestHelper;
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.common.RestorableSystemProperties;
import io.quarkus.test.common.TestClassIndexer;
import io.quarkus.test.common.WithTestResource;

public class AbstractJvmQuarkusTestExtension extends AbstractQuarkusTestWithContextExtension {

Expand Down Expand Up @@ -270,43 +267,6 @@ private Class<? extends QuarkusTestProfile> findTestProfileAnnotation(Class<?> c
return null;
}

protected static boolean hasPerTestResources(ExtensionContext extensionContext) {
return hasPerTestResources(extensionContext.getRequiredTestClass());
}

public static boolean hasPerTestResources(Class<?> requiredTestClass) {
while (requiredTestClass != Object.class) {
for (WithTestResource testResource : requiredTestClass.getAnnotationsByType(WithTestResource.class)) {
if (testResource.restrictToAnnotatedClass()) {
return true;
}
}

for (QuarkusTestResource testResource : requiredTestClass.getAnnotationsByType(QuarkusTestResource.class)) {
if (testResource.restrictToAnnotatedClass()) {
return true;
}
}
// scan for meta-annotations
for (Annotation annotation : requiredTestClass.getAnnotations()) {
// skip TestResource annotations
var annotationType = annotation.annotationType();

if ((annotationType != WithTestResource.class) && (annotationType != QuarkusTestResource.class)) {
// look for a TestResource on the annotation itself
if ((annotationType.getAnnotationsByType(WithTestResource.class).length > 0)
|| (annotationType.getAnnotationsByType(QuarkusTestResource.class).length > 0)) {
// meta-annotations are per-test scoped for now
return true;
}
}
}
// look up
requiredTestClass = requiredTestClass.getSuperclass();
}
return false;
}

protected static class PrepareResult {
protected final AugmentAction augmentAction;
protected final QuarkusTestProfile profileInstance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
Expand All @@ -19,10 +17,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
Expand Down Expand Up @@ -155,38 +150,6 @@ static TestProfileAndProperties determineTestProfileAndProperties(Class<? extend
return new TestProfileAndProperties(testProfile, properties);
}

/**
* 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
*/
static <T> List<T> 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, Annotation.class, boolean.class);

List<QuarkusTestProfile.TestResourceEntry> testResources = profileInstance.testResources();
List<T> result = new ArrayList<>(testResources.size());
for (QuarkusTestProfile.TestResourceEntry testResource : testResources) {
T instance = (T) testResourceClassEntryConstructor.newInstance(
Class.forName(testResource.getClazz().getName(), true, classLoader), testResource.getArgs(),
null, testResource.isParallel());
result.add(instance);
}

return result;
} catch (Exception e) {
throw new IllegalStateException("Unable to handle profile " + profileInstance.getClass(), e);
}
}

static void startLauncher(ArtifactLauncher launcher, Map<String, String> additionalProperties, Runnable sslSetter)
throws IOException {
launcher.includeAsSysProps(additionalProperties);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
import static io.quarkus.test.junit.IntegrationTestUtil.doProcessTestInstance;
import static io.quarkus.test.junit.IntegrationTestUtil.ensureNoInjectAnnotationIsUsed;
import static io.quarkus.test.junit.IntegrationTestUtil.findProfile;
import static io.quarkus.test.junit.IntegrationTestUtil.getAdditionalTestResources;
import static io.quarkus.test.junit.IntegrationTestUtil.getArtifactType;
import static io.quarkus.test.junit.IntegrationTestUtil.getSysPropsToRestore;
import static io.quarkus.test.junit.IntegrationTestUtil.handleDevServices;
import static io.quarkus.test.junit.IntegrationTestUtil.readQuarkusArtifactProperties;
import static io.quarkus.test.junit.IntegrationTestUtil.startLauncher;
import static io.quarkus.test.junit.TestResourceUtil.testResourcesRequireReload;
import static io.quarkus.test.junit.TestResourceUtil.TestResourceManagerReflections.copyEntriesFromProfile;

import java.io.Closeable;
import java.io.File;
Expand Down Expand Up @@ -75,7 +76,6 @@ public class QuarkusIntegrationTestExtension extends AbstractQuarkusTestWithCont
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 Class<?> currentJUnitTestClass;
private static boolean hasPerTestResources;

private static Map<String, String> devServicesProps;
private static String containerNetworkId;
Expand Down Expand Up @@ -154,9 +154,9 @@ private QuarkusTestExtensionState ensureStarted(ExtensionContext extensionContex
currentJUnitTestClass = extensionContext.getRequiredTestClass();
}
// we reload the test resources if we changed test class and if we had or will have per-test test resources
boolean reloadTestResources = isNewTestClass
&& (hasPerTestResources || QuarkusTestExtension.hasPerTestResources(extensionContext));
if ((state == null && !failedBoot) || wrongProfile || reloadTestResources) {
boolean reloadTestResources = false;
if ((state == null && !failedBoot) || wrongProfile || (reloadTestResources = isNewTestClass
&& TestResourceUtil.testResourcesRequireReload(state, extensionContext.getRequiredTestClass()))) {
if (wrongProfile || reloadTestResources) {
if (state != null) {
try {
Expand Down Expand Up @@ -217,15 +217,15 @@ private QuarkusTestExtensionState doProcessStart(Properties quarkusArtifactPrope
TestProfileAndProperties testProfileAndProperties = determineTestProfileAndProperties(profile, sysPropRestore);

testResourceManager = new TestResourceManager(requiredTestClass, quarkusTestProfile,
getAdditionalTestResources(testProfileAndProperties.testProfile,
copyEntriesFromProfile(testProfileAndProperties.testProfile,
context.getRequiredTestClass().getClassLoader()),
testProfileAndProperties.testProfile != null
&& testProfileAndProperties.testProfile.disableGlobalTestResources(),
devServicesProps, containerNetworkId == null ? Optional.empty() : Optional.of(containerNetworkId));
testResourceManager.init(
testProfileAndProperties.testProfile != null ? testProfileAndProperties.testProfile.getClass().getName()
: null);
hasPerTestResources = testResourceManager.hasPerTestResources();

if (isCallbacksEnabledForIntegrationTests()) {
populateCallbacks(requiredTestClass.getClassLoader());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import static io.quarkus.test.junit.IntegrationTestUtil.activateLogging;
import static io.quarkus.test.junit.IntegrationTestUtil.determineBuildOutputDirectory;
import static io.quarkus.test.junit.IntegrationTestUtil.determineTestProfileAndProperties;
import static io.quarkus.test.junit.IntegrationTestUtil.getAdditionalTestResources;
import static io.quarkus.test.junit.IntegrationTestUtil.getSysPropsToRestore;
import static io.quarkus.test.junit.IntegrationTestUtil.handleDevServices;
import static io.quarkus.test.junit.IntegrationTestUtil.readQuarkusArtifactProperties;
import static io.quarkus.test.junit.TestResourceUtil.TestResourceManagerReflections.copyEntriesFromProfile;

import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
Expand Down Expand Up @@ -122,7 +122,7 @@ private ArtifactLauncher.LaunchResult doProcessStart(ExtensionContext context, S
TestProfileAndProperties testProfileAndProperties = determineTestProfileAndProperties(profile, sysPropRestore);

testResourceManager = new TestResourceManager(requiredTestClass, profile,
getAdditionalTestResources(testProfileAndProperties.testProfile,
copyEntriesFromProfile(testProfileAndProperties.testProfile,
context.getRequiredTestClass().getClassLoader()),
testProfileAndProperties.testProfile != null
&& testProfileAndProperties.testProfile.disableGlobalTestResources());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.quarkus.test.junit;

import static io.quarkus.test.junit.IntegrationTestUtil.activateLogging;
import static io.quarkus.test.junit.IntegrationTestUtil.getAdditionalTestResources;
import static io.quarkus.test.junit.TestResourceUtil.TestResourceManagerReflections.copyEntriesFromProfile;

import java.io.Closeable;
import java.lang.reflect.Method;
Expand Down Expand Up @@ -41,7 +41,6 @@ public class QuarkusMainTestExtension extends AbstractJvmQuarkusTestExtension
AfterAllCallback {

PrepareResult prepareResult;
private static boolean hasPerTestResources;

/**
* The result from an {@link Launch} test
Expand All @@ -64,9 +63,9 @@ private void ensurePrepared(ExtensionContext extensionContext, Class<? extends Q
QuarkusTestExtensionState state = getState(extensionContext);
boolean wrongProfile = !Objects.equals(profile, quarkusTestProfile);
// we reload the test resources if we changed test class and if we had or will have per-test test resources
boolean reloadTestResources = !Objects.equals(extensionContext.getRequiredTestClass(), currentJUnitTestClass)
&& (hasPerTestResources || hasPerTestResources(extensionContext));
if (wrongProfile || reloadTestResources) {
boolean isNewTestClass = !Objects.equals(extensionContext.getRequiredTestClass(), currentJUnitTestClass);
if (wrongProfile || (isNewTestClass
&& TestResourceUtil.testResourcesRequireReload(state, extensionContext.getRequiredTestClass()))) {
if (state != null) {
try {
state.close();
Expand Down Expand Up @@ -187,20 +186,18 @@ private int doJavaStart(ExtensionContext context, Class<? extends QuarkusTestPro
QuarkusTestProfile profileInstance = prepareResult.profileInstance;

//must be done after the TCCL has been set
testResourceManager = (Closeable) startupAction.getClassLoader().loadClass(TestResourceManager.class.getName())
.getConstructor(Class.class, Class.class, List.class, boolean.class, Map.class, Optional.class)
.newInstance(context.getRequiredTestClass(),
profile != null ? profile : null,
getAdditionalTestResources(profileInstance, startupAction.getClassLoader()),
profileInstance != null && profileInstance.disableGlobalTestResources(),
startupAction.getDevServicesProperties(), Optional.empty());
testResourceManager.getClass().getMethod("init", String.class).invoke(testResourceManager,
profile != null ? profile.getName() : null);
Map<String, String> properties = (Map<String, String>) testResourceManager.getClass().getMethod("start")
.invoke(testResourceManager);
Class<?> testResourceManagerClass = startupAction.getClassLoader().loadClass(TestResourceManager.class.getName());
testResourceManager = TestResourceUtil.TestResourceManagerReflections.createReflectively(testResourceManagerClass,
context.getRequiredTestClass(),
profile,
copyEntriesFromProfile(profileInstance, startupAction.getClassLoader()),
profileInstance != null && profileInstance.disableGlobalTestResources(),
startupAction.getDevServicesProperties(),
Optional.empty());
TestResourceUtil.TestResourceManagerReflections.initReflectively(testResourceManager, profile);
Map<String, String> properties = TestResourceUtil.TestResourceManagerReflections
.startReflectively(testResourceManager);
startupAction.overrideConfig(properties);
hasPerTestResources = (boolean) testResourceManager.getClass().getMethod("hasPerTestResources")
.invoke(testResourceManager);

testResourceManager.getClass().getMethod("inject", Object.class)
.invoke(testResourceManager, context.getRequiredTestInstance());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.quarkus.test.junit;

import static io.quarkus.test.junit.IntegrationTestUtil.activateLogging;
import static io.quarkus.test.junit.IntegrationTestUtil.getAdditionalTestResources;

import java.io.Closeable;
import java.io.IOException;
Expand Down Expand Up @@ -123,7 +122,6 @@ public class QuarkusTestExtension extends AbstractJvmQuarkusTestExtension
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 Class<?> quarkusTestMethodContextClass;
private static boolean hasPerTestResources;
private static List<Function<Class<?>, String>> testHttpEndpointProviders;

private static List<Object> testMethodInvokers;
Expand Down Expand Up @@ -220,21 +218,21 @@ public Thread newThread(Runnable r) {
populateDeepCloneField(startupAction);

//must be done after the TCCL has been set
testResourceManager = (Closeable) startupAction.getClassLoader().loadClass(TestResourceManager.class.getName())
.getConstructor(Class.class, Class.class, List.class, boolean.class, Map.class, Optional.class, Path.class)
.newInstance(requiredTestClass,
profile != null ? profile : null,
getAdditionalTestResources(profileInstance, startupAction.getClassLoader()),
profileInstance != null && profileInstance.disableGlobalTestResources(),
startupAction.getDevServicesProperties(), Optional.empty(), result.testClassLocation);
testResourceManager.getClass().getMethod("init", String.class).invoke(testResourceManager,
profile != null ? profile.getName() : null);
Map<String, String> properties = (Map<String, String>) testResourceManager.getClass().getMethod("start")
.invoke(testResourceManager);
Class<?> testResourceManagerClass = startupAction.getClassLoader().loadClass(TestResourceManager.class.getName());
testResourceManager = TestResourceUtil.TestResourceManagerReflections.createReflectively(testResourceManagerClass,
requiredTestClass,
profile,
TestResourceUtil.TestResourceManagerReflections.copyEntriesFromProfile(profileInstance,
startupAction.getClassLoader()),
profileInstance != null && profileInstance.disableGlobalTestResources(),
startupAction.getDevServicesProperties(),
Optional.empty(),
result.testClassLocation);
TestResourceUtil.TestResourceManagerReflections.initReflectively(testResourceManager, profile);
Map<String, String> properties = TestResourceUtil.TestResourceManagerReflections
.startReflectively(testResourceManager);
startupAction.overrideConfig(properties);
startupAction.addRuntimeCloseTask(testResourceManager);
hasPerTestResources = (boolean) testResourceManager.getClass().getMethod("hasPerTestResources")
.invoke(testResourceManager);

// make sure that we start over every time we populate the callbacks
// otherwise previous runs of QuarkusTest (with different TestProfile values can leak into the new run)
Expand Down Expand Up @@ -584,8 +582,9 @@ private QuarkusTestExtensionState ensureStarted(ExtensionContext extensionContex
currentJUnitTestClass = extensionContext.getRequiredTestClass();
}
// we reload the test resources if we changed test class and the new test class is not a nested class, and if we had or will have per-test test resources
boolean reloadTestResources = isNewTestClass && (hasPerTestResources || hasPerTestResources(extensionContext));
if ((state == null && !failedBoot) || wrongProfile || reloadTestResources) {
boolean reloadTestResources = false;
if ((state == null && !failedBoot) || wrongProfile || (reloadTestResources = isNewTestClass
&& TestResourceUtil.testResourcesRequireReload(state, extensionContext.getRequiredTestClass()))) {
if (wrongProfile || reloadTestResources) {
if (state != null) {
try {
Expand Down Expand Up @@ -1175,7 +1174,7 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con
+ "' disabled because 'quarkus.profile.test.tags' don't match the tags of '" + testProfile + "'");
}

public class ExtensionState extends QuarkusTestExtensionState {
public static class ExtensionState extends QuarkusTestExtensionState {

public ExtensionState(Closeable testResourceManager, Closeable resource) {
super(testResourceManager, resource);
Expand Down
Loading

0 comments on commit e7b2153

Please sign in to comment.