#[cfg(not(feature = "no_parallel"))]
use rayon::prelude::*;
use std::{fs, path::PathBuf};
use walkdir::WalkDir;
enum TestFailure {
Panicked(String),
Mismatch { got: String, expected: String },
}
pub fn run_tests(category: &str, runner: fn(&str) -> String) {
unsafe {
std::env::set_var("NOCOLOR", "x");
}
let base_tests_dir: PathBuf = [env!("CARGO_MANIFEST_DIR"), "..", "tests"].iter().collect();
let base_tests_dir = base_tests_dir.canonicalize().unwrap();
let tests_dir = base_tests_dir.join("tests").join(category);
let expectations_dir = base_tests_dir.join("expectations").join(category);
let filter_string = std::env::var("TEST_FILTER").unwrap_or_default();
let rewrite_expectations = std::env::var("REWRITE_EXPECTATIONS").is_ok();
struct TestResult {
failure: Option<TestFailure>,
name: PathBuf,
wrote: bool,
}
let paths: Vec<PathBuf> = WalkDir::new(&tests_dir)
.into_iter()
.flatten()
.filter_map(|entry| {
let path = entry.path();
if path.to_str().is_none() {
panic!("Path not unicode: {}.", path.display());
};
let path_str = path.to_str().unwrap();
if !path_str.contains(&filter_string) || !path_str.ends_with(".leo") {
return None;
}
Some(path.into())
})
.collect();
let run_test = |path: &PathBuf| -> TestResult {
let contents =
fs::read_to_string(path).unwrap_or_else(|e| panic!("Failed to read file {}: {e}.", path.display()));
let result_output = std::panic::catch_unwind(|| runner(&contents));
if let Err(payload) = result_output {
let s1 = payload.downcast_ref::<&str>().map(|s| s.to_string());
let s2 = payload.downcast_ref::<String>().cloned();
let s = s1.or(s2).unwrap_or_else(|| "Unknown panic payload".to_string());
return TestResult { failure: Some(TestFailure::Panicked(s)), name: path.clone(), wrote: false };
}
let output = result_output.unwrap();
let mut expectation_path: PathBuf = expectations_dir.join(path.strip_prefix(&tests_dir).unwrap());
expectation_path.set_extension("out");
if rewrite_expectations || !expectation_path.exists() {
fs::write(&expectation_path, &output)
.unwrap_or_else(|e| panic!("Failed to write file {}: {e}.", expectation_path.display()));
TestResult { failure: None, name: path.clone(), wrote: true }
} else {
let expected = fs::read_to_string(&expectation_path)
.unwrap_or_else(|e| panic!("Failed to read file {}: {e}.", expectation_path.display()));
if output == expected {
TestResult { failure: None, name: path.clone(), wrote: false }
} else {
TestResult {
failure: Some(TestFailure::Mismatch { got: output, expected }),
name: path.clone(),
wrote: false,
}
}
}
};
#[cfg(feature = "no_parallel")]
let results: Vec<TestResult> = paths.iter().map(run_test).collect();
#[cfg(not(feature = "no_parallel"))]
let results: Vec<TestResult> = paths.par_iter().map(run_test).collect();
println!("Ran {} tests.", results.len());
let failure_count = results.iter().filter(|test_result| test_result.failure.is_some()).count();
if failure_count != 0 {
eprintln!("{failure_count}/{} tests failed.", results.len());
}
let writes = results.iter().filter(|test_result| test_result.wrote).count();
for test_result in results.iter() {
if let Some(test_failure) = &test_result.failure {
eprintln!("FAILURE: {}:", test_result.name.display());
match test_failure {
TestFailure::Panicked(s) => eprintln!("Rust panic:\n{s}"),
TestFailure::Mismatch { got, expected } => {
eprintln!("\ngot:\n{got}\nexpected:\n{expected}\n")
}
}
}
}
if writes != 0 {
println!("Wrote {}/{} expectation files for tests:", writes, results.len());
}
for test_result in results.iter() {
if test_result.wrote {
println!("{}", test_result.name.display());
}
}
assert!(failure_count == 0);
}