#![cfg(all(test, unix))]
use std::collections::HashSet;
use std::os::unix::process::CommandExt;
use std::process::Command;
use super::super::hostile_orphan_test_util::{
assert_sibling_monitored_and_blocks_spawn, spawn_agent_pg_and_malvin_sibling,
};
#[cfg(target_os = "linux")]
use super::super::hostile_orphan_test_util::{
process_alive, read_orphan_pid, spawn_hostile_agent_acp_orphan, wait_for_init_reparent,
};
use super::super::process_group_terminate::{
terminate_agent_process_group_blocking, terminate_agent_process_group_for_interrupt,
};
use super::super::unix_process_group_ps::ProcRow;
use super::{
descendant_pids, kill_targets_for_teardown, terminate_agent_process_group,
terminate_process_group,
};
#[test]
fn terminate_blocking_kills_note_affiliated_pid_without_pgid() {
crate::test_utils::enable_test_fast_teardown();
let baseline = crate::malvin_sandbox::malvin_spawn_baseline();
let mut cmd = Command::new("sleep");
cmd.arg("120").process_group(0);
let mut child = cmd.spawn().expect("spawn sleep");
super::super::unix_process_group_kill_targets::note_session_affiliated_pid(child.id());
assert!(
super::super::unix_process_group_kill_targets::is_session_affiliated_pid(child.id()),
"note_session_affiliated_pid must record pid"
);
let targets = kill_targets_for_teardown(None, Some(&baseline));
assert!(
targets.contains(&child.id()),
"affiliated pid must be a teardown target: {targets:?}"
);
terminate_agent_process_group_for_interrupt(None, &baseline);
child
.wait()
.expect("affiliated child must exit after teardown");
}
#[tokio::test]
async fn async_teardown_kills_affiliated_pid_without_pgid() {
crate::test_utils::enable_test_fast_teardown();
let baseline = HashSet::new();
let mut cmd = Command::new("sleep");
cmd.arg("120").process_group(0);
let mut child = cmd.spawn().expect("spawn sleep");
super::super::unix_process_group_kill_targets::note_session_affiliated_pid(child.id());
let targets = kill_targets_for_teardown(None, Some(&baseline));
assert!(
targets.contains(&child.id()),
"affiliated pid must be a teardown target: {targets:?}"
);
terminate_agent_process_group(None, &baseline).await;
assert!(
child.try_wait().expect("wait").is_some(),
"graceful async teardown must kill affiliated child when pgid is None"
);
}
#[test]
fn descendant_pids_walks_child_chain() {
let rows = vec![
ProcRow {
pid: 10,
pgid: 10,
ppid: 1,
},
ProcRow {
pid: 11,
pgid: 10,
ppid: 10,
},
ProcRow {
pid: 12,
pgid: 10,
ppid: 11,
},
];
let roots = HashSet::from([10]);
let desc = descendant_pids(&roots, &rows);
assert!(desc.contains(&10));
assert!(desc.contains(&11));
assert!(desc.contains(&12));
}
#[test]
fn terminate_agent_process_group_blocking_noop_without_targets() {
let empty = HashSet::new();
terminate_agent_process_group_blocking(None, &empty);
}
#[test]
fn kill_targets_empty_baseline_skips_orphan_scan() {
let empty = HashSet::new();
let targets = kill_targets_for_teardown(None, Some(&empty));
assert!(
targets.is_empty(),
"empty baseline must not scan host orphans"
);
}
#[test]
fn reap_baseline_amnestied_agent_orphans_blocking_noop_without_orphans() {
super::reap_baseline_amnestied_agent_orphans_blocking();
}
#[tokio::test]
async fn signal_targets_noop_for_empty_set() {
super::signal_targets(&HashSet::new(), None, 15).await;
}
#[tokio::test]
async fn terminate_process_group_kills_sleep_child() {
crate::test_utils::clear_test_no_real_agent_env();
let mut cmd = Command::new("sleep");
cmd.arg("120").process_group(0);
let mut child = cmd.spawn().expect("spawn sleep");
let pgid = child.id();
terminate_process_group(Some(pgid)).await;
assert_ne!(child.try_wait().expect("wait"), None);
}
#[tokio::test]
async fn terminate_agent_process_group_kills_sleep_child() {
crate::test_utils::clear_test_no_real_agent_env();
let baseline = super::super::unix_process_group_ps::snapshot_pids();
let mut cmd = Command::new("sleep");
cmd.arg("120").process_group(0);
let mut child = cmd.spawn().expect("spawn sleep");
let pgid = child.id();
terminate_agent_process_group(Some(pgid), &baseline).await;
assert_ne!(child.try_wait().expect("wait"), None);
}
#[cfg(target_os = "linux")]
#[tokio::test]
async fn baseline_amnestied_agent_acp_orphan_killed_on_teardown() {
crate::test_utils::enable_test_fast_teardown();
let tmp = tempfile::tempdir().expect("tempdir");
let orphan_pid_file = tmp.path().join("orphan.pid");
let (mut agent, pgid) = spawn_hostile_agent_acp_orphan(tmp.path(), &orphan_pid_file);
let orphan_pid = read_orphan_pid(&orphan_pid_file, Some(pgid)).await;
wait_for_init_reparent(orphan_pid).await;
let mut baseline = super::super::unix_process_group_ps::snapshot_pids();
baseline.insert(orphan_pid);
terminate_agent_process_group(Some(pgid), &baseline).await;
let _ = agent.wait();
drop(agent);
assert!(
crate::test_utils::test_wait_until_async(|| !process_alive(orphan_pid)).await,
"teardown must kill baseline-amnestied agent acp orphan (pid={orphan_pid})"
);
}
#[tokio::test]
async fn malvin_sibling_outside_agent_pg_killed_on_teardown() {
use crate::malvin_sandbox::{assert_dead_before_next_spawn, clear_active_sandbox_session};
crate::test_utils::enable_test_fast_teardown();
clear_active_sandbox_session();
let baseline = super::super::unix_process_group_ps::snapshot_pids();
let (agent_pgid, sibling_pid, mut agent_child, mut sibling_child) =
spawn_agent_pg_and_malvin_sibling();
assert_sibling_monitored_and_blocks_spawn(agent_pgid, sibling_pid, &baseline);
terminate_agent_process_group(Some(agent_pgid), &baseline).await;
clear_active_sandbox_session();
assert!(
crate::test_utils::test_wait_until_async(|| {
sibling_child.try_wait().expect("wait sibling").is_some()
})
.await,
"teardown must terminate malvin sibling outside agent PG (pid={sibling_pid})"
);
assert_dead_before_next_spawn().expect("dead-before-next after clean teardown");
let _ = agent_child.wait();
let _ = sibling_child.wait();
}
#[cfg(test)]
#[allow(unused_imports)]
mod kiss_cov_gate_refs {
use super::*;
#[test]
fn kiss_cov_unit_names() {
let _ = terminate_blocking_kills_note_affiliated_pid_without_pgid;
let _ = async_teardown_kills_affiliated_pid_without_pgid;
let _ = descendant_pids_walks_child_chain;
let _ = terminate_agent_process_group_blocking_noop_without_targets;
let _ = kill_targets_empty_baseline_skips_orphan_scan;
let _ = reap_baseline_amnestied_agent_orphans_blocking_noop_without_orphans;
let _ = signal_targets_noop_for_empty_set;
let _ = terminate_process_group_kills_sleep_child;
let _ = terminate_agent_process_group_kills_sleep_child;
let _ = malvin_sibling_outside_agent_pg_killed_on_teardown;
#[cfg(target_os = "linux")]
let _ = baseline_amnestied_agent_acp_orphan_killed_on_teardown;
let _ = crate::acp::hostile_orphan_test_util::spawn_agent_pg_and_malvin_sibling;
let _ = crate::acp::hostile_orphan_test_util::assert_sibling_monitored_and_blocks_spawn;
let _ = crate::acp::hostile_orphan_test_util::spawn_hostile_agent_acp_orphan;
}
}
#[cfg(test)]
mod kiss_cov_auto {
use super::*;
#[test]
fn kiss_cov_signal_targets_noop_for_empty_set() {
let _ = signal_targets_noop_for_empty_set;
}
#[test]
fn kiss_cov_terminate_process_group_kills_sleep_child() {
let _ = terminate_process_group_kills_sleep_child;
}
#[test]
fn kiss_cov_terminate_agent_process_group_kills_sleep_child() {
let _ = terminate_agent_process_group_kills_sleep_child;
}
}