use anyhow::Result;
use std::path::Path;
mod test_discovery;
mod test_execution;
mod test_reporting;
mod util;
pub use util::{copy_dir_recursive, path_to_toml_string};
pub fn run_tests(
path: Option<&Path>,
filter: Option<&str>,
nocapture: bool,
parallel: bool,
json: bool,
) -> Result<()> {
use colored::*;
use std::fs;
use std::process::Command;
use std::time::Instant;
use test_discovery::{compile_test_file, discover_test_files};
use test_execution::generate_test_harness;
use test_reporting::{generate_coverage_report, parse_test_output};
let start_time = Instant::now();
let test_dir = path.unwrap_or_else(|| Path::new("."));
if !test_dir.exists() {
anyhow::bail!("Test path does not exist: {:?}", test_dir);
}
if !json {
println!();
println!(
"{}",
"╭─────────────────────────────────────────────╮".cyan()
);
println!(
"{}",
"│ 🧪 Windjammer Test Framework │"
.cyan()
.bold()
);
println!(
"{}",
"╰─────────────────────────────────────────────╯".cyan()
);
println!();
println!("{} Discovering tests...", "→".bright_blue().bold());
}
let test_files = discover_test_files(test_dir)?;
if test_files.is_empty() {
if json {
println!("{{\"error\": \"No test files found\", \"files\": [], \"tests\": []}}");
} else {
println!();
println!("{} No test files found", "✗".red().bold());
println!();
println!(" {} Test files should:", "ℹ".blue());
println!(
" • Be named {} or {}",
"*_test.wj".yellow(),
"test_*.wj".yellow()
);
println!(" • Contain functions starting with {}", "test_".yellow());
println!();
}
return Ok(());
}
if !json {
println!(
"{} Found {} test file(s)",
"✓".green().bold(),
test_files.len().to_string().bright_white().bold()
);
for file in &test_files {
println!(
" {} {}",
"•".bright_black(),
file.display().to_string().bright_white()
);
}
println!();
}
let temp_dir = std::env::temp_dir().join(format!(
"windjammer-test-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
));
if temp_dir.exists() {
fs::remove_dir_all(&temp_dir)?;
}
fs::create_dir_all(&temp_dir)?;
if !json {
println!("{} Compiling tests...", "→".bright_blue().bold());
}
let mut all_tests = Vec::new();
for test_file in &test_files {
let tests = compile_test_file(test_file, &temp_dir)?;
all_tests.extend(tests);
}
if !json {
println!(
"{} Found {} test function(s)",
"✓".green().bold(),
all_tests.len().to_string().bright_white().bold()
);
println!();
}
let project_root = std::env::current_dir()?;
generate_test_harness(&temp_dir, &all_tests, filter, &project_root)?;
if !json {
println!("{}", "─".repeat(50).bright_black());
println!("{} Running tests...", "▶".bright_green().bold());
println!("{}", "─".repeat(50).bright_black());
println!();
}
let mut cmd = Command::new("cargo");
cmd.arg("test").current_dir(&temp_dir);
if !parallel {
cmd.arg("--").arg("--test-threads").arg("1");
}
if let Some(filter_str) = filter {
cmd.arg("--").arg(filter_str);
}
if nocapture {
if filter.is_none() {
cmd.arg("--");
}
cmd.arg("--nocapture");
}
let output = cmd.output()?;
let duration = start_time.elapsed();
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let test_results = parse_test_output(&stdout, &stderr);
if json {
println!("{{");
println!(" \"success\": {},", output.status.success());
println!(" \"duration_ms\": {},", duration.as_millis());
println!(" \"test_files\": {},", test_files.len());
println!(" \"total_tests\": {},", all_tests.len());
println!(" \"passed\": {},", test_results.passed);
println!(" \"failed\": {},", test_results.failed);
println!(" \"ignored\": {},", test_results.ignored);
println!(" \"files\": [");
for (i, file) in test_files.iter().enumerate() {
println!(
" \"{}\"{}",
file.display(),
if i < test_files.len() - 1 { "," } else { "" }
);
}
println!(" ],");
println!(" \"tests\": [");
for (i, test) in all_tests.iter().enumerate() {
let full_test_name = format!(
"{}::{}",
test.file.file_stem().unwrap().to_string_lossy(),
test.name
);
let status = test_results
.individual_results
.get(&full_test_name)
.or_else(|| test_results.individual_results.get(&test.name))
.map(|s| s.as_str())
.unwrap_or("unknown");
println!(
" {{\"name\": \"{}\", \"file\": \"{}\", \"status\": \"{}\"}}{}",
test.name,
test.file.display(),
status,
if i < all_tests.len() - 1 { "," } else { "" }
);
}
println!(" ]");
println!("}}");
} else {
print!("{}", stdout);
print!("{}", stderr);
println!();
println!("{}", "─".repeat(50).bright_black());
if output.status.success() {
println!();
println!(
"{} {} All tests passed! {}",
"✓".green().bold(),
"🎉".bright_white(),
"✓".green().bold()
);
println!();
println!(
" {} {} passed",
"✓".green(),
test_results.passed.to_string().bright_white().bold()
);
if test_results.ignored > 0 {
println!(
" {} {} ignored",
"○".yellow(),
test_results.ignored.to_string().bright_white()
);
}
println!(
" {} Completed in {}",
"⏱".bright_blue(),
format!("{:.2}s", duration.as_secs_f64())
.bright_white()
.bold()
);
} else {
println!();
println!(
"{} {} Tests failed {}",
"✗".red().bold(),
"⚠".bright_yellow(),
"✗".red().bold()
);
println!();
println!(
" {} {} passed",
"✓".green(),
test_results.passed.to_string().bright_white()
);
println!(
" {} {} failed",
"✗".red().bold(),
test_results.failed.to_string().bright_white().bold()
);
if test_results.ignored > 0 {
println!(
" {} {} ignored",
"○".yellow(),
test_results.ignored.to_string().bright_white()
);
}
println!(
" {} Completed in {}",
"⏱".bright_blue(),
format!("{:.2}s", duration.as_secs_f64()).bright_white()
);
}
println!();
println!("{}", "─".repeat(50).bright_black());
println!();
if std::env::var("WINDJAMMER_COVERAGE").is_ok() {
println!("{} Generating coverage report...", "→".bright_blue().bold());
generate_coverage_report(&temp_dir)?;
}
}
if !output.status.success() {
anyhow::bail!("Tests failed");
}
if temp_dir.exists() {
fs::remove_dir_all(&temp_dir)?;
}
Ok(())
}