use std::path::{Path, PathBuf};
pub(crate) fn find_runner_binary() -> anyhow::Result<PathBuf> {
if let Ok(exe) = std::env::current_exe() {
for candidate in &[
exe.parent()
.unwrap_or_else(|| Path::new("/usr/bin"))
.join("dirge-microvm-runner"),
exe.parent()
.and_then(|p| p.parent())
.unwrap_or_else(|| Path::new("/usr/bin"))
.join("dirge-microvm-runner"),
] {
if candidate.exists() {
return Ok(candidate.clone());
}
}
}
for dir in std::env::var("PATH").unwrap_or_default().split(':') {
let candidate = Path::new(dir).join("dirge-microvm-runner");
if candidate.exists() {
return Ok(candidate);
}
}
anyhow::bail!("dirge-microvm-runner not found — build it and place it alongside dirge")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn find_runner_binary_found_in_build_tree() {
let result = find_runner_binary();
if result.is_err() {
eprintln!(
"skipping: runner binary not found in build tree: {}",
result.unwrap_err()
);
return;
}
let path = result.unwrap();
assert!(
path.ends_with("dirge-microvm-runner"),
"path should end with dirge-microvm-runner, got: {}",
path.display()
);
}
#[test]
fn find_runner_binary_from_empty_path_is_error() {
if find_runner_binary().is_ok() {
return;
}
let empty_dir = std::env::temp_dir().join(format!(
"dirge-test-runner-empty-{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
));
std::fs::create_dir_all(&empty_dir).unwrap();
let saved_path = std::env::var("PATH").unwrap_or_default();
unsafe { std::env::set_var("PATH", &empty_dir) };
let result = find_runner_binary();
unsafe { std::env::set_var("PATH", &saved_path) };
let _ = std::fs::remove_dir_all(&empty_dir);
let err = result.unwrap_err().to_string();
assert!(
err.contains("dirge-microvm-runner not found"),
"expected 'not found' error, got: {err}"
);
}
#[test]
fn runner_stderr_captured_on_crash() {
let binary = match find_runner_binary() {
Ok(b) => b,
Err(e) => {
eprintln!("skipping: runner binary not found: {e}");
return;
}
};
use std::process::Stdio;
let output = std::process::Command::new(&binary)
.arg("not-valid-json")
.stdout(Stdio::null())
.stderr(Stdio::piped())
.output()
.expect("spawn runner");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.is_empty(),
"runner stderr should contain crash diagnostics"
);
assert!(
stderr.contains("rror") || stderr.contains("sage") || stderr.contains("thread"),
"runner stderr should contain diagnostic text, got: {stderr}"
);
}
}