use maned::Error;
use std::path::{Path, PathBuf};
fn corpus(name: &str) -> PathBuf {
let dir = std::env::var("MANED_CONFORMANCE_DIR")
.unwrap_or_else(|_| "/Users/fabianodicheti/Documents/maned/capi-freeze/conformance".into());
PathBuf::from(dir).join(name)
}
struct Scratch {
dir: PathBuf,
}
impl Scratch {
fn new(tag: &str) -> Scratch {
let dir = std::env::temp_dir().join(format!(
"maned-rs-test-{tag}-{}-{:?}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&dir).unwrap();
Scratch { dir }
}
fn script(&self, name: &str, source: &str) -> PathBuf {
let p = self.dir.join(name);
std::fs::write(&p, source).unwrap();
p
}
fn path(&self) -> &Path {
&self.dir
}
}
impl Drop for Scratch {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.dir);
}
}
const ROUTED: &str = r#"mnd::quantmax=1000;
mnd::quantmin=-1000;
mnd::quantres=0;
device::rex { host = "127.0.0.1"; port = 1; user = "u"; pass = env("MANED_RS_TEST_PASS"); }
calc::lambda_flow f(x) @device(rex) {
x x add r =
} return r;
"#;
#[test]
fn into_shared_refuses_a_routed_script_and_hands_it_back() {
std::env::set_var("MANED_RS_TEST_PASS", "unused");
let scratch = Scratch::new("routed");
let path = scratch.script("routed.mnd", ROUTED);
let script = maned::builder(&path)
.synthesize_outputs(true)
.open()
.expect("a routed script still loads — mnd_open plans, it does not dial");
assert!(script.is_routed());
let refusal = script
.into_shared()
.expect_err("a routed script must not become a SharedScript");
let message = refusal.to_string();
assert!(message.contains("routes to a device"), "{message}");
assert!(message.contains("single-connection"), "{message}");
assert!(message.contains(&path.display().to_string()), "{message}");
let recovered = refusal.into_script();
assert!(recovered.is_routed());
assert_eq!(recovered.path(), path);
drop(recovered);
}
#[test]
fn a_local_script_is_not_routed_and_shares_freely() {
let script = maned::load(corpus("01_local_matmul.mnd")).unwrap();
assert!(!script.is_routed());
let shared = script.into_shared().expect("not routed");
assert!(!shared.is_routed());
assert_eq!(
shared
.run(&serde_json::json!({"h0": [[3,1],[2,4]]}))
.unwrap()
.status(),
200
);
let mut script = shared.into_script();
assert_eq!(
script
.run(&serde_json::json!({"h0": [[1,0],[0,1]]}))
.unwrap()
.status(),
200
);
}
#[test]
fn synthesize_outputs_is_the_difference_between_loading_and_not() {
let path = corpus("09_synthesized.mnd");
let refused = maned::load(&path).expect_err("no out::/profile::/frame:: without the flag");
match &refused {
Error::Load { path: p, message } => {
assert_eq!(p, &path);
assert!(message.contains("declares no out::"), "{message}");
assert!(message.contains("working directory"), "{message}");
}
other => panic!("{other:?}"),
}
let mut script = maned::builder(&path)
.synthesize_outputs(true)
.open()
.unwrap();
let r = script
.run(&serde_json::json!({"x": [[3,-1],[2,4]]}))
.unwrap();
assert_eq!(r.body(), br#"{"outputs":{"y":[[3,0],[2,4]]},"notes":[]}"#);
assert_eq!(r.tensor("y").unwrap().data, vec![3, 0, 2, 4]);
}
#[test]
fn no_vm_fallback_reaches_mnd_open() {
let mut script = maned::builder(corpus("09_synthesized.mnd"))
.synthesize_outputs(true)
.no_vm_fallback(true)
.open()
.unwrap();
assert_eq!(
script
.run(&serde_json::json!({"x": [[1,1],[1,1]]}))
.unwrap()
.status(),
200
);
}
#[test]
fn tensor_reads_the_run_json_outputs_object() {
let mut script = maned::load(corpus("04_multi_output.mnd")).unwrap();
let r = script
.run(&serde_json::json!({"h0": [[3,1],[2,4]]}))
.unwrap();
let a = r.tensor("a").unwrap();
assert_eq!(a.shape, vec![2, 2]);
assert_eq!(a.data, vec![7, 9, 2, 4]);
let t = r.tensor("t").unwrap();
assert_eq!(t.data, vec![7, 2, 9, 4]);
let missing = r.tensor("nope").unwrap_err().to_string();
assert!(missing.contains("outputs are: a, t"), "{missing}");
#[derive(serde::Deserialize)]
struct Run {
outputs: std::collections::BTreeMap<String, Vec<Vec<i64>>>,
}
let typed: Run = r.deserialize().unwrap();
assert_eq!(typed.outputs["a"], vec![vec![7, 9], vec![2, 4]]);
assert_eq!(r.json().unwrap()["outputs"]["a"][0][1], 9);
assert_eq!(r.notes(), Vec::<String>::new().as_slice());
}
#[test]
fn decimal_returns_the_exact_printed_text() {
let scratch = Scratch::new("decimal");
let path = scratch.script(
"dec.mnd",
"mnd::quantmax=30000;\nmnd::quantmin=-30000;\nmnd::quantres=2;\n\n\
calc::lambda_flow f(x) {\n x x add y =\n} return y;\n",
);
let mut script = maned::builder(&path)
.synthesize_outputs(true)
.open()
.unwrap();
let r = script
.request(
"POST",
"/run.json",
None,
br#"{"x": [[1.5, 2.25],[0.5, 1.0]]}"#,
)
.unwrap();
assert_eq!(r.status(), 200);
assert_eq!(
r.body_str().unwrap(),
r#"{"outputs":{"y":[[3.00,4.50],[1.00,2.00]]},"notes":[]}"#
);
assert_eq!(r.decimal("y").unwrap(), "[[3.00,4.50],[1.00,2.00]]");
let refused = r.tensor("y").unwrap_err().to_string();
assert!(refused.contains("not an integer"), "{refused}");
assert!(refused.contains("decimal(\"y\")"), "{refused}");
assert!(r
.decimal("zz")
.unwrap_err()
.to_string()
.contains("no output named 'zz'"));
}
#[test]
fn body_is_borrowed_bytes_of_body_len() {
let mut script = maned::load(corpus("04_multi_output.mnd")).unwrap();
let boundary = "----maned-rs-test";
let body = format!(
"--{boundary}\r\nContent-Disposition: form-data; name=\"h0\"\r\n\r\n3,1\r\n2,4\r\n--{boundary}--\r\n"
);
let r = script
.request(
"POST",
"/run",
Some(&format!("multipart/form-data; boundary={boundary}")),
body.as_bytes(),
)
.unwrap();
assert_eq!(r.status(), 200);
assert!(
r.content_type().starts_with("multipart/"),
"{}",
r.content_type()
);
let bytes = r.body();
let text = String::from_utf8_lossy(bytes);
assert!(text.contains("7,9"), "{text}");
assert!(
text.contains("name=\"t\"") || text.contains("name=t"),
"{text}"
);
assert!(bytes.len() > 100, "{} bytes", bytes.len());
assert_eq!(r.body().as_ptr(), bytes.as_ptr());
}
#[test]
fn find_searches_manedpath_then_search_path_then_cwd_then_mnd() {
let scratch = Scratch::new("find");
scratch.script(
"findme.mnd",
"mnd::quantmax=1000;\nmnd::quantmin=-1000;\nmnd::quantres=0;\n\n\
calc::lambda_flow f(x) {\n x x add y =\n} return y;\n",
);
let miss = maned::find("findme").unwrap_err().to_string();
assert!(miss.contains("no script named 'findme'"), "{miss}");
assert!(miss.contains("search order"), "{miss}");
maned::add_search_path(scratch.path());
assert!(maned::search_path().contains(&scratch.path().to_path_buf()));
let found = maned::builder(scratch.path().join("findme.mnd"))
.synthesize_outputs(true)
.open();
assert!(found.is_ok());
let s = maned::find("findme");
match s {
Err(Error::Load { path, message }) => {
assert_eq!(path, scratch.path().join("findme.mnd"));
assert!(message.contains("declares no out::"), "{message}");
}
other => panic!("{:?}", other.map(|_| ())),
}
maned::set_search_path(Vec::<PathBuf>::new());
assert!(maned::search_path().is_empty());
}
#[test]
fn load_of_a_missing_file_names_the_path() {
let e = maned::load("/definitely/not/here.mnd").unwrap_err();
match e {
Error::Load { path, message } => {
assert_eq!(path, PathBuf::from("/definitely/not/here.mnd"));
assert!(!message.is_empty());
}
other => panic!("{other:?}"),
}
}
#[test]
fn kernel_threads_and_version_are_process_wide_reads_and_writes() {
maned::set_kernel_threads(2);
let mut script = maned::load(corpus("01_local_matmul.mnd")).unwrap();
let a = script
.run(&serde_json::json!({"h0": [[3,1],[2,4]]}))
.unwrap();
maned::set_kernel_threads(1);
let b = script
.run(&serde_json::json!({"h0": [[3,1],[2,4]]}))
.unwrap();
assert_eq!(a.body(), b.body());
assert!(maned::abi_version() >= 1);
assert!(!maned::version().is_empty());
}