spaik/
lisp_test.rs

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
69/// Run SPAIK tests from the `./tests` directory and report any errors.
70pub 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}