1use crate::r8vm::R8VM;
2use crate::SPV;
3use crate::Builtin;
4use crate::stylize::Stylize;
5use std::fmt;
6use std::error::Error;
7use std::fs;
8
9enum TestResult {
10 Pass,
11 Fail {
12 expect: SPV,
13 got: SPV
14 }
15}
16
17impl TestResult {
18 pub fn new(res: SPV, vm: &mut R8VM) -> Option<TestResult> {
19 Some(match res.bt_op(vm) {
20 Some(Builtin::KwPass) => TestResult::Pass,
21 Some(Builtin::KwFail) => {
22 let args = res.args_vec(vm);
23 match &args[..] {
24 [expect, got] => TestResult::Fail { expect: expect.clone(),
25 got: got.clone() },
26 _ => return None
27 }
28 }
29 _ => return None
30 })
31 }
32}
33
34#[derive(Debug)]
35pub enum TestError {
36 WrongResult {
37 expect: String,
38 got: String,
39 },
40 RuntimeError {
41 origin: crate::error::Error,
42 }
43}
44
45impl fmt::Display for TestError {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
47 match self {
48 TestError::RuntimeError { origin } => write!(f, "{origin}"),
49 TestError::WrongResult { expect, got } => {
50 write!(f, "{expect} != {got}")
51 }
52 }
53 }
54}
55
56impl Error for TestError {
57 fn source(&self) -> Option<&(dyn Error + 'static)> {
58 match self {
59 TestError::RuntimeError { origin } => Some(origin),
60 _ => None
61 }
62 }
63
64 fn cause(&self) -> Option<&dyn Error> {
65 self.source()
66 }
67}
68
69pub fn run_tests() -> Result<Vec<TestError>, Box<dyn Error>> {
71 let mut vm = R8VM::new();
72 let tests_path = "./tests";
73 let test = vm.sym_id("test");
74 vm.eval(r#"(push sys/load-path "./lisp")"#).unwrap();
75
76 if let Err(e) = vm.load(test) {
77 vmprintln!(vm, "{e}");
78 return Err(e.into());
79 }
80
81 let paths = fs::read_dir(tests_path)?.map(|p| p.map(|p| p.path()))
82 .collect::<Result<Vec<_>, _>>()?;
83 for path in paths {
84 match vm.read_compile_from(&path) {
85 Ok(_) => (),
86 Err(e) => {
87 vmprintln!(vm, "Error when loading {}", path.display());
88 vmprintln!(vm, "{e}");
89 return Err(e.to_string().into());
90 },
91 }
92 }
93
94 vm.minimize();
95
96 let test_fn_prefix = "tests/";
97 let test_fns = vm.get_funcs_with_prefix(test_fn_prefix);
98 let mut err_results = vec![];
99
100 for func in test_fns.iter() {
101 let name = func.as_ref()
102 .chars()
103 .skip(test_fn_prefix.len())
104 .collect::<String>();
105 match vm.call_spv(*func, ()) {
106 Ok(res) => match TestResult::new(res, &mut vm) {
107 Some(TestResult::Pass) =>
108 vmprintln!(vm, "test {} ... [{}]",
109 name.style_info(),
110 "✓".style_success()),
111 Some(TestResult::Fail { expect, got }) => {
112 let expect = expect.to_string(&vm);
113 let got = got.to_string(&vm);
114
115 vmprintln!(vm, "test {} ... [{}]",
116 name.style_error(),
117 "✘".style_error());
118 vmprintln!(vm, " Expected:");
119 for line in expect.lines() {
120 vmprintln!(vm, " {}", line);
121 }
122 vmprintln!(vm, " Got:");
123 for line in got.to_string().lines() {
124 vmprintln!(vm, " {}", line)
125 }
126
127 err_results.push(TestError::WrongResult { expect, got });
128 }
129 _ => ()
130 }
131 Err(e) => {
132 vmprintln!(vm, "test {} [{}]",
133 name.style_error(),
134 "✘".style_error());
135 for line in e.to_string().lines() {
136 vmprintln!(vm, " {}", line);
137 }
138 err_results.push(TestError::RuntimeError { origin: e })
139 },
140 }
141 }
142
143 Ok(err_results)
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn lisp_tests() {
152 let results = run_tests().unwrap();
153 for res in results {
154 panic!("{res}");
155 }
156 }
157}