use super::*;
use tempfile::tempdir;
fn sink_in(dir: &Path) -> PathBuf {
dir.join("sink").join("child.err")
}
fn make_fifo(path: &Path) {
let status = std::process::Command::new("mkfifo")
.args(["-m", "600"])
.arg(path)
.status()
.expect("spawn mkfifo");
assert!(status.success(), "mkfifo");
}
#[cfg(target_os = "linux")]
const GROUP_DRIVER: &str = "#!/bin/sh\nread _ _ _ _ pgid _ < /proc/self/stat\nprintf '%s %s\\n' \"$$\" \"$pgid\" > \"$1\"\n";
#[cfg(not(target_os = "linux"))]
const GROUP_DRIVER: &str =
"#!/bin/sh\nprintf '%s %s\\n' \"$$\" \"$(ps -o pgid= -p $$)\" > \"$1\"\n";
#[test]
fn detached_child_survives_and_leads_own_group() {
let dir = tempdir().unwrap();
let fifo = dir.path().join("report");
make_fifo(&fifo);
let (bin, _guard) = write_script(dir.path(), "driver", GROUP_DRIVER);
let fifo_str = fifo.to_str().unwrap();
let pid = Cli::new(bin)
.spawn_detached(None, &sink_in(dir.path()), &[fifo_str])
.unwrap();
let recorded = std::fs::read_to_string(&fifo).unwrap();
let mut fields = recorded.split_whitespace();
let child_pid: u32 = fields.next().unwrap().parse().unwrap();
let child_leader: u32 = fields.next().unwrap().parse().unwrap();
assert_eq!(
child_pid, pid,
"returned pid is the detached child's own pid"
);
assert_eq!(
child_leader, pid,
"process_group(0): detached child leads its own group"
);
}
#[test]
fn spawn_detached_propagates_cwd() {
let dir = tempdir().unwrap();
let fifo = dir.path().join("report");
make_fifo(&fifo);
let (bin, _guard) = write_script(dir.path(), "driver", "#!/bin/sh\npwd -P > \"$1\"\n");
let fifo_str = fifo.to_str().unwrap();
Cli::new(bin)
.spawn_detached(Some(dir.path()), &sink_in(dir.path()), &[fifo_str])
.unwrap();
let recorded = std::fs::read_to_string(&fifo).unwrap();
let cwd = recorded.trim();
assert_eq!(
std::fs::canonicalize(cwd).unwrap(),
std::fs::canonicalize(dir.path()).unwrap(),
);
}
#[test]
fn spawn_detached_errors_on_missing_binary() {
let _g = spawn_guard();
let dir = tempdir().unwrap();
let cli = Cli::new("/definitely/not/a/real/binary/lernie-detach");
let err = cli
.spawn_detached(None, &sink_in(dir.path()), &[])
.unwrap_err();
assert!(err.to_string().contains("failed to spawn"), "{err}");
}
const DYING_DRIVER: &str =
"#!/bin/sh\nprintf 'version skew: refusing\\n' >&2\nprintf done > \"$1\"\n";
#[test]
fn detached_child_stderr_lands_in_the_sink_file() {
let dir = tempdir().unwrap();
let fifo = dir.path().join("report");
make_fifo(&fifo);
let (bin, _guard) = write_script(dir.path(), "driver", DYING_DRIVER);
let sink = sink_in(dir.path());
let fifo_str = fifo.to_str().unwrap();
assert!(!sink.parent().unwrap().exists());
Cli::new(bin)
.spawn_detached(None, &sink, &[fifo_str])
.unwrap();
assert_eq!(std::fs::read_to_string(&fifo).unwrap(), "done");
assert_eq!(
std::fs::read_to_string(&sink).unwrap(),
"version skew: refusing\n",
"the detached child's stderr is captured, not dropped (§8.1)"
);
}
#[test]
fn an_unopenable_sink_degrades_to_null_and_still_launches() {
let dir = tempdir().unwrap();
let fifo = dir.path().join("report");
make_fifo(&fifo);
let (bin, _guard) = write_script(dir.path(), "driver", DYING_DRIVER);
let blocker = dir.path().join("blocker");
fs::write(&blocker, "not a directory").unwrap();
let fifo_str = fifo.to_str().unwrap();
Cli::new(bin)
.spawn_detached(None, &blocker.join("child.err"), &[fifo_str])
.unwrap();
assert_eq!(std::fs::read_to_string(&fifo).unwrap(), "done");
}