use std::time::{Duration, Instant};
use processkit::{Command, ProcessGroup};
#[tokio::test]
#[ignore = "spawns a real subprocess and shuts it down gracefully"]
async fn shutdown_lets_a_term_handling_child_end_the_grace_early() {
#[allow(clippy::needless_update)]
let group = ProcessGroup::with_options(processkit::ProcessGroupOptions {
shutdown_timeout: Duration::from_secs(10),
..Default::default()
})
.expect("create group");
let mut run = group
.start(
&Command::new("sh")
.args(["-c", "trap 'exit 0' TERM; echo ready; read line"])
.keep_stdin_open(),
)
.await
.expect("start");
run.wait_for_line(|l| l.contains("ready"), Duration::from_secs(10))
.await
.expect("trap installed");
let waiter = tokio::spawn(run.wait());
let start = Instant::now();
tokio::time::timeout(Duration::from_secs(20), group.shutdown())
.await
.expect("shutdown bounded")
.expect("shutdown ok");
assert!(
start.elapsed() < Duration::from_secs(8),
"a TERM-handling child must end the 10s grace early (took {:?})",
start.elapsed()
);
let code = waiter.await.expect("join").expect("wait");
assert_eq!(code, Some(0), "the child exited via its TERM trap");
}
#[tokio::test]
#[ignore = "spawns a TERM-ignoring subprocess and escalates to SIGKILL"]
async fn shutdown_escalates_to_kill_after_the_grace_window() {
#[allow(clippy::needless_update)]
let group = ProcessGroup::with_options(processkit::ProcessGroupOptions {
shutdown_timeout: Duration::from_millis(500),
escalate_to_kill: true,
..Default::default()
})
.expect("create group");
let mut run = group
.start(&Command::new("sh").args(["-c", "trap '' TERM; echo ready; while :; do :; done"]))
.await
.expect("start");
run.wait_for_line(|l| l.contains("ready"), Duration::from_secs(10))
.await
.expect("trap installed");
let waiter = tokio::spawn(run.wait());
let start = Instant::now();
tokio::time::timeout(Duration::from_secs(15), group.shutdown())
.await
.expect("escalation keeps shutdown bounded")
.expect("shutdown ok");
let elapsed = start.elapsed();
assert!(
elapsed >= Duration::from_millis(300),
"the grace window must be waited out before escalating ({elapsed:?})"
);
let code = waiter.await.expect("join").expect("wait");
assert_eq!(code, None, "SIGKILL leaves no exit code, got {code:?}");
}