From acb6963c48768fe6a8873fc83d3db9ea7f855ea7 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 20 Mar 2023 11:35:22 -0400 Subject: [PATCH] finish quarkus:run and integration tests --- .../deployment/run/RunCommandHandler.java | 31 ++++---- .../run/RunCommandLauncherBuildItem.java | 7 +- .../deployment/run/RunCommandProcessor.java | 67 +++++++++++++++++ .../main/java/io/quarkus/maven/RunMojo.java | 53 ++++++++----- .../deployment/AzureFunctionsRunCommand.java | 8 +- .../test/common/RunCommandLauncher.java | 74 ++++++++----------- .../QuarkusIntegrationTestExtension.java | 9 ++- .../test/junit/launcher/ConfigUtil.java | 5 ++ 8 files changed, 168 insertions(+), 86 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/run/RunCommandHandler.java b/core/deployment/src/main/java/io/quarkus/deployment/run/RunCommandHandler.java index 34921d50d57724..e33d01ded2a949 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/run/RunCommandHandler.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/run/RunCommandHandler.java @@ -1,10 +1,11 @@ package io.quarkus.deployment.run; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Consumer; -import java.util.stream.Collectors; import io.quarkus.builder.BuildResult; @@ -13,25 +14,25 @@ public class RunCommandHandler implements BiConsumer { @Override public void accept(Object o, BuildResult buildResult) { RunCommandLaunchResultBuildItem result = buildResult.consume(RunCommandLaunchResultBuildItem.class); - Consumer consumer = (Consumer) o; - LinkedList list = new LinkedList(); - // Classloaders are different when called so we have to pass back - // instances of class that would be in system classloader - if (result.getCmds() == null || result.getCmds().isEmpty()) { - list.add(new NullPointerException()); // no command found - return; - } - if (result.getCmds().size() > 1) { - list.add(new IndexOutOfBoundsException( - result.getCmds().stream().map(i -> i.getCommand()).collect(Collectors.joining(" ")))); + + // FYI: AugmentAction.performCustomBuild runs in its own classloader + // so we can only pass back instances of those classes in the system classloader + + Consumer> consumer = (Consumer>) o; + Map entries = new HashMap<>(); + for (RunCommandLauncherBuildItem item : result.getCmds()) { + LinkedList itemList = new LinkedList(); + addLaunchCommand(itemList, item); + entries.put(item.getCommand(), itemList); } - RunCommandLauncherBuildItem item = result.getCmds().get(0); + consumer.accept(entries); + } + + private void addLaunchCommand(List list, RunCommandLauncherBuildItem item) { list.add(item.getArgs()); list.add(item.getWorkingDirectory()); list.add(item.getStartedExpression()); list.add(item.isNeedsLogfile()); list.add(item.getLogFile()); - - consumer.accept(list); } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/run/RunCommandLauncherBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/run/RunCommandLauncherBuildItem.java index 72e3a799242a2f..c8c166242a2be5 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/run/RunCommandLauncherBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/run/RunCommandLauncherBuildItem.java @@ -1,21 +1,22 @@ package io.quarkus.deployment.run; import java.nio.file.Path; -import java.util.ArrayList; import java.util.List; import io.quarkus.builder.item.MultiBuildItem; public final class RunCommandLauncherBuildItem extends MultiBuildItem { private final String command; - private final List args = new ArrayList<>(); + private final List args; private Path workingDirectory; private String startedExpression; private Path logFile; private boolean needsLogfile; - public RunCommandLauncherBuildItem(String command, Path workingDirectory, String startedExpression, Path logFile, + public RunCommandLauncherBuildItem(String command, List args, Path workingDirectory, String startedExpression, + Path logFile, boolean needsLogfile) { + this.args = args; this.command = command; this.workingDirectory = workingDirectory; this.startedExpression = startedExpression; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/run/RunCommandProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/run/RunCommandProcessor.java index d09feeb4fe149f..86efdb31a5ee57 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/run/RunCommandProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/run/RunCommandProcessor.java @@ -1,13 +1,80 @@ package io.quarkus.deployment.run; +import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.DEFAULT_FAST_JAR_DIRECTORY_NAME; +import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.QUARKUS_RUN_JAR; + +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; +import java.util.Map; +import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.pkg.PackageConfig; +import io.quarkus.deployment.pkg.builditem.LegacyJarRequiredBuildItem; +import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; +import io.quarkus.deployment.pkg.builditem.UberJarRequiredBuildItem; public class RunCommandProcessor { + private static final String JAVA_HOME_SYS = "java.home"; + private static final String JAVA_HOME_ENV = "JAVA_HOME"; + @BuildStep public RunCommandLaunchResultBuildItem commands(List cmds) { return new RunCommandLaunchResultBuildItem(cmds); } + @BuildStep + public void defaultJavaCommand(PackageConfig packageConfig, + OutputTargetBuildItem jar, + List uberJarRequired, + List legacyJarRequired, + BuildProducer cmds) { + + Path jarPath = null; + if (legacyJarRequired.isEmpty() && (!uberJarRequired.isEmpty() + || packageConfig.type.equalsIgnoreCase(PackageConfig.UBER_JAR))) { + jarPath = jar.getOutputDirectory() + .resolve(jar.getBaseName() + packageConfig.getRunnerSuffix() + ".jar"); + } else if (!legacyJarRequired.isEmpty() || packageConfig.isLegacyJar() + || packageConfig.type.equalsIgnoreCase(PackageConfig.LEGACY)) { + jarPath = jar.getOutputDirectory() + .resolve(jar.getBaseName() + packageConfig.getRunnerSuffix() + ".jar"); + } else { + jarPath = jar.getOutputDirectory().resolve(DEFAULT_FAST_JAR_DIRECTORY_NAME).resolve(QUARKUS_RUN_JAR); + + } + + List args = new ArrayList<>(); + args.add(determineJavaPath()); + + for (Map.Entry e : System.getProperties().entrySet()) { + args.add("-D" + e.getKey().toString() + "=" + e.getValue().toString()); + } + args.add("-jar"); + args.add(jarPath.toAbsolutePath().toString()); + cmds.produce(new RunCommandLauncherBuildItem("java", args, null, null, null, false)); + } + + private String determineJavaPath() { + // try system property first - it will be the JAVA_HOME used by the current JVM + String home = System.getProperty(JAVA_HOME_SYS); + if (home == null) { + // No luck, somewhat a odd JVM not enforcing this property + // try with the JAVA_HOME environment variable + home = System.getenv(JAVA_HOME_ENV); + } + if (home != null) { + File javaHome = new File(home); + File file = new File(javaHome, "bin/java"); + if (file.exists()) { + return file.getAbsolutePath(); + } + } + + // just assume 'java' is on the system path + return "java"; + } + } diff --git a/devtools/maven/src/main/java/io/quarkus/maven/RunMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/RunMojo.java index 9768cd4bbe9520..ab1b595c0f26e1 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/RunMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/RunMojo.java @@ -8,6 +8,7 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import java.util.stream.Collectors; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -48,29 +49,45 @@ protected void doExecute() throws MojoExecutionException, MojoFailureException { try (CuratedApplication curatedApplication = bootstrapApplication()) { AugmentAction action = curatedApplication.createAugmentor(); - AtomicReference exists = new AtomicReference<>(false); + AtomicReference exists = new AtomicReference<>(); AtomicReference tooMany = new AtomicReference<>(); - action.performCustomBuild(RunCommandHandler.class.getName(), new Consumer() { + String target = System.getProperty("quarkus.run.target"); + action.performCustomBuild(RunCommandHandler.class.getName(), new Consumer>() { @Override - public void accept(List cmd) { - if (cmd.get(0) instanceof NullPointerException) { - exists.set(false); + public void accept(Map cmds) { + List cmd = null; + if (target != null) { + cmd = cmds.get(target); + if (cmd == null) { + exists.set(false); + return; + } + } else if (cmds.size() == 1) { // defaults to pure java run + cmd = cmds.values().iterator().next(); + } else if (cmds.size() == 2) { // choose not default + for (Map.Entry entry : cmds.entrySet()) { + if (entry.getKey().equals("java")) + continue; + cmd = entry.getValue(); + break; + } + } else if (cmds.size() > 2) { + tooMany.set(cmds.keySet().stream().collect(Collectors.joining(" "))); return; } else { - exists.set(true); - } - if (cmd.get(0) instanceof IndexOutOfBoundsException) { - tooMany.set(((IndexOutOfBoundsException) cmd.get(0)).getMessage()); - return; + throw new RuntimeException("Should never reach this!"); } List args = (List) cmd.get(0); + System.out.println("Executing \"" + String.join(" ", args) + "\""); Path workingDirectory = (Path) cmd.get(1); try { - Process process = new ProcessBuilder() + ProcessBuilder builder = new ProcessBuilder() .command(args) - .inheritIO() - .directory(workingDirectory.toFile()) - .start(); + .inheritIO(); + if (workingDirectory != null) { + builder.directory(workingDirectory.toFile()); + } + Process process = builder.start(); int exit = process.waitFor(); } catch (Exception e) { throw new RuntimeException(e); @@ -78,13 +95,13 @@ public void accept(List cmd) { } }, RunCommandLaunchResultBuildItem.class.getName()); - if (!exists.get()) { - getLog().info( - "You do not need quarkus:run to execute your Quarkus project. Just execute it like a plain Java application with java -jar"); + if (target != null && !exists.get()) { + getLog().error("quarkus.run.target " + target + " is not found"); return; } if (tooMany.get() != null) { - getLog().error("Run Failed: Too many installed extensions support quarkus:run. Can only have one"); + getLog().error( + "Too many installed extensions support quarkus:run. Use -Dquarkus.run.target= to choose"); getLog().error("Extensions: " + tooMany.get()); } } finally { diff --git a/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsRunCommand.java b/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsRunCommand.java index 73772f4dd580e7..cdc8b33e6ef244 100644 --- a/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsRunCommand.java +++ b/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsRunCommand.java @@ -4,6 +4,7 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; +import java.util.LinkedList; import java.util.List; import java.util.OptionalInt; @@ -47,11 +48,12 @@ public RunCommandLauncherBuildItem run(List functions, O checkRuntimeExistence(commandHandler); - RunCommandLauncherBuildItem launcher = new RunCommandLauncherBuildItem("azure-functions", stagingDir, + String cmd = getStartFunctionHostCommand(config); + List args = new LinkedList<>(); + Arrays.stream(cmd.split(" ")).forEach(s -> args.add(s)); + RunCommandLauncherBuildItem launcher = new RunCommandLauncherBuildItem("azure-functions", args, stagingDir, STARTED_EXPRESSION, null, true); - String cmd = getStartFunctionHostCommand(config); - Arrays.stream(cmd.split(" ")).forEach(s -> launcher.getArgs().add(s)); return launcher; } diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/RunCommandLauncher.java b/test-framework/common/src/main/java/io/quarkus/test/common/RunCommandLauncher.java index 5c8e289912a297..171affcbc1f761 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/RunCommandLauncher.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/RunCommandLauncher.java @@ -11,7 +11,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -19,9 +18,9 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.apache.commons.io.input.TeeInputStream; @@ -31,7 +30,6 @@ import io.quarkus.bootstrap.app.QuarkusBootstrap; import io.quarkus.deployment.run.RunCommandHandler; import io.quarkus.deployment.run.RunCommandLaunchResultBuildItem; -import io.quarkus.deployment.run.RunCommandLauncherBuildItem; import io.quarkus.test.common.http.TestHTTPResourceManager; public class RunCommandLauncher implements ArtifactLauncher { @@ -48,59 +46,49 @@ public class RunCommandLauncher implements ArtifactLauncher exists = new AtomicReference<>(false); - AtomicReference tooMany = new AtomicReference<>(); - List args = new ArrayList<>(); - AtomicReference workingDir = new AtomicReference<>(); - AtomicReference startedExpression = new AtomicReference<>(); - AtomicReference needsLogfile = new AtomicReference<>(); - AtomicReference logFilePath = new AtomicReference<>(); + public static RunCommandLauncher tryLauncher(QuarkusBootstrap bootstrap, String target, Duration waitTime) { + Map cmds = new HashMap<>(); try (CuratedApplication curatedApplication = bootstrap.bootstrap()) { AugmentAction action = curatedApplication.createAugmentor(); - action.performCustomBuild(RunCommandHandler.class.getName(), new Consumer() { + action.performCustomBuild(RunCommandHandler.class.getName(), new Consumer>() { @Override - public void accept(List cmd) { - if (cmd.get(0) instanceof NullPointerException) { - exists.set(false); - return; - } else { - exists.set(true); - } - if (cmd.get(0) instanceof IndexOutOfBoundsException) { - tooMany.set(((IndexOutOfBoundsException) cmd.get(0)).getMessage()); - return; - } - args.addAll((List) cmd.get(0)); - workingDir.set((Path) cmd.get(1)); - startedExpression.set((String) cmd.get(2)); - needsLogfile.set((Boolean) cmd.get(3)); - logFilePath.set((Path) cmd.get(4)); + public void accept(Map accepted) { + cmds.putAll(accepted); } }, RunCommandLaunchResultBuildItem.class.getName()); } catch (BootstrapException ex) { throw new RuntimeException(ex); - } catch (IllegalArgumentException ill) { - if (ill.getMessage().contains(RunCommandLauncherBuildItem.class.getName())) { - return null; - } - throw ill; } - if (!exists.get()) { + List cmd = null; + if (target != null) { + cmd = cmds.get(target); + if (cmd == null) { + throw new RuntimeException("quarkus.run.target \"" + target + "\" does not exist"); + } + } else if (cmds.size() == 1) { // defaults to pure java run return null; - } - if (tooMany.get() != null) { + } else if (cmds.size() == 2) { // choose not default + for (Map.Entry entry : cmds.entrySet()) { + if (entry.getKey().equals("java")) + continue; + cmd = entry.getValue(); + break; + } + } else if (cmds.size() > 2) { + String tooMany = cmds.keySet().stream().collect(Collectors.joining(" ")); throw new RuntimeException( - "Too many extensions support quarkus:run. Need to remove one for integration tests to work: " - + tooMany.get()); + "Too many extensions support quarkus:run. Set quarkus.run.target to pick one to run during integration tests: " + + tooMany); + } else { + throw new RuntimeException("Should never reach this!"); } RunCommandLauncher launcher = new RunCommandLauncher(); - launcher.args = args; - launcher.workingDir = workingDir.get(); - launcher.startedExpression = startedExpression.get(); - launcher.needsLogFile = needsLogfile.get(); - launcher.logFilePath = logFilePath.get(); + launcher.args = (List) cmd.get(0); + launcher.workingDir = (Path) cmd.get(1); + launcher.startedExpression = (String) cmd.get(2); + launcher.needsLogFile = (Boolean) cmd.get(3); + launcher.logFilePath = (Path) cmd.get(4); launcher.waitTimeSeconds = waitTime.getSeconds(); return launcher; } diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusIntegrationTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusIntegrationTestExtension.java index 217102305baa49..4d954fabc00f89 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusIntegrationTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusIntegrationTestExtension.java @@ -29,6 +29,7 @@ import java.util.function.Function; import java.util.stream.Collectors; +import org.eclipse.microprofile.config.Config; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.AfterTestExecutionCallback; @@ -54,7 +55,6 @@ import io.quarkus.test.junit.callback.QuarkusTestMethodContext; import io.quarkus.test.junit.launcher.ArtifactLauncherProvider; import io.quarkus.test.junit.launcher.ConfigUtil; -import io.smallrye.config.SmallRyeConfig; public class QuarkusIntegrationTestExtension extends AbstractQuarkusTestWithContextExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback, BeforeEachCallback, AfterEachCallback, @@ -262,11 +262,12 @@ public void close() throws Throwable { if ((testHost != null) && !testHost.isEmpty()) { launcher = new TestHostLauncher(); } else { - SmallRyeConfig config = (SmallRyeConfig) LauncherUtil.installAndGetSomeConfig(); + Config config = LauncherUtil.installAndGetSomeConfig(); Duration waitDuration = ConfigUtil.waitTimeValue(config); - // try to execute a run command if it exists. We do this so that extensions that have a custom run don't have to create any special artifact type + String target = ConfigUtil.runTarget(config); + // try to execute a run command published by an extension if it exists. We do this so that extensions that have a custom run don't have to create any special artifact type launcher = RunCommandLauncher.tryLauncher(devServicesLaunchResult.getCuratedApplication().getQuarkusBootstrap(), - waitDuration); + target, waitDuration); if (launcher == null) { ServiceLoader loader = ServiceLoader.load(ArtifactLauncherProvider.class); for (ArtifactLauncherProvider launcherProvider : loader) { diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/ConfigUtil.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/ConfigUtil.java index 2e77ecb7c9ea4f..bb7933ce295515 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/ConfigUtil.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/launcher/ConfigUtil.java @@ -44,4 +44,9 @@ public static String integrationTestProfile(Config config) { .orElseGet(() -> config.getOptionalValue("quarkus.test.native-image-profile", String.class) .orElse(null)); } + + public static String runTarget(Config config) { + return config.getOptionalValue("quarkus.run.target", String.class) + .orElse(null); + } }