leo_test_framework/
lib.rs1#[cfg(not(feature = "no_parallel"))]
20use rayon::prelude::*;
21
22use std::{
23 fs,
24 path::{Path, PathBuf},
25};
26use walkdir::WalkDir;
27
28enum TestFailure {
29 Panicked(String),
30 Mismatch { got: String, expected: String },
31}
32
33pub fn run_tests(category: &str, runner: fn(&str) -> String) {
47 unsafe {
49 std::env::set_var("NOCOLOR", "x");
54 }
55
56 let base_tests_dir: PathBuf = [env!("CARGO_MANIFEST_DIR"), "..", "tests"].iter().collect();
57
58 let base_tests_dir = base_tests_dir.canonicalize().unwrap();
59 let tests_dir = base_tests_dir.join("tests").join(category);
60 let expectations_dir = base_tests_dir.join("expectations").join(category);
61
62 let filter_string = std::env::var("TEST_FILTER").unwrap_or_default();
63 let rewrite_expectations = std::env::var("REWRITE_EXPECTATIONS").is_ok();
64
65 struct TestResult {
66 failure: Option<TestFailure>,
67 name: PathBuf,
68 wrote: bool,
69 }
70
71 let paths: Vec<PathBuf> = WalkDir::new(&tests_dir)
72 .into_iter()
73 .flatten()
74 .filter_map(|entry| {
75 let path = entry.path();
76
77 if path.to_str().is_none() {
78 panic!("Path not unicode: {}.", path.display());
79 };
80
81 let path_str = path.to_str().unwrap();
82
83 if !path_str.contains(&filter_string) || !path_str.ends_with(".leo") {
84 return None;
85 }
86
87 Some(path.into())
88 })
89 .collect();
90
91 let run_test = |path: &PathBuf| -> TestResult {
92 let contents =
93 fs::read_to_string(path).unwrap_or_else(|e| panic!("Failed to read file {}: {e}.", path.display()));
94 let result_output = std::panic::catch_unwind(|| runner(&contents));
95 if let Err(payload) = result_output {
96 let s1 = payload.downcast_ref::<&str>().map(|s| s.to_string());
97 let s2 = payload.downcast_ref::<String>().cloned();
98 let s = s1.or(s2).unwrap_or_else(|| "Unknown panic payload".to_string());
99
100 return TestResult { failure: Some(TestFailure::Panicked(s)), name: path.clone(), wrote: false };
101 }
102 let output = result_output.unwrap();
103
104 let mut expectation_path: PathBuf = expectations_dir.join(path.strip_prefix(&tests_dir).unwrap());
105 expectation_path.set_extension("out");
106
107 if rewrite_expectations || !expectation_path.exists() {
109 fs::write(&expectation_path, &output)
110 .unwrap_or_else(|e| panic!("Failed to write file {}: {e}.", expectation_path.display()));
111 TestResult { failure: None, name: path.clone(), wrote: true }
112 } else {
113 let expected = fs::read_to_string(&expectation_path)
114 .unwrap_or_else(|e| panic!("Failed to read file {}: {e}.", expectation_path.display()));
115 if output == expected {
116 TestResult { failure: None, name: path.clone(), wrote: false }
117 } else {
118 TestResult {
119 failure: Some(TestFailure::Mismatch { got: output, expected }),
120 name: path.clone(),
121 wrote: false,
122 }
123 }
124 }
125 };
126
127 #[cfg(feature = "no_parallel")]
128 let results: Vec<TestResult> = paths.iter().map(run_test).collect();
129
130 #[cfg(not(feature = "no_parallel"))]
131 let results: Vec<TestResult> = paths.par_iter().map(run_test).collect();
132
133 println!("Ran {} tests.", results.len());
134
135 let failure_count = results.iter().filter(|test_result| test_result.failure.is_some()).count();
136
137 if failure_count != 0 {
138 eprintln!("{failure_count}/{} tests failed.", results.len());
139 }
140
141 let writes = results.iter().filter(|test_result| test_result.wrote).count();
142
143 for test_result in results.iter() {
144 if let Some(test_failure) = &test_result.failure {
145 eprintln!("FAILURE: {}:", test_result.name.display());
146 match test_failure {
147 TestFailure::Panicked(s) => eprintln!("Rust panic:\n{s}"),
148 TestFailure::Mismatch { got, expected } => {
149 eprintln!("\ngot:\n{got}\nexpected:\n{expected}\n")
150 }
151 }
152 }
153 }
154
155 if writes != 0 {
156 println!("Wrote {}/{} expectation files for tests:", writes, results.len());
157 }
158
159 for test_result in results.iter() {
160 if test_result.wrote {
161 println!("{}", test_result.name.display());
162 }
163 }
164
165 assert!(failure_count == 0);
166}
167
168pub fn run_single_test(category: &str, path: &Path, runner: fn(&str) -> String) {
169 use std::fs;
170
171 let path = path.canonicalize().unwrap();
173
174 unsafe {
175 std::env::set_var("NOCOLOR", "x");
177 }
178
179 let base_tests_dir: PathBuf = [env!("CARGO_MANIFEST_DIR"), "..", "tests"].iter().collect();
181 let base_tests_dir = base_tests_dir.canonicalize().unwrap();
182
183 let tests_dir = base_tests_dir.join("tests").join(category);
184 let expectations_dir = base_tests_dir.join("expectations").join(category);
185
186 let rewrite_expectations = std::env::var("REWRITE_EXPECTATIONS").is_ok();
187
188 println!("Running: {}", path.display());
190 let contents = fs::read_to_string(&path).unwrap_or_else(|e| panic!("Failed to read file {}: {e}.", path.display()));
191
192 let result_output = std::panic::catch_unwind(|| runner(&contents));
194
195 let mut wrote = false;
196
197 match result_output {
198 Err(payload) => {
199 let s1 = payload.downcast_ref::<&str>().map(|s| s.to_string());
200 let s2 = payload.downcast_ref::<String>().cloned();
201 let s = s1.or(s2).unwrap_or_else(|| "Unknown panic payload".to_string());
202
203 eprintln!("FAILURE: {}:", path.display());
204 eprintln!("Rust panic:\n{s}");
205 panic!("Test failed: {}", path.display());
206 }
207 Ok(output) => {
208 let mut expectation_path = expectations_dir.join(path.strip_prefix(&tests_dir).unwrap());
210 expectation_path.set_extension("out");
211
212 if rewrite_expectations || !expectation_path.exists() {
213 fs::write(&expectation_path, &output)
214 .unwrap_or_else(|e| panic!("Failed to write file {}: {e}.", expectation_path.display()));
215 wrote = true;
216 } else {
217 let expected = fs::read_to_string(&expectation_path)
218 .unwrap_or_else(|e| panic!("Failed to read file {}: {e}.", expectation_path.display()));
219
220 if output != expected {
221 eprintln!("FAILURE: {}:", path.display());
222 eprintln!("\ngot:\n{output}\nexpected:\n{expected}\n");
223 panic!("Test failed: {}", path.display());
224 }
225 }
226 }
227 }
228
229 if wrote {
230 println!("Wrote expectation file for test: {}", path.display());
231 }
232}