Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
bd5c66b
STX-221 Fix flaky parallel tests
valkovich-ds Jul 29, 2026
17d771b
[no ci] [maven-release-plugin] prepare release 2.0.4-rc.bd5c66b
streamx-cli-release-bot[bot] Jul 29, 2026
15abeff
[no ci] [maven-release-plugin] prepare for next development iteration
streamx-cli-release-bot[bot] Jul 29, 2026
0d681b8
[no ci] [maven-release-plugin] prepare release 2.0.4-rc.15abeff
streamx-cli-release-bot[bot] Jul 29, 2026
f9e8ec2
[no ci] [maven-release-plugin] prepare for next development iteration
streamx-cli-release-bot[bot] Jul 29, 2026
1470b91
Force CI build
valkovich-ds Jul 29, 2026
0962686
Merge branch 'STX-221-fix-flaky-parallel-tests' of github-dspl:stream…
valkovich-ds Jul 29, 2026
078c684
Force CI build
valkovich-ds Jul 29, 2026
5be8699
Force CI build
valkovich-ds Jul 29, 2026
482a306
Force CI build
valkovich-ds Jul 29, 2026
71e40ae
Force CI build
valkovich-ds Jul 29, 2026
d57b09f
Force CI build
valkovich-ds Jul 29, 2026
937927c
Force CI build
valkovich-ds Jul 29, 2026
b8ec72f
STX-221 Fix flaky parallel tests
valkovich-ds Jul 29, 2026
4cb7651
Force CI build
valkovich-ds Jul 29, 2026
7c061c2
Force CI build
valkovich-ds Jul 29, 2026
1af751b
STX-221 Fix flaky parallel tests and harden the local-run test setup
valkovich-ds Jul 31, 2026
41b4c2a
Force CI build
valkovich-ds Jul 31, 2026
3629d55
Force CI build
valkovich-ds Jul 31, 2026
a2a66a2
Force CI build
valkovich-ds Jul 31, 2026
511b8ec
Force CI build
valkovich-ds Jul 31, 2026
9111892
Force CI build
valkovich-ds Jul 31, 2026
0945602
Force CI build
valkovich-ds Jul 31, 2026
db32b98
Force CI build
valkovich-ds Jul 31, 2026
cd5c80a
Force CI build
valkovich-ds Jul 31, 2026
8cca522
Force CI build
valkovich-ds Jul 31, 2026
a7c4cd3
Force CI build
valkovich-ds Jul 31, 2026
c9ffc6d
Force CI build
valkovich-ds Jul 31, 2026
4ade3aa
Force CI build
valkovich-ds Jul 31, 2026
6333dfe
Force CI build
valkovich-ds Jul 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions .github/scripts/with-capture-docker-logs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ log_event() {
# Named pipes so we can capture PIDs of both sides of each pipeline
IMAGE_FIFO=$(mktemp -u)
CONTAINER_FIFO=$(mktemp -u)
mkfifo "$IMAGE_FIFO" "$CONTAINER_FIFO"
NETWORK_FIFO=$(mktemp -u)
mkfifo "$IMAGE_FIFO" "$CONTAINER_FIFO" "$NETWORK_FIFO"

# --- Image pull logging ---
docker events \
Expand All @@ -32,6 +33,18 @@ while read -r line; do
done < "$IMAGE_FIFO" &
IMAGE_READER_PID=$!

# --- Network lifecycle logging ---
docker events \
--filter 'type=network' \
--format '{{.Time}} NETWORK {{.Action}} name={{.Actor.Attributes.name}} id={{.Actor.ID}} container={{.Actor.Attributes.container}}' \
> "$NETWORK_FIFO" &
NETWORK_EVENTS_PID=$!

while read -r line; do
log_event "$line"
done < "$NETWORK_FIFO" &
NETWORK_READER_PID=$!

# --- Container lifecycle logging ---
docker events \
--filter 'type=container' \
Expand Down Expand Up @@ -82,10 +95,12 @@ CONTAINER_READER_PID=$!
cleanup() {
log_event "Shutting down docker log collector"
kill "$IMAGE_EVENTS_PID" "$IMAGE_READER_PID" \
"$NETWORK_EVENTS_PID" "$NETWORK_READER_PID" \
"$CONTAINER_EVENTS_PID" "$CONTAINER_READER_PID" 2>/dev/null || true
pkill -f "docker logs -f" 2>/dev/null || true
rm -f "$IMAGE_FIFO" "$CONTAINER_FIFO"
rm -f "$IMAGE_FIFO" "$CONTAINER_FIFO" "$NETWORK_FIFO"
wait "$IMAGE_EVENTS_PID" "$IMAGE_READER_PID" \
"$NETWORK_EVENTS_PID" "$NETWORK_READER_PID" \
"$CONTAINER_EVENTS_PID" "$CONTAINER_READER_PID" 2>/dev/null || true
}
trap cleanup EXIT
Expand Down
9 changes: 7 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<compiler-plugin.version>3.13.0</compiler-plugin.version>
<checkstyle-plugin.version>3.6.0</checkstyle-plugin.version>
<graalvm.version>25.0.2</graalvm.version>
<streamx.version>2.0.37</streamx.version>
<streamx.version>2.1.0-dev.STX-221.4</streamx.version>
<cloudevents.version>4.0.1</cloudevents.version>

<test.output.toFile>false</test.output.toFile>
Expand Down Expand Up @@ -167,6 +167,11 @@
<version>${cloudevents.version}</version>
</dependency>

<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
</dependency>

<!-- Native image dependencies -->
<dependency>
<groupId>org.graalvm.sdk</groupId>
Expand Down Expand Up @@ -586,4 +591,4 @@
</build>
</profile>
</profiles>
</project>
</project>
128 changes: 67 additions & 61 deletions src/test/java/com/streamx/cli/commands/local/run/RunCommandIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import io.quarkus.test.junit.QuarkusTest;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.List;
import java.util.UUID;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.AfterEach;
Expand All @@ -26,17 +27,21 @@
@DisabledIfDockerUnavailable
public class RunCommandIT extends CliBaseIT {

private static final String PREFIX =
"sx-run-" + UUID.randomUUID().toString().substring(0, 4) + "-";
private static String freshPrefix() {
return "sx-run-" + UUID.randomUUID().toString().substring(0, 4) + "-";
}

private static final String WEB_SERVER_SINK_IMAGE =
"ghcr.io/streamx-com/streamx-blueprints/web-server-sink:3.0.7-jvm";

private static final String BLOCKER_IMAGE = "alpine:3.20";

private static final int SIGTERM_EXIT_CODE = 128 + 15;

@BeforeEach
void isolateRunFromConcurrentInstances() {
System.setProperty("streamx.container.startup-timeout-seconds", "180");
System.setProperty("streamx.container.startup-timeout-seconds",
MeshTestSupport.CONTAINER_STARTUP_TIMEOUT_SECONDS);
System.setProperty("streamx.runner.pulsar.broker-port",
String.valueOf(MeshTestSupport.freePort()));
System.setProperty("streamx.runner.pulsar.http-port",
Expand All @@ -51,6 +56,7 @@ void stopMeshAndResetRunnerState() {
} catch (Exception ignored) {
// best-effort cleanup
}
awaitAsyncCommands();
System.clearProperty("streamx.runner.mesh-name-prefix");
System.clearProperty("streamx.container.startup-timeout-seconds");
System.clearProperty("streamx.runner.pulsar.broker-port");
Expand All @@ -60,7 +66,7 @@ void stopMeshAndResetRunnerState() {

@Test
void shouldWarnWhenEnvVariableIsUndefined() throws Exception {
System.setProperty("streamx.runner.mesh-name-prefix", PREFIX);
System.setProperty("streamx.runner.mesh-name-prefix", freshPrefix());
exec("settings", "set", "config.image.interpolated", WEB_SERVER_SINK_IMAGE);
exec("settings", "unset", "STREAMX_OWNER_SERVICE_NAME");
clearEnv("STREAMX_OWNER_SERVICE_NAME");
Expand All @@ -73,18 +79,14 @@ void shouldWarnWhenEnvVariableIsUndefined() throws Exception {
AsyncProcessHandle handle = execAsync("local", "run", "-f=" + meshPath);

try {
Awaitility.await()
.atMost(Duration.ofMinutes(3))
.pollInterval(Duration.ofSeconds(1))
.until(() -> handle.getStderr()
.contains("Environment variable 'STREAMX_OWNER_SERVICE_NAME'"));
awaitStderrContains(handle, "Environment variable 'STREAMX_OWNER_SERVICE_NAME'");

assertThat(handle.getStderr())
.contains("WARNING:")
.contains("STREAMX_OWNER_SERVICE_NAME");
} finally {
if (handle.thread().isAlive()) {
handle.interruptAndJoin(Duration.ofSeconds(30).toMillis());
if (handle.isAlive()) {
handle.stopAndJoin(Duration.ofSeconds(30).toMillis());
}
}
}
Expand All @@ -101,9 +103,10 @@ void shouldFailWhenMeshFileDoesNotExist() throws Exception {

@Test
void shouldReportContainerFailureWhenItsHostPortIsAlreadyTaken() throws Exception {
System.setProperty("streamx.runner.mesh-name-prefix", PREFIX);
String prefix = freshPrefix();
System.setProperty("streamx.runner.mesh-name-prefix", prefix);
exec("settings", "set", "config.image.interpolated", WEB_SERVER_SINK_IMAGE);
exec("settings", "set", "STREAMX_OWNER_SERVICE_NAME", PREFIX + "test-owner");
exec("settings", "set", "STREAMX_OWNER_SERVICE_NAME", prefix + "test-owner");

String meshPath = Paths.get("target/test-classes/mesh-interpolated.yaml")
.toAbsolutePath()
Expand All @@ -114,27 +117,32 @@ void shouldReportContainerFailureWhenItsHostPortIsAlreadyTaken() throws Exceptio
String blockerId = startPortBlocker(blockedPort);
System.setProperty("test.proxy.host-port", String.valueOf(blockedPort));

AsyncProcessHandle handle = execAsync("local", "run", "-f=" + meshPath);
AsyncProcessHandle handle = execAsync("local", "run", "--verbose", "-f=" + meshPath);
try {
Awaitility.await()
.atMost(Duration.ofMinutes(3))
.pollInterval(Duration.ofSeconds(1))
.untilAsserted(() -> {
assertThat(handle.getStdout())
.as("the user must be told which container failed")
.contains("rest-ingestion.proxy failed");
assertThat(handle.getStderr())
.as("the run must be reported as failed")
.contains(msg.somethingWentWrong().strip());
});
awaitStderrContains(handle, msg.somethingWentWrong().strip(),
String.valueOf(blockedPort));
} finally {
if (handle.thread().isAlive()) {
handle.interruptAndJoin(Duration.ofSeconds(30).toMillis());
if (handle.isAlive()) {
handle.stopAndJoin(Duration.ofSeconds(30).toMillis());
}
removePortBlocker(blockerId);
}
}

private static void awaitNoContainersWithPrefix(String prefix) throws Exception {
try (DockerClient docker = DockerClientFactory.create()) {
Awaitility.await()
.atMost(Duration.ofSeconds(60))
.pollInterval(Duration.ofSeconds(1))
.untilAsserted(() -> assertThat(docker.listContainersCmd()
.withShowAll(true)
.withNameFilter(List.of(prefix))
.exec())
.as("mesh containers with prefix %s must be removed after the run stops", prefix)
.isEmpty());
}
}

private static String startPortBlocker(int hostPort) throws Exception {
try (DockerClient docker = DockerClientFactory.create()) {
docker.pullImageCmd(BLOCKER_IMAGE).start().awaitCompletion();
Expand Down Expand Up @@ -162,9 +170,10 @@ private static void removePortBlocker(String containerId) {

@Test
void shouldFailWhenSystemPropertyIsUndefined() throws Exception {
System.setProperty("streamx.runner.mesh-name-prefix", PREFIX);
String prefix = freshPrefix();
System.setProperty("streamx.runner.mesh-name-prefix", prefix);
exec("settings", "unset", "config.image.interpolated");
exec("settings", "set", "STREAMX_OWNER_SERVICE_NAME", PREFIX + "test-owner");
exec("settings", "set", "STREAMX_OWNER_SERVICE_NAME", prefix + "test-owner");

String meshPath = Paths.get("target/test-classes/mesh-interpolated.yaml")
.toAbsolutePath()
Expand All @@ -177,16 +186,16 @@ void shouldFailWhenSystemPropertyIsUndefined() throws Exception {
Awaitility.await()
.atMost(Duration.ofMinutes(3))
.pollInterval(Duration.ofSeconds(1))
.until(() -> !handle.thread().isAlive());
.until(() -> !handle.isAlive());

ProcessResult result = handle.toResult();
assertThat(result.exitCode()).isNotEqualTo(0);
assertThat(result.stderr())
.contains("Property 'config.image.interpolated'")
.contains("is not set");
} finally {
if (handle.thread().isAlive()) {
handle.interruptAndJoin(Duration.ofSeconds(30).toMillis());
if (handle.isAlive()) {
handle.stopAndJoin(Duration.ofSeconds(30).toMillis());
}
}
}
Expand Down Expand Up @@ -219,27 +228,26 @@ void shouldBridgeRunnerSettingToSystemPropertyForLocalRun() throws Exception {
AsyncProcessHandle handle = execAsync("local", "run", "-f=" + meshPath);

try {
Awaitility.await()
.atMost(Duration.ofMinutes(3))
.pollInterval(Duration.ofSeconds(1))
.until(() -> handle.getStdout().contains("STREAMX IS READY!"));
awaitStdoutContains(handle, "STREAMX IS READY!");

assertThat(handle.getStdout())
.as("runner should use the prefix from streamxHome settings via the bridge")
.contains(bridgedPrefix);
} finally {
if (handle.thread().isAlive()) {
handle.interruptAndJoin(Duration.ofSeconds(30).toMillis());
if (handle.isAlive()) {
handle.stopAndJoin(Duration.ofSeconds(30).toMillis());
}
exec("settings", "unset", "streamx.runner.mesh-name-prefix");
System.clearProperty("streamx.runner.mesh-name-prefix");
}
}

@Test
void shouldSucceedWhenInterpolationValuesAreDefined() throws Exception {
System.setProperty("streamx.runner.mesh-name-prefix", PREFIX);
String prefix = freshPrefix();
System.setProperty("streamx.runner.mesh-name-prefix", prefix);
exec("settings", "set", "config.image.interpolated", WEB_SERVER_SINK_IMAGE);
exec("settings", "set", "STREAMX_OWNER_SERVICE_NAME", PREFIX + "test-owner");
exec("settings", "set", "STREAMX_OWNER_SERVICE_NAME", prefix + "test-owner");

String meshPath = Paths.get("target/test-classes/mesh-interpolated.yaml")
.toAbsolutePath()
Expand All @@ -249,33 +257,28 @@ void shouldSucceedWhenInterpolationValuesAreDefined() throws Exception {
AsyncProcessHandle handle = execAsync("local", "run", "-f=" + meshPath);

try {
Awaitility.await()
.atMost(Duration.ofMinutes(3))
.pollInterval(Duration.ofSeconds(1))
.until(() -> handle.getStdout().contains("STREAMX IS READY!"));
awaitStdoutContains(handle, "STREAMX IS READY!");

Thread.sleep(Duration.ofSeconds(5));
assertThat(handle.thread().isAlive()).isTrue();
assertThat(handle.isAlive()).isTrue();

handle.interruptAndJoin(Duration.ofSeconds(30).toMillis());
assertThat(handle.thread().isAlive()).isFalse();
handle.stopAndJoin(Duration.ofSeconds(30).toMillis());
assertThat(handle.isAlive()).isFalse();

ProcessResult result = handle.toResult();
result.assertSuccess();
assertThat(result.stdout()).contains("Stopping mesh...");
assertThat(result.stderr()).doesNotContain("Exception");
assertThat(result.exitCode()).isIn(0, SIGTERM_EXIT_CODE);
awaitNoContainersWithPrefix(prefix);
} finally {
if (handle.thread().isAlive()) {
handle.interruptAndJoin(Duration.ofSeconds(30).toMillis());
if (handle.isAlive()) {
handle.stopAndJoin(Duration.ofSeconds(30).toMillis());
}
}
}

@Test
void shouldStartMeshSecondTimeAfterPreviousStopped() throws Exception {
System.setProperty("streamx.runner.mesh-name-prefix", PREFIX);
exec("settings", "set", "config.image.interpolated", WEB_SERVER_SINK_IMAGE);
exec("settings", "set", "STREAMX_OWNER_SERVICE_NAME", PREFIX + "test-owner");
exec("settings", "set", "STREAMX_OWNER_SERVICE_NAME", freshPrefix() + "test-owner");

String meshPath = Paths.get("target/test-classes/mesh-interpolated.yaml")
.toAbsolutePath()
Expand All @@ -286,19 +289,22 @@ void shouldStartMeshSecondTimeAfterPreviousStopped() throws Exception {
runUntilReadyThenStop(meshPath);
}

private void runUntilReadyThenStop(String meshPath) throws InterruptedException {
private void runUntilReadyThenStop(String meshPath) throws Exception {
System.setProperty("streamx.runner.mesh-name-prefix", freshPrefix());
System.setProperty("streamx.runner.pulsar.broker-port",
String.valueOf(MeshTestSupport.freePort()));
System.setProperty("streamx.runner.pulsar.http-port",
String.valueOf(MeshTestSupport.freePort()));
System.setProperty("test.proxy.host-port", String.valueOf(MeshTestSupport.freePort()));
AsyncProcessHandle handle = execAsync("local", "run", "-f=" + meshPath);
try {
Awaitility.await()
.atMost(Duration.ofMinutes(3))
.pollInterval(Duration.ofSeconds(1))
.until(() -> handle.getStdout().contains("STREAMX IS READY!"));
awaitStdoutContains(handle, "STREAMX IS READY!");

assertThat(handle.getStderr())
.doesNotContain("MissingReflectionRegistrationError");
} finally {
if (handle.thread().isAlive()) {
handle.interruptAndJoin(Duration.ofSeconds(30).toMillis());
if (handle.isAlive()) {
handle.stopAndJoin(Duration.ofSeconds(30).toMillis());
}
}
}
Expand Down
Loading