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 Aug 29, 2024
1 parent 8be69b4 commit 0cc4b1b
Show file tree
Hide file tree
Showing 9 changed files with 442 additions and 239 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 @@ -6,7 +6,6 @@

import java.io.FileOutputStream;
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 @@ -41,9 +40,9 @@
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.TestClassIndexer;
import io.quarkus.test.common.WithTestResource;
import io.quarkus.test.common.TestResourceManager;
import io.quarkus.test.common.TestResourceScope;

public class AbstractJvmQuarkusTestExtension extends AbstractQuarkusTestWithContextExtension {

Expand Down Expand Up @@ -307,41 +306,14 @@ private Class<? extends QuarkusTestProfile> findTestProfileAnnotation(Class<?> c
return null;
}

protected static boolean hasPerTestResources(ExtensionContext extensionContext) {
return hasPerTestResources(extensionContext.getRequiredTestClass());
protected static boolean newTestClassHasPerTestResources(ExtensionContext extensionContext) {
return newTestClassHasPerTestResources(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;
private static boolean newTestClassHasPerTestResources(Class<?> requiredTestClass) {
var entries = TestResourceManager.getUniqueTestResourceClassEntries(requiredTestClass,
getTestClassesLocation(requiredTestClass));
return TestResourceManager.narrowestScope(entries) != TestResourceScope.GLOBAL;
}

protected static class PrepareResult {
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,12 @@
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.TestResourceManagerReflections.copyEntriesFromProfile;

import java.io.Closeable;
import java.io.File;
Expand Down Expand Up @@ -56,6 +56,7 @@
import io.quarkus.test.common.TestConfigUtil;
import io.quarkus.test.common.TestHostLauncher;
import io.quarkus.test.common.TestResourceManager;
import io.quarkus.test.common.TestResourceScope;
import io.quarkus.test.common.TestScopeManager;
import io.quarkus.test.junit.callback.QuarkusTestMethodContext;
import io.quarkus.test.junit.launcher.ArtifactLauncherProvider;
Expand Down Expand Up @@ -155,7 +156,7 @@ private QuarkusTestExtensionState ensureStarted(ExtensionContext extensionContex
}
// 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));
&& (hasPerTestResources || QuarkusTestExtension.newTestClassHasPerTestResources(extensionContext));
if ((state == null && !failedBoot) || wrongProfile || reloadTestResources) {
if (wrongProfile || reloadTestResources) {
if (state != null) {
Expand Down Expand Up @@ -217,15 +218,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();
hasPerTestResources = testResourceManager.narrowestScope() != TestResourceScope.GLOBAL;
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.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.TestResourceManagerReflections.copyEntriesFromProfile;

import java.io.Closeable;
import java.lang.reflect.Method;
Expand Down Expand Up @@ -65,7 +65,7 @@ private void ensurePrepared(ExtensionContext extensionContext, Class<? extends Q
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));
&& (hasPerTestResources || newTestClassHasPerTestResources(extensionContext));
if (wrongProfile || reloadTestResources) {
if (state != null) {
try {
Expand Down Expand Up @@ -190,7 +190,7 @@ private int doJavaStart(ExtensionContext context, Class<? extends QuarkusTestPro
.getConstructor(Class.class, Class.class, List.class, boolean.class, Map.class, Optional.class)
.newInstance(context.getRequiredTestClass(),
profile != null ? profile : null,
getAdditionalTestResources(profileInstance, startupAction.getClassLoader()),
copyEntriesFromProfile(profileInstance, startupAction.getClassLoader()),
profileInstance != null && profileInstance.disableGlobalTestResources(),
startupAction.getDevServicesProperties(), Optional.empty());
testResourceManager.getClass().getMethod("init", String.class).invoke(testResourceManager,
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 @@ -96,6 +95,7 @@
import io.quarkus.test.common.RestorableSystemProperties;
import io.quarkus.test.common.TestClassIndexer;
import io.quarkus.test.common.TestResourceManager;
import io.quarkus.test.common.TestResourceScope;
import io.quarkus.test.common.TestScopeManager;
import io.quarkus.test.common.http.TestHTTPEndpoint;
import io.quarkus.test.common.http.TestHTTPResourceManager;
Expand Down Expand Up @@ -124,7 +124,7 @@ 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 boolean runningHasPerTestResources;
private static List<Function<Class<?>, String>> testHttpEndpointProviders;

private static List<Object> testMethodInvokers;
Expand Down Expand Up @@ -221,21 +221,22 @@ 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 = TestResourceManagerReflections.createReflectively(testResourceManagerClass,
requiredTestClass,
profile,
TestResourceManagerReflections.copyEntriesFromProfile(profileInstance,
startupAction.getClassLoader()),
profileInstance != null && profileInstance.disableGlobalTestResources(),
startupAction.getDevServicesProperties(),
Optional.empty(),
result.testClassLocation);
TestResourceManagerReflections.initReflectively(testResourceManager, profile);
Map<String, String> properties = TestResourceManagerReflections.startReflectively(testResourceManager);
startupAction.overrideConfig(properties);
startupAction.addRuntimeCloseTask(testResourceManager);
hasPerTestResources = (boolean) testResourceManager.getClass().getMethod("hasPerTestResources")
.invoke(testResourceManager);
TestResourceScope narrowestScope = TestResourceManagerReflections.narrowestScope(testResourceManager);
runningHasPerTestResources = narrowestScope != TestResourceScope.GLOBAL;

// 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 @@ -594,7 +595,8 @@ 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));
boolean reloadTestResources = isNewTestClass && (runningHasPerTestResources
|| newTestClassHasPerTestResources(extensionContext));
if ((state == null && !failedBoot) || wrongProfile || reloadTestResources) {
if (wrongProfile || reloadTestResources) {
if (state != null) {
Expand Down
Loading

0 comments on commit 0cc4b1b

Please sign in to comment.