use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Instant;
const TESTS_DIR: &str = "tests";
const UNIT_SUBDIR: &str = "unit";
const AGENT_SUBDIR: &str = "agent";
const TEST_PREFIX: &str = "test-";
const TEST_EXT: &str = "sh";
pub fn run(scope: &str) -> netsky_core::Result<()> {
let tests = resolve_tests(scope)?;
if tests.is_empty() {
println!("no tests found");
return Ok(());
}
let tests_dir = Path::new(TESTS_DIR);
let mut pass = 0;
let mut fail = 0;
let mut failed = Vec::new();
for t in &tests {
let name = t.strip_prefix(tests_dir).unwrap_or(t).display().to_string();
print!(" {name:<50} ");
let start = Instant::now();
let out = Command::new("bash").arg(t).output()?;
let elapsed = start.elapsed().as_secs();
if out.status.success() {
println!("pass {elapsed}s");
pass += 1;
} else {
println!("FAIL {elapsed}s");
for l in String::from_utf8_lossy(&out.stdout).lines() {
println!(" {l}");
}
for l in String::from_utf8_lossy(&out.stderr).lines() {
println!(" {l}");
}
fail += 1;
failed.push(name);
}
}
println!();
if fail == 0 {
println!("summary: {pass} passed");
Ok(())
} else {
println!("summary: {fail} failed, {pass} passed");
for n in &failed {
println!(" - {n}");
}
netsky_core::bail!("tests failed")
}
}
fn resolve_tests(scope: &str) -> netsky_core::Result<Vec<PathBuf>> {
let tests_dir = Path::new(TESTS_DIR);
match scope {
"all" => {
let mut v = collect_in(&tests_dir.join(UNIT_SUBDIR));
v.extend(collect_in(&tests_dir.join(AGENT_SUBDIR)));
v.sort();
Ok(v)
}
UNIT_SUBDIR | AGENT_SUBDIR => {
let mut v = collect_in(&tests_dir.join(scope));
v.sort();
Ok(v)
}
other => {
let candidate = tests_dir.join(other);
if candidate.is_file() {
Ok(vec![candidate])
} else if Path::new(other).is_file() {
Ok(vec![PathBuf::from(other)])
} else {
netsky_core::bail!("no such test: {other}")
}
}
}
}
fn collect_in(dir: &Path) -> Vec<PathBuf> {
let Ok(entries) = fs::read_dir(dir) else {
return Vec::new();
};
entries
.filter_map(|e| e.ok())
.map(|e| e.path())
.filter(|p| {
p.extension().and_then(|s| s.to_str()) == Some(TEST_EXT)
&& p.file_name()
.and_then(|s| s.to_str())
.is_some_and(|n| n.starts_with(TEST_PREFIX))
})
.collect()
}