use std::fs;
use std::path::{Path, PathBuf};
use std::process;
use std::time::Instant;
use crate::vm;
#[derive(Debug)]
pub struct TestResult {
pub path: PathBuf,
pub duration_ms: u128,
pub success: bool,
pub error: Option<String>,
}
pub fn run_test_file_with_options(path: &Path, options: crate::compiler::Options) -> TestResult {
let start = Instant::now();
let path_str = path.to_str().expect("path should be valid UTF-8");
let res = vm::run_file_with_options(path_str, options);
let duration_ms = start.elapsed().as_millis();
match res {
Ok(()) => TestResult {
path: path.to_path_buf(),
duration_ms,
success: true,
error: None,
},
Err(err) => TestResult {
path: path.to_path_buf(),
duration_ms,
success: false,
error: Some(err),
},
}
}
pub fn run_test_file(path: &Path) -> TestResult {
run_test_file_with_options(path, crate::compiler::Options::RUN)
}
pub fn collect_test_files(tests_dir: &Path, filter: Option<&str>) -> Vec<PathBuf> {
let mut files: Vec<PathBuf> = fs::read_dir(tests_dir)
.expect("failed to read tests directory")
.filter_map(|entry| entry.ok())
.map(|entry| entry.path())
.filter(|path| path.extension().is_some_and(|ext| ext == "nyk"))
.filter(|path| {
if let Some(pattern) = filter {
path.file_name()
.and_then(|n| n.to_str())
.is_some_and(|s| s.contains(pattern))
} else {
true
}
})
.collect();
files.sort();
files
}
pub fn run_all_tests() {
let tests_dir = Path::new("tests");
let files = collect_test_files(tests_dir, None);
if files.is_empty() {
println!("No .nyk test files found in tests/");
return;
}
println!(
"Running {} integration test file(s) from tests/:",
files.len()
);
let suite_start = Instant::now();
let mut passed = 0;
let mut failed = 0;
for path in &files {
let result = run_test_file(path);
let name = path.display();
if result.success {
passed += 1;
println!(" test {:<40} ... ok ({}ms)", name, result.duration_ms);
} else {
failed += 1;
println!(" test {:<40} ... FAILED", name);
if let Some(err) = &result.error {
eprintln!(" --> error: {}", err);
}
}
}
let elapsed = suite_start.elapsed().as_secs_f64();
println!(
"\nTest result: {}. {} passed; {} failed; finished in {:.2}s\n",
if failed == 0 { "ok" } else { "FAILED" },
passed,
failed,
elapsed
);
if failed > 0 {
process::exit(1);
}
}
#[cfg(test)]
mod integration_tests {
use super::*;
#[test]
fn test_all_nyk_integration_files() {
let tests_dir = Path::new("tests");
let files = collect_test_files(tests_dir, None);
assert!(!files.is_empty(), "expected tests/*.nyk files to exist");
for path in &files {
let result = run_test_file(path);
assert!(
result.success,
"Test script '{}' failed: {:?}",
path.display(),
result.error
);
}
for path in &files {
let result = run_test_file_with_options(path, crate::compiler::Options::DEBUG);
assert!(
result.success,
"Test script '{}' (unoptimized) failed: {:?}",
path.display(),
result.error
);
}
}
#[test]
fn test_cargo_fmt_check() {
let output = std::process::Command::new("cargo")
.args(["fmt", "--all", "--", "--check"])
.output();
if let Ok(out) = output {
assert!(
out.status.success(),
"cargo fmt --all -- --check failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
}
}
}