use spaik::r8vm::R8VM;
use spaik::nkgc::SPV;
use spaik::compile::Builtin;
use colored::*;
use std::{fs, fmt};
use std::fs::File;
use std::io::prelude::*;
use std::error::Error;
use std::process::exit;
enum TestResult {
Pass,
Fail {
expect: SPV,
got: SPV
}
}
impl TestResult {
pub fn new(res: SPV) -> Option<TestResult> {
Some(match res.bt_op() {
Some(Builtin::KwPass) => TestResult::Pass,
Some(Builtin::KwFail) => {
let args = res.args().collect::<Vec<_>>();
match &args[..] {
[expect, got] => TestResult::Fail { expect: expect.clone(),
got: got.clone() },
_ => return None
}
}
_ => return None
})
}
}
#[derive(Debug)]
enum TestError {
WrongResult {
expect: String,
got: String,
},
RuntimeError {
origin: spaik::error::Error,
}
}
impl fmt::Display for TestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
TestError::RuntimeError { origin } => write!(f, "{origin}"),
TestError::WrongResult { expect, got } => {
write!(f, "{expect} != {got}")
}
}
}
}
impl Error for TestError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
TestError::RuntimeError { origin } => Some(origin),
_ => None
}
}
fn cause(&self) -> Option<&dyn Error> {
self.source()
}
}
fn run_tests() -> Result<Vec<TestError>, Box<dyn Error>> {
let mut vm = R8VM::new();
let tests_path = "./tests";
let stdlib = vm.sym_id("stdlib");
let test = vm.sym_id("test");
match vm.load(stdlib).and_then(|_| vm.load(test)) {
Err(e) => {
println!("{}", e.to_string(&vm));
return Err(e.into());
}
_ => ()
}
let paths = fs::read_dir(tests_path)?.map(|p| p.map(|p| p.path()))
.collect::<Result<Vec<_>, _>>()?;
for path in paths {
let mut file = File::open(&path)?;
let mut test_src = String::new();
file.read_to_string(&mut test_src)?;
match vm.eval(&test_src) {
Ok(_) => println!("Loaded {}", path.display()),
Err(e) => {
println!("Error when loading {}", path.display());
println!("{}", e.to_string(&vm));
return Err(e.into());
},
}
}
vm.minimize();
let test_fn_prefix = "tests/";
let test_fns = vm.get_funcs_with_prefix(test_fn_prefix);
let mut err_results = vec![];
println!("Running tests ...");
for func in test_fns.iter() {
let name = vm.sym_name(*func)
.chars()
.skip(test_fn_prefix.len())
.collect::<String>();
match vm.call_spv(*func, ()) {
Ok(res) => match TestResult::new(res) {
Some(TestResult::Pass) =>
println!(" - {} [{}]", name.bold(), "✓".green().bold()),
Some(TestResult::Fail { expect, got }) => {
let expect = expect.to_string();
let got = got.to_string();
println!(" - {} [{}]", name.red().bold(), "✘".red().bold());
println!(" Expected:");
for line in expect.lines() {
println!(" {}", line);
}
println!(" Got:");
for line in got.to_string().lines() {
println!(" {}", line);
}
err_results.push(TestError::WrongResult { expect, got });
}
_ => ()
}
Err(e) => {
println!(" - {} [{}]", name.red().bold(), "✘".red().bold());
for line in e.to_string(&vm).lines() {
println!(" {}", line)
}
err_results.push(TestError::RuntimeError { origin: e })
},
}
}
Ok(err_results)
}
fn main() {
exit(match run_tests() {
Ok(_) => 0,
Err(_) => 1
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lisp_tests() {
let results = run_tests().unwrap();
for res in results {
panic!("{res}");
}
}
}