use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::OnceLock;
static COUNTER: AtomicU64 = AtomicU64::new(0);
pub fn vetto_bin() -> &'static str {
env!("CARGO_BIN_EXE_vetto")
}
pub fn doctor_output() -> String {
Command::new(vetto_bin())
.arg("doctor")
.output()
.map(|o| String::from_utf8_lossy(&o.stdout).to_string())
.unwrap_or_default()
}
pub fn detected_tier() -> Option<String> {
let out = doctor_output();
out.lines()
.find(|l| l.trim_start().starts_with("chosen tier:"))
.and_then(|l| l.split(':').nth(1))
.map(|s| s.trim().to_string())
.filter(|s| s == "full" || s == "fs-only")
}
pub fn have_landlock() -> bool {
detected_tier().is_some()
}
pub fn run_vetto_env_in(cwd: &Path, args: &[&str], envs: &[(&str, &str)]) -> Output {
Command::new(vetto_bin())
.args(args)
.current_dir(cwd)
.env("HOME", test_home())
.envs(envs.iter().copied())
.output()
.expect("spawn vetto")
}
pub fn run_vetto_in(cwd: &Path, args: &[&str]) -> Output {
run_vetto_env_in(cwd, args, &[])
}
pub struct TempProject(PathBuf);
impl TempProject {
pub fn new(tag: &str) -> Self {
let n = COUNTER.fetch_add(1, Ordering::SeqCst);
let dir = std::env::temp_dir().join(format!("vetto-it-{}-{}-{n}", tag, std::process::id()));
std::fs::create_dir_all(&dir).expect("create temp project dir");
Self(dir)
}
pub fn path(&self) -> &Path {
&self.0
}
}
impl Drop for TempProject {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
pub fn write_file(path: &Path, content: &str) {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).expect("create parent");
}
std::fs::write(path, content).expect("write file");
}
#[cfg(target_os = "linux")]
pub fn fixture(name: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures")
.join(name)
}
#[cfg(target_os = "linux")]
pub fn stage_fixture(project: &Path, name: &str) -> String {
let src = fixture(name);
let dst = project.join(name);
std::fs::copy(&src, &dst).expect("stage fixture");
format!("./{name}")
}
pub fn stdout(out: &Output) -> String {
String::from_utf8_lossy(&out.stdout).to_string()
}
pub fn stderr(out: &Output) -> String {
String::from_utf8_lossy(&out.stderr).to_string()
}
#[cfg(target_os = "linux")]
pub fn tool_available(name: &str) -> bool {
Command::new(name)
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
pub fn test_home() -> &'static Path {
static TEST_HOME: OnceLock<PathBuf> = OnceLock::new();
TEST_HOME
.get_or_init(|| {
let nonce = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("clock before epoch")
.as_nanos();
let path =
std::env::temp_dir().join(format!("vetto-it-home-{}-{nonce}", std::process::id()));
std::fs::create_dir_all(&path).expect("create isolated test HOME");
path
})
.as_path()
}
pub fn ensure_fake_ssh_key() {
let ssh = test_home().join(".ssh");
let _ = std::fs::create_dir_all(&ssh);
let key = ssh.join("id_rsa");
if !key.exists() {
let _ = std::fs::write(&key, "FAKE-TEST-KEY-MATERIAL-FOR-VETTO-IT\n");
}
}