diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/FacadeClassLoader.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/FacadeClassLoader.java index 31e240d884726..3808959e79bcd 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/FacadeClassLoader.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/FacadeClassLoader.java @@ -230,7 +230,7 @@ private QuarkusClassLoader makeClassLoader(String key, Class requiredTestClass, // .unwrap(SmallRyeConfig.class) // .getProfiles()); - System.out.println("HOLLY interceipt original" + originalClassLoader); + System.out.println("HOLLY facade original" + originalClassLoader); AppMakerHelper appMakerHelper = new AppMakerHelper(); CuratedApplication curatedApplication = curatedApplications.get(key); @@ -333,6 +333,7 @@ public String getName() { @Override public void close() throws IOException { + // TODO clearly, an implementation is needed! } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java index 16f377261714d..e20a15b964e62 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java @@ -135,6 +135,8 @@ public Runnable prepare() { try { long start = System.currentTimeMillis(); ClassLoader old = Thread.currentThread().getContextClassLoader(); + System.out.println("HOLLU junit prepare, old is " + old); + QuarkusClassLoader tcl = testApplication.createDeploymentClassLoader(); LogCapturingOutputFilter logHandler = new LogCapturingOutputFilter(testApplication, true, true, TestSupport.instance().get()::isDisplayTestOutput); @@ -584,6 +586,7 @@ private DiscoveryResult discoverTestClasses() { Set quarkusTestClasses = new HashSet<>(); for (var a : Arrays.asList(QUARKUS_TEST, QUARKUS_MAIN_TEST)) { for (AnnotationInstance i : index.getAnnotations(a)) { + DotName name = i.target().asClass().name(); quarkusTestClasses.add(name.toString()); for (ClassInfo clazz : index.getAllKnownSubclasses(name)) { @@ -597,6 +600,8 @@ private DiscoveryResult discoverTestClasses() { Set allTestAnnotations = collectTestAnnotations(index); Set allTestClasses = new HashSet<>(); Map enclosingClasses = new HashMap<>(); + Map profiles = new HashMap<>(); + Map rcls = new HashMap<>(); for (DotName annotation : allTestAnnotations) { for (AnnotationInstance instance : index.getAnnotations(annotation)) { if (instance.target().kind() == AnnotationTarget.Kind.METHOD) { @@ -646,6 +651,17 @@ private DiscoveryResult discoverTestClasses() { if (Modifier.isAbstract(clazz.flags())) { continue; } + AnnotationInstance testProfile = clazz.declaredAnnotation(TEST_PROFILE); + // TODO is there a cleaner way to do this? probably! + if (testProfile == null) { + profiles.put(name, "no-profile"); + } else { + System.out.println( + "HOLLY profile is " + testProfile + testProfile.value().asString() + testProfile.value().asClass() + + testProfile.value().name()); + profiles.put(name, + testProfile.value().asString()); + } unitTestClasses.add(name); } @@ -658,28 +674,45 @@ private DiscoveryResult discoverTestClasses() { System.out.println( "HOLLY after the re-add or whatever? quarkus test classes is " + Arrays.toString(quarkusTestClasses.toArray())); - ClassLoader rcl = null; System.out.println("classload thread is " + Thread.currentThread()); for (String i : quarkusTestClasses) { ClassLoader old = Thread.currentThread().getContextClassLoader(); + String profileName = profiles.get(i); + if (profileName == null) { + profileName = "no-profile"; + } + System.out.println("HOLLY profile name is " + profileName); + ClassLoader rcl = rcls.get(profileName); + System.out.println("HOLLY rcl is " + rcl); + // rcl = null; // TODO diagnostics try { if (rcl == null) { System.out.println("HOLLY Making a java start with " + testApplication); // Although it looks like we need to start once per class, the class is just indicative of where classes for this module live + Class profile = null; + // TODO diagnostics + if (!"no-profile".equals(profileName)) { + //TODO is this the right classloader to use? + profile = Class.forName(profileName); + System.out.println("HOLLY setting profile to " + profile); + } // CuratedApplications cannot (right now) be re-used between restarts. So even though the builder gave us a // curated application, don't use it. // TODO can we make the app re-usable, or otherwise leverage the app that we got passed in? - // TODO sort out profiles! + System.out.println("HOLLY will make an app using profile " + profile); rcl = new AppMakerHelper() .getStartupAction(Thread.currentThread().getContextClassLoader().loadClass(i), null, - true, null); + true, profile); + rcls.put(profileName, rcl); } + // TODO do we need to set a TCCL? Behaviour seems the same either way Thread.currentThread().setContextClassLoader(rcl); System.out.println("639 HOLLY loading quarkus test with " + Thread.currentThread().getContextClassLoader()); - itClasses.add(Thread.currentThread().getContextClassLoader().loadClass(i)); + + itClasses.add(rcl.loadClass(i)); } catch (Exception e) { System.out.println("HOLLY BAD BAD" + e); log.warnf( diff --git a/extensions/vertx-http/dev-ui-tests/src/main/java/io/quarkus/devui/tests/DevUIJsonRPCTest.java b/extensions/vertx-http/dev-ui-tests/src/main/java/io/quarkus/devui/tests/DevUIJsonRPCTest.java index 2c6e51397d64d..164e2f643dccc 100644 --- a/extensions/vertx-http/dev-ui-tests/src/main/java/io/quarkus/devui/tests/DevUIJsonRPCTest.java +++ b/extensions/vertx-http/dev-ui-tests/src/main/java/io/quarkus/devui/tests/DevUIJsonRPCTest.java @@ -46,6 +46,9 @@ public DevUIJsonRPCTest(String namespace) { public DevUIJsonRPCTest(String namespace, String testUrl) { this.namespace = namespace; this.testUrl = testUrl; + System.out.println("HOLLY config loading TCCL " + Thread.currentThread().getContextClassLoader()); + System.out.println("HOLLY config loading config ptovider class " + ConfigProvider.class.getClassLoader()); + System.out.println("HOLLY config loading this class " + this.getClass().getClassLoader()); String nonApplicationRoot = ConfigProvider.getConfig() .getOptionalValue("quarkus.http.non-application-root-path", String.class).orElse("q"); if (!nonApplicationRoot.startsWith("/")) { diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java index 38da1ca9714db..239e49aaa9397 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java @@ -215,7 +215,7 @@ public Thread newThread(Runnable r) { testHttpEndpointProviders = TestHttpEndpointProvider.load(); System.out.println("HOLLY during execution, TCCL is " + Thread.currentThread().getContextClassLoader()); - System.out.println("HOLLY the test was loaded with " + requiredTestClass); + System.out.println("HOLLY the test was loaded with " + requiredTestClass + requiredTestClass.getClassLoader()); // StartupAction startupAction = augmentAction.createInitialRuntimeApplication(); // clear the test.url system property as the value leaks into the run when using different profiles @@ -281,8 +281,10 @@ public Thread newThread(Runnable r) { TracingHandler.quarkusStarted(); + // TODO infinite loops? also causes all paramstests to fail + 37 failures?? + // ... and doesn't even fix the config problem + // ConfigProviderResolver.setInstance(new RunningAppConfigResolver(runningQuarkusApplication)); //now we have full config reset the hang timer - if (hangTaskKey != null) { hangTaskKey.cancel(false); hangTimeout = runningQuarkusApplication.getConfigValue(QUARKUS_TEST_HANG_DETECTION_TIMEOUT, Duration.class) diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/CustomLauncherInterceptor.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/CustomLauncherInterceptor.java index 48bd13b5e37c3..8cb1919a909f8 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/CustomLauncherInterceptor.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/CustomLauncherInterceptor.java @@ -7,23 +7,21 @@ public class CustomLauncherInterceptor implements LauncherInterceptor { - private final ClassLoader customClassLoader; private static int count = 0; private static int constructCount = 0; + FacadeClassLoader facadeLoader = null; public CustomLauncherInterceptor() throws Exception { - System.out.println(constructCount++ + "HOLLY interceipt construct" + getClass().getClassLoader()); + System.out.println(constructCount++ + "HOLLY interceipt constructor" + getClass().getClassLoader()); ClassLoader parent = Thread.currentThread() .getContextClassLoader(); - System.out.println("HOLLY CCL is " + parent); + System.out.println("HOLLY at interceptor construction, CCL is " + parent); - customClassLoader = parent; - System.out.println("HOLLY stored variable loader" + customClassLoader); } @Override public T intercept(Invocation invocation) { - System.out.println("HOLLY intercept"); + System.out.println("HOLLY intercept top level" + invocation); if (System.getProperty("prod.mode.tests") != null) { return invocation.proceed(); @@ -39,21 +37,33 @@ public T intercept(Invocation invocation) { private T nintercept(Invocation invocation) { ClassLoader old = Thread.currentThread().getContextClassLoader(); - System.out.println("Interceipt, TCCL is " + old); + System.out.println("Interceipt, TCCL is " + old + Thread.currentThread()); // Don't make a facade loader if the JUnitRunner got there ahead of us // they set a runtime classloader so handle that too if (!(old instanceof FacadeClassLoader) || old instanceof QuarkusClassLoader && old.getName().contains("Runtime")) { - System.out.println("HOLLY INTERCEPT RESTART ------------------------------"); + System.out.println( + "HOLLY intercept constructing a classloader ------------------------------" + Thread.currentThread()); try { + // TODO we should be able to do better than this here + //TODO We want to tidy up classloaders we created, but not ones created upstream + facadeLoader = null; // TODO diagnostics // TODO should this be a static variable, so we don't make zillions and cause too many files exceptions? // Although in principle we only go through a few times - FacadeClassLoader facadeLoader = new FacadeClassLoader(old); + if (facadeLoader == null) { + facadeLoader = new FacadeClassLoader(old); + } Thread.currentThread() .setContextClassLoader(facadeLoader); + System.out.println("HOLLY did set TCCL " + Thread.currentThread()); return invocation.proceed(); } finally { - Thread.currentThread() - .setContextClassLoader(old); + System.out.println("HOLLY doing finally, setting back to " + old); + + // TODO It would clearly be nice to tidy up, but I think the gradle + // devtools tests may be asynchronous, because if we reset the TCCL + // at this point, the test loads with the wrong classloader + // Thread.currentThread() + // .setContextClassLoader(old); } } else { return invocation.proceed(); @@ -63,11 +73,10 @@ private T nintercept(Invocation invocation) { @Override public void close() { - // // try { - // // // TODO customClassLoader.close(); - // // } catch (Exception e) { - // // throw new UncheckedIOException("Failed to close custom class - // loader", e); - // // } + try { + facadeLoader.close(); + } catch (Exception e) { + throw new RuntimeException("Failed to close custom classloader", e); + } } }