use std::process::Command;
fn ilo() -> Command {
Command::new(env!("CARGO_BIN_EXE_ilo"))
}
fn run_err(engine: &str, src: &str) -> String {
let out = ilo()
.args([src, engine, "f"])
.output()
.expect("failed to run ilo");
assert!(
!out.status.success(),
"engine={engine}: expected runtime error for `{src}`, stdout={}",
String::from_utf8_lossy(&out.stdout)
);
String::from_utf8_lossy(&out.stderr).into_owned()
}
fn extract_span(stderr: &str) -> Option<(u32, u32)> {
if stderr.contains("\"labels\":[]") {
return None;
}
let start_key = "\"start\":";
let end_key = "\"end\":";
let s_pos = stderr.find(start_key)?;
let after_start = &stderr[s_pos + start_key.len()..];
let start: u32 = after_start
.split(|c: char| !c.is_ascii_digit())
.next()?
.parse()
.ok()?;
let e_pos = stderr.find(end_key)?;
let after_end = &stderr[e_pos + end_key.len()..];
let end: u32 = after_end
.split(|c: char| !c.is_ascii_digit())
.next()?
.parse()
.ok()?;
Some((start, end))
}
#[cfg(feature = "cranelift")]
fn assert_span_parity(src: &str) {
let vm_err = run_err("--run-vm", src);
let cl_err = run_err("--run-cranelift", src);
let vm_span = extract_span(&vm_err)
.unwrap_or_else(|| panic!("VM stderr had no labels for `{src}`: {vm_err}"));
let cl_span = extract_span(&cl_err).unwrap_or_else(|| {
panic!(
"cranelift stderr had no labels for `{src}` (span did not survive JIT helper): {cl_err}"
)
});
assert_eq!(
cl_span, vm_span,
"engine span divergence for `{src}`: cranelift={cl_span:?} vs vm={vm_span:?}\nvm stderr={vm_err}\ncl stderr={cl_err}"
);
}
#[test]
#[cfg(feature = "cranelift")]
fn hd_empty_list_span_matches_vm() {
assert_span_parity("f>n;hd []");
}
#[test]
#[cfg(feature = "cranelift")]
fn hd_empty_text_span_matches_vm() {
assert_span_parity("f>t;hd \"\"");
}
#[test]
#[cfg(feature = "cranelift")]
fn tl_empty_list_span_matches_vm() {
assert_span_parity("f>L n;tl []");
}
#[test]
#[cfg(feature = "cranelift")]
fn tl_empty_text_span_matches_vm() {
assert_span_parity("f>t;tl \"\"");
}
#[test]
#[cfg(feature = "cranelift")]
fn at_oob_positive_list_span_matches_vm() {
assert_span_parity("f>n;at [1,2,3] 99");
}
#[test]
#[cfg(feature = "cranelift")]
fn at_oob_negative_list_span_matches_vm() {
assert_span_parity("f>n;at [1,2,3] -99");
}
#[test]
#[cfg(feature = "cranelift")]
fn at_oob_text_span_matches_vm() {
assert_span_parity("f>t;at \"hi\" 99");
}
#[test]
#[cfg(feature = "cranelift")]
fn at_fractional_index_span_matches_vm() {
assert_span_parity("f>n;at [1,2,3] 1.5");
}
#[test]
#[cfg(feature = "cranelift")]
fn cranelift_hd_error_carries_some_span() {
let stderr = run_err("--run-cranelift", "f>n;hd []");
assert!(
!stderr.contains("\"labels\":[]"),
"cranelift hd error must carry a source span, got: {stderr}"
);
let (start, end) = extract_span(&stderr).expect("expected start/end in stderr");
assert!(
end > start,
"span end must be > start, got start={start} end={end}"
);
}