Run the stand-in ssh for tests without writing a script file

This commit is contained in:
2026-10-15 15:13:02 +02:00
parent 48e5e8c8ce
commit 16b0363d07
2 changed files with 31 additions and 26 deletions
+9 -7
View File
@@ -1,6 +1,6 @@
use serde::Serialize; use serde::Serialize;
use crate::location::{Location, remote_command}; use crate::location::{Location, remote_command};
use std::ffi::OsStr; use std::ffi::OsString;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
@@ -53,13 +53,14 @@ pub fn git(loc: &Location, args: &[&str]) -> Result<String, String> {
// Tests swap in a script that acts like ssh. // Tests swap in a script that acts like ssh.
#[cfg(test)] #[cfg(test)]
if let Some(fake) = crate::test_ssh::SSH_OVERRIDE.get() { if let Some(fake) = crate::test_ssh::SSH_OVERRIDE.get() {
return git_via(fake.as_os_str(), loc, args); return git_via(fake, loc, args);
} }
git_via(OsStr::new("ssh"), loc, args) git_via(&[OsString::from("ssh")], loc, args)
} }
/// Does the work of `git`, starting the program `ssh` for a remote location. /// Does the work of `git`. For a remote location it starts `ssh`: the first word of `ssh` is
pub fn git_via(ssh: &OsStr, loc: &Location, args: &[&str]) -> Result<String, String> { /// the program and the other words come before the ssh options (tests use them).
pub fn git_via(ssh: &[OsString], loc: &Location, args: &[&str]) -> Result<String, String> {
let mut cmd = match loc { let mut cmd = match loc {
Location::Local(dir) => { Location::Local(dir) => {
let mut cmd = Command::new("git"); let mut cmd = Command::new("git");
@@ -67,7 +68,8 @@ pub fn git_via(ssh: &OsStr, loc: &Location, args: &[&str]) -> Result<String, Str
cmd cmd
} }
Location::Ssh { target, path } => { Location::Ssh { target, path } => {
let mut cmd = Command::new(ssh); let mut cmd = Command::new(&ssh[0]);
cmd.args(&ssh[1..]);
// Never ask for a password: gites has no terminal to ask on. // Never ask for a password: gites has no terminal to ask on.
cmd.args(["-o", "BatchMode=yes", "-o", "ConnectTimeout=10"]); cmd.args(["-o", "BatchMode=yes", "-o", "ConnectTimeout=10"]);
// Later commands to the same server reuse one connection for a while. Windows ssh // Later commands to the same server reuse one connection for a while. Windows ssh
@@ -294,7 +296,7 @@ mod tests {
#[test] #[test]
fn reports_a_missing_ssh_program() { fn reports_a_missing_ssh_program() {
let loc = Location::Ssh { target: "myserver".into(), path: "/x".into() }; let loc = Location::Ssh { target: "myserver".into(), path: "/x".into() };
let err = git_via(OsStr::new("/no/such/ssh"), &loc, &["status"]).unwrap_err(); let err = git_via(&[OsString::from("/no/such/ssh")], &loc, &["status"]).unwrap_err();
assert!(err.starts_with("could not run ssh:"), "{err}"); assert!(err.starts_with("could not run ssh:"), "{err}");
} }
} }
+22 -19
View File
@@ -1,25 +1,28 @@
//! A stand-in for ssh, so tests can run the remote code path without a server. //! A stand-in for ssh, so tests can run the remote code path without a server.
use std::path::PathBuf; use std::ffi::OsString;
use std::sync::OnceLock; use std::sync::OnceLock;
/// The program `git::git` starts instead of `ssh` while tests run. /// The words `git::git` starts instead of `ssh` while tests run: a program, then its arguments.
pub static SSH_OVERRIDE: OnceLock<PathBuf> = OnceLock::new(); pub static SSH_OVERRIDE: OnceLock<Vec<OsString>> = OnceLock::new();
/// Writes (once per test run) a script that acts like ssh: it skips the `-o` options, /// The shell script that acts like ssh. It checks the arguments the way a real ssh would
/// the `--` and the target, then runs the command on this machine. Returns its path. /// need them (so a wrong argument list fails a test), skips the options and the target,
pub fn fake_ssh() -> PathBuf { /// then runs the command on this machine.
SSH_OVERRIDE const SCRIPT: &str = r#"
.get_or_init(|| { ok=
let path = std::env::temp_dir().join(format!("gites-fake-ssh-{}", std::process::id())); while [ "$1" = "-o" ]; do
let script = "#!/bin/sh\nwhile [ \"$1\" = \"-o\" ]; do shift 2; done\nshift 2\nexec sh -c \"$1\"\n"; [ "$2" = "BatchMode=yes" ] && ok=1
std::fs::write(&path, script).unwrap(); shift 2
#[cfg(unix)] done
{ [ -n "$ok" ] || { echo "fake ssh: BatchMode=yes is missing" >&2; exit 98; }
use std::os::unix::fs::PermissionsExt; [ "$1" = "--" ] || { echo "fake ssh: expected -- before the target" >&2; exit 99; }
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755)).unwrap(); shift 2
} exec sh -c "$1"
path "#;
})
.clone() /// Makes `git::git` use the stand-in for the rest of the test run. No file is written:
/// the script is given to `sh` on its command line.
pub fn fake_ssh() {
SSH_OVERRIDE.get_or_init(|| ["sh", "-c", SCRIPT, "fake-ssh"].map(OsString::from).to_vec());
} }