diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java index 67b74acdc1be7..555277471721d 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java @@ -7,6 +7,7 @@ import org.gradle.api.plugins.BasePlugin; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.tasks.TaskContainer; +import org.gradle.api.tasks.testing.Test; import org.gradle.util.GradleVersion; import io.quarkus.gradle.tasks.QuarkusAddExtension; @@ -15,6 +16,7 @@ import io.quarkus.gradle.tasks.QuarkusGenerateConfig; import io.quarkus.gradle.tasks.QuarkusListExtensions; import io.quarkus.gradle.tasks.QuarkusNative; +import io.quarkus.gradle.tasks.QuarkusTestConfig; /** * @author Ståle Pedersen @@ -51,6 +53,10 @@ private void registerTasks(Project project) { }); tasks.create("buildNative", QuarkusNative.class).dependsOn(quarkusBuild); + + // Quarkus test configuration task which should be executed before any Quarkus test + final QuarkusTestConfig quarkusTestConfig = tasks.create("quarkusTestConfig", QuarkusTestConfig.class); + tasks.withType(Test.class).forEach(t -> t.dependsOn(quarkusTestConfig)); } private void verifyGradleVersion() { diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusTestConfig.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusTestConfig.java new file mode 100644 index 0000000000000..459de25f8dec8 --- /dev/null +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusTestConfig.java @@ -0,0 +1,44 @@ +package io.quarkus.gradle.tasks; + +import java.util.List; +import java.util.Map; + +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.testing.Test; + +import io.quarkus.bootstrap.BootstrapClassLoaderFactory; +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.gradle.QuarkusPluginExtension; + +public class QuarkusTestConfig extends QuarkusTask { + + public QuarkusTestConfig() { + super("Sets the necessary system properties for the Quarkus tests to run."); + } + + @TaskAction + public void setupTest() { + final QuarkusPluginExtension quarkusExt = extension(); + try { + final List deploymentDeps = quarkusExt.resolveAppModel().resolveModel(quarkusExt.getAppArtifact()) + .getDeploymentDependencies(); + final StringBuilder buf = new StringBuilder(); + for (AppDependency dep : deploymentDeps) { + buf.append(dep.getArtifact().getPath().toUri().toURL().toExternalForm()); + buf.append(' '); + } + final String deploymentCp = buf.toString(); + final String nativeRunner = getProject().getBuildDir().toPath().resolve(quarkusExt.finalName() + "-runner") + .toAbsolutePath() + .toString(); + + for (Test test : getProject().getTasks().withType(Test.class)) { + final Map props = test.getSystemProperties(); + props.put(BootstrapClassLoaderFactory.PROP_DEPLOYMENT_CP, deploymentCp); + props.put("native.image.path", nativeRunner); + } + } catch (Exception e) { + throw new IllegalStateException("Failed to resolve deployment classpath", e); + } + } +} diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapClassLoaderFactory.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapClassLoaderFactory.java index 0bccbb9171bce..47ba356641da1 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapClassLoaderFactory.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapClassLoaderFactory.java @@ -35,8 +35,9 @@ public class BootstrapClassLoaderFactory { private static final String DEPLOYMENT_CP = "deployment.cp"; public static final String PROP_CP_CACHE = "quarkus-classpath-cache"; - public static final String PROP_WS_DISCOVERY = "quarkus-workspace-discovery"; + public static final String PROP_DEPLOYMENT_CP = "quarkus-deployment-cp"; public static final String PROP_OFFLINE = "quarkus-bootstrap-offline"; + public static final String PROP_WS_DISCOVERY = "quarkus-workspace-discovery"; private static final int CP_CACHE_FORMAT_ID = 1; 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 c122b7898cbd1..7c9fbb9ce8a61 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 @@ -7,11 +7,13 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Map; @@ -73,18 +75,7 @@ private ExtensionState doJavaStart(ExtensionContext context, TestResourceManager Path appClassLocation = getAppClassLocation(context.getRequiredTestClass()); - try { - appCl = BootstrapClassLoaderFactory.newInstance() - .setAppClasses(appClassLocation) - .setParent(getClass().getClassLoader()) - .setOffline(PropertyUtils.getBooleanOrNull(BootstrapClassLoaderFactory.PROP_OFFLINE)) - .setLocalProjectsDiscovery( - PropertyUtils.getBoolean(BootstrapClassLoaderFactory.PROP_WS_DISCOVERY, true)) - .setEnableClasspathCache(PropertyUtils.getBoolean(BootstrapClassLoaderFactory.PROP_CP_CACHE, true)) - .newDeploymentClassLoader(); - } catch (BootstrapException e) { - throw new IllegalStateException("Failed to create the boostrap class loader", e); - } + appCl = createQuarkusBuildClassLoader(appClassLocation); originalCl = setCCL(appCl); final Path testClassLocation = getTestClassesLocation(context.getRequiredTestClass()); @@ -262,6 +253,47 @@ public void run() { return new ExtensionState(testResourceManager, shutdownTask, false); } + /** + * Creates a classloader that will be used to build the test application. + * + * This method assumes that the runtime classes are already on the classpath + * of the classloader that loaded this class. + * What this method does is it resolves the required deployment classpath + * and creates a new URL classloader that includes the deployment CP with + * the classloader that loaded this class as its parent. + * + * @param appClassLocation location of the test application classes + * @return application build classloader + */ + private URLClassLoader createQuarkusBuildClassLoader(Path appClassLocation) { + // The deployment classpath could be passed in as a system property. + // This is how integration with the Gradle plugin is achieved. + final String deploymentCp = PropertyUtils.getProperty(BootstrapClassLoaderFactory.PROP_DEPLOYMENT_CP); + if (deploymentCp != null && !deploymentCp.isEmpty()) { + final List list = new ArrayList<>(); + for (String entry : deploymentCp.split("\\s")) { + try { + list.add(new URL(entry)); + } catch (MalformedURLException e) { + throw new IllegalStateException("Failed to parse a deployment classpath entry " + entry, e); + } + } + return new URLClassLoader(list.toArray(new URL[list.size()]), getClass().getClassLoader()); + } + try { + return BootstrapClassLoaderFactory.newInstance() + .setAppClasses(appClassLocation) + .setParent(getClass().getClassLoader()) + .setOffline(PropertyUtils.getBooleanOrNull(BootstrapClassLoaderFactory.PROP_OFFLINE)) + .setLocalProjectsDiscovery( + PropertyUtils.getBoolean(BootstrapClassLoaderFactory.PROP_WS_DISCOVERY, true)) + .setEnableClasspathCache(PropertyUtils.getBoolean(BootstrapClassLoaderFactory.PROP_CP_CACHE, true)) + .newDeploymentClassLoader(); + } catch (BootstrapException e) { + throw new IllegalStateException("Failed to create the boostrap class loader", e); + } + } + @Override public void afterEach(ExtensionContext context) throws Exception { restAssuredURLManager.clearURL();