use std::process::Command;
use std::sync::atomic::{AtomicU64, Ordering};
fn ilo() -> Command {
Command::new(env!("CARGO_BIN_EXE_ilo"))
}
fn run_file(engine: &str, src: &str, entry: &str) -> String {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let seq = COUNTER.fetch_add(1, Ordering::SeqCst);
let path = std::env::temp_dir().join(format!(
"ilo_default_on_err_{}_{}.ilo",
std::process::id(),
seq
));
std::fs::write(&path, src).unwrap();
let out = ilo()
.args([path.to_str().unwrap(), engine, entry])
.output()
.expect("failed to run ilo");
assert!(
out.status.success(),
"ilo {engine} failed for `{src}`: stderr={}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).trim().to_string()
}
fn run_file_expect_err(engine: &str, src: &str) -> String {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let seq = COUNTER.fetch_add(1, Ordering::SeqCst);
let path = std::env::temp_dir().join(format!(
"ilo_default_on_err_err_{}_{}.ilo",
std::process::id(),
seq
));
std::fs::write(&path, src).unwrap();
let out = ilo()
.args([path.to_str().unwrap(), engine])
.output()
.expect("failed to run ilo");
assert!(
!out.status.success(),
"expected verify/runtime failure but ilo {engine} succeeded for `{src}`"
);
String::from_utf8_lossy(&out.stderr).to_string()
}
const DOE_OK_NUM: &str = r#"f>n;default-on-err (num "42") 0"#;
const DOE_ERR_FALLBACK: &str = r#"f>n;default-on-err (num "bad") 0"#;
const DOE_OK_TEXT: &str = r#"f>t;r=~"hello";default-on-err r "fallback""#;
const DOE_ERR_TEXT: &str = r#"f>t;r=^"oops";default-on-err r "fallback""#;
const DOE_CHAIN: &str = r#"f>n;a=default-on-err (num "10") 0;b=default-on-err (num "bad") 0;+ a b"#;
fn check_all(engine: &str) {
assert_eq!(
run_file(engine, DOE_OK_NUM, "f"),
"42",
"default-on-err ok num {engine}"
);
assert_eq!(
run_file(engine, DOE_ERR_FALLBACK, "f"),
"0",
"default-on-err err fallback {engine}"
);
assert_eq!(
run_file(engine, DOE_OK_TEXT, "f"),
"hello",
"default-on-err ok text {engine}"
);
assert_eq!(
run_file(engine, DOE_ERR_TEXT, "f"),
"fallback",
"default-on-err err text {engine}"
);
assert_eq!(
run_file(engine, DOE_CHAIN, "f"),
"10",
"default-on-err chain {engine}"
);
}
#[test]
fn default_on_err_vm() {
check_all("--vm");
}
#[cfg(feature = "cranelift")]
#[test]
fn default_on_err_cranelift() {
check_all("--jit");
}
#[test]
fn default_on_err_wrong_default_type_rejected() {
let src = r#"f>n;default-on-err (num "42") "wrong""#;
let stderr = run_file_expect_err("--vm", src);
assert!(
stderr.contains("ILO-T042"),
"expected ILO-T042 for wrong default type, got: {stderr}"
);
assert!(
stderr.contains("default-on-err"),
"expected message to mention default-on-err, got: {stderr}"
);
}
#[test]
fn default_on_err_non_result_arg_rejected() {
let src = r#"f>n;default-on-err 42 0"#;
let stderr = run_file_expect_err("--vm", src);
assert!(
stderr.contains("ILO-T040"),
"expected ILO-T040 for non-result first arg, got: {stderr}"
);
assert!(
!stderr.contains("use `?? v d` for Optional"),
"hint should not steer to ?? when first arg is plain n: {stderr}"
);
}
#[test]
fn default_on_err_optional_arg_hints_at_nil_coalesce() {
let src = "mk x:n>O n;>=x 1{x}\nf>n;v=mk 0;default-on-err v 99";
let stderr = run_file_expect_err("--vm", src);
assert!(
stderr.contains("ILO-T040"),
"expected ILO-T040 for Optional first arg, got: {stderr}"
);
assert!(
stderr.contains("?? v d"),
"expected hint to steer at `?? v d` for Optional, got: {stderr}"
);
}
#[test]
fn nil_coalesce_on_result_emits_ilo_t041() {
let src = r#"f>n;s="42";??(num s)0"#;
let stderr = run_file_expect_err("--vm", src);
assert!(
stderr.contains("ILO-T041"),
"expected ILO-T041 for ?? on Result, got: {stderr}"
);
assert!(
stderr.contains("default-on-err"),
"expected diagnostic to suggest default-on-err, got: {stderr}"
);
assert!(
stderr.contains("~v:v;^_:"),
"expected diagnostic to suggest `?<r>{{~v:v;^_:<d>}}` rewrite, got: {stderr}"
);
}
#[test]
fn nil_coalesce_on_optional_still_works() {
let src = "mk x:n>n;>=x 1{x}\nf>n;v=mk 0;v??99";
assert_eq!(run_file("--vm", src, "f"), "99");
}
#[test]
fn nil_coalesce_on_unknown_skips_ilo_t041() {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let seq = COUNTER.fetch_add(1, Ordering::SeqCst);
let path = std::env::temp_dir().join(format!(
"ilo_default_on_err_unknown_{}_{}.ilo",
std::process::id(),
seq
));
std::fs::write(&path, "g x:a>a;x??0\nf>n;g 7").unwrap();
let out = ilo()
.args([path.to_str().unwrap(), "--vm", "f"])
.output()
.expect("failed to run ilo");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
!stderr.contains("ILO-T041"),
"ILO-T041 should not fire when lhs type is Unknown: {stderr}"
);
}