Skip to content

Commit

Permalink
Hackily force use of FacadeLoader as TCCL in multimodule tests
Browse files Browse the repository at this point in the history
  • Loading branch information
holly-cummins committed Jan 14, 2025
1 parent ff60a3a commit 73c1dd4
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,15 @@ public class QuarkusClassLoader extends ClassLoader implements Closeable {
registerAsParallelCapable();
}

private ClassLoader facade;

private static RuntimeException nonQuarkusClassLoaderError() {
return new IllegalStateException("The current classloader is not an instance of "
+ QuarkusClassLoader.class.getName() + " but "
+ Thread.currentThread().getContextClassLoader().getClass().getName());
+ Thread.currentThread()
.getContextClassLoader()
.getClass()
.getName());
}

/**
Expand All @@ -72,7 +77,8 @@ private static RuntimeException nonQuarkusClassLoaderError() {
* @param visitor runtime resource visitor
*/
public static void visitRuntimeResources(String resourceName, Consumer<PathVisit> visitor) {
if (Thread.currentThread().getContextClassLoader() instanceof QuarkusClassLoader classLoader) {
if (Thread.currentThread()
.getContextClassLoader() instanceof QuarkusClassLoader classLoader) {
for (var element : classLoader.getElementsWithResource(resourceName)) {
if (element.isRuntime()) {
element.apply(tree -> {
Expand All @@ -87,7 +93,8 @@ public static void visitRuntimeResources(String resourceName, Consumer<PathVisit
}

public static List<ClassPathElement> getElements(String resourceName, boolean onlyFromCurrentClassLoader) {
if (Thread.currentThread().getContextClassLoader() instanceof QuarkusClassLoader classLoader) {
if (Thread.currentThread()
.getContextClassLoader() instanceof QuarkusClassLoader classLoader) {
return classLoader.getElementsWithResource(resourceName, onlyFromCurrentClassLoader);
}
throw nonQuarkusClassLoaderError();
Expand All @@ -107,7 +114,8 @@ public static boolean isClassPresentAtRuntime(String className) {
* Indicates if a given class is considered an application class.
*/
public static boolean isApplicationClass(String className) {
if (Thread.currentThread().getContextClassLoader() instanceof QuarkusClassLoader classLoader) {
if (Thread.currentThread()
.getContextClassLoader() instanceof QuarkusClassLoader classLoader) {
String resourceName = fromClassNameToResourceName(className);
ClassPathResourceIndex classPathResourceIndex = classLoader.getClassPathResourceIndex();

Expand All @@ -126,7 +134,8 @@ public static boolean isApplicationClass(String className) {
public static boolean isResourcePresentAtRuntime(String resourcePath) {
List<ClassPathElement> classPathElements = QuarkusClassLoader.getElements(resourcePath, false);
for (int i = 0; i < classPathElements.size(); i++) {
if (classPathElements.get(i).isRuntime()) {
if (classPathElements.get(i)
.isRuntime()) {
return true;
}
}
Expand Down Expand Up @@ -172,7 +181,8 @@ public static boolean isResourcePresentAtRuntime(String resourcePath) {
static {
ClassLoader cl = null;
try {
cl = (ClassLoader) ClassLoader.class.getDeclaredMethod("getPlatformClassLoader").invoke(null);
cl = (ClassLoader) ClassLoader.class.getDeclaredMethod("getPlatformClassLoader")
.invoke(null);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {

}
Expand All @@ -186,6 +196,9 @@ private QuarkusClassLoader(Builder builder) {
// Not passing the name to the parent constructor on purpose:
// stacktraces become very ugly if we do that.
super(builder.parent);

this.facade = Thread.currentThread().getContextClassLoader();

this.name = builder.name;
this.status = STATUS_OPEN;
this.normalPriorityElements = builder.normalPriorityElements;
Expand Down Expand Up @@ -249,7 +262,8 @@ public Enumeration<URL> getResources(String unsanitisedName, boolean parentAlrea
ensureOpen(unsanitisedName);

for (int i = 0; i < classLoaderEventListeners.size(); i++) {
classLoaderEventListeners.get(i).enumeratingResourceURLs(unsanitisedName, this.name);
classLoaderEventListeners.get(i)
.enumeratingResourceURLs(unsanitisedName, this.name);
}
ClassPathResourceIndex classPathResourceIndex = getClassPathResourceIndex();
String name = sanitizeName(unsanitisedName);
Expand Down Expand Up @@ -284,7 +298,8 @@ public Enumeration<URL> getResources(String unsanitisedName, boolean parentAlrea
if (!classPathElements.isEmpty()) {
boolean endsWithTrailingSlash = unsanitisedName.endsWith("/");
for (int i = 0; i < classPathElements.size(); i++) {
List<ClassPathResource> resList = classPathElements.get(i).getResources(name);
List<ClassPathResource> resList = classPathElements.get(i)
.getResources(name);
//if the requested name ends with a trailing / we make sure
//that the resource is a directory, and return a URL that ends with a /
//this matches the behaviour of URLClassLoader
Expand All @@ -293,7 +308,8 @@ public Enumeration<URL> getResources(String unsanitisedName, boolean parentAlrea
if (endsWithTrailingSlash) {
if (res.isDirectory()) {
try {
resources.add(new URL(res.getUrl().toString() + "/"));
resources.add(new URL(res.getUrl()
.toString() + "/"));
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
Expand Down Expand Up @@ -403,15 +419,17 @@ public URL getResource(String unsanitisedName) {
private static URL getClassPathElementResourceUrl(List<ClassPathElement> classPathElements, String name,
boolean endsWithTrailingSlash) {
for (int i = 0; i < classPathElements.size(); i++) {
ClassPathResource res = classPathElements.get(i).getResource(name);
ClassPathResource res = classPathElements.get(i)
.getResource(name);
if (res != null) {
//if the requested name ends with a trailing / we make sure
//that the resource is a directory, and return a URL that ends with a /
//this matches the behaviour of URLClassLoader
if (endsWithTrailingSlash) {
if (res.isDirectory()) {
try {
return new URL(res.getUrl().toString() + "/");
return new URL(res.getUrl()
.toString() + "/");
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
Expand All @@ -430,7 +448,8 @@ public InputStream getResourceAsStream(String unsanitisedName) {
ensureOpen(unsanitisedName);

for (int i = 0; i < classLoaderEventListeners.size(); i++) {
classLoaderEventListeners.get(i).openResourceStream(unsanitisedName, this.name);
classLoaderEventListeners.get(i)
.openResourceStream(unsanitisedName, this.name);
}
String name = sanitizeName(unsanitisedName);
ClassPathResourceIndex classPathResourceIndex = getClassPathResourceIndex();
Expand Down Expand Up @@ -468,7 +487,8 @@ private static InputStream getClassPathElementResourceInputStream(List<ClassPath
if (res != null) {
if (res.isDirectory()) {
try {
return res.getUrl().openStream();
return res.getUrl()
.openStream();
} catch (IOException e) {
log.debug("Ignoring exception that occurred while opening a stream for resource " + name,
e);
Expand Down Expand Up @@ -589,7 +609,8 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
} finally {
if (interrupted) {
//restore interrupt state
Thread.currentThread().interrupt();
Thread.currentThread()
.interrupt();
}
}
}
Expand Down Expand Up @@ -766,7 +787,8 @@ public void close() {
try (InputStream is = getClass().getResourceAsStream("DriverRemover.class")) {
byte[] data = is.readAllBytes();
Runnable r = (Runnable) defineClass(DriverRemover.class.getName(), data, 0, data.length)
.getConstructor(ClassLoader.class).newInstance(this);
.getConstructor(ClassLoader.class)
.newInstance(this);
r.run();
} catch (Exception e) {
log.debug("Failed to clean up DB drivers");
Expand Down Expand Up @@ -834,6 +856,10 @@ public void setStartupAction(StartupAction startupAction) {
this.startupAction = startupAction;
}

public ClassLoader getFacadeClassloader() {
return facade;
}

public static class Builder {
final String name;
final ClassLoader parent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public class AbstractJvmQuarkusTestExtension extends AbstractQuarkusTestWithCont

protected ClassLoader originalCl;

protected final ClassLoader facade;

// Used to preserve state from the previous run, so we know if we should restart an application
protected static RunningQuarkusApplication runningQuarkusApplication;

Expand All @@ -57,6 +59,11 @@ public class AbstractJvmQuarkusTestExtension extends AbstractQuarkusTestWithCont
protected static final Deque<Class<?>> currentTestClassStack = new ArrayDeque<>();
protected static Class<?> currentJUnitTestClass;

protected AbstractJvmQuarkusTestExtension() {
// TODO where is right place to get this?
facade = Thread.currentThread().getContextClassLoader();
}

// TODO only used by QuarkusMainTest, fix that class and delete this
protected PrepareResult createAugmentor(ExtensionContext context, Class<? extends QuarkusTestProfile> profile,
Collection<Runnable> shutdownTasks) throws Exception {
Expand Down Expand Up @@ -261,21 +268,44 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con
System.out.println("OK smallrye " + SmallRyeConfig.class.getClassLoader());

// TODO this should not be necessary, because we want the config provider to be for our class
// ClassLoader original = Thread.currentThread().getContextClassLoader();
// Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
ClassLoader original = Thread.currentThread().getContextClassLoader();

QuarkusClassLoader mecl = (QuarkusClassLoader) this.getClass().getClassLoader();
ClassLoader facade = mecl.getFacadeClassloader();

// Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());

// SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class);
// TestConfig testConfig = config.getConfigMapping(TestConfig.class);

// At this point, the TCCL is the FacadeClassLoader; trying to do getConfig with that TCCL fails with java.util.ServiceConfigurationError: io.smallrye.config.SmallRyeConfigFactory: io.quarkus.runtime.configuration.QuarkusConfigFactory not a subtype
// If we set the TCCL to be this.getClass().getClassLoader(), get config succeeds, but config.getConfigMapping(TestConfig.class) fails, because the mapping was registered when the TCCL was the FacadeClassLoader
TestConfig testConfig = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class).getConfigMapping(TestConfig.class);
// In nested tests and multimodule tests, the TCCL may not be the facade classloader

// ClassLoader facade = FacadeClassLoader.instance(Exception.class.getClassLoader());
// Thread.currentThread().setContextClassLoader(Error.class.getClassLoader());
Thread.currentThread().setContextClassLoader(facade);
System.out.println("HOLLY Config using facade TCCL " + Thread.currentThread().getContextClassLoader());

TestConfig testConfig = null;
System.out.println("HOLLY CONFIG " + " and the class of the mapping is "
+ TestConfig.class.getClassLoader());
try {
testConfig = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class).getConfigMapping(TestConfig.class);
} catch (RuntimeException | Error e) {
System.out.println("HOLLY Config DOOOOOOM " + e);
System.out.println("HOLLY Config TCCL is " + Thread.currentThread().getContextClassLoader()
+ " and the class of the mapping is "
+ TestConfig.class.getClassLoader());
System.out.println("HOLLY Config my class is " + this.getClass().getClassLoader());
throw e;
}

Optional<List<String>> tags = testConfig.profile().tags();
if (tags.isEmpty() || tags.get().isEmpty()) {
return ConditionEvaluationResult.enabled("No Quarkus Test Profile tags");
}
// Thread.currentThread().setContextClassLoader(original);
Thread.currentThread().setContextClassLoader(original);

Class<? extends QuarkusTestProfile> testProfile = getQuarkusTestProfile(context);
if (testProfile == null) {
Expand Down

0 comments on commit 73c1dd4

Please sign in to comment.