use std::time::Duration;
use tokio::process::Command;
pub const RESOLVE: Duration = Duration::from_secs(5);
pub const QUICK: Duration = Duration::from_secs(5);
pub const SLOW: Duration = Duration::from_secs(10);
pub async fn run_with_timeout(mut cmd: Command, dur: Duration) -> Option<std::process::Output> {
match tokio::time::timeout(dur, cmd.output()).await {
Ok(Ok(output)) => Some(output),
Ok(Err(_)) => None,
Err(_) => None,
}
}
pub async fn lookup_host_timeout(addr: String, dur: Duration) -> Option<Vec<std::net::SocketAddr>> {
match tokio::time::timeout(dur, tokio::net::lookup_host(addr)).await {
Ok(Ok(addrs)) => Some(addrs.collect()),
Ok(Err(_)) => None,
Err(_) => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn fast_command_returns_some() {
#[cfg(windows)]
let mut cmd = {
let mut c = Command::new("cmd");
c.args(["/C", "exit", "0"]);
c
};
#[cfg(unix)]
let mut cmd = Command::new("true");
let _ = &mut cmd;
let out = run_with_timeout(cmd, QUICK).await;
assert!(out.is_some(), "fast command should finish within budget");
}
#[tokio::test]
async fn slow_command_times_out_to_none() {
#[cfg(windows)]
let cmd = {
let mut c = Command::new("cmd");
c.args(["/C", "ping", "-n", "3", "127.0.0.1"]);
c
};
#[cfg(unix)]
let cmd = {
let mut c = Command::new("sleep");
c.arg("2");
c
};
let out = run_with_timeout(cmd, Duration::from_millis(1)).await;
assert!(out.is_none(), "command exceeding budget should yield None");
}
#[tokio::test]
async fn missing_binary_returns_none() {
let cmd = Command::new("nd300-definitely-not-a-real-binary-xyz");
let out = run_with_timeout(cmd, QUICK).await;
assert!(out.is_none(), "missing binary should yield None");
}
#[tokio::test]
async fn localhost_resolves_to_some() {
let addrs = lookup_host_timeout("localhost:80".to_string(), RESOLVE).await;
assert!(
addrs.is_some_and(|v| !v.is_empty()),
"localhost:80 should resolve to at least one address"
);
}
}