feat: add SSHTest connection preflight diagnostics - #22
Conversation
4483777 to
06d422c
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The diagnostics output currently drops repeated ssh -G keys (e.g., multiple identityfile entries) and the surfaced SSHFS option ordering is non-deterministic, which can make reported commands inaccurate/unstable.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds a new read-only :SSHTest preflight diagnostic flow to help users validate SSH connectivity, resolved config, and derived commands before attempting an SSHFS mount, while refactoring shared SSH/SSHFS command construction to keep diagnostics aligned with the real connect/mount path.
Changes:
- Introduces
sshfs.diagnosticto runssh -G, batch-auth, and optional remote-home resolution, then render a scratch-buffer report. - Refactors SSH command assembly in
sshfs.lib.sshto accept{name,user,port}host objects and exposes reusable builders (batch/auth/home/control). - Exposes
require("sshfs").test(host)and adds the:SSHTestuser command entry point, plus a reusableSshfs.build_mount_command(...)builder.
File summaries
| File | Description |
|---|---|
| lua/sshfs/lib/sshfs.lua | Extracts a reusable SSHFS command builder and updates call sites to pass full host objects through auth/home flows. |
| lua/sshfs/lib/ssh.lua | Adds host normalization + shared SSH command builders for batch/auth/home/control and updates APIs to accept host objects. |
| lua/sshfs/init.lua | Exposes test on the public module surface. |
| lua/sshfs/diagnostic.lua | New diagnostic runner/report renderer for :SSHTest (ssh -G, auth, home resolution, prospective sshfs command). |
| lua/sshfs/api.lua | Adds Api.test and registers the :SSHTest user command. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
c1cba99 to
95668d5
Compare
Proposed merge orderTo avoid creating a circular dependency around #24, I suggest treating #24 as the shared test-infrastructure epic rather than requiring one monolithic test PR to contain tests for code that has not merged yet.
This keeps #24 as the common testing requirement while avoiding tests for unmerged feature behavior being forced into a prerequisite PR. #24 can remain open until the required regression suites for #20/#21/#22 are all represented. |
759fd05 to
11a941a
Compare
`ssh -G` prints the entire resolved configuration, which added roughly 85 lines and buried the test results. The "Resolved SSH configuration" section already summarizes the relevant fields, so stdout is now suppressed for that step while stderr is still shown.
Cleanup previously ran whenever no master existed before the test, which races with a concurrent :SSHConnect: a socket created in that window was closed by the preflight, tearing down the other connection. Replace has_control_master with control_master_pid and claim ownership only when no master existed beforehand and the batch command connected successfully, then verify the pid is unchanged before sending -O exit.
Adds a dependency-free unit test harness that runs inside headless Neovim, plus regression coverage for the behavior that exists on main. The harness provides describe/it, deep-equality and error assertions, and a runner (`nvim -l tests/run.lua`, wrapped as `make test`) that exits non-zero on failure so CI can gate on it. A stub module replaces the handful of system-facing calls the plugin makes (vim.fn.system, vim.fn.executable, vim.system, vim.notify) and restores them per case, so no test contacts a real SSH server or mount table. Baseline suites cover mount table parsing for Linux fuse.sshfs, macFUSE and findmnt output, mount directory creation, SSH command construction including tilde expansion and quote escaping, ad-hoc host string parsing, configuration merging with its deprecation shims, and remote path mapping. CI runs the suite and stylua --check on every pull request. Refs #24
A call under test can return more than one value, and keeping only the first silently turned assertions on the later ones into assertions on nil.
Adds the regression suite this PR describes, on top of the shared harness. Covers the SSH command builders that SSHTest shares with SSHConnect: the batch probe forces a master and disables prompts, the home lookup reuses the socket without renegotiating one, interactive authentication stays promptable, and control operations address the socket. Explicit user and port values propagate through all of them and are omitted when unset. Covers control_master_pid parsing for a running master, a missing socket, and unparseable output. Covers the report itself: the resolved configuration summary, joined identity files, the omission of the raw ssh -G dump, pass and fail rendering with exit codes and stderr, the prospective sshfs command (shown but never executed), tilde resolution through the remote home, the socket directory failure path, and the disposable scratch buffer. Covers ControlMaster lifecycle: a master the preflight created is closed, a pre-existing one is preserved, and one that appeared from a concurrent connect or was replaced mid-test is left alone. Refs #24
The assertion that SSHTest never runs sshfs was too broad. Once the fuse-t work in #20 lands, build_mount_command probes `sshfs --version` to decide whether to render 3.x or 2.x cache option names, so the preflight legitimately spawns sshfs without mounting anything. Narrow the assertion to what it was meant to catch: an actual mount.
Summary
Adds a read-only
:SSHTestpreflight command for diagnosing SSH connections before mounting.:SSHConnectssh -Gcommand and the same batch-authentication command used bySSHConnectuserandportvalues through the real SSH authentication and remote-home-resolution pathsSSHConnectSSHTestrequire("sshfs").test(host)for programmatic useExample
Design
SSHTestandSSHConnectshare SSH command builders for batch authentication, interactive authentication, remote home resolution, and ControlMaster management. This keeps the diagnostic path aligned with the production connection path instead of maintaining a second implementation.The preflight records the ControlMaster pid before authentication. If a master already exists, it is preserved. A master is closed only when none existed beforehand, the batch command itself connected successfully, and the pid is unchanged at cleanup time. That prevents the preflight from tearing down a connection created by a concurrent
:SSHConnectwhile it was running. This keeps the command diagnostic-only without breaking an existing shared SSH session.The diagnostic intentionally does not execute the SSHFS mount. Running
sshfssimply to obtain an exit code would mutate system/plugin state. When a remote path is supplied, the report shows the SSHFS command thatSSHConnectwould build, using a<mount-point>placeholder becauseSSHTestdoes not create the local mount directory.Testing
Regression coverage is included, on top of the shared harness from #25. Run it with
make test.user/portpropagation through each builder, and their omission when unsetcontrol_master_pidparsing for a running master, a missing socket, and unparseable output:SSHConnector was replaced mid-test is left aloneCloses #12