use std::process::Output;
use std::time::{Duration, Instant};
use tokio::process::Command;
pub const TIMEOUT_QUICK: Duration = Duration::from_secs(15);
pub const TIMEOUT_MEDIUM: Duration = Duration::from_secs(30);
pub const TIMEOUT_SLOW: Duration = Duration::from_secs(60);
pub async fn run_cmd(mut cmd: Command, timeout: Duration) -> Result<Output, String> {
let label = format!("{:?}", cmd.as_std().get_program());
match tokio::time::timeout(timeout, cmd.output()).await {
Ok(Ok(output)) => Ok(output),
Ok(Err(e)) => Err(format!("{} failed: {}", label, e)),
Err(_) => Err(format!("{} timed out after {}s", label, timeout.as_secs())),
}
}
#[derive(Debug, Clone)]
pub struct CmdOutcome {
pub command: String,
pub args: Vec<String>,
pub exit_code: Option<i32>,
pub stdout: String,
pub stderr: String,
pub duration: Duration,
pub ok: bool,
pub error: Option<String>,
}
impl CmdOutcome {
pub fn summary(&self) -> String {
if self.ok {
format!("ok ({:.1}s)", self.duration.as_secs_f64())
} else if let Some(err) = &self.error {
format!("failed: {}", err)
} else if let Some(code) = self.exit_code {
format!("exit {} ({:.1}s)", code, self.duration.as_secs_f64())
} else {
"failed".to_string()
}
}
pub fn cmdline(&self) -> String {
let mut s = self.command.clone();
for a in &self.args {
s.push(' ');
if a.contains(' ') {
s.push('"');
s.push_str(a);
s.push('"');
} else {
s.push_str(a);
}
}
s
}
}
pub async fn run_cmd_capture(mut cmd: Command, timeout: Duration) -> CmdOutcome {
let std_cmd = cmd.as_std();
let command = std_cmd.get_program().to_string_lossy().into_owned();
let args: Vec<String> = std_cmd
.get_args()
.map(|s| s.to_string_lossy().into_owned())
.collect();
let started = Instant::now();
let raced = tokio::time::timeout(timeout, cmd.output()).await;
let duration = started.elapsed();
match raced {
Ok(Ok(output)) => {
let exit_code = output.status.code();
let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
let ok = output.status.success();
CmdOutcome {
command,
args,
exit_code,
stdout,
stderr,
duration,
ok,
error: None,
}
}
Ok(Err(e)) => CmdOutcome {
command,
args,
exit_code: None,
stdout: String::new(),
stderr: String::new(),
duration,
ok: false,
error: Some(format!("spawn failed: {}", e)),
},
Err(_) => CmdOutcome {
command,
args,
exit_code: None,
stdout: String::new(),
stderr: String::new(),
duration,
ok: false,
error: Some(format!("timed out after {}s", timeout.as_secs())),
},
}
}