diff --git a/core/deployment/src/main/java/io/quarkus/deployment/console/AeshConsole.java b/core/deployment/src/main/java/io/quarkus/deployment/console/AeshConsole.java index f9be7498a0d3e..76825c073ceb7 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/console/AeshConsole.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/console/AeshConsole.java @@ -225,10 +225,12 @@ public void run() { if (delegateConnection != null) { //console mode //just sent the input to the delegate - for (var k : keys) { - if (k == 27) { // escape key - exitCliMode(); - return; + if (keys.length == 1) { + for (var k : keys) { + if (k == 27) { // escape key + exitCliMode(); + return; + } } } if (delegateConnection.getStdinHandler() != null) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleProcessor.java index 49177547b2542..3182290ffd151 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleProcessor.java @@ -88,7 +88,10 @@ ConsoleInstalledBuildItem setupConsole(TestConfig config, @Consume(ConsoleInstalledBuildItem.class) @BuildStep void setupExceptionHandler(BuildProducer exceptionNotificationBuildItem, - EffectiveIdeBuildItem ideSupport) { + EffectiveIdeBuildItem ideSupport, LaunchModeBuildItem launchModeBuildItem) { + if (launchModeBuildItem.isAuxiliaryApplication()) { + return; + } final AtomicReference lastUserCode = new AtomicReference<>(); exceptionNotificationBuildItem .produce(new ExceptionNotificationBuildItem(new BiConsumer() { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleStateManager.java b/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleStateManager.java index 9a67625a9cb05..e6671d5ae3516 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleStateManager.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleStateManager.java @@ -244,6 +244,7 @@ void redraw() { .collect(Collectors.toList()); if (sorted.isEmpty()) { QuarkusConsole.INSTANCE.setPromptMessage(null); + oldPrompt = null; } else { StringBuilder sb = new StringBuilder("Press"); boolean first = true; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/console/QuarkusCommand.java b/core/deployment/src/main/java/io/quarkus/deployment/console/QuarkusCommand.java new file mode 100644 index 0000000000000..bf20745411b88 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/console/QuarkusCommand.java @@ -0,0 +1,26 @@ +package io.quarkus.deployment.console; + +import org.aesh.command.Command; +import org.aesh.command.CommandException; +import org.aesh.command.CommandResult; +import org.aesh.command.invocation.CommandInvocation; +import org.aesh.command.option.Option; + +import io.quarkus.dev.console.QuarkusConsole; + +public abstract class QuarkusCommand implements Command { + + @Option(shortName = 'd', hasValue = false) + public boolean done; + + @Override + public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException { + var result = doExecute(commandInvocation); + if (done) { + QuarkusConsole.INSTANCE.exitCliMode(); + } + return result; + } + + public abstract CommandResult doExecute(CommandInvocation commandInvocation) throws CommandException, InterruptedException; +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestTracingProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestTracingProcessor.java index e37b6a8cf397f..06571c6abd1e5 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestTracingProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestTracingProcessor.java @@ -1,10 +1,26 @@ package io.quarkus.deployment.dev.testing; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.function.BiFunction; +import org.aesh.command.Command; +import org.aesh.command.CommandDefinition; +import org.aesh.command.CommandException; +import org.aesh.command.CommandResult; +import org.aesh.command.GroupCommandDefinition; +import org.aesh.command.invocation.CommandInvocation; +import org.aesh.command.option.Argument; +import org.aesh.command.option.Arguments; +import org.aesh.command.option.Option; +import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.junit.jupiter.api.Tag; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -19,13 +35,18 @@ import io.quarkus.deployment.annotations.Produce; import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.ConsoleCommandBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.LiveReloadBuildItem; import io.quarkus.deployment.builditem.LogHandlerBuildItem; import io.quarkus.deployment.builditem.ServiceStartBuildItem; +import io.quarkus.deployment.console.QuarkusCommand; +import io.quarkus.deployment.console.SetCompleter; import io.quarkus.deployment.logging.LogCleanupFilterBuildItem; +import io.quarkus.dev.config.CurrentConfig; import io.quarkus.dev.spi.DevModeType; import io.quarkus.dev.testing.ContinuousTestingSharedStateManager; +import io.quarkus.dev.testing.KnownTags; import io.quarkus.dev.testing.TracingHandler; import io.quarkus.gizmo.Gizmo; @@ -95,6 +116,7 @@ public void instrumentTestClasses(CombinedIndexBuildItem combinedIndexBuildItem, if (!launchModeBuildItem.isAuxiliaryApplication()) { return; } + for (ClassInfo clazz : combinedIndexBuildItem.getIndex().getKnownClasses()) { String theClassName = clazz.name().toString(); if (isAppClass(theClassName)) { @@ -110,6 +132,22 @@ public ClassVisitor apply(String s, ClassVisitor classVisitor) { } + @BuildStep(onlyIf = IsTest.class) + public ServiceStartBuildItem searchForTags(CombinedIndexBuildItem combinedIndexBuildItem, + LaunchModeBuildItem launchModeBuildItem) { + if (!launchModeBuildItem.isAuxiliaryApplication()) { + return null; + } + + Set ret = new HashSet<>(); + for (AnnotationInstance clazz : combinedIndexBuildItem.getIndex() + .getAnnotations(DotName.createSimple(Tag.class.getName()))) { + ret.add(clazz.value().asString()); + } + KnownTags.setKnownTags(ret); + return null; + } + public boolean isAppClass(String theClassName) { QuarkusClassLoader cl = (QuarkusClassLoader) Thread.currentThread() .getContextClassLoader(); @@ -146,4 +184,177 @@ public void visitCode() { }; } } + + @BuildStep + ConsoleCommandBuildItem testConsoleCommand(CombinedIndexBuildItem indexBuildItem) { + return new ConsoleCommandBuildItem(new TestCommand()); + } + + @GroupCommandDefinition(name = "test", description = "Test Commands", groupCommands = { TagsCommand.class, + PatternCommand.class }) + public static class TestCommand implements Command { + + @Override + public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException { + return CommandResult.SUCCESS; + } + } + + @GroupCommandDefinition(name = "tags", description = "Tag Commands", groupCommands = { IncludeTagsCommand.class, + ExcludeTagsCommand.class }) + public static class TagsCommand implements Command { + + @Override + public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException { + return CommandResult.SUCCESS; + } + } + + @CommandDefinition(name = "include", description = "Sets the current included tags") + public static class IncludeTagsCommand extends TestSelectionCommand { + + @Arguments(completer = TagCompleter.class) + private List tags; + + @Override + protected String configValue() { + if (tags == null) { + return ""; + } else { + return String.join(",", tags); + } + } + + @Override + protected String configKey() { + return "quarkus.test.include-tags"; + } + + @Override + protected void configure(TestSupport testSupport) { + testSupport.setTags(tags == null ? List.of() : tags, testSupport.excludeTags); + } + } + + @CommandDefinition(name = "exclude", description = "Sets the current excluded tags") + public static class ExcludeTagsCommand extends TestSelectionCommand { + + @Arguments(completer = TagCompleter.class) + private List tags; + + @Override + protected String configValue() { + if (tags == null) { + return ""; + } else { + return String.join(",", tags); + } + } + + @Override + protected String configKey() { + return "quarkus.test.exclude-tags"; + } + + @Override + protected void configure(TestSupport testSupport) { + testSupport.setTags(testSupport.includeTags, tags == null ? List.of() : tags); + } + } + + @GroupCommandDefinition(name = "pattern", description = "Include/Exclude pattern Commands", groupCommands = { + IncludePatternCommand.class, + ExcludePatternCommand.class }) + public static class PatternCommand implements Command { + + @Override + public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException { + return CommandResult.SUCCESS; + } + } + + @CommandDefinition(name = "include", description = "Sets the current include pattern") + public static class IncludePatternCommand extends TestSelectionCommand { + + @Argument + private String pattern; + + @Override + protected String configValue() { + return Objects.requireNonNullElse(pattern, ""); + } + + @Override + protected String configKey() { + return "quarkus.test.include-pattern"; + } + + @Override + protected void configure(TestSupport testSupport) { + testSupport.setPatterns(pattern, testSupport.exclude.pattern()); + } + } + + @CommandDefinition(name = "exclude", description = "Sets the current excluded tags") + public static class ExcludePatternCommand extends TestSelectionCommand { + + @Argument(completer = TagCompleter.class) + private String pattern; + + @Override + protected String configValue() { + return Objects.requireNonNullElse(pattern, ""); + } + + @Override + protected String configKey() { + return "quarkus.test.exclude-pattern"; + } + + @Override + protected void configure(TestSupport testSupport) { + testSupport.setPatterns(testSupport.include.pattern(), pattern); + } + } + + static abstract class TestSelectionCommand extends QuarkusCommand { + + @Option(shortName = 'p', hasValue = false) + protected boolean persistent; + + @Option(shortName = 'r', hasValue = false) + protected boolean run; + + protected abstract String configValue(); + + protected abstract String configKey(); + + @Override + public final CommandResult doExecute(CommandInvocation commandInvocation) + throws CommandException, InterruptedException { + TestSupport testSupport = TestSupport.instance().get(); + configure(testSupport); + if (persistent) { + CurrentConfig.EDITOR.accept(Map.of(configKey(), configValue())); + } + if (run) { + if (!testSupport.isStarted()) { + testSupport.start(); + } + testSupport.runAllTests(); + } + return CommandResult.SUCCESS; + } + + protected abstract void configure(TestSupport testSupport); + } + + public static class TagCompleter extends SetCompleter { + + @Override + protected Set allOptions(String soFar) { + return KnownTags.getKnownTags(); + } + } + } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java index 305f078ba8324..0cec6529d9bf7 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java @@ -64,6 +64,7 @@ import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; import io.quarkus.deployment.console.ConsoleInstalledBuildItem; +import io.quarkus.deployment.console.QuarkusCommand; import io.quarkus.deployment.console.SetCompleter; import io.quarkus.deployment.dev.ExceptionNotificationBuildItem; import io.quarkus.deployment.dev.testing.MessageFormat; @@ -536,7 +537,7 @@ public CommandResult execute(CommandInvocation commandInvocation) throws Command } @CommandDefinition(name = "set-level", description = "Sets the log level for a logger") - public static class SetLogLevelCommand implements Command { + public static class SetLogLevelCommand extends QuarkusCommand { @Option(required = true, completer = LoggerCompleter.class) private String logger; @@ -545,7 +546,7 @@ public static class SetLogLevelCommand implements Command { private String level; @Override - public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException { + public CommandResult doExecute(CommandInvocation commandInvocation) throws CommandException, InterruptedException { java.util.logging.Logger logger = LogManager.getLogManager().getLogger(this.logger); Level level = org.jboss.logmanager.Level.parse(this.level); logger.setLevel(level); diff --git a/core/devmode-spi/src/main/java/io/quarkus/dev/testing/KnownTags.java b/core/devmode-spi/src/main/java/io/quarkus/dev/testing/KnownTags.java new file mode 100644 index 0000000000000..da9bbc29b8daa --- /dev/null +++ b/core/devmode-spi/src/main/java/io/quarkus/dev/testing/KnownTags.java @@ -0,0 +1,17 @@ +package io.quarkus.dev.testing; + +import java.util.Collections; +import java.util.Set; + +public class KnownTags { + + private static volatile Set knownTags = Set.of(); + + public static Set getKnownTags() { + return knownTags; + } + + public static void setKnownTags(Set knownTag) { + knownTags = Collections.unmodifiableSet(knownTag); + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveDevModeProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveDevModeProcessor.java index 128b5a1062871..ce567d446c0b5 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveDevModeProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveDevModeProcessor.java @@ -10,7 +10,6 @@ import java.util.Set; import java.util.stream.Collectors; -import org.aesh.command.Command; import org.aesh.command.CommandDefinition; import org.aesh.command.CommandException; import org.aesh.command.CommandResult; @@ -29,6 +28,7 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; import io.quarkus.deployment.builditem.ConsoleCommandBuildItem; +import io.quarkus.deployment.console.QuarkusCommand; import io.quarkus.resteasy.reactive.server.runtime.ExceptionMapperRecorder; import io.quarkus.resteasy.reactive.server.runtime.NotFoundExceptionMapper; import io.quarkus.resteasy.reactive.spi.CustomExceptionMapperBuildItem; @@ -128,7 +128,7 @@ ConsoleCommandBuildItem openCommand(HttpRootPathBuildItem rp, NonApplicationRoot } @CommandDefinition(name = "open", description = "Opens a path in a web browser") - public static class OpenCommand implements Command { + public static class OpenCommand extends QuarkusCommand { @Argument(required = true, completer = PathCompleter.class) private String url; @@ -146,7 +146,7 @@ public OpenCommand(HttpRootPathBuildItem rp, NonApplicationRootPathBuildItem np, } @Override - public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException { + public CommandResult doExecute(CommandInvocation commandInvocation) throws CommandException, InterruptedException { DevConsoleProcessor.openBrowser(rp, np, url.startsWith("/") ? url : "/" + url, host, port); return CommandResult.SUCCESS; }