use std::process::Command;
fn ilo() -> Command {
Command::new(env!("CARGO_BIN_EXE_ilo"))
}
fn write_src(name: &str, src: &str) -> std::path::PathBuf {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
let mut path = std::env::temp_dir();
path.push(format!("ilo_cbind_{name}_{}_{n}.ilo", std::process::id()));
std::fs::write(&path, src).expect("write src");
path
}
fn run_ok(src: &str, entry: &str, args: &[&str]) -> String {
let path = write_src(entry, src);
let mut cmd = ilo();
cmd.arg(&path).arg("--run-tree").arg(entry);
for a in args {
cmd.arg(a);
}
let out = cmd.output().expect("failed to run ilo");
let _ = std::fs::remove_file(&path);
assert!(
out.status.success(),
"ilo failed for `{src}`: stderr={}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).trim().to_string()
}
fn run_err(src: &str, entry: &str) -> String {
let path = write_src(entry, src);
let out = ilo()
.arg(&path)
.arg("--run-tree")
.arg(entry)
.output()
.expect("failed to run ilo");
let _ = std::fs::remove_file(&path);
assert!(
!out.status.success(),
"expected failure but ilo succeeded for `{src}`"
);
let mut s = String::from_utf8_lossy(&out.stderr).into_owned();
s.push_str(&String::from_utf8_lossy(&out.stdout));
s
}
const SRT_BY_LOOKUP: &str = "\
pri sym:t m:M t n>n;r=mget m sym;?r{n v:v;_:99999}
top pri-map:M t n syms:L t>L t;srt pri pri-map syms";
#[test]
fn srt_with_external_lookup() {
let src = format!(
"{SRT_BY_LOOKUP}\nmain>L t;m=mset mmap \"a\" 2;m=mset m \"b\" 3;m=mset m \"c\" 1;\
top m [\"a\",\"b\",\"c\"]"
);
assert_eq!(run_ok(&src, "main", &[]), "[c, a, b]");
}
const MAP_WITH_LOOKUP: &str = "\
look sym:t m:M t n>n;r=mget m sym;?r{n v:v;_:0}
prices pm:M t n syms:L t>L n;map look pm syms";
#[test]
fn map_with_lookup() {
let src = format!(
"{MAP_WITH_LOOKUP}\nmain>L n;m=mset mmap \"a\" 10;m=mset m \"b\" 20;\
prices m [\"a\",\"b\",\"a\"]"
);
assert_eq!(run_ok(&src, "main", &[]), "[10, 20, 10]");
}
const FLT_WITH_THRESHOLD: &str = "\
big x:n thr:n>b;>=x thr
above t:n xs:L n>L n;flt big t xs";
#[test]
fn flt_with_threshold() {
assert_eq!(
run_ok(
&format!("{FLT_WITH_THRESHOLD}\nmain>L n;above 4 [1,5,3,8,2]"),
"main",
&[]
),
"[5, 8]"
);
}
const FLD_WITH_ACCUM: &str = "\
add-scaled acc:n x:n k:n>n;+acc *x k
total k:n xs:L n>n;fld add-scaled k xs 0";
#[test]
fn fld_with_external_accumulator() {
assert_eq!(
run_ok(
&format!("{FLD_WITH_ACCUM}\nmain>n;total 10 [1,2,3]"),
"main",
&[]
),
"60"
);
}
const SRT_2ARG: &str = "abs1 x:n>n;abs x\nf xs:L n>L n;srt abs1 xs";
#[test]
fn srt_2arg_unchanged() {
assert_eq!(run_ok(SRT_2ARG, "f", &["[-3,1,-5,2]"]), "[1, 2, -3, -5]");
}
#[test]
fn srt_3arg_rejects_1arg_fn() {
let src = "abs1 x:n>n;abs x\nf c:n xs:L n>L n;srt abs1 c xs";
let err = run_err(src, "f");
assert!(
err.contains("srt") && (err.contains("2 args") || err.contains("closure-bind")),
"expected verifier error about srt fn arity, got: {err}"
);
}
#[test]
fn map_3arg_rejects_1arg_fn() {
let src = "abs1 x:n>n;abs x\nf c:n xs:L n>L n;map abs1 c xs";
let err = run_err(src, "f");
assert!(
err.contains("map") && (err.contains("2 args") || err.contains("closure-bind")),
"expected verifier error about map fn arity, got: {err}"
);
}
#[test]
fn fld_4arg_rejects_2arg_fn() {
let src = "add a:n b:n>n;+a b\nf c:n xs:L n>n;fld add c xs 0";
let err = run_err(src, "f");
assert!(
err.contains("fld") && (err.contains("3 args") || err.contains("closure-bind")),
"expected verifier error about fld fn arity, got: {err}"
);
}