use std::io::Read;
use std::process::{Child, ExitStatus, Output};
use std::time::Duration;
use wait_timeout::ChildExt;
pub enum TimedOutput {
Finished(Output),
TimedOut { stdout: Vec<u8>, stderr: Vec<u8> },
}
impl TimedOutput {
pub fn timed_out(&self) -> bool {
matches!(self, TimedOutput::TimedOut { .. })
}
pub fn stdout(self) -> Vec<u8> {
match self {
TimedOutput::Finished(output) => output.stdout,
TimedOutput::TimedOut { stdout, .. } => stdout,
}
}
pub fn status(&self) -> Option<ExitStatus> {
match self {
TimedOutput::Finished(output) => Some(output.status),
TimedOutput::TimedOut { .. } => None,
}
}
}
pub fn wait_with_output_timeout(
mut child: Child,
timeout: Duration,
) -> Result<TimedOutput, std::io::Error> {
let stdout_pipe = child.stdout.take();
let stderr_pipe = child.stderr.take();
let stdout_thread = std::thread::spawn(move || {
let mut buf = Vec::new();
if let Some(mut pipe) = stdout_pipe {
let _ = pipe.read_to_end(&mut buf);
}
buf
});
let stderr_thread = std::thread::spawn(move || {
let mut buf = Vec::new();
if let Some(mut pipe) = stderr_pipe {
let _ = pipe.read_to_end(&mut buf);
}
buf
});
let status = child.wait_timeout(timeout)?;
match status {
Some(exit_status) => {
let stdout = stdout_thread.join().unwrap_or_default();
let stderr = stderr_thread.join().unwrap_or_default();
Ok(TimedOutput::Finished(Output {
status: exit_status,
stdout,
stderr,
}))
}
None => {
let _ = child.kill();
let _ = child.wait();
let stdout = stdout_thread.join().unwrap_or_default();
let stderr = stderr_thread.join().unwrap_or_default();
Ok(TimedOutput::TimedOut { stdout, stderr })
}
}
}