use std::path::PathBuf;
use arcane_core::scripting::{run_test_file_with_import_map, TestResult, TestSummary};
use super::{create_import_map, type_check};
pub fn run(path: Option<String>) -> anyhow::Result<()> {
let root = path
.map(PathBuf::from)
.unwrap_or_else(|| std::env::current_dir().expect("cannot read current directory"));
let test_files = discover_test_files(&root)?;
if test_files.is_empty() {
println!("No test files found.");
return Ok(());
}
println!("Discovered {} test file(s)\n", test_files.len());
if !type_check::should_skip_type_check() {
for file in &test_files {
type_check::check_types(file)?;
}
}
let import_map = create_import_map(&root);
let mut grand_total = TestSummary {
total: 0,
passed: 0,
failed: 0,
results: Vec::new(),
};
let mut any_failure = false;
let mut all_failures: Vec<TestResult> = Vec::new();
for file in &test_files {
let display = file
.strip_prefix(&root)
.unwrap_or(file)
.display();
print!("{display} ... ");
match run_test_file_with_import_map(file, import_map.clone()) {
Ok(summary) => {
grand_total.total += summary.total;
grand_total.passed += summary.passed;
grand_total.failed += summary.failed;
if summary.failed > 0 {
any_failure = true;
println!("FAIL ({} passed, {} failed)", summary.passed, summary.failed);
for result in &summary.results {
if !result.passed {
all_failures.push(result.clone());
}
}
} else {
println!("ok ({} tests)", summary.total);
}
}
Err(e) => {
any_failure = true;
println!("ERROR: {e}");
}
}
}
if !all_failures.is_empty() {
println!("\n--- Failures ---\n");
for failure in &all_failures {
println!("\x1b[1;31mFAIL\x1b[0m {} > {}", failure.suite, failure.name);
if let Some(ref error) = failure.error {
for line in error.lines() {
println!(" {line}");
}
}
println!();
}
}
println!(
"\n{} tests, {} passed, {} failed",
grand_total.total, grand_total.passed, grand_total.failed
);
if any_failure {
std::process::exit(1);
}
Ok(())
}
fn discover_test_files(root: &PathBuf) -> anyhow::Result<Vec<PathBuf>> {
let mut files = Vec::new();
if root.is_file() {
if root.to_string_lossy().ends_with(".test.ts") {
files.push(root.clone());
}
return Ok(files);
}
walk_dir(root, &mut files)?;
files.sort();
Ok(files)
}
fn walk_dir(dir: &PathBuf, files: &mut Vec<PathBuf>) -> anyhow::Result<()> {
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
if path.is_symlink() {
continue;
}
let name = entry.file_name();
let name = name.to_string_lossy();
if name == "target"
|| name == "node_modules"
|| name == "templates"
|| name == "tools"
|| name.starts_with('.')
{
continue;
}
walk_dir(&path, files)?;
} else if path.to_string_lossy().ends_with(".test.ts") {
if path.is_symlink() {
continue;
}
files.push(path);
}
}
Ok(())
}