[wagon-3.x] Revive the embedded SSH tests and run them in CI - #904
Merged
Conversation
The -Dssh-tests suite has been dead for years. Three separate faults kept the tests that use the embedded Apache MINA sshd from passing: * plexus-interactivity-api stopped shipping META-INF/plexus/components.xml in 1.3, so plexus-container-default - which is what PlexusTestCase runs - could no longer see a Prompter, and every lookup of a jsch Wagon failed because ConsoleInteractiveUserInfo and PrompterUIKeyboardInteractive both require one. Supply a Prompter for the tests. * ShellCommand flushed the channel's output stream after calling ExitCallback.onExit, which closes it. The resulting SshChannelClosedException tore down the session, so the command after every executeCommand failed with "session is down". * The expected modification time on a get was compared at millisecond precision, but the scp "T" header carries whole seconds. Also register the SFTP subsystem on the embedded server: OpenSSH 9 and later drive scp over SFTP, so wagon-ssh-external's tests, which shell out to the host's scp, could not connect at all. ScpWagon now maps a missing file to ResourceDoesNotExistException when the server reports it with the scp fatal-error code (2) and says so in the message, not only with the warning code (1) that OpenSSH uses. CI passes -Dssh-tests -Dssh-embedded=true, which runs the 42 embedded jsch tests. The rest of the suite needs an sshd on localhost:22 and the developer's own account there, and stays excluded.
This was referenced Aug 8, 2026
[wagon-3.x] Move wagon-ssh to the maintained JSch fork and stop a stray key shadowing the agent
#902
Closed
There was a problem hiding this comment.
Pull request overview
This PR revives the embedded SSH provider test suite (primarily the embedded Apache MINA sshd-backed tests) and ensures it runs in CI by restoring Plexus wiring needed for PlexusTestCase and addressing several embedded-server/test-harness incompatibilities with modern OpenSSH/scp behavior.
Changes:
- Add a test-scope Plexus component descriptor and a non-interactive
Prompterimplementation soPlexusTestCasecan construct SSH wagons again. - Fix embedded SSH test harness behavior (SFTP subsystem for OpenSSH 9+ scp-over-SFTP, command execution flush ordering, and scp timestamp precision expectations).
- Update CI workflow and Maven profiles to run only the embedded SSH tests safely (including avoiding Windows activation).
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
.github/workflows/maven-verify.yml |
Runs embedded SSH tests in CI via -Dssh-tests -Dssh-embedded=true. |
wagon-providers/wagon-ssh/pom.xml |
Adjusts ssh-embedded profile activation/excludes to avoid unintended execution (esp. on Windows). |
wagon-providers/wagon-ssh-external/pom.xml |
Excludes a currently non-deterministic embedded scp test from CI due to OpenSSH version-dependent behavior. |
wagon-providers/wagon-ssh/src/main/java/.../ScpWagon.java |
Treats MINA sshd’s scp “missing file” ack behavior as ResourceDoesNotExistException. |
wagon-providers/wagon-ssh/src/test/java/.../TestPrompter.java |
Adds a test-only Prompter that throws on prompts to keep tests non-interactive. |
wagon-providers/wagon-ssh/src/test/resources/META-INF/plexus/components.xml |
Wires TestPrompter into the Plexus container for PlexusTestCase. |
wagon-providers/wagon-ssh-common-test/src/main/java/.../SshServerEmbedded.java |
Enables SFTP subsystem to support OpenSSH 9+ scp-over-SFTP. |
wagon-providers/wagon-ssh-common-test/src/main/java/.../ShellCommand.java |
Avoids flushing after the callback closes the channel (stability for multi-command sessions). |
wagon-providers/wagon-ssh-common-test/src/main/java/.../AbstractEmbeddedScpWagonTest.java |
Truncates expected mtime to whole seconds to match scp protocol precision. |
wagon-providers/wagon-ssh-common-test/src/main/java/.../AbstractEmbeddedScpWagonWithKeyTest.java |
Same mtime precision adjustment for key-based embedded scp tests. |
Suppressed comments (1)
wagon-providers/wagon-ssh/src/main/java/org/apache/maven/wagon/providers/ssh/jsch/ScpWagon.java:234
- This comment describes scp ack code 1 as a "warning". Regardless of server-specific behavior, the code path throws a ResourceDoesNotExistException, so calling it a warning/recoverable is misleading. Please reword the comment to describe it as an error code and keep the rest of the rationale unchanged.
// OpenSSH reports a missing file with the scp "warning" code (1); other servers - Apache MINA
// sshd, which the tests run against - report it with the "fatal error" code (2), so fall back on
// the message for those
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+58
to
+62
| /** scp acknowledgement byte for a recoverable error, what OpenSSH sends for a missing file */ | ||
| private static final int SCP_WARNING = 1; | ||
|
|
||
| /** scp acknowledgement byte for a fatal error */ | ||
| private static final int SCP_ERROR = 2; |
slachiewicz
pushed a commit
that referenced
this pull request
Aug 8, 2026
The -Dssh-tests suite has been dead for years. Three faults kept the tests that use the embedded Apache MINA sshd from passing: * ShellCommand flushed the channel's output stream after calling ExitCallback.onExit, which closes it. The resulting SshChannelClosedException tore down the session, so the command after every executeCommand failed with "session is down". * The expected modification time on a get was compared at millisecond precision, but the scp "T" header carries whole seconds. * The SFTP subsystem was not registered on the embedded server. OpenSSH 9 and later drive scp over SFTP, so wagon-ssh-external's tests, which shell out to the host's scp, could not connect at all. ScpWagon now maps a missing file to ResourceDoesNotExistException when the server reports it with the scp fatal-error code (2) and says so in the message, not only with the warning code (1) that OpenSSH uses. CI passes -Dssh-tests -Dssh-embedded=true, which runs the 42 embedded jsch tests. This is the 3.x change from #904 without its fourth part. There, a TestPrompter and a test component descriptor had to be supplied by hand, because plexus-interactivity-api stopped shipping META-INF/plexus/components.xml in 1.3 and plexus-container-default could see neither the sisu index nor DefaultPrompter's constructor injection. Since #911 the tests run on the Sisu shim, which reads that index and satisfies the constructor, so the real DefaultPrompter resolves and the workaround is not needed here. Verified: 42 tests, 0 failures without it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The embedded SSH tests have not run in over a decade. This gets the embedded half of them green and runs it in CI.
Why the suite could not start
PlexusTestCaseusesplexus-container-default, which readsMETA-INF/plexus/components.xml.plexus-interactivity-apistopped shipping that descriptor in 1.3 and now ships only a sisu index — I checked the jars: 1.1 has the plexus descriptor and no sisu index, 1.5.1 the reverse. Maven itself runs on sisu, so nothing is wrong in production; onlyPlexusTestCaseis affected.The
ComponentLookupExceptionon thescprole hint hid five levels of cause. The bottom is a missingPrompter, whichConsoleInteractiveUserInfoandPrompterUIKeyboardInteractiveboth require andAbstractJschWagonneeds both of. All 94 errors had that same bottom cause. It arrived with the 1.1 → 1.3 bump in42ee3769.Writing a descriptor for the real
DefaultPrompterdoes not work: it takes its collaborators through a constructor into final fields, and the container's builder only does no-arg construction plus field injection. So the tests get their ownPrompter, in test scope, which throws rather than blocking on stdin.It was three faults, not one
The Prompter alone took 94 errors to 87.
ShellCommandflushed the output stream afterExitCallback.onExit()had closed the channel. The server loggedSshChannelClosedException, the session went down, and the next command in it died. Removing that one strayflush()took the embedded tests from 19 errors to 6.getExpectedLastModifiedOnGetcompared at millisecond precision while the scpTheader carries whole seconds, so the mock transfer event never matched and the progress assertion saw 0 bytes. Truncated to seconds.scpover SFTP, so every shelled-outscpexited 255.One production change, called out for review
ScpWagon.fillInputDatamapped only scp ack code 1 toResourceDoesNotExistException; MINA sshd reports a missing file with code 2. Without this, four tests stay red.While there:
line.indexOf("no such file or directory") != 1is a typo for!= -1, which makes every code-1 error aResourceDoesNotExistException. I deliberately preserved that behaviour rather than quietly tightening it, so only code 2 is genuinely new. Worth fixing separately, with its own thought about what it changes.Counts
wagon-ssh,-Dssh-tests -Dssh-embedded=truewagon-ssh,-Dssh-testsaloneNo test was disabled or deleted.
Why bare
-Dssh-testsstill cannot be greenBy design of those tests, not by breakage.
ScpWagonTest,SshCommandExecutorTestandKnownHostsProviderTestexpect a real sshd on localhost:22 and the developer's own account.SftpWagonTestandScpWagonWithSshPrivateKeySearchTestconnect toscp://localhost:0/, broken since35ff4024removedgetTestRepositoryPort(). OnlyEmbedded*Testuses the embedded server, which is exactly what the existingssh-embeddedprofile selects — hence the two-property combination in CI.CI
Two pom changes make that safe, both checked empirically rather than assumed. Surefire
<excludes>from two active profiles override rather than merge, last declared winning — verified by running-Dssh-embedded=truewithout-Dssh-testsand watching the Embedded tests run anyway. On Windows that would have letssh-embeddedbeatwindauzeand run the/bin/sh-dependent tests, sossh-embeddednow also requires a non-Windows OS. Andwagon-ssh-external's ownssh-embeddedprofile would have run a test that is not green, so it is excluded there with a comment; that module runs no ssh tests today, so nothing is lost.If you would rather not carry a switch at all, dropping
**/Embedded*Test.*fromno-ssh-testsgives identical coverage with no workflow change.Caveats
Only exercised on JDK 21; the CI matrix includes JDK 8, and these tests are new to CI on every version.
wagon-ssh-external'sEmbeddedScpExternalWagonWithKeyTeststill fails on path quoting —ScpExternalWagonbackslash-escapes spaces for the legacy remote-shell protocol, which SFTP-mode scp passes through verbatim. That needs a decision about-Oversus changed quoting, and the answer depends on the host's OpenSSH version, so it is left alone here.Why this matters beyond itself
This is the only harness that runs the SSH provider against a real server. It is the precursor to #902 and #903: with it in place, the JSch swap those PRs make is verified rather than asserted — and it immediately earned that, catching a
NoClassDefFoundErrorthat would have stoppedwagon-sshloading at all.