1use std::env::current_dir;
10use std::env::set_current_dir;
11use std::ffi::OsString;
12use std::fs::create_dir_all;
13use std::io;
14use std::io::Cursor;
15use std::io::Write;
16use std::io::stdout;
17use std::path::Path;
18use std::path::PathBuf;
19use std::sync::atomic::AtomicBool;
20use std::sync::atomic::Ordering;
21use std::sync::Arc;
22use std::sync::RwLock;
23use crate::env::*;
24use crate::error::*;
25use crate::fs::*;
26use crate::interp::*;
27use crate::mod_node::*;
28use crate::parser::*;
29use crate::utils::*;
30use crate::value::*;
31
32pub struct TestResult
38{
39 error_pair: Option<(Error, Vec<(Option<Value>, Pos)>)>,
40 stdout: Option<Arc<RwLock<Cursor<Vec<u8>>>>>,
41 stderr: Option<Arc<RwLock<Cursor<Vec<u8>>>>>,
42}
43
44impl TestResult
45{
46 pub fn new(error_pair: Option<(Error, Vec<(Option<Value>, Pos)>)>, stdout: Option<Arc<RwLock<Cursor<Vec<u8>>>>>, stderr: Option<Arc<RwLock<Cursor<Vec<u8>>>>>) -> TestResult
48 { TestResult { error_pair, stdout, stderr } }
49
50 pub fn is_success(&self) -> bool
52 { self.error_pair.is_none() }
53
54 pub fn is_failure(&self) -> bool
56 { self.error_pair.is_some() }
57
58 pub fn error_pair(&self) -> Option<&(Error, Vec<(Option<Value>, Pos)>)>
61 {
62 match &self.error_pair {
63 Some(error_pair) => Some(error_pair),
64 None => None,
65 }
66 }
67
68 pub fn stdout(&self) -> Option<&Arc<RwLock<Cursor<Vec<u8>>>>>
71 {
72 match &self.stdout {
73 Some(stdout) => Some(stdout),
74 None => None,
75 }
76 }
77
78 pub fn stderr(&self) -> Option<&Arc<RwLock<Cursor<Vec<u8>>>>>
81 {
82 match &self.stderr {
83 Some(stderr) => Some(stderr),
84 None => None,
85 }
86 }
87
88 pub fn has_stdout_data(&self) -> Result<bool>
90 {
91 match &self.stdout {
92 Some(stdout) => {
93 let stdout_g = rw_lock_read(stdout)?;
94 Ok(!stdout_g.get_ref().is_empty())
95 },
96 None => Ok(false),
97 }
98 }
99
100 pub fn has_stderr_data(&self) -> Result<bool>
102 {
103 match &self.stderr {
104 Some(stderr) => {
105 let stderr_g = rw_lock_read(stderr)?;
106 Ok(!stderr_g.get_ref().is_empty())
107 },
108 None => Ok(false),
109 }
110 }
111}
112
113pub trait Print
117{
118 fn print_loading(&self, is_done: bool);
120
121 fn print_running_test(&self, idents: &Vec<String>, ident: &String, is_done: bool, is_ok: bool);
126
127 fn print_empty_line(&self);
129
130 fn print_successes(&self);
132
133 fn print_failures(&self);
135
136 fn print_test_result(&self, idents: &Vec<String>, ident: &String, test_result: &TestResult) -> Result<()>;
138
139 fn print_test_counts(&self, passed_test_count: usize, failed_test_count: usize);
141
142 fn print_lf_for_error(&self);
144}
145
146#[derive(Copy, Clone, Debug)]
150pub struct EmptyPrinter;
151
152impl EmptyPrinter
153{
154 pub fn new() -> Self
156 { EmptyPrinter }
157}
158
159impl Print for EmptyPrinter
160{
161 fn print_loading(&self, _is_done: bool)
162 {}
163
164 fn print_running_test(&self, _idents: &Vec<String>, _ident: &String, _is_done: bool, _is_ok: bool)
165 {}
166
167 fn print_empty_line(&self)
168 {}
169
170 fn print_successes(&self)
171 {}
172
173 fn print_failures(&self)
174 {}
175
176 fn print_test_result(&self, _idents: &Vec<String>, _ident: &String, _test_result: &TestResult) -> Result<()>
177 { Ok(()) }
178
179 fn print_test_counts(&self, _passed_test_count: usize, _failed_test_count: usize)
180 {}
181
182 fn print_lf_for_error(&self)
183 {}
184}
185
186fn idents_and_ident_to_string(idents: &[String], ident: &String) -> String
187{
188 let mut s = String::new();
189 let mut is_first = true;
190 for ident2 in idents {
191 if !is_first {
192 s.push_str("::");
193 }
194 s.push_str(ident2.as_str());
195 is_first = false;
196 }
197 s.push_str("::");
198 s.push_str(ident.as_str());
199 s
200}
201
202#[derive(Debug)]
206pub struct StdPrinter
207{
208 has_lf_for_error: AtomicBool,
209}
210
211impl StdPrinter
212{
213 pub fn new() -> Self
215 { StdPrinter { has_lf_for_error: AtomicBool::new(false), } }
216}
217
218impl Print for StdPrinter
219{
220 fn print_loading(&self, is_done: bool)
221 {
222 if is_done {
223 println!(" done");
224 self.has_lf_for_error.store(false, Ordering::SeqCst);
225 } else {
226 print!("Loading tests ...");
227 let _res = stdout().flush();
228 self.has_lf_for_error.store(true, Ordering::SeqCst);
229 }
230 }
231
232 fn print_running_test(&self, idents: &Vec<String>, ident: &String, is_done: bool, is_ok: bool)
233 {
234 if is_done {
235 if is_ok {
236 println!(" ok");
237 } else {
238 println!(" FAILED");
239 }
240 self.has_lf_for_error.store(false, Ordering::SeqCst);
241 } else {
242 print!("Test {} ...", idents_and_ident_to_string(idents, ident));
243 let _res = stdout().flush();
244 self.has_lf_for_error.store(true, Ordering::SeqCst);
245 }
246 }
247
248 fn print_empty_line(&self)
249 { println!(""); }
250
251 fn print_successes(&self)
252 {
253 println!("Successes:");
254 println!("");
255 }
256
257 fn print_failures(&self)
258 {
259 println!("Failures:");
260 println!("");
261 }
262
263 fn print_test_result(&self, idents: &Vec<String>, ident: &String, test_result: &TestResult) -> Result<()>
264 {
265 match &test_result.stdout {
266 Some(stdout) => {
267 let stdout_g = rw_lock_read(stdout)?;
268 if !stdout_g.get_ref().is_empty() {
269 println!("---- {} stdout ----", idents_and_ident_to_string(idents, ident));
270 let _res = io::stdout().write_all(stdout_g.get_ref().as_slice());
271 }
272 },
273 None => (),
274 }
275 match &test_result.stderr {
276 Some(stderr) => {
277 let stderr_g = rw_lock_read(stderr)?;
278 if !stderr_g.get_ref().is_empty() {
279 println!("---- {} stderr ----", idents_and_ident_to_string(idents, ident));
280 let _res = io::stdout().write_all(stderr_g.get_ref().as_slice());
281 }
282 },
283 None => (),
284 }
285 match &test_result.error_pair {
286 Some((err, stack_trace)) => {
287 println!("Test {} failed", idents_and_ident_to_string(idents, ident));
288 println!("{}", err);
289 for (fun_value, pos) in stack_trace {
290 match fun_value {
291 Some(fun_value) => println!(" at {} ({}: {}.{})", fun_value, pos.path, pos.line, pos.column),
292 None => println!(" at {}: {}.{}", pos.path, pos.line, pos.column),
293 }
294 }
295 },
296 None => (),
297 }
298 println!("");
299 Ok(())
300 }
301
302 fn print_test_counts(&self, passed_test_count: usize, failed_test_count: usize)
303 {
304 if failed_test_count == 0 {
305 println!("Test result: ok. {} passed; {} failed", passed_test_count, failed_test_count);
306 } else {
307 println!("Test result: FAILED. {} passed; {} failed", passed_test_count, failed_test_count);
308 }
309 }
310
311 fn print_lf_for_error(&self)
312 {
313 if self.has_lf_for_error.swap(false, Ordering::SeqCst) {
314 println!("");
315 }
316 }
317}
318
319fn create_and_change_dir<P: AsRef<Path>>(path: P) -> io::Result<PathBuf>
320{
321 let saved_current_dir = current_dir()?;
322 create_dir_all(path.as_ref())?;
323 set_current_dir(path.as_ref())?;
324 Ok(saved_current_dir)
325}
326
327fn change_and_recusively_remove_dir<P: AsRef<Path>, Q: AsRef<Path>>(path: P, saved_current_dir: Q) -> io::Result<()>
328{
329 set_current_dir(saved_current_dir)?;
330 recursively_remove(path, true)?;
331 Ok(())
332}
333
334pub struct Tester
340{
341 root_mod: Arc<RwLock<ModNode<Value, ()>>>,
342 shared_env: Arc<RwLock<SharedEnv>>,
343 stack_trace: Vec<(Option<Value>, Pos)>,
344 test_results: Vec<((Vec<String>, String), TestResult)>,
345 printer: Arc<dyn Print + Send + Sync>,
346 has_stdout_cursors: bool,
347 has_stderr_cursors: bool,
348}
349
350impl Tester
351{
352 pub fn new(root_mod: Arc<RwLock<ModNode<Value, ()>>>, lib_path: OsString, doc_path: OsString, printer: Arc<dyn Print + Send + Sync>, are_stdout_cursors: bool, are_stderr_cursors: bool) -> Self
358 {
359 Tester {
360 root_mod,
361 shared_env: Arc::new(RwLock::new(SharedEnv::new(lib_path, doc_path, Vec::new()))),
362 stack_trace: Vec::new(),
363 test_results: Vec::new(),
364 printer,
365 has_stdout_cursors: are_stdout_cursors,
366 has_stderr_cursors: are_stderr_cursors,
367 }
368 }
369
370 pub fn root_mod(&self) -> &Arc<RwLock<ModNode<Value, ()>>>
372 { &self.root_mod }
373
374 pub fn shared_env(&self) -> &Arc<RwLock<SharedEnv>>
376 { &self.shared_env }
377
378 pub fn stack_trace(&self) -> &[(Option<Value>, Pos)]
380 { self.stack_trace.as_slice() }
381
382 pub fn test_results(&self) -> &[((Vec<String>, String), TestResult)]
384 { self.test_results.as_slice() }
385
386 pub fn printer(&self) -> &Arc<dyn Print + Send + Sync>
388 { &self.printer }
389
390 pub fn has_stdout_cursors(&self) -> bool
392 { self.has_stdout_cursors }
393
394 pub fn has_stderr_cursors(&self) -> bool
396 { self.has_stderr_cursors }
397
398 pub fn load(&mut self) -> Result<()>
400 {
401 self.printer.print_loading(false);
402 let test_paths = match paths_in_dir("tests", Some(2)) {
403 Ok(tmp_paths) => tmp_paths,
404 Err(err) => return Err(Error::Io(err)),
405 };
406 for test_path in test_paths {
407 let mut script_dir = PathBuf::from("tests");
408 script_dir.push(test_path.as_path());
409 let mut path = script_dir.clone();
410 path.push("tests.un");
411 let mut domain_path_buf = test_path.clone();
412 let domain = if domain_path_buf.components().count() >= 2 {
413 domain_path_buf.pop();
414 match domain_path_buf.to_str() {
415 Some(tmp_domain) => Some(String::from(tmp_domain)),
416 None => return Err(Error::Tester(String::from("test path component contains invalid UTF-8 character"))),
417 }
418 } else {
419 None
420 };
421 let tree = parse(path)?;
422 let mut env = Env::new_with_script_dir_and_domain_and_shared_env(self.root_mod.clone(), script_dir.clone(), domain, self.shared_env.clone());
423 let mut interp = Interp::new();
424 env.set_stdin(Input::Null);
425 env.set_stdout(Output::Null);
426 env.set_stderr(Output::Null);
427 match interp.interpret(&mut env, &tree) {
428 Ok(()) => (),
429 Err(err) => {
430 self.stack_trace = interp.stack_trace().to_vec();
431 return Err(err);
432 },
433 }
434 }
435 self.printer.print_loading(true);
436 Ok(())
437 }
438
439 pub fn run_test(&mut self, idents: &Vec<String>, ident: &String) -> Result<()>
441 {
442 self.printer.print_running_test(idents, ident, false, false);
443 let is_test_suite = {
444 let shared_env_g = rw_lock_read(&self.shared_env)?;
445 shared_env_g.has_test_suite(idents)
446 };
447 let mut is_ok = false;
448 if is_test_suite {
449 match ModNode::mod_from(&self.root_mod, idents.as_slice(), false)? {
450 Some(mod1) => {
451 let fun_value = {
452 let mod_g = rw_lock_read(&mod1)?;
453 match mod_g.var(ident) {
454 Some(fun_value) => fun_value.clone(),
455 None => return Err(Error::Tester(String::from("undefined test function"))),
456 }
457 };
458 let mut work_test_dir = PathBuf::from("work");
459 work_test_dir.push("test");
460 let saved_current_dir = match create_and_change_dir(work_test_dir.as_path()) {
461 Ok(tmp_saved_current_dir) => tmp_saved_current_dir,
462 Err(err) => return Err(Error::Io(err)),
463 };
464 let mut env = Env::new_with_script_dir_and_domain_and_shared_env(self.root_mod.clone(), PathBuf::from("."), None, self.shared_env.clone());
465 env.set_stdin(Input::Null);
466 if self.has_stdout_cursors {
467 env.set_stdout(Output::Cursor(Arc::new(RwLock::new(Cursor::new(Vec::new())))));
468 }
469 if self.has_stderr_cursors {
470 env.set_stderr(Output::Cursor(Arc::new(RwLock::new(Cursor::new(Vec::new())))));
471 }
472 let mut interp = Interp::new();
473 let error_pair = match fun_value.apply(&mut interp, &mut env, &[]) {
474 Ok(_) => {
475 is_ok = true;
476 None
477 },
478 Err(err) => Some((err, interp.stack_trace().to_vec())),
479 };
480 let stdout = match env.stdout() {
481 Output::Cursor(cursor) => Some(cursor.clone()),
482 _ => None,
483 };
484 let stderr = match env.stderr() {
485 Output::Cursor(cursor) => Some(cursor.clone()),
486 _ => None,
487 };
488 self.test_results.push(((idents.clone(), ident.clone()), TestResult::new(error_pair, stdout, stderr)));
489 match change_and_recusively_remove_dir(work_test_dir, saved_current_dir) {
490 Ok(tmp_saved_current_dir) => tmp_saved_current_dir,
491 Err(err) => return Err(Error::Io(err)),
492 }
493 },
494 None => return Err(Error::Tester(String::from("undefined test module"))),
495 }
496 } else {
497 return Err(Error::Tester(String::from("module isn't test suite")));
498 }
499 self.printer.print_running_test(idents, ident, true, is_ok);
500 Ok(())
501 }
502
503 pub fn run_tests_in_test_suite(&mut self, idents: &Vec<String>) -> Result<()>
505 {
506 let is_test_suite = {
507 let shared_env_g = rw_lock_read(&self.shared_env)?;
508 shared_env_g.has_test_suite(idents)
509 };
510 if is_test_suite {
511 match ModNode::mod_from(&self.root_mod, idents.as_slice(), false)? {
512 Some(mod1) => {
513 let mut fun_idents: Vec<String> = {
514 let mod_g = rw_lock_read(&mod1)?;
515 mod_g.vars().keys().map(|id| id.clone()).collect()
516 };
517 fun_idents.sort();
518 for fun_ident in &fun_idents {
519 self.run_test(idents, fun_ident)?;
520 }
521 },
522 None => return Err(Error::Tester(String::from("undefined test module"))),
523 }
524 } else {
525 return Err(Error::Tester(String::from("module isn't test suite")));
526 }
527 Ok(())
528 }
529
530 pub fn run_all_tests(&mut self) -> Result<()>
532 {
533 let mut test_suites: Vec<Vec<String>> = {
534 let shared_env_g = rw_lock_read(&self.shared_env)?;
535 shared_env_g.test_suites().iter().map(|ids| ids.clone()).collect()
536 };
537 test_suites.sort();
538 for test_suite in &test_suites {
539 self.run_tests_in_test_suite(test_suite)?;
540 }
541 Ok(())
542 }
543
544 pub fn print_empty_line(&self)
546 { self.printer.print_empty_line() }
547
548 pub fn print_successes(&self) -> Result<()>
550 {
551 let mut count = 0usize;
552 for (_, test_result) in &self.test_results {
553 if test_result.is_success() && (test_result.has_stdout_data()? || test_result.has_stderr_data()?) {
554 count += 1;
555 }
556 }
557 if count > 0 {
558 self.printer.print_successes();
559 for ((idents, ident), test_result) in &self.test_results {
560 if test_result.is_success() && (test_result.has_stdout_data()? || test_result.has_stderr_data()?) {
561 self.printer.print_test_result(idents, ident, test_result)?;
562 }
563 }
564 }
565 Ok(())
566 }
567
568 pub fn print_failures(&self) -> Result<()>
570 {
571 let mut count = 0usize;
572 for (_, test_result) in &self.test_results {
573 if !test_result.is_success() {
574 count += 1;
575 }
576 }
577 if count > 0 {
578 self.printer.print_failures();
579 for ((idents, ident), test_result) in &self.test_results {
580 if !test_result.is_success() {
581 self.printer.print_test_result(idents, ident, test_result)?;
582 }
583 }
584 }
585 Ok(())
586 }
587
588 pub fn print_test_counts(&self)
590 {
591 let mut passed_test_count = 0usize;
592 let mut failed_test_count = 0usize;
593 for (_, test_result) in &self.test_results {
594 if test_result.is_success() {
595 passed_test_count += 1;
596 } else {
597 failed_test_count += 1;
598 }
599 }
600 self.printer.print_test_counts(passed_test_count, failed_test_count);
601 }
602}
603
604#[cfg(test)]
605mod tests;