use std::io::Read;
use std::process::{Command, Stdio};
use std::thread::JoinHandle;
use std::time::{Duration, Instant};
const TIMEOUT: Duration = Duration::from_mins(2);
pub fn run(cmd: &mut Command, label: &str) -> (bool, Vec<u8>, Vec<u8>) {
cmd.stdin(Stdio::null());
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());
let mut child = cmd
.spawn()
.unwrap_or_else(|e| panic!("cannot spawn `{label}`: {e}"));
let stdout = drain(child.stdout.take());
let stderr = drain(child.stderr.take());
let deadline = Instant::now() + TIMEOUT;
loop {
match child.try_wait() {
Ok(Some(status)) => {
return (
status.success(),
stdout.join().expect("stdout reader"),
stderr.join().expect("stderr reader"),
);
}
Ok(None) => {
if Instant::now() >= deadline {
if let Err(e) = child.kill() {
eprintln!("cannot kill timed out `{label}`: {e}");
}
panic!(
"`{label}` still running after {} seconds, likely an interpreter hang or deadlock",
TIMEOUT.as_secs()
);
}
std::thread::sleep(Duration::from_millis(20));
}
Err(e) => panic!("cannot wait for `{label}`: {e}"),
}
}
}
fn drain<R: Read + Send + 'static>(pipe: Option<R>) -> JoinHandle<Vec<u8>> {
std::thread::spawn(move || {
let mut buf = Vec::new();
if let Some(mut pipe) = pipe {
if let Err(e) = pipe.read_to_end(&mut buf) {
eprintln!("pipe read failed: {e}");
}
}
buf
})
}