Skip to content

Commit

Permalink
Use system classloader as TCCL for storing pre-test config
Browse files Browse the repository at this point in the history
  • Loading branch information
holly-cummins committed Jan 15, 2025
1 parent bfd590d commit 74695d3
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -633,18 +633,28 @@ private AppMakerHelper.DumbHolder makeClassLoader(String key, Class requiredTest
AppMakerHelper.DumbHolder holder = appMakerHelper.getStartupAction(requiredTestClass,
curatedApplication, isAuxiliaryApplication, profile);

ClassLoader original = Thread.currentThread().getContextClassLoader();
// See comments on AbstractJVMTestExtension#evaluateExecutionCondition for why this is the system classloader
Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());

QuarkusClassLoader loader = (QuarkusClassLoader) holder.startupAction()
.getClassLoader();

Class<?> configProviderResolverClass = loader.loadClass(ConfigProviderResolver.class.getName());
Object configProviderResolver = configProviderResolverClass.getMethod("instance").invoke(null);

Class<?> testConfigProviderResolverClass = loader.loadClass(QuarkusTestConfigProviderResolver.class.getName());
Object testConfigProviderResolver = testConfigProviderResolverClass.getDeclaredConstructor(ClassLoader.class)
.newInstance(loader);

configProviderResolverClass.getDeclaredMethod("setInstance", configProviderResolverClass).invoke(null,
testConfigProviderResolver);
try {
Class<?> configProviderResolverClass = loader.loadClass(ConfigProviderResolver.class.getName());
Object configProviderResolver = configProviderResolverClass.getMethod("instance")
.invoke(null);

Class<?> testConfigProviderResolverClass = loader.loadClass(QuarkusTestConfigProviderResolver.class.getName());
Object testConfigProviderResolver = testConfigProviderResolverClass.getDeclaredConstructor(ClassLoader.class)
.newInstance(loader);

configProviderResolverClass.getDeclaredMethod("setInstance", configProviderResolverClass)
.invoke(null,
testConfigProviderResolver);
} finally {
Thread.currentThread().setContextClassLoader(original);
}

// TODO is this a good idea?
// TODO without this, the parameter dev mode tests regress, but it feels kind of wrong - is there some use of TCCL in JUnitRunner we need to find
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,21 +261,30 @@ 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();
// TODO stale comments below
// At this point, the TCCL is usually 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

// At this point, the TCCL is usually the FacadeClassLoader, but sometimes it's a deployment classloader (for multimodule tests), or the runtime classloader (for nested tests)
// Getting back to the FacadeClassLoader is non-trivial. We can't use a simple singleton on the class, because we will be accessing it from different classloaders.
// We can't have a hook back from the runtime classloader to the facade classloader, because
// when evaluating execution conditions for native tests, the test will have been loaded with the system classloader, not the runtime classloader.
// The one classloader we can reliably get to when evaluating test execution is the system classloader, so hook our config on that.

ClassLoader original = Thread.currentThread().getContextClassLoader();
// Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());

// 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);

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 74695d3

Please sign in to comment.