Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
Expand Down Expand Up @@ -89,6 +90,9 @@ public class JunitTestRunner {
public static final DotName QUARKUS_TEST = DotName.createSimple("io.quarkus.test.junit.QuarkusTest");
public static final DotName QUARKUS_MAIN_TEST = DotName.createSimple("io.quarkus.test.junit.main.QuarkusMainTest");
public static final DotName QUARKUS_INTEGRATION_TEST = DotName.createSimple("io.quarkus.test.junit.QuarkusIntegrationTest");
public static final DotName QUARKUS_COMPONENT_TEST = DotName.createSimple("io.quarkus.test.component.QuarkusComponentTest");
public static final DotName QUARKUS_COMPONENT_TEST_EXTENSION = DotName
.createSimple("io.quarkus.test.component.QuarkusComponentTestExtension");
public static final DotName TEST_PROFILE = DotName.createSimple("io.quarkus.test.junit.TestProfile");
public static final DotName TEST = DotName.createSimple(Test.class.getName());
public static final DotName REPEATED_TEST = DotName.createSimple(RepeatedTest.class.getName());
Expand All @@ -99,6 +103,7 @@ public class JunitTestRunner {
public static final DotName NESTED = DotName.createSimple(Nested.class.getName());
private static final String ARCHUNIT_FIELDSOURCE_FQCN = "com.tngtech.archunit.junit.FieldSource";
private static final String FACADE_CLASS_LOADER_NAME = "io.quarkus.test.junit.classloading.FacadeClassLoader";
private static final String TEST_DISCOVERY_PROPERTY = "quarkus.continuous-tests-discovery";

private final long runId;
private final DevModeContext.ModuleInfo moduleInfo;
Expand Down Expand Up @@ -568,6 +573,9 @@ private DiscoveryResult discoverTestClasses() {
//for now this is out of scope, we are just going to do annotation based discovery
//we will need to fix this sooner rather than later though

// Set the system property that is used for QuarkusComponentTest
System.setProperty(TEST_DISCOVERY_PROPERTY, "true");

if (moduleInfo.getTest().isEmpty()) {
return DiscoveryResult.EMPTY;
}
Expand Down Expand Up @@ -613,6 +621,22 @@ private DiscoveryResult discoverTestClasses() {
}
}

Set<String> quarkusComponentTestClasses = new HashSet<>();
for (AnnotationInstance a : index.getAnnotations(QUARKUS_COMPONENT_TEST)) {
DotName name = a.target().asClass().name();
quarkusComponentTestClasses.add(name.toString());
for (ClassInfo subclass : index.getAllKnownSubclasses(name)) {
quarkusComponentTestClasses.add(subclass.name().toString());
}
}
for (ClassInfo clazz : index.getKnownUsers(QUARKUS_COMPONENT_TEST_EXTENSION)) {
DotName name = clazz.name();
quarkusComponentTestClasses.add(name.toString());
for (ClassInfo subclass : index.getAllKnownSubclasses(name)) {
quarkusComponentTestClasses.add(subclass.name().toString());
}
}

// The FacadeClassLoader approach of loading test classes with the classloader we will use to run them can only work for `@QuarkusTest` and not main or integration tests
// Most logic in the JUnitRunner counts main tests as quarkus tests, so do a (mildly irritating) special pass to get the ones which are strictly @QuarkusTest

Expand Down Expand Up @@ -676,7 +700,8 @@ private DiscoveryResult discoverTestClasses() {
for (DotName testClass : allTestClasses) {
String name = testClass.toString();
if (integrationTestClasses.contains(name)
|| quarkusTestClasses.contains(name)) {
|| quarkusTestClasses.contains(name)
|| quarkusComponentTestClasses.contains(name)) {
continue;
}
var enclosing = enclosingClasses.get(testClass);
Expand Down Expand Up @@ -705,11 +730,14 @@ private DiscoveryResult discoverTestClasses() {
// if we didn't find any test classes, let's return early
// Make sure you also update the logic for the non-empty case above if you adjust this part
if (testType == TestType.ALL) {
if (unitTestClasses.isEmpty() && quarkusTestClasses.isEmpty()) {
if (unitTestClasses.isEmpty()
&& quarkusTestClasses.isEmpty()
&& quarkusComponentTestClasses.isEmpty()) {
return DiscoveryResult.EMPTY;
}
} else if (testType == TestType.UNIT) {
if (unitTestClasses.isEmpty()) {
if (unitTestClasses.isEmpty()
&& quarkusComponentTestClasses.isEmpty()) {
return DiscoveryResult.EMPTY;
}
} else if (quarkusTestClasses.isEmpty()) {
Expand Down Expand Up @@ -784,8 +812,9 @@ public String apply(Class<?> aClass) {
return testProfile.value().asClass().name().toString() + "$$" + aClass.getName();
}
}));

QuarkusClassLoader cl = null;
if (!unitTestClasses.isEmpty()) {
if (!unitTestClasses.isEmpty() || !quarkusComponentTestClasses.isEmpty()) {
//we need to work the unit test magic
//this is a lot more complex
//we need to transform the classes to make the tracing magic work
Expand All @@ -810,6 +839,7 @@ public String apply(Class<?> aClass) {
cl = testApplication.createDeploymentClassLoader();
deploymentClassLoader = cl;
cl.reset(Collections.emptyMap(), transformedClasses);

for (String i : unitTestClasses) {
try {
utClasses.add(cl.loadClass(i));
Expand All @@ -820,6 +850,30 @@ public String apply(Class<?> aClass) {
}
}

if (!quarkusComponentTestClasses.isEmpty()) {
try {
// We use the deployment class loader to load the test class
Class<?> qcfcClazz = cl.loadClass("io.quarkus.test.component.QuarkusComponentFacadeClassLoaderProvider");
Constructor<?> c = qcfcClazz.getConstructor(Class.class, Set.class);
Method getClassLoader = qcfcClazz.getMethod("getClassLoader", String.class, ClassLoader.class);
for (String componentTestClass : quarkusComponentTestClasses) {
try {
Class<?> testClass = cl.loadClass(componentTestClass);
Object ecl = c.newInstance(testClass, classesToTransform);
ClassLoader excl = (ClassLoader) getClassLoader.invoke(ecl, componentTestClass, cl);
utClasses.add(excl.loadClass(componentTestClass));
} catch (Exception e) {
log.debug(e);
log.warnf("Failed to load component test class %s, it will not be executed this run.",
componentTestClass);
}
}
} catch (ClassNotFoundException | IllegalArgumentException
| SecurityException | NoSuchMethodException e) {
log.warn(
"Failed to load QuarkusComponentFacadeClassLoaderProvider, component test classes will not be executed this run.");
}
}
}

if (classLoaderToClose != null) {
Expand All @@ -832,6 +886,9 @@ public String apply(Class<?> aClass) {
}
}

// Unset the system property that is used for QuarkusComponentTest
System.clearProperty(TEST_DISCOVERY_PROPERTY);

// Make sure you also update the logic for the empty case above if you adjust this part
if (testType == TestType.ALL) {
//run unit style tests first
Expand Down
8 changes: 8 additions & 0 deletions integration-tests/devmode/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<!-- quarkus-junit5 provides QuarkusTestConfigProviderResolver
that overrides io.quarkus.test.config.TestConfigProviderResolver
to avoid Context ClassLoader mismatch -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-component</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.quarkus.test.common;

/**
* This internal SPI is used by {@code io.quarkus.test.junit.classloading.FacadeClassLoader} from quarkus-junit5 to extend its
* functionality.
*/
public interface FacadeClassLoaderProvider {

/**
* @param name The binary name of a class
* @param parent
* @return the class loader or null if no dedicated CL exists for the given class
*/
ClassLoader getClassLoader(String name, ClassLoader parent);

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

import java.util.Map;
import java.util.Set;

record BuildResult(Map<String, byte[]> generatedClasses,
byte[] componentsProvider,
// prefix -> config mapping FQCN
Map<String, Set<String>> configMappings,
// key -> [testClass, methodName, paramType1, paramType2]
Map<String, String[]> interceptorMethods,
Throwable failure) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.quarkus.test.component;

class ComponentClassLoader extends ClassLoader {

private final QuarkusComponentFacadeClassLoaderProvider cls = new QuarkusComponentFacadeClassLoaderProvider();

ComponentClassLoader(ClassLoader parent) {
super(parent);
}

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
ClassLoader cl = cls.getClassLoader(name, getParent());
if (cl != null) {
return cl.loadClass(name);
}
return getParent().loadClass(name);
}

}
Loading
Loading