#![cfg(unix)]
use test_bounded::output_bounded;
use std::process::Command;
use std::time::{Duration, Instant};
const GRANDCHILD_SLEEP_SECS: u64 = 6;
const OUTPUT_BLOCK_WINDOW: Duration = Duration::from_secs(2);
const DEADLINE: Duration = Duration::from_secs(4);
fn stub(body: &str) -> Command {
let mut cmd = Command::new("/bin/sh");
cmd.arg("-c").arg(body);
cmd
}
fn leaky_stub() -> Command {
stub(&format!(
"sleep {GRANDCHILD_SLEEP_SECS} &\necho hello\nexit 0"
))
}
#[test]
fn pipe_based_output_hangs_on_a_pipe_holding_grandchild() {
let mut tool = leaky_stub();
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let out = tool.output().expect("spawn");
let _ = tx.send(out);
});
assert!(
rx.recv_timeout(OUTPUT_BLOCK_WINDOW).is_err(),
"output() was expected to block on the grandchild-held pipe"
);
}
#[test]
fn bounded_runner_returns_despite_pipe_holding_grandchild() {
let mut tool = leaky_stub();
let start = Instant::now();
let out = output_bounded(&mut tool, DEADLINE).expect("run");
assert!(out.status.success());
assert_eq!(out.stdout, b"hello\n");
assert!(
start.elapsed() < Duration::from_secs(GRANDCHILD_SLEEP_SECS),
"returned only after the grandchild exited: {:?}",
start.elapsed()
);
}
#[test]
fn bounded_runner_kills_an_overrunning_tool_with_a_clear_error() {
let mut tool = stub("exec sleep 30");
let start = Instant::now();
let err = output_bounded(&mut tool, Duration::from_millis(300)).expect_err("must time out");
assert_eq!(err.kind(), std::io::ErrorKind::TimedOut);
assert!(err.to_string().contains("/bin/sh"), "{err}");
assert!(err.to_string().contains("hard deadline"), "{err}");
assert!(start.elapsed() < Duration::from_secs(5));
}