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

jbonofre pushed a commit to branch karaf-4.4.x
in repository https://gitbox.apache.org/repos/asf/karaf.git


The following commit(s) were added to refs/heads/karaf-4.4.x by this push:
     new 53885f157c Fix flaky SSH security integration tests on Windows CI 
(#2764) (#2766)
53885f157c is described below

commit 53885f157c862670e778ae1d005628febd63bccf
Author: JB Onofré <[email protected]>
AuthorDate: Fri Jul 10 06:40:27 2026 +0200

    Fix flaky SSH security integration tests on Windows CI (#2764) (#2766)
    
    * Fix flaky SSH security itests on Windows by waiting for command completion
    
    The SSH command security integration tests (SshCommandTestBase) wrote a
    command to the SSH channel and immediately sent "logout" to tear down the
    session. On slower runners - in particular the Windows CI - the session
    could be closed while the command output was still in flight, producing
    truncated output (e.g. a partial command echo "she" instead of
    "shell:nano") and spurious assertion failures such as
    "Should contain 'Command not found'".
    
    Introduce a writeCommandAndWait() helper that appends a sentinel
    "echo <marker>" command and blocks until the unique marker appears in the
    captured output before closing the channel. Because the remote shell reads
    and executes its input line by line, the marker cannot appear before the
    command under test has been fully executed and flushed back to the client,
    providing a deterministic completion signal that also works for the OK case
    (which has no positive marker of its own). "echo" is a gogo built-in that is
    not restricted by any command ACL, so it is safe for every test user.
    
    The helper is also used by addUsers()/addViewer() so the JAAS users are
    fully created before the test logs in as them.
    
    * Potential fix for pull request finding
    
    
    
    ---------
    
    Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
---
 .../karaf/itests/ssh/SshCommandTestBase.java       | 47 +++++++++++++++++-----
 1 file changed, 38 insertions(+), 9 deletions(-)

diff --git 
a/itests/test/src/test/java/org/apache/karaf/itests/ssh/SshCommandTestBase.java 
b/itests/test/src/test/java/org/apache/karaf/itests/ssh/SshCommandTestBase.java
index ac1d6904aa..e3c87931eb 100644
--- 
a/itests/test/src/test/java/org/apache/karaf/itests/ssh/SshCommandTestBase.java
+++ 
b/itests/test/src/test/java/org/apache/karaf/itests/ssh/SshCommandTestBase.java
@@ -20,6 +20,7 @@ import java.io.PipedInputStream;
 import java.io.PipedOutputStream;
 import java.util.EnumSet;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.karaf.itests.BaseTest;
 import org.apache.sshd.client.SshClient;
@@ -29,6 +30,7 @@ import org.apache.sshd.client.future.ConnectFuture;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.client.session.ClientSession.ClientSessionEvent;
 import org.awaitility.Awaitility;
+import org.awaitility.core.ConditionTimeoutException;
 import org.junit.Assert;
 import org.junit.runner.RunWith;
 import org.ops4j.pax.exam.junit.PaxExam;
@@ -48,7 +50,7 @@ public class SshCommandTestBase extends BaseTest {
     void addUsers(String manageruser, String vieweruser) throws Exception {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         OutputStream pipe = openSshChannel("karaf", "karaf", out);
-        pipe.write(("jaas:realm-manage --realm=karaf"
+        writeCommandAndWait(pipe, out, "jaas:realm-manage --realm=karaf"
                 + ";jaas:user-add " + manageruser + " " + manageruser
                 + ";jaas:role-add " + manageruser + " manager"
                 + ";jaas:role-add " + manageruser + " viewer"
@@ -56,8 +58,7 @@ public class SshCommandTestBase extends BaseTest {
                 + ";jaas:user-add " + vieweruser + " " + vieweruser
                 + ";jaas:role-add " + vieweruser + " viewer"
                 + ";jaas:role-add " + vieweruser + " ssh"
-                + ";jaas:update;jaas:realm-manage 
--realm=karaf;jaas:user-list\n").getBytes());
-        pipe.flush();
+                + ";jaas:update;jaas:realm-manage 
--realm=karaf;jaas:user-list");
         closeSshChannel(pipe);
         System.out.println(new String(out.toByteArray()));
     }
@@ -65,24 +66,52 @@ public class SshCommandTestBase extends BaseTest {
     void addViewer(String vieweruser) throws Exception {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         OutputStream pipe = openSshChannel("karaf", "karaf", out);
-        pipe.write(("jaas:realm-manage --realm=karaf"
+        writeCommandAndWait(pipe, out, "jaas:realm-manage --realm=karaf"
                 + ";jaas:user-add " + vieweruser + " " + vieweruser
                 + ";jaas:role-add " + vieweruser + " viewer"
                 + ";jaas:role-add " + vieweruser + " ssh"
-                + ";jaas:update;jaas:realm-manage 
--realm=karaf;jaas:user-list\n").getBytes());
-        pipe.flush();
+                + ";jaas:update;jaas:realm-manage 
--realm=karaf;jaas:user-list");
         closeSshChannel(pipe);
         System.out.println(new String(out.toByteArray()));
     }
 
-    String assertCommand(String user, String command, Result result) throws 
Exception {
+    /**
+     * Writes the given command(s) to the SSH channel and blocks until they 
have been fully
+     * processed by the remote shell.
+     *
+     * <p>A sentinel {@code echo <marker>} command is appended after the 
supplied command and
+     * this method waits until the unique marker shows up in the captured 
output. Because the
+     * remote shell reads and executes its input line by line, the marker 
cannot appear before
+     * the supplied command has been fully executed and its output flushed 
back to the client.
+     * Without this synchronization the SSH session could be torn down (see
+     * {@link #closeSshChannel(OutputStream)}) while the command output is 
still in flight,
+     * producing truncated output and spurious assertion failures - regularly 
observed on slower
+     * Windows CI runners. {@code echo} is a gogo built-in that is not 
restricted by any command
+     * ACL, so it is safe to use for every test user.</p>
+     */
+    private void writeCommandAndWait(OutputStream pipe, ByteArrayOutputStream 
out, String command) throws IOException {
         if (!command.endsWith("\n"))
             command += "\n";
+        pipe.write(command.getBytes());
+        String marker = "KARAF_ITEST_MARKER_" + System.nanoTime();
+        pipe.write(("echo " + marker + "\n").getBytes());
+        pipe.flush();
 
+        try {
+            Awaitility.await().atMost(60, TimeUnit.SECONDS)
+                    .pollInterval(200, TimeUnit.MILLISECONDS)
+                    .until(() -> out.toString().contains(marker));
+        } catch (ConditionTimeoutException e) {
+            throw new AssertionError(
+                    "Timed out waiting for SSH command completion marker. 
Output so far: " + out,
+                    e);
+        }
+    }
+
+    String assertCommand(String user, String command, Result result) throws 
Exception {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         OutputStream pipe = openSshChannel(user, user, out, out);
-        pipe.write(command.getBytes());
-        pipe.flush();
+        writeCommandAndWait(pipe, out, command);
 
         closeSshChannel(pipe);
         String output = new String(out.toByteArray());

Reply via email to