use super::*;
use std::io::{BufRead, BufReader};
use std::os::unix::process::CommandExt;
use std::process::{Child, Command, ExitStatus, Stdio};
use std::time::{Duration, Instant};
const ENDING_BOUND: Duration = Duration::from_secs(10);
fn shell_with_a_sleeper() -> (Child, i32) {
let mut shell: Child = Command::new("sh")
.args(["-c", "sleep 600 & echo $!; wait"])
.process_group(0)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.spawn()
.expect("the shell starts");
let mut line: String = String::new();
BufReader::new(shell.stdout.take().expect("piped stdout"))
.read_line(&mut line)
.expect("the sleeper's number");
let sleeper: i32 = line.trim().parse().expect("a process number");
return (shell, sleeper);
}
fn reap(shell: &mut Child) {
let group: i32 = i32::try_from(shell.id()).expect("a process number");
if shell.try_wait().expect("the shell is polled").is_none() {
unsafe { libc::kill(-group, libc::SIGKILL) };
}
shell.wait().expect("the shell is collected");
}
fn status_within_bound(shell: &mut Child) -> Option<ExitStatus> {
let deadline: Instant = Instant::now() + ENDING_BOUND;
loop {
if let Some(status) = shell.try_wait().expect("the shell is polled") {
return Some(status);
}
if Instant::now() >= deadline {
return None;
}
std::thread::sleep(Duration::from_millis(10));
}
}
#[cfg(target_os = "linux")]
#[test]
fn a_stat_line_is_read_after_the_last_parenthesis() {
let line: &str = "1234 (we ) ird) S 99 1 1 0 -1 4194304 0 0 0 0 0 0 0 0 20 0 1 0 555 0 0";
assert_eq!(parse_stat(line), Some((99, 555, false)));
let zombie: &str = "1234 (cat) Z 7 1 1 0 -1 4194304 0 0 0 0 0 0 0 0 20 0 1 0 9 0 0";
assert_eq!(parse_stat(zombie), Some((7, 9, true)));
assert_eq!(parse_stat("1234 (cat) S 7"), None);
assert_eq!(parse_stat("no parenthesis"), None);
}
#[cfg(target_os = "linux")]
#[test]
fn a_descendant_is_found_and_signalled() {
let (mut shell, sleeper): (Child, i32) = shell_with_a_sleeper();
let root: i32 = i32::try_from(shell.id()).expect("a process number");
let found: Vec<TreeMember> = descendants_of(root);
let member: &TreeMember = found.iter().find(|candidate| { return candidate.pid == sleeper; }).expect("the sleeper is below the shell");
assert!(still_running(member));
signal_members(&found, libc::SIGKILL);
let status: Option<ExitStatus> = status_within_bound(&mut shell);
let sleeper_ended: bool = !still_running(member);
reap(&mut shell);
assert!(status.is_some_and(|ended| { return ended.success(); }), "the shell ended once the sleeper did: {status:?}");
assert!(sleeper_ended, "the sleeper ended");
}
#[cfg(target_os = "linux")]
#[test]
fn the_named_wrappers_end_the_found_members() {
for (name, end) in [("terminate_members", terminate_members as fn(&[TreeMember])), ("kill_members", kill_members)] {
let (mut shell, sleeper): (Child, i32) = shell_with_a_sleeper();
let root: i32 = i32::try_from(shell.id()).expect("a process number");
let found: Vec<TreeMember> = descendants_of(root);
let member: &TreeMember = found.iter().find(|candidate| { return candidate.pid == sleeper; }).expect("the sleeper is below the shell");
end(&found);
let deadline: Instant = Instant::now() + ENDING_BOUND;
while still_running(member) && Instant::now() < deadline {
std::thread::sleep(Duration::from_millis(10));
}
let ended: bool = !still_running(member);
reap(&mut shell);
assert!(ended, "{name} ended the sleeper");
}
}
#[cfg(target_os = "linux")]
#[test]
fn a_reused_number_is_not_signalled() {
let (mut shell, sleeper): (Child, i32) = shell_with_a_sleeper();
let root: i32 = i32::try_from(shell.id()).expect("a process number");
let found: Vec<TreeMember> = descendants_of(root);
let real: &TreeMember = found.iter().find(|candidate| { return candidate.pid == sleeper; }).expect("the sleeper is below the shell");
let stale: TreeMember = TreeMember { pid: real.pid, started: real.started + 1 };
assert!(!still_running(&stale));
signal_members(&[stale], libc::SIGKILL);
let left_alone: bool = still_running(real);
reap(&mut shell);
assert!(left_alone, "the process with the other start time was left alone");
}
#[cfg(target_os = "linux")]
#[test]
fn a_leaf_has_no_descendants() {
let mut sleeper: Child = Command::new("sleep").arg("600").stdin(Stdio::null()).spawn().expect("sleep starts");
assert_eq!(descendants_of(i32::try_from(sleeper.id()).expect("a process number")), Vec::<TreeMember>::new());
sleeper.kill().expect("sleep is killed");
sleeper.wait().expect("sleep is collected");
}