Files
dexpr/tests/data_driven_tests.rs
2026-04-06 02:51:42 +03:00

145 lines
4.3 KiB
Rust

use dexpr::{ast::value::Value, compiler::Compiler, parser, vm::VM};
use indexmap::IndexMap;
use rust_decimal::Decimal;
use smol_str::SmolStr;
use std::collections::HashMap;
use std::rc::Rc;
use std::str::FromStr;
#[derive(serde::Deserialize)]
struct TestCase {
name: String,
code: String,
#[serde(default)]
globals: HashMap<String, ValueDef>,
expected: ValueDef,
}
#[derive(serde::Deserialize)]
struct ValueDef {
#[serde(rename = "type")]
typ: String,
#[serde(default)]
value: Option<serde_json::Value>,
}
fn value_def_to_value(def: &ValueDef) -> Value {
match def.typ.as_str() {
"null" => Value::Null,
"number" => {
let s = def.value.as_ref().unwrap().as_str().unwrap();
Value::Number(Decimal::from_str(s).unwrap())
}
"string" => {
let s = def.value.as_ref().unwrap().as_str().unwrap();
Value::String(s.into())
}
"boolean" => {
let b = def.value.as_ref().unwrap().as_bool().unwrap();
Value::Boolean(b)
}
"object" => {
fn json_obj_to_value(obj: &serde_json::Map<String, serde_json::Value>) -> Value {
let mut map = IndexMap::new();
for (k, v) in obj {
let val = match v {
serde_json::Value::String(s) => {
if let Ok(d) = Decimal::from_str(s) {
Value::Number(d)
} else {
Value::String(SmolStr::from(s.as_str()))
}
}
serde_json::Value::Bool(b) => Value::Boolean(*b),
serde_json::Value::Object(nested) => json_obj_to_value(nested),
_ => Value::String(SmolStr::from(v.to_string())),
};
map.insert(SmolStr::from(k.as_str()), val);
}
Value::Object(Rc::new(map))
}
let obj = def.value.as_ref().unwrap().as_object().unwrap();
json_obj_to_value(obj)
}
other => panic!("Unknown type: {other}"),
}
}
#[test]
fn test_all_cases() {
let json = include_str!("test_cases.json");
let cases: Vec<TestCase> =
serde_json::from_str(json).expect("Failed to parse test_cases.json");
let mut failures = Vec::new();
let total = cases.len();
for case in &cases {
let ast = match parser::program(&case.code) {
Ok(ast) => ast,
Err(e) => {
failures.push(format!(
"FAIL: {}\n code: {}\n error: parse failed: {e}",
case.name,
case.code.replace('\n', "\\n"),
));
continue;
}
};
let mut compiler = Compiler::new();
let bytecode = match compiler.compile(ast) {
Ok(bc) => bc,
Err(e) => {
failures.push(format!(
"FAIL: {}\n code: {}\n error: compile failed: {e}",
case.name,
case.code.replace('\n', "\\n"),
));
continue;
}
};
let mut vm = VM::new(&bytecode);
for (name, def) in &case.globals {
vm.set_global(name, value_def_to_value(def));
}
let result = match vm.execute() {
Ok(v) => v,
Err(e) => {
failures.push(format!(
"FAIL: {}\n code: {}\n error: execute failed: {e}",
case.name,
case.code.replace('\n', "\\n"),
));
continue;
}
};
let expected = value_def_to_value(&case.expected);
if result != expected {
failures.push(format!(
"FAIL: {}\n code: {}\n expected: {:?}\n got: {:?}",
case.name,
case.code.replace('\n', "\\n"),
expected,
result
));
}
}
if !failures.is_empty() {
panic!(
"\n{} / {} test cases failed:\n\n{}\n",
failures.len(),
total,
failures.join("\n\n")
);
}
eprintln!("All {total} test cases passed.");
}