use std::io::Read;
use std::path::Path;
use std::process::{Command, ExitStatus, Stdio};
use std::time::{Duration, Instant};
const DEFAULT_TIMEOUT_SECS: u64 = 10;
pub(crate) enum RunResult {
Success { stdout: String },
Failed { stderr: String, status: ExitStatus },
Timeout { timeout_secs: u64 },
Error(String),
}
pub(crate) fn run_with_timeout(path: &Path) -> RunResult {
let timeout_secs = std::env::var("SEQ_REPL_TIMEOUT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(DEFAULT_TIMEOUT_SECS);
let timeout = Duration::from_secs(timeout_secs);
let mut child = match Command::new(path)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
{
Ok(c) => c,
Err(e) => return RunResult::Error(format!("Failed to start: {}", e)),
};
let start = Instant::now();
let poll_interval = Duration::from_millis(50);
loop {
match child.try_wait() {
Ok(Some(status)) => {
let stream = if status.success() {
drain_pipe(child.stdout.take())
} else {
drain_pipe(child.stderr.take())
};
if status.success() {
return RunResult::Success { stdout: stream };
} else {
return RunResult::Failed {
stderr: stream,
status,
};
}
}
Ok(None) => {
if start.elapsed() >= timeout {
let _ = child.kill();
let _ = child.wait(); return RunResult::Timeout { timeout_secs };
}
std::thread::sleep(poll_interval);
}
Err(e) => {
return RunResult::Error(format!("Wait error: {}", e));
}
}
}
}
fn drain_pipe<R: Read>(pipe: Option<R>) -> String {
pipe.map(|mut s| {
let mut buf = String::new();
let _ = s.read_to_string(&mut buf);
buf
})
.unwrap_or_default()
}