diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 5acc5e25034549..9f2337941880c8 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -138,7 +138,7 @@ 11.5.8.0 1.2.6 5.3.0 - 5.9.3 + 5.10.0 1.5.0 14.0.11.Final 4.6.2.Final diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/CoreQuarkusTestExtension.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/CoreQuarkusTestExtension.java index 8d85ff1ba81852..ca9d10545bfcc7 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/CoreQuarkusTestExtension.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/CoreQuarkusTestExtension.java @@ -57,8 +57,6 @@ public class CoreQuarkusTestExtension { protected static final String TEST_LOCATION = "test-location"; protected static final String TEST_CLASS = "test-class"; - protected static Class currentJUnitTestClass; - /// end copied private static final Logger log = Logger.getLogger(CoreQuarkusTestExtension.class); @@ -94,10 +92,17 @@ public PrepareResult(AugmentAction augmentAction, QuarkusTestProfile profileInst } } - // Re-used from AbstractJvmQuarkusTestExtension protected PrepareResult createAugmentor(Class requiredTestClass, Class profile, Collection shutdownTasks) throws Exception { + Path testClassLocation = getTestClassesLocation(requiredTestClass); + return createAugmentor(testClassLocation, profile, shutdownTasks); + } + + // Re-used from AbstractJvmQuarkusTestExtension + protected PrepareResult createAugmentor(Path testClassLocation, Class profile, + Collection shutdownTasks) throws Exception { + // I think the required test class is just an example, since the augmentor is only // created once per test profile final PathList.Builder rootBuilder = PathList.builder(); @@ -108,9 +113,6 @@ protected PrepareResult createAugmentor(Class requiredTestClass, Class props = new HashMap<>(); props.put(TEST_LOCATION, testClassLocation); - props.put(TEST_CLASS, requiredTestClass); + // TODO surely someone reads this? props.put(TEST_CLASS, requiredTestClass); // TODO what's going on here with the profile? Class quarkusTestProfile = profile; PrepareResult result = new PrepareResult(curatedApplication @@ -301,7 +303,8 @@ public ClassLoader doJavaStart(Class testClass, CuratedApplication curatedApplic QuarkusTestProfile profileInstance = result.profileInstance; testHttpEndpointProviders = TestHttpEndpointProvider.load(); - System.out.println("CORE MAKER SEES CLASS OF STARTUP " + StartupAction.class.getClassLoader()); + System.out.println( + "CORE MAKER SEES CLASS OF STARTUP " + StartupAction.class.getClassLoader()); System.out.println("HOLLY about to make app for " + testClass); StartupAction startupAction = augmentAction.createInitialRuntimeApplication(); @@ -312,6 +315,27 @@ public ClassLoader doJavaStart(Class testClass, CuratedApplication curatedApplic } + public ClassLoader doJavaStart(Path location, CuratedApplication curatedApplication) throws Exception { + Class profile = null; + // TODO do we want any of these? + Collection shutdownTasks = new HashSet(); + PrepareResult result = createAugmentor(location, profile, shutdownTasks); + AugmentAction augmentAction = result.augmentAction; + QuarkusTestProfile profileInstance = result.profileInstance; + + testHttpEndpointProviders = TestHttpEndpointProvider.load(); + System.out.println( + "CORE MAKER SEES CLASS OF STARTUP " + StartupAction.class.getClassLoader()); + + System.out.println("HOLLY about to make app for " + location); + StartupAction startupAction = augmentAction.createInitialRuntimeApplication(); + // TODO this seems to be safe to do because the classloaders are the same + startupAction.store(); + System.out.println("HOLLY did store " + startupAction); + return startupAction.getClassLoader(); + + } + // TODO can we defer this and move it back to the junit5 module? public static class TestBuildChainFunction implements Function, List>> { @@ -319,8 +343,15 @@ public static class TestBuildChainFunction implements Function> apply(Map stringObjectMap) { Path testLocation = (Path) stringObjectMap.get(TEST_LOCATION); // the index was written by the extension - Index testClassesIndex = TestClassIndexer.readIndex(testLocation, (Class) stringObjectMap.get(TEST_CLASS)); - + Class testClass = (Class) stringObjectMap.get(TEST_CLASS); + // TODO is this at all safe? + + Index testClassesIndex; + if (testClass != null) { + testClassesIndex = TestClassIndexer.readIndex(testLocation, testClass); + } else { + testClassesIndex = TestClassIndexer.readIndex(testLocation); + } List> allCustomizers = new ArrayList<>(1); Consumer defaultCustomizer = new Consumer() { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/ModuleTestRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/ModuleTestRunner.java index ef68382b374b6e..787c2d859d7d4b 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/ModuleTestRunner.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/ModuleTestRunner.java @@ -25,6 +25,7 @@ public class ModuleTestRunner { public ModuleTestRunner(TestSupport testSupport, CuratedApplication testApplication, DevModeContext.ModuleInfo moduleInfo) { + System.out.println("HOLLY making module test runner"); this.testSupport = testSupport; this.testApplication = testApplication; this.moduleInfo = moduleInfo; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestClassIndexer.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestClassIndexer.java index cf594a78e35707..4ac2ddd04d17f4 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestClassIndexer.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestClassIndexer.java @@ -68,6 +68,10 @@ public static Index readIndex(Class testClass) { return readIndex(getTestClassesLocation(testClass), testClass); } + public static Index readIndex(Path testLocation) { + return indexTestClasses(testLocation); + } + public static Index readIndex(Path testClassLocation, Class testClass) { Path path = indexPath(testClassLocation, testClass); if (path.toFile().exists()) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java index a03138e2f7e3a5..d7eb95512624af 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java @@ -86,6 +86,7 @@ public class TestSupport implements TestController { public TestSupport(CuratedApplication curatedApplication, List compilationProviders, DevModeContext context, DevModeType devModeType) { + System.out.println("HOLLY making test support"); this.curatedApplication = curatedApplication; this.compilationProviders = compilationProviders; this.context = context; diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java index 82f918503bd704..1566048b145f64 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java @@ -7,7 +7,6 @@ import java.nio.file.Path; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Deque; import java.util.HashMap; @@ -323,9 +322,9 @@ private boolean isRuntimeArtifact(ArtifactKey key) { } private void visitRuntimeDependencies(List list) { - System.out.println(list.size() + "HOLLY will visit " + Arrays.toString(list.toArray())); + // System.out.println(list.size() + "HOLLY will visit " + Arrays.toString(list.toArray())); for (DependencyNode n : list) { - System.out.println("HOLLY visiting " + n); + // System.out.println("HOLLY visiting " + n); visitRuntimeDependency(n); } } @@ -348,9 +347,9 @@ private void visitRuntimeDependency(DependencyNode node) { } try { - System.out.println("about to get " + node + ".>" + artifact); + // System.out.println("about to get " + node + ".>" + artifact); final ExtensionDependency extDep = getExtensionDependencyOrNull(node, artifact); - System.out.println("got " + extDep); + // System.out.println("got " + extDep); if (dep == null) { WorkspaceModule module = null; diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index f74bee7337f3d9..38a0bdb333884c 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -47,7 +47,7 @@ 3.24.2 0.9.5 3.5.1.Final - 5.9.3 + 5.10.0 3.9.3 0.3.5 3.7.1 diff --git a/independent-projects/extension-maven-plugin/pom.xml b/independent-projects/extension-maven-plugin/pom.xml index dd6b9984ea31c8..b384a3f9a83801 100644 --- a/independent-projects/extension-maven-plugin/pom.xml +++ b/independent-projects/extension-maven-plugin/pom.xml @@ -45,7 +45,7 @@ 3.8.1 2.15.2 1.3.2 - 5.9.3 + 5.10.0 diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index a59202baa9c898..f67a7e9a13efee 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -55,7 +55,7 @@ 3.24.2 2.15.2 4.0.1 - 5.9.3 + 5.10.0-M1 1.23.0 3.5.1.Final 5.3.1 diff --git a/test-framework/junit5-properties/src/main/resources/junit-platform.properties b/test-framework/junit5-properties/src/main/resources/junit-platform.properties index cdac134076ffb3..d25c41defe02a7 100644 --- a/test-framework/junit5-properties/src/main/resources/junit-platform.properties +++ b/test-framework/junit5-properties/src/main/resources/junit-platform.properties @@ -1,2 +1,3 @@ junit.jupiter.extensions.autodetection.enabled=true junit.jupiter.testclass.order.default=io.quarkus.test.junit.util.QuarkusTestProfileAwareClassOrderer +junit.platform.launcher.interceptors.enabled=true diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java index af079e89bb715c..d2ea47bbb195cc 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java @@ -254,9 +254,9 @@ public static boolean hasPerTestResources(Class requiredTestClass) { return false; } - protected static class PrepareResult { - protected final AugmentAction augmentAction; - protected final QuarkusTestProfile profileInstance; + public static class PrepareResult { + public final AugmentAction augmentAction; + public final QuarkusTestProfile profileInstance; protected final CuratedApplication curatedApplication; protected final Path testClassLocation; 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 new file mode 100644 index 00000000000000..8f26c2a796aa3c --- /dev/null +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/CustomLauncherInterceptor.java @@ -0,0 +1,177 @@ +package io.quarkus.test.junit.launcher; + +import java.nio.file.Path; +import java.util.function.Consumer; + +import org.eclipse.microprofile.config.ConfigProvider; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherInterceptor; +import org.junit.platform.launcher.LauncherSession; + +import io.quarkus.bootstrap.app.CuratedApplication; +import io.quarkus.bootstrap.app.QuarkusBootstrap; +import io.quarkus.bootstrap.app.StartupActionHolder; +import io.quarkus.bootstrap.classloading.QuarkusClassLoader; +import io.quarkus.deployment.dev.testing.CoreQuarkusTestExtension; +import io.quarkus.deployment.dev.testing.CurrentTestApplication; +import io.quarkus.deployment.dev.testing.TestSupport; +import io.quarkus.runtime.LaunchMode; +import io.smallrye.config.SmallRyeConfig; + +public class CustomLauncherInterceptor implements LauncherInterceptor { + + protected static final String TEST_LOCATION = "test-location"; + protected static final String TEST_CLASS = "test-class"; + + private final ClassLoader customClassLoader; + + public CustomLauncherInterceptor() throws Exception { + System.out.println("HOLLY interceipt construct" + getClass().getClassLoader()); + ClassLoader parent = Thread.currentThread() + .getContextClassLoader(); + System.out.println("HOLLY CCL is " + parent); + + customClassLoader = parent; + System.out.println("HOLLY stored variable loader" + customClassLoader); + } + + @Override + public T intercept(Invocation invocation) { + // We visit this several times + System.out.println("HOLLY interceipt doing" + invocation); + System.out.println("HOLLY interceipt support is " + TestSupport.instance().isPresent()); + System.out.println("HOLLY interceipt holder is " + StartupActionHolder.getStored()); + // System.out.println("HOLLY classpaht is " + System.getProperty("java.class.path")); + + // Bypass all this in continuous testing mode; the startup action holder is our best way of detecting it + if (StartupActionHolder.getStored() == null) { + if (invocation instanceof LauncherSession) { + LauncherSession sess = (LauncherSession) invocation; + sess.getLauncher().registerLauncherDiscoveryListeners(new LauncherDiscoveryListener() { + @Override + public void launcherDiscoveryStarted(LauncherDiscoveryRequest request) { + System.out.println("YOYO discovery started " + request); + LauncherDiscoveryListener.super.launcherDiscoveryStarted(request); + } + }); + } + + Thread currentThread = Thread.currentThread(); + ClassLoader originalClassLoader = currentThread.getContextClassLoader(); + + // infinite loop, this is what triggers this method LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() + // .filters(includeClassNamePatterns(".*")) + // .build(); + // + // TestPlan plan = LauncherFactory.create().discover(request); + // + // for (TestIdentifier root : plan.getRoots()) { + // System.out.println("Root: " + root.toString()); + // + // for (TestIdentifier test : plan.getChildren(root)) { + // System.out.println("Found test: " + test.toString()); + // } + // } + + System.out.println("HOLLY before launch mode is " + LaunchMode.current()); + System.out.println("HOLLY other way us " + ConfigProvider.getConfig().unwrap(SmallRyeConfig.class).getProfiles()); + try { + System.out.println("HOLLY interceipt original" + originalClassLoader); + CoreQuarkusTestExtension coreQuarkusTestExtension = new CoreQuarkusTestExtension(); + + // TODO we normally start with a test class and work out the runtime classpath from that; + // here we need to go in the other direction. There's probably existing logic we can re-use, because this + // is seriously brittle + // TODO massive assumptions, completely ignoring multi module projects + // Assume the test class lives in the first element on the classpath + String s = System.getProperty("java.class.path") + .split(":")[0]; + System.out.println("HOLLY taking classath as " + s); + Path applicationRoot; + if (s.endsWith("jar")) { + applicationRoot = Path.of(s); + } else { + // Even if it's a test-classes dir, no need to go up two levels to the main project + // The PathTestHelper will find the path we need + applicationRoot = Path.of(s); + } + System.out.println("made app root" + applicationRoot); + + CuratedApplication curatedApplication; + // TODO this makes no sense here because we're on the wrong classloader unless a TCCL is already around, and we reset it + if (CurrentTestApplication.curatedApplication != null) { + System.out.println("Re-using curated application"); + curatedApplication = CurrentTestApplication.curatedApplication; + } else { + + System.out.println("MAKING Curated application"); + curatedApplication = QuarkusBootstrap.builder() + //.setExistingModel(gradleAppModel) + // unfortunately this model is not re-usable + // due + // to PathTree serialization by Gradle + .setIsolateDeployment(true) + .setMode( + QuarkusBootstrap.Mode.TEST) // Even in continuous testing, we set the mode to test - here, if we go down this path we know it's normal mode + // is this always right? + .setTest(true) + + .setApplicationRoot(applicationRoot) + .setProjectRoot(applicationRoot) + + // .setTargetDirectory( + // PathTestHelper + // .getProjectBuildDir( + // projectRoot, testClassLocation)) + // .setProjectRoot + // (projectRoot) + // .setApplicationRoot(rootBuilder.build()) + .build() + .bootstrap(); + + QuarkusClassLoader tcl = curatedApplication.createDeploymentClassLoader(); + + // TODO should we set the context classloader to the deployment classloader? + // If not, how will anyone retrieve it? + Consumer currentTestAppConsumer = (Consumer) tcl.loadClass(CurrentTestApplication.class.getName()) + .getDeclaredConstructor().newInstance(); + currentTestAppConsumer.accept(curatedApplication); + + // TODO move this to close shutdownTasks.add(curatedApplication::close); + } + + System.out.println("HOLLY after launch mode is " + LaunchMode.current()); + final QuarkusBootstrap.Mode currentMode = curatedApplication.getQuarkusBootstrap().getMode(); + ClassLoader loader = coreQuarkusTestExtension.doJavaStart(applicationRoot, + curatedApplication); + currentThread.setContextClassLoader(loader); + + System.out.println("HOLLY did set to " + currentThread.getContextClassLoader()); + + return invocation.proceed(); + + } catch (Exception e) { + e.printStackTrace(); + return invocation.proceed(); + } finally { + currentThread.setContextClassLoader(originalClassLoader); + } + } else { + // TODO should we be unsetting the classloader somewhere? + Thread.currentThread().setContextClassLoader(StartupActionHolder.getStored().getClassLoader()); + System.out.println("HOLLY NOW TCCL IS " + Thread.currentThread().getContextClassLoader()); + return invocation.proceed(); + } + } + + @Override + public void close() { + + // // try { + // // // TODO customClassLoader.close(); + // // } catch (Exception e) { + // // throw new UncheckedIOException("Failed to close custom class loader", e); + // // } + } +} diff --git a/test-framework/junit5/src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherInterceptor b/test-framework/junit5/src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherInterceptor new file mode 100644 index 00000000000000..94c0c6bbff4f92 --- /dev/null +++ b/test-framework/junit5/src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherInterceptor @@ -0,0 +1 @@ +io.quarkus.test.junit.launcher.CustomLauncherInterceptor diff --git a/test-framework/junit5/src/test/java/io/quarkus/test/junit/util/QuarkusTestProfileAwareClassOrdererTest.java b/test-framework/junit5/src/test/java/io/quarkus/test/junit/util/QuarkusTestProfileAwareClassOrdererTest.java index 4aaf942d225a31..a2dfbcb985cb2f 100644 --- a/test-framework/junit5/src/test/java/io/quarkus/test/junit/util/QuarkusTestProfileAwareClassOrdererTest.java +++ b/test-framework/junit5/src/test/java/io/quarkus/test/junit/util/QuarkusTestProfileAwareClassOrdererTest.java @@ -50,6 +50,7 @@ void singleClass() { } @Test + //@Disabled // TODO we broke test profiles void allVariants() { ClassDescriptor quarkusTest1Desc = quarkusDescriptorMock(Test01.class, null); ClassDescriptor quarkusTestWithUnrestrictedResourceDesc = quarkusDescriptorMock(Test02.class, Manager3.class, false);