use super::*;
use std::thread;
use std::time::{Duration, Instant};
use tempfile::tempdir;
#[test]
fn run_errors_on_missing_binary() {
let cli = Cli::new("/definitely/not/a/real/binary/litany-xyz");
let Err(err) = cli.run(&[]) else {
panic!("expected spawn failure");
};
let msg = err.to_string();
assert!(msg.contains("failed to spawn"), "{msg}");
}
#[test]
fn a_missing_work_directory_is_named_instead_of_the_binary() {
let dir = tempdir().unwrap();
let bin = write_script(dir.path(), "fake_litany", "#!/bin/sh\nexit 0\n");
let missing = dir.path().join("nonexistent-uat-dir");
let cli = Cli::new(bin);
let Err(err) = cli.run_in(&missing, &[]) else {
panic!("expected spawn failure");
};
assert_eq!(
err.to_string(),
format!("work directory does not exist: {}", missing.display())
);
}
#[test]
fn a_work_directory_that_is_not_a_directory_is_named_as_such() {
let dir = tempdir().unwrap();
let bin = write_script(dir.path(), "fake_litany", "#!/bin/sh\nexit 0\n");
let file = dir.path().join("a-file");
std::fs::write(&file, b"x").unwrap();
let cli = Cli::new(bin);
let Err(err) = cli.run_in(&file, &[]) else {
panic!("expected spawn failure");
};
assert_eq!(
err.to_string(),
format!("work directory is not a directory: {}", file.display())
);
}
#[test]
fn drop_terminates_long_running_child() {
let dir = tempdir().unwrap();
let bin = write_script(dir.path(), "fake_litany", "#!/bin/sh\nsleep 30\n");
let cli = Cli::new(bin);
let stream = cli.run(&[]).unwrap();
let pid = stream.pid().unwrap();
drop(stream);
let deadline = Instant::now() + Duration::from_secs(2);
while Instant::now() < deadline {
if !process_exists(pid) {
return;
}
thread::sleep(Duration::from_millis(25));
}
panic!("child pid {pid} still alive after drop");
}
#[test]
fn drop_escalates_to_sigkill_if_sigterm_ignored() {
let dir = tempdir().unwrap();
let bin = write_script(
dir.path(),
"fake_litany",
"#!/bin/sh\ntrap '' TERM\nprintf 'ready\\n'\nwhile :; do sleep 1; done\n",
);
let cli = Cli::new(bin);
let mut stream = cli.run(&[]).unwrap();
let pid = stream.pid().unwrap();
let ready_deadline = Instant::now() + Duration::from_secs(3);
loop {
assert!(
Instant::now() < ready_deadline,
"child pid {pid} did not signal ready before drop"
);
match stream.next() {
Some(Chunk::Stdout(b)) if b.starts_with(b"ready") => break,
Some(_) => {}
None => panic!("child pid {pid} exited before signaling ready"),
}
}
drop(stream);
let deadline = Instant::now() + Duration::from_secs(3);
while Instant::now() < deadline {
if !process_exists(pid) {
return;
}
thread::sleep(Duration::from_millis(50));
}
panic!("child pid {pid} survived SIGKILL escalation");
}