This is an automated email from the ASF dual-hosted git repository.
davsclaus 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 5cf8b1c632a3 CAMEL-22149: jbang-it, make tests Windows compatible
(#21493)
5cf8b1c632a3 is described below
commit 5cf8b1c632a30683b502614c57cb79582e569e90
Author: Marco Carletti <[email protected]>
AuthorDate: Tue Feb 17 13:58:33 2026 +0100
CAMEL-22149: jbang-it, make tests Windows compatible (#21493)
---
dsl/camel-jbang/camel-jbang-it/README.adoc | 11 +
dsl/camel-jbang/camel-jbang-it/pom.xml | 18 +-
.../camel/dsl/jbang/it/CamelCatalogITCase.java | 4 +
.../camel/dsl/jbang/it/CamelDebugITCase.java | 4 +
.../camel/dsl/jbang/it/CmdStartStopITCase.java | 14 +-
.../camel/dsl/jbang/it/CustomJarsITCase.java | 3 +-
.../apache/camel/dsl/jbang/it/DevModeITCase.java | 2 +
.../camel/dsl/jbang/it/InfrastructureITCase.java | 6 +
.../apache/camel/dsl/jbang/it/JolokiaITCase.java | 6 +-
.../apache/camel/dsl/jbang/it/OpenApiITCase.java | 2 +
.../camel/dsl/jbang/it/RunCommandITCase.java | 12 +-
.../camel/dsl/jbang/it/RunCommandOnMqttITCase.java | 6 +-
.../dsl/jbang/it/ScriptingWithPipesITCase.java | 4 +
.../dsl/jbang/it/support/JBangTestSupport.java | 40 +-
test-infra/camel-test-infra-cli/README.adoc | 55 ++-
test-infra/camel-test-infra-cli/pom.xml | 10 +-
.../camel/test/infra/cli/common/CliProperties.java | 2 +
.../test/infra/cli/it/AbstractTestSupport.java | 5 +
.../camel/test/infra/cli/it/CliConfigITCase.java | 11 +-
.../apache/camel/test/infra/cli/it/RunITCase.java | 3 +
.../test/infra/cli/services/CliBuiltContainer.java | 2 +-
.../cli/services/CliLocalContainerService.java | 8 +-
.../infra/cli/services/CliLocalProcessService.java | 480 +++++++++++++++++++++
.../camel/test/infra/cli/services/CliService.java | 4 +-
.../test/infra/cli/services/CliServiceFactory.java | 1 +
.../infra/common/services/TestServiceUtil.java | 16 +-
26 files changed, 659 insertions(+), 70 deletions(-)
diff --git a/dsl/camel-jbang/camel-jbang-it/README.adoc
b/dsl/camel-jbang/camel-jbang-it/README.adoc
index 274c9d1bace3..be7963cbcb5f 100644
--- a/dsl/camel-jbang/camel-jbang-it/README.adoc
+++ b/dsl/camel-jbang/camel-jbang-it/README.adoc
@@ -13,6 +13,17 @@ To run tests use the dedicated Maven profile `jbang-it-test`
activated by the sa
mvn verify -Djbang-it-test
----
+== Run tests locally
+
+To run tests using a local Camel CLI process instead of a container, activate
the `local-camel-cli-process` profile:
+
+[source,bash]
+----
+mvn verify -Djbang-it-test -Dcamel-cli.instance.type=local-camel-cli-process
+----
+
+Tests tagged with `container-only` will be automatically excluded when this
profile is active.
+
== Configuration
Camel CLI infra configuration is defined in
link:../../../test-infra/camel-test-infra-cli/README.adoc[README.adoc]
diff --git a/dsl/camel-jbang/camel-jbang-it/pom.xml
b/dsl/camel-jbang/camel-jbang-it/pom.xml
index 9abfc35706ea..44122c9fbc6e 100644
--- a/dsl/camel-jbang/camel-jbang-it/pom.xml
+++ b/dsl/camel-jbang/camel-jbang-it/pom.xml
@@ -232,7 +232,9 @@
<maven.test.skip>false</maven.test.skip>
<shared.data.folder>target/data</shared.data.folder>
<shared.maven.local.repo>${settings.localRepository}</shared.maven.local.repo>
+ <failsafe.forkCount>1C</failsafe.forkCount>
<x11.display>:0</x11.display>
+ <failsafe.excludedGroups/>
</properties>
<build>
<plugins>
@@ -267,8 +269,9 @@
</executions>
<configuration>
<argLine>-DforkNumber=${surefire.forkNumber}</argLine>
- <forkCount>1C</forkCount>
+ <forkCount>${failsafe.forkCount}</forkCount>
<groups>${export.runtime}</groups>
+
<excludedGroups>${failsafe.excludedGroups}</excludedGroups>
<rerunFailingTestsCount>${surefire.rerunFailingTestsCount}</rerunFailingTestsCount>
<systemPropertyVariables>
<cli.service.data.folder>${shared.data.folder}</cli.service.data.folder>
@@ -287,6 +290,19 @@
</plugins>
</build>
</profile>
+ <profile>
+ <id>local-camel-cli-process</id>
+ <activation>
+ <property>
+ <name>camel-cli.instance.type</name>
+ <value>local-camel-cli-process</value>
+ </property>
+ </activation>
+ <properties>
+ <failsafe.forkCount>1</failsafe.forkCount>
+
<failsafe.excludedGroups>container-only</failsafe.excludedGroups>
+ </properties>
+ </profile>
</profiles>
</project>
diff --git
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CamelCatalogITCase.java
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CamelCatalogITCase.java
index 2c3723deee8f..daa14055150b 100644
---
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CamelCatalogITCase.java
+++
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CamelCatalogITCase.java
@@ -18,7 +18,11 @@ package org.apache.camel.dsl.jbang.it;
import org.apache.camel.dsl.jbang.it.support.JBangTestSupport;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import static org.junit.jupiter.api.condition.OS.WINDOWS;
+
+@DisabledOnOs(WINDOWS)
public class CamelCatalogITCase extends JBangTestSupport {
private static final String COMPONENT_REGEX
diff --git
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CamelDebugITCase.java
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CamelDebugITCase.java
index 885bc664f331..910eded0830a 100644
---
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CamelDebugITCase.java
+++
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CamelDebugITCase.java
@@ -20,7 +20,11 @@ import java.io.IOException;
import org.apache.camel.dsl.jbang.it.support.JBangTestSupport;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import static org.junit.jupiter.api.condition.OS.WINDOWS;
+
+@DisabledOnOs(WINDOWS)
public class CamelDebugITCase extends JBangTestSupport {
@Test
public void testDebug() throws IOException {
diff --git
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CmdStartStopITCase.java
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CmdStartStopITCase.java
index f99a8c8da832..91fa4a74f64d 100644
---
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CmdStartStopITCase.java
+++
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CmdStartStopITCase.java
@@ -20,8 +20,13 @@ import java.io.IOException;
import org.apache.camel.dsl.jbang.it.support.JBangTestSupport;
import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import static org.junit.jupiter.api.condition.OS.WINDOWS;
+
+@DisabledOnOs(WINDOWS)
public class CmdStartStopITCase extends JBangTestSupport {
@Test
@@ -43,7 +48,7 @@ public class CmdStartStopITCase extends JBangTestSupport {
String process = executeBackground(String.format("run
%s/FromDirectoryRoute.java", mountPoint()));
executeBackground(String.format("run %s/route2.yaml", mountPoint()));
checkLogContains("Hello world!");
- execute("cmd stop-route " + getPID(process));
+ execute("cmd stop-route " + process);
checkCommandOutputsPattern("get route",
"route1\\s+timer:\\/\\/(yaml|java)\\?period=1000\\s+Stopped.*\\n.*route2.*timer:\\/\\/(yaml|java)\\?period=1000\\s+Started",
ASSERTION_WAIT_SECONDS);
@@ -82,7 +87,7 @@ public class CmdStartStopITCase extends JBangTestSupport {
executeBackground(String.format("run %s/route2.yaml", mountPoint()));
checkLogContains("Hello world!");
execute("cmd stop-route");
- execute("cmd start-route " + getPID(process));
+ execute("cmd start-route " + process);
checkCommandOutputsPattern("get route",
"route1\\s+timer:\\/\\/(yaml|java)\\?period=1000\\s+Started.*\\n.*route2.*timer:\\/\\/(yaml|java)\\?period=1000\\s+Stopped",
ASSERTION_WAIT_SECONDS);
@@ -102,6 +107,7 @@ public class CmdStartStopITCase extends JBangTestSupport {
}
@Test
+ @Tag("container-only")
public void testCamelWatch() throws IOException {
copyResourceInDataFolder(TestResources.ROUTE2);
String process = executeBackground(String.format("run %s/route2.yaml",
mountPoint()));
@@ -111,7 +117,7 @@ public class CmdStartStopITCase extends JBangTestSupport {
execInContainer(String.format("chmod +x %s/watch-sleep",
mountPoint()));
Assertions.assertThat(
execInContainer(String.format("%s/watch-sleep", mountPoint())))
- .as("watch command should output PID" + getPID(process))
- .contains(getPID(process));
+ .as("watch command should output PID" + process)
+ .contains(process);
}
}
diff --git
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CustomJarsITCase.java
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CustomJarsITCase.java
index b78ccb0c4d47..b612b90b24d4 100644
---
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CustomJarsITCase.java
+++
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CustomJarsITCase.java
@@ -28,7 +28,8 @@ public class CustomJarsITCase extends JBangTestSupport {
public void testCustomJars() throws IOException {
copyResourceInDataFolder(TestResources.CIRCUIT_BREAKER);
Assertions
- .assertThatCode(() -> execute(String.format("run
%s/CircuitBreakerRoute.java --dep=camel-timer", mountPoint())))
+ .assertThatCode(() -> execute(
+ String.format("run %s/CircuitBreakerRoute.java
--max-seconds=30", mountPoint())))
.as("the application without dependency will cause error")
.hasStackTraceContaining("Failed to create route:
circuitBreaker")
.hasStackTraceContaining(
diff --git
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/DevModeITCase.java
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/DevModeITCase.java
index c530e381ca2d..0d22f8220b03 100644
---
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/DevModeITCase.java
+++
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/DevModeITCase.java
@@ -31,6 +31,7 @@ import org.apache.camel.dsl.jbang.it.support.JBangTestSupport;
import org.apache.camel.dsl.jbang.it.support.JiraIssue;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
public class DevModeITCase extends JBangTestSupport {
@@ -75,6 +76,7 @@ public class DevModeITCase extends JBangTestSupport {
@Test
@JiraIssue("CAMEL-20939")
+ @Tag("container-only")
public void runUsingProfileTest() throws IOException {
copyResourceInDataFolder(TestResources.HELLO_NAME);
copyResourceInDataFolder(TestResources.TEST_PROFILE_PROP);
diff --git
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/InfrastructureITCase.java
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/InfrastructureITCase.java
index ae27a71a6d2a..c5edb826f04d 100644
---
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/InfrastructureITCase.java
+++
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/InfrastructureITCase.java
@@ -21,9 +21,15 @@ import java.time.Duration;
import org.apache.camel.dsl.jbang.it.support.JBangTestSupport;
import org.assertj.core.api.Assertions;
import org.awaitility.Awaitility;
+import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import static org.junit.jupiter.api.condition.OS.WINDOWS;
+
+@Tag("container-only")
+@DisabledOnOs(WINDOWS)
public class InfrastructureITCase extends JBangTestSupport {
private static final String SERVICE = "ftp";
private static final String IMPL_SERVICE = "artemis";
diff --git
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/JolokiaITCase.java
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/JolokiaITCase.java
index b6281a00f363..1b348b7566ed 100644
---
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/JolokiaITCase.java
+++
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/JolokiaITCase.java
@@ -23,7 +23,11 @@ import
org.apache.camel.dsl.jbang.it.support.JBangTestSupport;
import org.assertj.core.api.Assertions;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import static org.junit.jupiter.api.condition.OS.WINDOWS;
+
+@DisabledOnOs(WINDOWS)
public class JolokiaITCase extends JBangTestSupport {
@Test
@@ -37,7 +41,7 @@ public class JolokiaITCase extends JBangTestSupport {
.contains("\"agentContext\":\"/jolokia\"");
Assertions.assertThat(execute("jolokia FromDirectoryRoute --stop"))
.as("Jolokia should stop")
- .contains("Stopped Jolokia for PID " + getPID(process));
+ .contains("Stopped Jolokia for PID " + process);
}
@Test
diff --git
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/OpenApiITCase.java
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/OpenApiITCase.java
index b9cb27dd6737..dd68bdd37511 100644
---
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/OpenApiITCase.java
+++
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/OpenApiITCase.java
@@ -30,9 +30,11 @@ import java.util.Random;
import org.apache.camel.dsl.jbang.it.support.InVersion;
import org.apache.camel.dsl.jbang.it.support.JBangTestSupport;
import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;
+@Tag("container-only")
public class OpenApiITCase extends JBangTestSupport {
final HttpClient httpClient = HttpClient.newHttpClient();
diff --git
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/RunCommandITCase.java
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/RunCommandITCase.java
index 86bbe5ebef5d..c202246a8ebc 100644
---
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/RunCommandITCase.java
+++
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/RunCommandITCase.java
@@ -31,14 +31,15 @@ import org.apache.camel.test.infra.cli.common.CliProperties;
import org.assertj.core.api.Assertions;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Assumptions;
+import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
-import org.junit.jupiter.api.condition.EnabledOnOs;
+import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
-import static org.junit.jupiter.api.condition.OS.LINUX;
+import static org.junit.jupiter.api.condition.OS.WINDOWS;
public class RunCommandITCase extends JBangTestSupport {
@@ -80,6 +81,7 @@ public class RunCommandITCase extends JBangTestSupport {
}
@Test
+ @Tag("container-only")
public void runRoutesFromMultipleFilesUsingWildcardTest() {
execute("init one.yaml --directory=/tmp/one");
execute("init two.xml --directory=/tmp/two");
@@ -126,6 +128,7 @@ public class RunCommandITCase extends JBangTestSupport {
}
@Test
+ @Tag("container-only")
public void runDownloadedFromGithubTest() {
execute("init
https://github.com/apache/camel-kamelets-examples/tree/main/jbang/dependency-injection");
Assertions.assertThat(containerService.listDirectory(DEFAULT_ROUTE_FOLDER))
@@ -156,7 +159,7 @@ public class RunCommandITCase extends JBangTestSupport {
final String process = executeBackground(String.format("run
%s/cheese.xml --camel-version=%s", mountPoint(), version));
checkLogContainsPattern(String.format(" Apache Camel %s .* started",
version));
checkLogContains(DEFAULT_MSG);
- execute("stop " + getPID(process));
+ execute("stop " + process);
}
@Test
@@ -168,7 +171,8 @@ public class RunCommandITCase extends JBangTestSupport {
}
@Test
- @EnabledOnOs(LINUX)
+ @DisabledOnOs(WINDOWS)
+ @Tag("container-only")
@DisabledIf(value = "java.awt.GraphicsEnvironment#isHeadless")
public void runFromClipboardTest() throws IOException {
Assumptions.assumeTrue(execInHost("command -v ssh").contains("ssh"));
diff --git
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/RunCommandOnMqttITCase.java
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/RunCommandOnMqttITCase.java
index 7e3ac64f26bc..26d0107f8529 100644
---
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/RunCommandOnMqttITCase.java
+++
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/RunCommandOnMqttITCase.java
@@ -24,9 +24,11 @@ import org.apache.camel.test.AvailablePortFinder;
import
org.apache.camel.test.infra.mosquitto.services.MosquittoLocalContainerService;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
+@Tag("container-only")
public class RunCommandOnMqttITCase extends JBangTestSupport {
private static int mqttPort = AvailablePortFinder.getNextAvailable();
@@ -54,7 +56,7 @@ public class RunCommandOnMqttITCase extends JBangTestSupport {
checkLogContains("Started route1 (kamelet:mqtt5-source)");
final String payloadFile = "payload.json";
newFileInDataFolder(payloadFile, "{\"value\": 21}");
- sendCmd(String.format("%s/%s", mountPoint(), payloadFile),
getPID(process));
+ sendCmd(String.format("%s/%s", mountPoint(), payloadFile), process);
checkLogContains("The temperature is 21");
}
@@ -63,7 +65,7 @@ public class RunCommandOnMqttITCase extends JBangTestSupport {
copyResourceInDataFolder(TestResources.STUB_ROUTE);
final String process = executeBackground(String.format("run %s/%s
--stub=jms",
mountPoint(), TestResources.STUB_ROUTE.getName()));
- checkCommandOutputs("cmd send --body='Hello camel from stubbed jms' "
+ getPID(process), "Sent (success)");
+ checkCommandOutputs("cmd send --body='Hello camel from stubbed jms' "
+ process, "Sent (success)");
checkCommandOutputs("cmd stub --browse", "Hello camel from stubbed
jms", ASSERTION_WAIT_SECONDS);
}
diff --git
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/ScriptingWithPipesITCase.java
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/ScriptingWithPipesITCase.java
index 6915b4c3c87a..2899bea8a5af 100644
---
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/ScriptingWithPipesITCase.java
+++
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/ScriptingWithPipesITCase.java
@@ -20,7 +20,11 @@ import java.io.IOException;
import org.apache.camel.dsl.jbang.it.support.JBangTestSupport;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import static org.junit.jupiter.api.condition.OS.WINDOWS;
+
+@DisabledOnOs(WINDOWS)
public class ScriptingWithPipesITCase extends JBangTestSupport {
@Test
public void testPipeScript() throws IOException {
diff --git
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/support/JBangTestSupport.java
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/support/JBangTestSupport.java
index df1060a7562a..74d9e8da358f 100644
---
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/support/JBangTestSupport.java
+++
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/support/JBangTestSupport.java
@@ -29,6 +29,7 @@ import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -66,6 +67,7 @@ public abstract class JBangTestSupport {
protected static final int ASSERTION_WAIT_SECONDS
=
Integer.parseInt(System.getProperty("jbang.it.assert.wait.timeout", "60"));
+ //to use only in case of container execution, so mark with the tag
@Tag("container-only") the test
protected static final String DEFAULT_ROUTE_FOLDER = "/home/jbang";
public static final String DEFAULT_MSG = "Hello Camel from";
@@ -76,7 +78,9 @@ public abstract class JBangTestSupport {
protected void beforeEach(TestInfo testInfo) throws IOException {
Assertions.assertThat(DATA_FOLDER).as("%s need to be set",
CliProperties.DATA_FOLDER).isNotBlank();
containerDataFolder = Files.createDirectory(Paths.get(DATA_FOLDER,
containerService.id())).toAbsolutePath().toString();
- Files.setPosixFilePermissions(Paths.get(containerDataFolder),
EnumSet.allOf(PosixFilePermission.class));
+ if
(FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) {
+ Files.setPosixFilePermissions(Paths.get(containerDataFolder),
EnumSet.allOf(PosixFilePermission.class));
+ }
logger.debug("running {}#{} using data folder {}",
getClass().getName(), testInfo.getDisplayName(), getDataFolder());
}
@@ -90,10 +94,6 @@ public abstract class JBangTestSupport {
}
}
- protected void stopAllRoutes() {
- execute("stop --all");
- }
-
protected enum TestResources {
ROUTE2("route2.yaml", "/jbang/it/route2.yaml"),
TEST_PROFILE_PROP("application-test.properties",
"/jbang/it/application-test.properties"),
@@ -146,7 +146,7 @@ public abstract class JBangTestSupport {
}
protected String mountPoint() {
- return String.format("%s/%s", containerService.getMountPoint(),
containerService.id());
+ return Paths.get(containerService.getMountPoint(),
containerService.id()).toString();
}
protected void initAndRunInBackground(String file) {
@@ -222,13 +222,6 @@ public abstract class JBangTestSupport {
.contains(contains)));
}
- protected void checkContainerLogContainsAllOf(int waitForSeconds,
String... contains) {
- Assertions.assertThatNoException().isThrownBy(() -> Awaitility.await()
- .atMost(waitForSeconds, TimeUnit.SECONDS)
- .untilAsserted(() -> Assertions.assertThat(getContainerLogs())
- .contains(contains)));
- }
-
protected void checkLogContains(String contains) {
checkLogContains(contains, ASSERTION_WAIT_SECONDS);
}
@@ -293,14 +286,6 @@ public abstract class JBangTestSupport {
return getLogs(null);
}
- protected String getPID(String startupMessage) {
- return startupMessage.split("PID:")[1].split("
")[1].replaceAll("[^0-9]", "");
- }
-
- protected String getContainerLogs() {
- return containerService.getContainerLogs();
- }
-
protected String getLogs(String route) {
return execute(Optional.ofNullable(route)
.map(r -> String.format("log %s --follow=false", r))
@@ -352,7 +337,10 @@ public abstract class JBangTestSupport {
protected String execInHost(String command) {
try {
- ProcessBuilder builder = new ProcessBuilder("/bin/bash", "-c",
command);
+ boolean isWindows = System.getProperty("os.name",
"").toLowerCase().startsWith("win");
+ ProcessBuilder builder = isWindows
+ ? new ProcessBuilder("cmd.exe", "/c", command)
+ : new ProcessBuilder("/bin/bash", "-c", command);
builder.redirectErrorStream(true);
final Process process = builder.start();
Awaitility.await("process is running")
@@ -419,9 +407,11 @@ public abstract class JBangTestSupport {
}
protected void assertFileInContainerExists(String fileAbsolutePath) {
- String fileName =
Path.of(fileAbsolutePath).getFileName().toFile().getName();
-
Assertions.assertThat(containerService.listDirectory(Path.of(fileAbsolutePath).getParent().toAbsolutePath().toString())
- .anyMatch(child -> fileName.equals(child)))
+ Path path = Path.of(fileAbsolutePath);
+ String fileName = path.getFileName().toString();
+ String parentDir = path.getParent().toString();
+ Assertions.assertThat(containerService.listDirectory(parentDir)
+ .anyMatch(fileName::equals))
.as("check if file " + fileAbsolutePath + " exists")
.isTrue();
}
diff --git a/test-infra/camel-test-infra-cli/README.adoc
b/test-infra/camel-test-infra-cli/README.adoc
index 30e2e8943131..326eae69f872 100644
--- a/test-infra/camel-test-infra-cli/README.adoc
+++ b/test-infra/camel-test-infra-cli/README.adoc
@@ -1,24 +1,65 @@
-:image-name: fedora
+:image-name: Fedora
:config-class:
src/test/java/org/apache/camel/test/infra/cli/common/CliProperties.java
-= Camel JBang CLI container
+= Camel JBang CLI test infrastructure
== Overview
-This module is used to build a container based on {image-name} image and Camel
JBang.
+This module provides test infrastructure for Camel CLI (Camel JBang)
integration tests.
+Two service implementations are available:
+
+* *Container-based* (`CliLocalContainerService`) -- builds and runs a Docker
container based on {image-name} image with Camel JBang. This is the default.
+* *Process-based* (`CliLocalProcessService`) -- runs JBang/Camel CLI directly
on the host via `ProcessBuilder`, without Docker. Useful on environments where
Docker is not available.
+
+== Service selection
+
+The service implementation is selected via the system property
`camel-cli.instance.type`:
+
+* Default (or `local-camel-cli-container`): uses the container-based service
+* `local-camel-cli-process`: uses the process-based service (requires JBang
pre-installed on the host)
+
+Example using the process-based service:
+
+[source,bash]
+----
+mvn verify -pl test-infra/camel-test-infra-cli -Pjbang-it-test
-Dcamel-cli.instance.type=local-camel-cli-process
+----
== Configuration
System variables are defined in link:{config-class}[CliProperties]
+=== Common properties (both services)
+
- `cli.service.repo` : the repo on github, default `apache/camel`
- `cli.service.branch` : the branch of the repo on github, default `main`
- `cli.service.version` : the version of the `camel.jbang.version` defined in
the `CamelJBang.java` file, default value is `default`, it means it uses the
default values in the file
- - `cli.service.data.folder` : mandatory - path to local folder to bind as
volume in the container
- - `cli.service.ssh.password` : ssh password set to access to the container
via ssh, default `jbang`
- - `cli.service.execute.version` : Camel version set just after container
start, default is empty so the version is the one in the branch
+ - `cli.service.data.folder` : path to local folder used as working directory
(container: bind as volume; process: working directory). For the process-based
service, a temp directory is created if not set.
+ - `cli.service.execute.version` : Camel version set just after service start,
default is empty so the version is the one in the branch
- `cli.service.mvn.repos` : comma separated list of custom Maven
repositories, default empty
- - `cli.service.mvn.local` : path to the host folder mounted as container
local maven repository
+ - `cli.service.mvn.local` : path to the host folder used as local maven
repository (container: mounted as bind volume; process: set via `JBANG_REPO`
env var and a temporary `settings.xml` passed with `--maven-settings`)
+
+=== Process-only properties
+
+ - `cli.service.skip.install` : when set to `true`, skips the JBang
trust/install/uninstall steps and uses the `camel` command already available on
the host, default `false`
+
+=== Container-only properties
+
+ - `cli.service.ssh.password` : ssh password set to access to the container
via ssh, default `jbang`
- `cli.service.extra.hosts` : comma separated host=ip pairs to add in the
hosts file
- `cli.service.trusted.paths` : commas separated paths, relative to the host,
of the files containing PEM trusted certificates
- `cli.service.docker.file` : path to a custom Dockerfile, by default the one
in the classpath at _org/apache/camel/test/infra/cli/services/Dockerfile_ will
be used
+
+== Process-based service details
+
+The `CliLocalProcessService` replicates the container setup sequence using
local JBang commands:
+
+1. `jbang trust add` for the configured repository
+2. `jbang app install` to install the Camel CLI app
+3. Optionally sets the Camel version and Maven repositories
+
+On shutdown, it runs `jbang app uninstall camel` and cleans up any
auto-created temp directory.
+
+*Prerequisites:* JBang must be installed and available on the host. The
`~/.jbang/bin` directory is automatically prepended to `PATH` for child
processes.
+
+*Limitations:* `getSshPort()` and `getSshPassword()` throw
`UnsupportedOperationException`. `getContainerLogs()` returns an empty string.
diff --git a/test-infra/camel-test-infra-cli/pom.xml
b/test-infra/camel-test-infra-cli/pom.xml
index 6c6d26e15679..3bc9cfdce494 100644
--- a/test-infra/camel-test-infra-cli/pom.xml
+++ b/test-infra/camel-test-infra-cli/pom.xml
@@ -122,6 +122,7 @@
<configuration>
<target>
<mkdir dir="target/tmp-repo"/>
+ <mkdir dir="target/data"/>
</target>
</configuration>
<goals>
@@ -133,15 +134,6 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
- <executions>
- <execution>
- <id>integration-test</id>
- <goals>
- <goal>integration-test</goal>
- <goal>verify</goal>
- </goals>
- </execution>
- </executions>
<configuration>
<rerunFailingTestsCount>${surefire.rerunFailingTestsCount}</rerunFailingTestsCount>
<systemPropertyVariables>
diff --git
a/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/common/CliProperties.java
b/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/common/CliProperties.java
index 7ad8e78064ab..914246208c5f 100644
---
a/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/common/CliProperties.java
+++
b/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/common/CliProperties.java
@@ -41,6 +41,8 @@ public final class CliProperties {
public static final String TRUSTED_CERT_PATHS =
"cli.service.trusted.paths";
+ public static final String SKIP_INSTALL = "cli.service.skip.install";
+
private CliProperties() {
}
}
diff --git
a/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/it/AbstractTestSupport.java
b/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/it/AbstractTestSupport.java
index 67b2d4d1fc82..a3b8e2aad798 100644
---
a/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/it/AbstractTestSupport.java
+++
b/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/it/AbstractTestSupport.java
@@ -23,6 +23,11 @@ import
org.apache.camel.test.infra.cli.services.CliServiceFactory;
public abstract class AbstractTestSupport {
+ static boolean isLocalProcessWithSkipInstall() {
+ return "true".equals(System.getProperty("cli.service.skip.install"))
+ &&
"local-camel-cli-process".equals(System.getProperty("camel-cli.instance.type"));
+ }
+
protected void execute(Consumer<CliService> consumer) {
try (CliService containerService = CliServiceFactory.createService()) {
containerService.beforeAll(null);
diff --git
a/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/it/CliConfigITCase.java
b/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/it/CliConfigITCase.java
index 01a727feca69..0823f0ab3719 100644
---
a/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/it/CliConfigITCase.java
+++
b/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/it/CliConfigITCase.java
@@ -22,6 +22,8 @@ import java.nio.file.Path;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledIf;
+import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
import org.junitpioneer.jupiter.ReadsSystemProperty;
import org.junitpioneer.jupiter.RestoreSystemProperties;
@@ -32,6 +34,7 @@ public class CliConfigITCase extends AbstractTestSupport {
@Test
@SetSystemProperty(key = "cli.service.version", value = "4.16.0")
+ @DisabledIf("isLocalProcessWithSkipInstall")
public void setJBangVersionTest() {
execute(cliService -> {
String version = cliService.version();
@@ -50,6 +53,7 @@ public class CliConfigITCase extends AbstractTestSupport {
@Test
@SetSystemProperty(key = "cli.service.branch", value = "camel-4.4.x")
+ @DisabledIf("isLocalProcessWithSkipInstall")
public void setBranchTest() {
execute(cliService -> {
String version = cliService.version();
@@ -60,6 +64,7 @@ public class CliConfigITCase extends AbstractTestSupport {
@Test
@SetSystemProperty(key = "cli.service.repo", value =
"mcarlett/apache-camel")
@SetSystemProperty(key = "cli.service.branch", value = "camel-cli-test")
+ @DisabledIf("isLocalProcessWithSkipInstall")
public void setRepoTest() {
execute(cliService -> {
String version = cliService.version();
@@ -71,6 +76,7 @@ public class CliConfigITCase extends AbstractTestSupport {
@ReadsSystemProperty
@EnabledIfSystemProperty(named = "currentProjectVersion", matches =
"^(?!\\s*$).+",
disabledReason = "currentProjectVersion system
property must be set")
+ @DisabledIf("isLocalProcessWithSkipInstall")
public void setCurrentProjectVersionTest() {
String currentCamelVersion =
System.getProperty("currentProjectVersion");
System.setProperty("cli.service.version", currentCamelVersion);
@@ -83,10 +89,13 @@ public class CliConfigITCase extends AbstractTestSupport {
@Test
@SetSystemProperty(key = "cli.service.mvn.local", value =
"target/tmp-repo")
+ @DisabledIfSystemProperty(named = "camel-cli.instance.type", matches =
"local-camel-cli-process",
+ disabledReason = "Test not supported with local
process service")
public void setLocalMavenRepoTest() {
final Path dir = Path.of("target/tmp-repo");
execute(cliService -> {
- cliService.version();
+ cliService.execute("init foo.yaml");
+ cliService.execute("run foo.yaml --max-seconds=5");
Assertions.assertTrue(dir.toFile().exists(), "Check the local
maven repository is created");
try {
Assertions.assertTrue(Files.list(dir).findFirst().isPresent(),
diff --git
a/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/it/RunITCase.java
b/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/it/RunITCase.java
index d64e43b57875..e58d2bc66ce9 100644
---
a/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/it/RunITCase.java
+++
b/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/it/RunITCase.java
@@ -20,6 +20,7 @@ import org.apache.camel.support.ObjectHelper;
import org.apache.camel.test.infra.cli.services.CliService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
import org.junitpioneer.jupiter.ReadsSystemProperty;
import org.junitpioneer.jupiter.RestoreSystemProperties;
@@ -32,6 +33,7 @@ public class RunITCase extends AbstractTestSupport {
@ReadsSystemProperty
@EnabledIfSystemProperty(named = "currentProjectVersion", matches =
"^(?!\\s*$).+",
disabledReason = "currentProjectVersion system
property must be set")
+ @DisabledIf("isLocalProcessWithSkipInstall")
public void readPidFromBackgroundExecutionInCurrentVersionTest() {
String currentCamelVersion =
System.getProperty("currentProjectVersion");
System.setProperty("cli.service.version", currentCamelVersion);
@@ -41,6 +43,7 @@ public class RunITCase extends AbstractTestSupport {
@Test
@SetSystemProperty(key = "cli.service.version", value = "4.14.2")
+ @DisabledIf("isLocalProcessWithSkipInstall")
public void readPidFromBackgroundExecutionInPreviousVersionTest() {
execute(this::checkPidFromBackgroundExec);
}
diff --git
a/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/services/CliBuiltContainer.java
b/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/services/CliBuiltContainer.java
index 9d359629d537..2dee9d5b2b7b 100644
---
a/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/services/CliBuiltContainer.java
+++
b/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/services/CliBuiltContainer.java
@@ -66,7 +66,7 @@ public class CliBuiltContainer extends
GenericContainer<CliBuiltContainer> {
UID = (Integer) Files.getAttribute(target, "unix:uid");
GID = (Integer) Files.getAttribute(target, "unix:gid");
LOGGER.info("building container using user {} and group {}", UID,
GID);
- } catch (IOException e) {
+ } catch (IOException | UnsupportedOperationException e) {
LOGGER.warn("unable to retrieve user id and group: {}",
e.getMessage());
}
}
diff --git
a/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/services/CliLocalContainerService.java
b/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/services/CliLocalContainerService.java
index 3f71c9958135..9f09e0208af4 100644
---
a/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/services/CliLocalContainerService.java
+++
b/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/services/CliLocalContainerService.java
@@ -35,7 +35,6 @@ import org.testcontainers.containers.Container;
public class CliLocalContainerService implements CliService,
ContainerService<CliBuiltContainer> {
public static final String CONTAINER_NAME = "camel-cli";
- public static final String MAIN_COMMAND =
System.getProperty("cli.service.command", "camel");
private static final Logger LOG =
LoggerFactory.getLogger(CliLocalContainerService.class);
private final CliBuiltContainer container;
private String version;
@@ -114,7 +113,7 @@ public class CliLocalContainerService implements
CliService, ContainerService<Cl
@Override
public String execute(String command) {
- return executeGenericCommand(MAIN_COMMAND + " " + command);
+ return executeGenericCommand(getMainCommand() + " " + command);
}
@Override
@@ -216,11 +215,6 @@ public class CliLocalContainerService implements
CliService, ContainerService<Cl
return container.getSshPassword();
}
- @Override
- public String getMainCommand() {
- return MAIN_COMMAND;
- }
-
private static Map<String, String> getHostsMap() {
return
Optional.ofNullable(System.getProperty(CliProperties.EXTRA_HOSTS))
.map(p -> p.split(","))
diff --git
a/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/services/CliLocalProcessService.java
b/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/services/CliLocalProcessService.java
new file mode 100644
index 000000000000..9a1c44ded42e
--- /dev/null
+++
b/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/services/CliLocalProcessService.java
@@ -0,0 +1,480 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.infra.cli.services;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+
+import org.apache.camel.test.infra.cli.common.CliProperties;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.StringHelper;
+import org.apache.commons.io.FileUtils;
+import org.junit.jupiter.api.Assertions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A {@link CliService} implementation that runs JBang/Camel CLI directly on
the host via {@link ProcessBuilder},
+ * eliminating the Docker dependency. Requires JBang to be pre-installed on
the host.
+ */
+public class CliLocalProcessService implements CliService {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(CliLocalProcessService.class);
+ private static final int COMMAND_TIMEOUT_SECONDS = 120;
+ private static final boolean IS_WINDOWS = System.getProperty("os.name",
"").toLowerCase().startsWith("win");
+
+ private final String repo;
+ private final String branch;
+ private final String jbangVersion;
+ private final String forceToRunVersion;
+ private final String mavenRepos;
+ private final String localMavenRepo;
+ private final boolean skipInstall;
+ private final Path workDir;
+ private final boolean tempWorkDir;
+ private Path mavenSettingsFile;
+ private final List<String> backgroundPids = new ArrayList<>();
+ private String version;
+
+ public CliLocalProcessService() {
+ this.repo = System.getProperty(CliProperties.REPO, "apache/camel");
+ this.branch = System.getProperty(CliProperties.BRANCH, "main");
+ this.jbangVersion = System.getProperty(CliProperties.VERSION,
"default");
+ this.forceToRunVersion =
System.getProperty(CliProperties.FORCE_RUN_VERSION, "");
+ this.mavenRepos = System.getProperty(CliProperties.MVN_REPOS);
+ this.localMavenRepo = System.getProperty(CliProperties.MVN_LOCAL_REPO);
+ this.skipInstall =
Boolean.parseBoolean(System.getProperty(CliProperties.SKIP_INSTALL, "false"));
+
+ String dataFolder = System.getProperty(CliProperties.DATA_FOLDER);
+ if (ObjectHelper.isNotEmpty(dataFolder)) {
+ this.workDir = Path.of(dataFolder);
+ this.tempWorkDir = false;
+ } else {
+ try {
+ this.workDir = Files.createTempDirectory("camel-cli-process-");
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to create temp work
directory", e);
+ }
+ this.tempWorkDir = true;
+ }
+ }
+
+ @Override
+ public void registerProperties() {
+ // no-op
+ }
+
+ @Override
+ public void initialize() {
+ LOG.info("Initializing local process CLI service");
+
+ if (ObjectHelper.isNotEmpty(localMavenRepo)) {
+ createMavenSettingsFile();
+ }
+
+ if (skipInstall) {
+ LOG.info("Skipping JBang installation, using existing camel
command");
+ } else {
+ backupUserFiles();
+
+ if (!isJBangInstalled()) {
+ installJBang();
+ }
+
+ executeGenericCommand(String.format("jbang trust add
https://github.com/%s/", repo));
+
+ String installCmd;
+ if ("default".equals(jbangVersion)) {
+ installCmd = String.format("jbang app install --force --fresh
camel@%s/%s", repo, branch);
+ } else {
+ installCmd = String.format(
+ "jbang app install --force --fresh
-Dcamel.jbang.version=%s camel@%s/%s", jbangVersion, repo, branch);
+ }
+ executeGenericCommand(installCmd);
+ }
+
+ if (ObjectHelper.isNotEmpty(forceToRunVersion)) {
+ LOG.info("force to use version {}", forceToRunVersion);
+ execute("version set " + forceToRunVersion);
+ }
+ if (ObjectHelper.isNotEmpty(mavenRepos)) {
+ LOG.info("set repositories {}", mavenRepos);
+ execute(String.format("config set repos=%s", mavenRepos));
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Camel JBang version {}", version());
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ LOG.info("Shutting down local process CLI service");
+ if (isCamelCommandAvailable()) {
+ stopBackgroundProcesses();
+ if (!skipInstall) {
+ try {
+ executeGenericCommand("jbang app uninstall camel");
+ } catch (Exception e) {
+ LOG.warn("Failed to uninstall camel jbang app: {}",
e.getMessage());
+ }
+ }
+ }
+ if (mavenSettingsFile != null) {
+ FileUtils.deleteQuietly(mavenSettingsFile.toFile());
+ }
+ if (tempWorkDir) {
+ FileUtils.deleteQuietly(workDir.toFile());
+ } else {
+
FileUtils.deleteQuietly(Path.of(workDir.toFile().getAbsolutePath(),
".camel-jbang").toFile());
+ }
+ FileUtils.deleteQuietly(new File(".camel-jbang"));
+ }
+
+ @Override
+ public String execute(String command) {
+ String camelCommand = getMainCommand() + " " + command;
+ if (mavenSettingsFile != null && command.startsWith("run ")) {
+ camelCommand += " --maven-settings=" +
mavenSettingsFile.toAbsolutePath();
+ }
+ return executeGenericCommand(camelCommand);
+ }
+
+ @Override
+ public String executeBackground(String command) {
+ final String pid = StringHelper.after(execute(command.concat("
--background")), "PID:").trim();
+ String cleanPid = org.apache.camel.support.ObjectHelper.isNumber(pid)
? pid : StringHelper.before(pid, " ");
+ if (cleanPid != null) {
+ backgroundPids.add(cleanPid);
+ }
+ return cleanPid;
+ }
+
+ @Override
+ public String executeGenericCommand(String command) {
+ try {
+ LOG.debug("Executing command: {}", command);
+
+ ProcessBuilder pb;
+ if (IS_WINDOWS) {
+ pb = new ProcessBuilder("cmd", "/c", command);
+ } else {
+ pb = new ProcessBuilder("/bin/bash", "-c", command);
+ }
+
+ pb.directory(workDir.toFile());
+
+ // Prepend ~/.jbang/bin to PATH
+ String jbangBin = Path.of(System.getProperty("user.home"),
".jbang", "bin").toString();
+ String currentPath = pb.environment().getOrDefault("PATH", "");
+ pb.environment().put("PATH", jbangBin + (IS_WINDOWS ? ";" : ":") +
currentPath);
+
+ if (ObjectHelper.isNotEmpty(localMavenRepo)) {
+ pb.environment().put("JBANG_REPO", localMavenRepo);
+ }
+
+ Process process = pb.start();
+
+ // Read stderr concurrently to avoid deadlocks
+ CompletableFuture<String> stderrFuture =
CompletableFuture.supplyAsync(() -> {
+ try (BufferedReader reader
+ = new BufferedReader(new
InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8))) {
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line).append(System.lineSeparator());
+ }
+ return sb.toString();
+ } catch (IOException e) {
+ return e.getMessage();
+ }
+ });
+
+ String stdout;
+ try (BufferedReader reader
+ = new BufferedReader(new
InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line).append(System.lineSeparator());
+ }
+ stdout = sb.toString();
+ }
+
+ boolean finished = process.waitFor(COMMAND_TIMEOUT_SECONDS,
TimeUnit.SECONDS);
+ if (!finished) {
+ process.destroyForcibly();
+ Assertions.fail(String.format("command '%s' timed out after %d
seconds", command, COMMAND_TIMEOUT_SECONDS));
+ }
+
+ String stderr = stderrFuture.join();
+
+ if (process.exitValue() != 0) {
+ Assertions.fail(String.format("command %s failed with output
%s and error %s", command, stdout, stderr));
+ }
+
+ if (LOG.isDebugEnabled()) {
+ if (ObjectHelper.isNotEmpty(stdout)) {
+ LOG.debug("result out {}", stdout);
+ }
+ if (ObjectHelper.isNotEmpty(stderr)) {
+ LOG.debug("result error {}", stderr);
+ }
+ }
+
+ return stdout;
+ } catch (IOException | InterruptedException e) {
+ LOG.error("ERROR running command: {}", command, e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void copyFileInternally(String source, String destination) {
+ try {
+ Files.copy(Path.of(source), Path.of(destination),
StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e) {
+ Assertions.fail(String.format("unable to copy file %s to %s",
source, destination), e);
+ }
+ }
+
+ @Override
+ public String getMountPoint() {
+ return workDir.toAbsolutePath().toString();
+ }
+
+ @Override
+ public String getContainerLogs() {
+ LOG.warn("getContainerLogs() is not supported for local process
service");
+ return "";
+ }
+
+ @Override
+ public int getDevConsolePort() {
+ return 8080;
+ }
+
+ @Override
+ public Stream<String> listDirectory(String directoryPath) {
+ try {
+ return Files.list(Path.of(directoryPath))
+ .map(p -> p.getFileName().toString());
+ } catch (IOException e) {
+ Assertions.fail("unable to list " + directoryPath, e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String id() {
+ return "local-" + ProcessHandle.current().pid();
+ }
+
+ @Override
+ public String version() {
+ return Optional.ofNullable(version)
+ .orElseGet(() -> {
+ final String versionSummary = execute("version");
+ if (versionSummary.contains("User configuration") &&
versionSummary.contains("camel-version = ")) {
+ version = StringHelper.between(versionSummary,
"camel-version = ", "\n").trim();
+ }
+ if (version == null) {
+ version = StringHelper.between(versionSummary, "Camel
JBang version:", "\n").trim();
+ }
+ return version;
+ });
+ }
+
+ private boolean isCamelCommandAvailable() {
+ try {
+ String checkCmd = IS_WINDOWS
+ ? "where " + getMainCommand()
+ : "which " + getMainCommand();
+ ProcessBuilder pb = IS_WINDOWS
+ ? new ProcessBuilder("cmd", "/c", checkCmd)
+ : new ProcessBuilder("/bin/bash", "-c", checkCmd);
+ String jbangBin = Path.of(System.getProperty("user.home"),
".jbang", "bin").toString();
+ String currentPath = pb.environment().getOrDefault("PATH", "");
+ pb.environment().put("PATH", jbangBin + (IS_WINDOWS ? ";" : ":") +
currentPath);
+ pb.redirectErrorStream(true);
+ Process process = pb.start();
+ boolean finished = process.waitFor(30, TimeUnit.SECONDS);
+ if (!finished) {
+ process.destroyForcibly();
+ return false;
+ }
+ boolean available = process.exitValue() == 0;
+ LOG.info("Camel command '{}' available: {}", getMainCommand(),
available);
+ return available;
+ } catch (IOException | InterruptedException e) {
+ LOG.info("Camel command '{}' not found: {}", getMainCommand(),
e.getMessage());
+ return false;
+ }
+ }
+
+ private boolean isJBangInstalled() {
+ try {
+ String jbangBin = Path.of(System.getProperty("user.home"),
".jbang", "bin").toString();
+ String pathSeparator = IS_WINDOWS ? ";" : ":";
+ ProcessBuilder pb = IS_WINDOWS
+ ? new ProcessBuilder("cmd", "/c", "jbang version")
+ : new ProcessBuilder("/bin/bash", "-c", "jbang version");
+ String currentPath = pb.environment().getOrDefault("PATH", "");
+ pb.environment().put("PATH", jbangBin + pathSeparator +
currentPath);
+ pb.redirectErrorStream(true);
+ Process process = pb.start();
+ boolean finished = process.waitFor(30, TimeUnit.SECONDS);
+ if (!finished) {
+ process.destroyForcibly();
+ return false;
+ }
+ boolean installed = process.exitValue() == 0;
+ LOG.info("JBang installed: {}", installed);
+ return installed;
+ } catch (IOException | InterruptedException e) {
+ LOG.info("JBang not found: {}", e.getMessage());
+ return false;
+ }
+ }
+
+ private void installJBang() {
+ LOG.info("Installing JBang");
+ try {
+ ProcessBuilder pb;
+ if (IS_WINDOWS) {
+ String script = "iex \"& { $(iwr -useb https://ps.jbang.dev) }
app setup\"";
+ String encoded = java.util.Base64.getEncoder().encodeToString(
+ script.getBytes(StandardCharsets.UTF_16LE));
+ pb = new ProcessBuilder("powershell", "-NoProfile",
"-EncodedCommand", encoded);
+ } else {
+ pb = new ProcessBuilder(
+ "/bin/bash", "-c",
+ "curl -Ls https://sh.jbang.dev | bash -s - app setup");
+ }
+ pb.directory(workDir.toFile());
+ pb.redirectErrorStream(true);
+ Process process = pb.start();
+
+ String output;
+ try (BufferedReader reader
+ = new BufferedReader(new
InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line).append(System.lineSeparator());
+ }
+ output = sb.toString();
+ }
+
+ boolean finished = process.waitFor(COMMAND_TIMEOUT_SECONDS,
TimeUnit.SECONDS);
+ if (!finished) {
+ process.destroyForcibly();
+ Assertions.fail("JBang installation timed out");
+ }
+ if (process.exitValue() != 0) {
+ Assertions.fail("JBang installation failed: " + output);
+ }
+ LOG.info("JBang installed successfully");
+ } catch (IOException | InterruptedException e) {
+ throw new RuntimeException("Failed to install JBang", e);
+ }
+ }
+
+ private void stopBackgroundProcesses() {
+ // Try graceful stop first
+ try {
+ execute("stop");
+ } catch (Exception e) {
+ LOG.warn("Failed to stop camel instances gracefully: {}",
e.getMessage());
+ }
+ // Force kill any remaining tracked PIDs
+ for (String pid : backgroundPids) {
+ try {
+ ProcessHandle.of(Long.parseLong(pid)).ifPresent(ph -> {
+ LOG.info("Force killing background process {}", pid);
+ ph.destroyForcibly();
+ });
+ } catch (NumberFormatException e) {
+ LOG.warn("Invalid PID: {}", pid);
+ }
+ }
+ backgroundPids.clear();
+ }
+
+ private Path getUserPropertiesFile() {
+ return Path.of(System.getProperty("user.home"),
".camel-jbang-user.properties");
+ }
+
+ private void backupUserFiles() {
+ backupFile(getUserPropertiesFile());
+ backupFile(Path.of(System.getProperty("user.home"),
".camel-jbang-plugins.json"));
+ }
+
+ private void backupFile(Path file) {
+ if (Files.exists(file)) {
+ try {
+ Path backup = Path.of(file + ".backup" +
System.currentTimeMillis());
+ Files.move(file, backup, StandardCopyOption.REPLACE_EXISTING);
+ LOG.info("Backed up {} to {}", file, backup);
+ } catch (IOException e) {
+ LOG.warn("Failed to backup {}: {}", file, e.getMessage());
+ }
+ }
+ }
+
+ private void createMavenSettingsFile() {
+ try {
+ mavenSettingsFile = Files.createTempFile(workDir,
"maven-settings", ".xml");
+ String settingsContent = String.format(
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>%n"
+ + "<settings
xmlns=\"http://maven.apache.org/SETTINGS/1.0.0\"%n"
+ + "
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"%n"
+ + "
xsi:schemaLocation=\"http://maven.apache.org/SETTINGS/1.0.0 "
+ +
"http://maven.apache.org/xsd/settings-1.0.0.xsd\">%n"
+ + "
<localRepository>%s</localRepository>%n"
+ + "</settings>%n",
+ Path.of(localMavenRepo).toAbsolutePath());
+ Files.writeString(mavenSettingsFile, settingsContent,
StandardCharsets.UTF_8);
+ LOG.info("Created temporary Maven settings file at {} with
localRepository={}", mavenSettingsFile,
+ localMavenRepo);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to create temporary Maven
settings file", e);
+ }
+ }
+
+ @Override
+ public int getSshPort() {
+ throw new UnsupportedOperationException("SSH is not supported for
local process service");
+ }
+
+ @Override
+ public String getSshPassword() {
+ throw new UnsupportedOperationException("SSH is not supported for
local process service");
+ }
+
+}
diff --git
a/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/services/CliService.java
b/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/services/CliService.java
index 4b6e49ea2262..9c81eff5e55d 100644
---
a/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/services/CliService.java
+++
b/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/services/CliService.java
@@ -75,5 +75,7 @@ public interface CliService extends BeforeEachCallback,
AfterEachCallback, TestS
String getSshPassword();
- String getMainCommand();
+ default String getMainCommand() {
+ return System.getProperty("cli.service.command", "camel");
+ }
}
diff --git
a/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/services/CliServiceFactory.java
b/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/services/CliServiceFactory.java
index 03f5eae91bcf..bad418ff5cad 100644
---
a/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/services/CliServiceFactory.java
+++
b/test-infra/camel-test-infra-cli/src/test/java/org/apache/camel/test/infra/cli/services/CliServiceFactory.java
@@ -30,6 +30,7 @@ public final class CliServiceFactory {
public static CliService createService() {
return builder()
.addLocalMapping(CliLocalContainerService::new)
+ .addMapping("local-camel-cli-process",
CliLocalProcessService::new)
.build();
}
}
diff --git
a/test-infra/camel-test-infra-common/src/test/java/org/apache/camel/test/infra/common/services/TestServiceUtil.java
b/test-infra/camel-test-infra-common/src/test/java/org/apache/camel/test/infra/common/services/TestServiceUtil.java
index de979691f9eb..ea40bb13e337 100644
---
a/test-infra/camel-test-infra-common/src/test/java/org/apache/camel/test/infra/common/services/TestServiceUtil.java
+++
b/test-infra/camel-test-infra-common/src/test/java/org/apache/camel/test/infra/common/services/TestServiceUtil.java
@@ -70,14 +70,18 @@ public final class TestServiceUtil {
*/
public static void logAndRethrow(TestService service, ExtensionContext
extensionContext, Exception exception)
throws Exception {
- final Object testInstance =
extensionContext.getTestInstance().orElse(null);
+ if (extensionContext != null) {
+ final Object testInstance =
extensionContext.getTestInstance().orElse(null);
- if (testInstance != null) {
- LOG.error("Failed to initialize service {} for test {} on ({})",
service.getClass().getSimpleName(),
- extensionContext.getDisplayName(),
testInstance.getClass().getName());
+ if (testInstance != null) {
+ LOG.error("Failed to initialize service {} for test {} on
({})", service.getClass().getSimpleName(),
+ extensionContext.getDisplayName(),
testInstance.getClass().getName());
+ } else {
+ LOG.error("Failed to initialize service {} for test {}",
service.getClass().getSimpleName(),
+ extensionContext.getDisplayName());
+ }
} else {
- LOG.error("Failed to initialize service {} for test {}",
service.getClass().getSimpleName(),
- extensionContext.getDisplayName());
+ LOG.error("Failed to initialize service {}",
service.getClass().getSimpleName());
}
throw exception;