Acord/core/src/interp/tests/loader.rs

181 lines
5.6 KiB
Rust

use std::fs;
use std::path::PathBuf;
use crate::interp::*;
#[allow(unused_imports)]
use super::helpers::*;
fn tmp_dir(tag: &str) -> PathBuf {
let p = std::env::temp_dir().join(format!("acord-loader-{}-{}", tag, std::process::id()));
let _ = fs::remove_dir_all(&p);
fs::create_dir_all(&p).unwrap();
p
}
#[test]
fn load_single_cord_file() {
let dir = tmp_dir("single");
fs::write(dir.join("mathy.cord"), "let pi_squared = 9.8696\nfn double(x) { x * 2 }").unwrap();
let mut i = Interpreter::new();
i.add_module_path("mathy", dir.join("mathy.cord"));
i.exec("use mathy").unwrap();
assert!(matches!(i.get_var("pi_squared"), Some(Value::Number(n)) if (n - 9.8696).abs() < 1e-9));
let v = i.eval("double(21)").unwrap();
assert!(matches!(v, Value::Number(n) if n == 42.0));
fs::remove_dir_all(&dir).ok();
}
#[test]
fn load_dir_with_mod_cord() {
let dir = tmp_dir("withmod");
let lib = dir.join("lib");
fs::create_dir_all(&lib).unwrap();
fs::write(lib.join("mod.cord"), "fn shout(s) { s + \"!\" }").unwrap();
fs::write(lib.join("other.cord"), "// ignored when mod.cord present").unwrap();
let mut i = Interpreter::new();
i.add_module_path("lib", &lib);
i.exec("use lib").unwrap();
let v = i.eval("shout(\"hi\")").unwrap();
assert!(matches!(v, Value::Str(ref s) if s == "hi!"));
fs::remove_dir_all(&dir).ok();
}
#[test]
fn load_dir_concats_cord_files_alphabetically() {
let dir = tmp_dir("concat");
let lib = dir.join("lib");
fs::create_dir_all(&lib).unwrap();
fs::write(lib.join("a.cord"), "let a_val = 1").unwrap();
fs::write(lib.join("b.cord"), "let b_val = 2").unwrap();
let mut i = Interpreter::new();
i.add_module_path("lib", &lib);
i.exec("use lib").unwrap();
assert!(matches!(i.get_var("a_val"), Some(Value::Number(n)) if n == 1.0));
assert!(matches!(i.get_var("b_val"), Some(Value::Number(n)) if n == 2.0));
fs::remove_dir_all(&dir).ok();
}
#[test]
fn use_item_imports_only_named_binding() {
let dir = tmp_dir("filter");
fs::write(dir.join("utils.cord"), "let alpha = 1\nlet beta = 2\nlet gamma = 3").unwrap();
let mut i = Interpreter::new();
i.add_module_path("utils", dir.join("utils.cord"));
i.exec("use utils::beta").unwrap();
assert!(i.get_var("alpha").is_none());
assert!(matches!(i.get_var("beta"), Some(Value::Number(n)) if n == 2.0));
assert!(i.get_var("gamma").is_none());
fs::remove_dir_all(&dir).ok();
}
#[test]
fn use_wildcard_imports_everything() {
let dir = tmp_dir("wildcard");
fs::write(dir.join("utils.cord"), "let alpha = 1\nlet beta = 2").unwrap();
let mut i = Interpreter::new();
i.add_module_path("utils", dir.join("utils.cord"));
i.exec("use utils::*").unwrap();
assert!(matches!(i.get_var("alpha"), Some(Value::Number(n)) if n == 1.0));
assert!(matches!(i.get_var("beta"), Some(Value::Number(n)) if n == 2.0));
fs::remove_dir_all(&dir).ok();
}
#[test]
fn sub_namespace_override_loads_distinct_source() {
let dir = tmp_dir("subns");
fs::write(dir.join("main.cord"), "let from_main = true").unwrap();
fs::write(dir.join("sub.cord"), "let from_sub = true").unwrap();
let mut i = Interpreter::new();
i.add_module_path("lib", dir.join("main.cord"));
i.add_module_subpath("lib", "extra", dir.join("sub.cord"));
i.exec("use lib::extra").unwrap();
assert!(matches!(i.get_var("from_sub"), Some(Value::Bool(true))));
assert!(i.get_var("from_main").is_none());
fs::remove_dir_all(&dir).ok();
}
#[test]
fn unregistered_module_no_ops() {
let mut i = Interpreter::new();
let result = i.exec("use spice");
assert!(result.is_ok());
assert!(i.spice_enabled());
}
#[test]
fn unknown_item_errors() {
let dir = tmp_dir("missing");
fs::write(dir.join("mod.cord"), "let present = 1").unwrap();
let mut i = Interpreter::new();
i.add_module_path("mod", dir.join("mod.cord"));
let err = i.exec("use mod::absent").unwrap_err();
assert!(err.contains("absent"));
fs::remove_dir_all(&dir).ok();
}
#[test]
fn exports_carry_methods_and_traits() {
let mut a = Interpreter::new();
a.exec("trait Named { fn show(self) }").unwrap();
a.exec("impl Named for Thing { fn show(self) { return self.name } }").unwrap();
a.exec("impl Thing { fn new(name) { return {__type: \"Thing\", name: name} } }").unwrap();
let exports = a.exports();
assert!(exports.traits.contains_key("Named"));
assert!(exports.methods.contains_key(&("Thing".to_string(), "show".to_string())));
assert!(exports.methods.contains_key(&("Thing".to_string(), "new".to_string())));
let mut b = Interpreter::new();
b.import_all(&exports);
let v = b.eval("Thing::new(\"box\").show()").unwrap();
assert!(matches!(v, Value::Str(ref s) if s == "box"));
}
#[test]
fn load_module_hooks_inherit_into_subinterp() {
use std::rc::Rc;
struct ConstHook;
impl InterpreterHook for ConstHook {
fn name(&self) -> &str { "const_hook" }
fn try_call(&self, _i: &mut Interpreter, name: &str, _args: &[Expr], _depth: u32)
-> Option<Result<Value, String>>
{
if name == "magic" { Some(Ok(Value::Number(99.0))) } else { None }
}
}
let dir = tmp_dir("hookinherit");
fs::write(dir.join("lib.cord"), "let secret = magic()").unwrap();
let mut i = Interpreter::new();
i.register_hook(Rc::new(ConstHook)).unwrap();
i.add_module_path("lib", dir.join("lib.cord"));
i.exec("use lib").unwrap();
assert!(matches!(i.get_var("secret"), Some(Value::Number(n)) if n == 99.0));
fs::remove_dir_all(&dir).ok();
}