This is an automated email from the ASF dual-hosted git repository.

christophd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 16815cdf1624 CAMEL-23817: Embed test plugin in Camel launcher
16815cdf1624 is described below

commit 16815cdf1624f6be81b1a10f6679aaa5dfd5dda4
Author: Christoph Deppisch <[email protected]>
AuthorDate: Wed Jun 24 22:21:32 2026 +0200

    CAMEL-23817: Embed test plugin in Camel launcher
    
    - Use Citrus JBang commands directly from launcher instead of spawning a 
new JBang process
    - Update to Citrus 5.0.0-M2 for Jackson 3 support and Camel 4.20+
---
 dsl/camel-jbang/camel-jbang-plugin-test/pom.xml    |  13 +-
 .../dsl/jbang/core/commands/test/TestCommand.java  |   3 +-
 .../dsl/jbang/core/commands/test/TestPlugin.java   | 175 ++++++++++++---------
 .../core/commands/test/TestPluginExporter.java     |   1 +
 dsl/camel-jbang/camel-launcher/pom.xml             |   6 +-
 .../dsl/jbang/launcher/CamelLauncherMain.java      |   3 +-
 parent/pom.xml                                     |   2 +-
 7 files changed, 125 insertions(+), 78 deletions(-)

diff --git a/dsl/camel-jbang/camel-jbang-plugin-test/pom.xml 
b/dsl/camel-jbang/camel-jbang-plugin-test/pom.xml
index 56f87f636aa0..173091e98cd3 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-test/pom.xml
+++ b/dsl/camel-jbang/camel-jbang-plugin-test/pom.xml
@@ -49,8 +49,19 @@
 
         <dependency>
             <groupId>org.citrusframework</groupId>
-            <artifactId>citrus-jbang-connector</artifactId>
+            <artifactId>citrus-jbang</artifactId>
             <version>${citrus-version}</version>
+            <exclusions>
+              <exclusion>
+                <groupId>org.apache.camel</groupId>
+                <artifactId>camel-tooling-maven</artifactId>
+              </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-tooling-maven</artifactId>
+            <version>${project.version}</version>
         </dependency>
 
        <!-- Test dependencies -->
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestCommand.java
 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestCommand.java
index a4ad5adb6c5b..0d0e80b79c5d 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestCommand.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestCommand.java
@@ -31,6 +31,7 @@ public class TestCommand extends CamelCommand {
     @Override
     public Integer doCall() throws Exception {
         // defaults to printing the help message
-        return new CommandLine(new CommandLine.HelpCommand()).execute();
+        new CommandLine(this).execute("--help");
+        return 0;
     }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestPlugin.java
 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestPlugin.java
index 275976307356..59a860268e7d 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestPlugin.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestPlugin.java
@@ -21,9 +21,6 @@ import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 import java.util.Optional;
 
 import org.apache.camel.RuntimeCamelException;
@@ -33,12 +30,19 @@ import 
org.apache.camel.dsl.jbang.core.commands.ExportHelper;
 import org.apache.camel.dsl.jbang.core.common.CamelJBangPlugin;
 import org.apache.camel.dsl.jbang.core.common.Plugin;
 import org.apache.camel.dsl.jbang.core.common.PluginExporter;
+import org.apache.camel.dsl.jbang.core.common.Printer;
 import org.apache.camel.util.IOHelper;
 import org.citrusframework.CitrusSettings;
 import org.citrusframework.CitrusVersion;
-import org.citrusframework.jbang.JBangSettings;
-import org.citrusframework.jbang.JBangSupport;
-import org.citrusframework.jbang.ProcessAndOutput;
+import org.citrusframework.jbang.CitrusJBangMain;
+import org.citrusframework.jbang.commands.Agent;
+import org.citrusframework.jbang.commands.AgentRun;
+import org.citrusframework.jbang.commands.AgentStart;
+import org.citrusframework.jbang.commands.AgentStop;
+import org.citrusframework.jbang.commands.Init;
+import org.citrusframework.jbang.commands.Inspect;
+import org.citrusframework.jbang.commands.ListTests;
+import org.citrusframework.jbang.commands.Run;
 import picocli.CommandLine;
 
 @CamelJBangPlugin(name = "camel-jbang-plugin-test", firstVersion = "4.14.0")
@@ -46,10 +50,21 @@ public class TestPlugin implements Plugin {
 
     @Override
     public void customize(CommandLine commandLine, CamelJBangMain main) {
-        commandLine.setExecutionStrategy(new CitrusExecutionStrategy(main))
-                .addSubcommand("test", new CommandLine(new TestCommand(main))
-                        .setUnmatchedArgumentsAllowed(true)
-                        .setUnmatchedOptionsAllowedAsOptionParameters(true));
+        CitrusJBangMain citrus = new CitrusJBangMain();
+        citrus.withPrinter(new PipedPrinter(main.getOut()));
+
+        var cmd = new CommandLine(new TestCommand(main))
+                .addSubcommand("init", new CommandLine(new Init(citrus)))
+                .addSubcommand("inspect", new CommandLine(new Inspect(citrus)))
+                .addSubcommand("run", new CommandLine(new Run(citrus)))
+                .addSubcommand("ps", new CommandLine(new ListTests(citrus)), 
"ls")
+                .addSubcommand("agent", new CommandLine(new Agent(citrus))
+                        .addSubcommand("start", new CommandLine(new 
AgentStart(citrus)))
+                        .addSubcommand("run", new CommandLine(new 
AgentRun(citrus)))
+                        .addSubcommand("stop", new CommandLine(new 
AgentStop(citrus))));
+
+        commandLine.addSubcommand("test", cmd)
+                .setExecutionStrategy(new CitrusExecutionStrategy(main));
     }
 
     @Override
@@ -58,10 +73,8 @@ public class TestPlugin implements Plugin {
     }
 
     /**
-     * Command execution strategy delegates to Citrus JBang for subcommands 
like init or run. Performs special command
-     * preparations and makes sure to run the proper Citrus version for this 
Camel release.
-     *
-     * @param main Camel CLI main that provides the output printer.
+     * Command execution strategy performs special command preparations and 
makes sure to set and run the proper Citrus
+     * version for this Camel release.
      */
     private record CitrusExecutionStrategy(CamelJBangMain main) implements 
CommandLine.IExecutionStrategy {
 
@@ -71,38 +84,49 @@ public class TestPlugin implements Plugin {
         public int execute(CommandLine.ParseResult parseResult)
                 throws CommandLine.ExecutionException, 
CommandLine.ParameterException {
 
-            String command;
-            List<String> args = Collections.emptyList();
-
-            if (parseResult.originalArgs().size() > 2) {
-                command = parseResult.originalArgs().get(1);
-                args = parseResult.originalArgs().subList(2, 
parseResult.originalArgs().size());
-            } else if (parseResult.originalArgs().size() == 2) {
-                command = parseResult.originalArgs().get(1);
-            } else {
-                // run help command by default
-                command = "--help";
+            if (!parseResult.errors().isEmpty() || 
!parseResult.unmatched().isEmpty()) {
+                // Something is wrong with the command - do not adjust anything
+                return new CommandLine.RunLast().execute(parseResult);
             }
 
-            JBangSupport citrus = 
JBangSupport.jbang().app(JBangSettings.getApp())
-                    .withSystemProperty("citrus.jbang.version", 
CitrusVersion.version())
-                    .withSystemProperty("citrus.camel.jbang.version", new 
VersionHelper().getVersion());
+            if (isCitrusCommand(parseResult)) {
+                String command = "";
+                if (parseResult.originalArgs().size() > 2) {
+                    command = parseResult.originalArgs().get(1);
+                } else if (parseResult.originalArgs().size() == 2) {
+                    command = parseResult.originalArgs().get(1);
+                }
+
+                System.setProperty("citrus.jbang.version", 
CitrusVersion.version());
+                System.setProperty("citrus.camel.jbang.version", new 
VersionHelper().getVersion());
+
+                // Prepare commands
+                if ("init".equals(command)) {
+                    prepareInitCommand();
+                }
 
-            // Prepare commands
-            if ("init".equals(command)) {
-                return executeInitCommand(citrus, args);
-            } else if ("run".equals(command)) {
-                return executeRunCommand(citrus, args);
+                if (!isCamelLauncherRuntime()) {
+                    var tccLoader = 
Thread.currentThread().getContextClassLoader();
+                    try {
+                        // Update class loader so Citrus is able to resolve 
resources
+                        ClassLoader cl = TestPlugin.class.getClassLoader();
+                        Thread.currentThread().setContextClassLoader(cl);
+
+                        return new CommandLine.RunLast().execute(parseResult);
+                    } finally {
+                        
Thread.currentThread().setContextClassLoader(tccLoader);
+                    }
+                }
             }
 
-            return execute(citrus, command, args);
+            return new CommandLine.RunLast().execute(parseResult);
         }
 
         /**
-         * Prepare and execute init command. Automatically uses test subfolder 
as a working directory for creating new
-         * tests. Automatically adds a jbang.properties configuration to add 
required Camel Citrus dependencies.
+         * Prepare init command. Automatically uses test subfolder as a 
working directory for creating new tests.
+         * Automatically adds a citrus-application.properties configuration if 
not present.
          */
-        private int executeInitCommand(JBangSupport citrus, List<String> args) 
{
+        private void prepareInitCommand() {
             Path currentDir = Paths.get(".");
             Path workingDir;
             // Automatically set test subfolder as a working directory
@@ -112,11 +136,11 @@ public class TestPlugin implements Plugin {
             } else if (currentDir.resolve(TEST_DIR).toFile().exists()) {
                 // navigate to existing test subfolder
                 workingDir = currentDir.resolve(TEST_DIR);
-                citrus.workingDir(workingDir);
+                System.setProperty(CitrusSettings.RESOURCES_WORKDIR_PROPERTY, 
workingDir.toString());
             } else if (currentDir.resolve(TEST_DIR).toFile().mkdirs()) {
                 // create test subfolder and navigate to it
                 workingDir = currentDir.resolve(TEST_DIR);
-                citrus.workingDir(workingDir);
+                System.setProperty(CitrusSettings.RESOURCES_WORKDIR_PROPERTY, 
workingDir.toString());
             } else {
                 throw new RuntimeCamelException("Cannot create test working 
directory in: " + currentDir);
             }
@@ -139,50 +163,55 @@ public class TestPlugin implements Plugin {
                             
.formatted(CitrusSettings.getApplicationPropertiesFile(), 
citrusApplicationProperties));
                 }
             }
+        }
 
-            return execute(citrus, "init", args);
+        /**
+         * Evaluate if the current runtime is using Camel Launcher. Camel 
launcher sets a System property marking the
+         * runtime nature. If the System property is not present we can assume 
that the runtime is something different
+         * e.g. Camel JBang.
+         */
+        private boolean isCamelLauncherRuntime() {
+            return Boolean.parseBoolean(System.getProperty("camel.launcher", 
"false"));
         }
 
         /**
-         * Prepare and execute Citrus run command. Automatically navigates to 
test subfolder if it is present and uses
-         * this as a working directory. Runs command asynchronous streaming 
logs to the main output of this Camel CLI
-         * process.
+         * Evaluate command user object and check if this is a Citrus command 
implementation.
          */
-        private int executeRunCommand(JBangSupport citrus, List<String> args) {
-            Path currentDir = Paths.get(".");
-            List<String> runArgs = new ArrayList<>(args);
-            // automatically navigate to test subfolder for test execution
-            if (currentDir.resolve(TEST_DIR).toFile().exists()) {
-                // set test subfolder as working directory
-                citrus.workingDir(currentDir.resolve(TEST_DIR));
-
-                // remove test folder prefix in test file path if present
-                if (!args.isEmpty() && args.get(0).startsWith(TEST_DIR + "/")) 
{
-                    runArgs = new ArrayList<>(args.subList(1, args.size()));
-                    runArgs.add(0, args.get(0).substring((TEST_DIR + 
"/").length()));
-                }
+        private boolean isCitrusCommand(CommandLine.ParseResult parseResult) {
+            CommandLine.ParseResult subcommand = parseResult;
+            while (subcommand.hasSubcommand()) {
+                subcommand = subcommand.subcommand();
             }
 
-            citrus.withOutputListener(output -> main.getOut().print(output));
-            ProcessAndOutput pao = citrus.runAsync("run", runArgs);
-            try {
-                pao.waitFor();
-            } catch (InterruptedException e) {
-                main.getOut().printErr("Interrupted while running Citrus 
command", e);
-            }
+            Object commandObject = subcommand.commandSpec().userObject();
+            return 
commandObject.getClass().getName().startsWith("org.citrusframework");
+        }
+    }
 
-            return pao.getProcess().exitValue();
+    /**
+     * Delegates Citrus printer API methods to Camel printer implementation.
+     *
+     * @param delegate the Camel printer.
+     */
+    private record PipedPrinter(Printer delegate) implements 
org.citrusframework.jbang.Printer {
+        @Override
+        public void println() {
+            delegate.println();
         }
 
-        /**
-         * Uses given Citrus JBang instance to run the given command using the 
given arguments.
-         *
-         * @return exit code of the command process.
-         */
-        private int execute(JBangSupport citrus, String command, List<String> 
args) {
-            ProcessAndOutput pao = citrus.run(command, args);
-            main.getOut().print(pao.getOutput());
-            return pao.getProcess().exitValue();
+        @Override
+        public void println(String line) {
+            delegate.println(line);
+        }
+
+        @Override
+        public void print(String output) {
+            delegate.print(output);
+        }
+
+        @Override
+        public void printf(String format, Object... args) {
+            delegate.printf(format, args);
         }
     }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestPluginExporter.java
 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestPluginExporter.java
index 2bf527e832d5..4ffb3ee4ecb1 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestPluginExporter.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-test/src/main/java/org/apache/camel/dsl/jbang/core/commands/test/TestPluginExporter.java
@@ -84,6 +84,7 @@ public class TestPluginExporter implements PluginExporter {
         deps.add(asDependency("citrus-yaml"));
         deps.add(asDependency("citrus-xml"));
         deps.add(asDependency("citrus-groovy"));
+        deps.add(asDependency("citrus-validation-text"));
         deps.add(asDependency("citrus-validation-xml"));
         deps.add(asDependency("citrus-validation-json"));
         deps.add(asDependency("citrus-validation-yaml"));
diff --git a/dsl/camel-jbang/camel-launcher/pom.xml 
b/dsl/camel-jbang/camel-launcher/pom.xml
index a5d5c08b01d5..00be949d3f02 100644
--- a/dsl/camel-jbang/camel-launcher/pom.xml
+++ b/dsl/camel-jbang/camel-launcher/pom.xml
@@ -66,12 +66,16 @@
         <!-- Pre-installed plugins -->
         <!-- the edit plugin is not pre-installed -->
         <!-- the route-parser plugin is not pre-installed -->
-        <!-- the citrus test plugin cannot be embedded -->
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-jbang-plugin-generate</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-jbang-plugin-test</artifactId>
+            <version>${project.version}</version>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-jbang-plugin-kubernetes</artifactId>
diff --git 
a/dsl/camel-jbang/camel-launcher/src/main/java/org/apache/camel/dsl/jbang/launcher/CamelLauncherMain.java
 
b/dsl/camel-jbang/camel-launcher/src/main/java/org/apache/camel/dsl/jbang/launcher/CamelLauncherMain.java
index 31f0d0017ff0..7c36cddb6b17 100644
--- 
a/dsl/camel-jbang/camel-launcher/src/main/java/org/apache/camel/dsl/jbang/launcher/CamelLauncherMain.java
+++ 
b/dsl/camel-jbang/camel-launcher/src/main/java/org/apache/camel/dsl/jbang/launcher/CamelLauncherMain.java
@@ -19,6 +19,7 @@ package org.apache.camel.dsl.jbang.launcher;
 import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
 import org.apache.camel.dsl.jbang.core.commands.generate.GeneratePlugin;
 import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesPlugin;
+import org.apache.camel.dsl.jbang.core.commands.test.TestPlugin;
 import org.apache.camel.dsl.jbang.core.commands.tui.TuiPlugin;
 import org.apache.camel.dsl.jbang.core.commands.validate.ValidatePlugin;
 import picocli.CommandLine;
@@ -35,7 +36,7 @@ public class CamelLauncherMain extends CamelJBangMain {
         new KubernetesPlugin().customize(commandLine, this);
         new TuiPlugin().customize(commandLine, this);
         new ValidatePlugin().customize(commandLine, this);
-        // citrus test plugin cannot (yet) be embedded
+        new TestPlugin().customize(commandLine, this);
     }
 
 }
diff --git a/parent/pom.xml b/parent/pom.xml
index 3854a6d740c5..727ad809799d 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -102,7 +102,7 @@
         <jta-api-1.2-version>1.2</jta-api-1.2-version>
         <chicory-version>1.7.5</chicory-version>
         <chunk-templates-version>3.6.2</chunk-templates-version>
-        <citrus-version>4.10.1</citrus-version>
+        <citrus-version>5.0.0-M2</citrus-version>
         <classgraph-version>4.8.184</classgraph-version>
         <cloudant-version>0.10.19</cloudant-version>
         
<com-ibm-mq-jakarta-client-version>9.4.5.1</com-ibm-mq-jakarta-client-version>

Reply via email to