use std::process::Command;
fn ilo() -> Command {
Command::new(env!("CARGO_BIN_EXE_ilo"))
}
fn run(engine: &str, src: &str, entry: &str, args: &[&str]) -> String {
let mut cmd = ilo();
cmd.arg(src).arg(engine).arg(entry);
for a in args {
cmd.arg(a);
}
let out = cmd.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_err(engine: &str, src: &str, entry: &str, args: &[&str]) -> String {
let mut cmd = ilo();
cmd.arg(src).arg(engine).arg(entry);
for a in args {
cmd.arg(a);
}
let out = cmd.output().expect("failed to run ilo");
assert!(
!out.status.success(),
"expected failure for `{src}` on {engine}, stdout={}",
String::from_utf8_lossy(&out.stdout)
);
String::from_utf8_lossy(&out.stderr).to_string()
}
const MISSING_FIELD: &str = "f j:t>R t t;r=jpar! j;~fmt \"{}\" r.?missing";
fn check_missing(engine: &str) {
assert_eq!(
run(engine, MISSING_FIELD, "f", &[r#"{"present":1}"#]),
"nil",
"engine={engine}"
);
}
#[test]
fn safe_field_missing_tree() {
check_missing("--run-tree");
}
#[test]
fn safe_field_missing_vm() {
check_missing("--run-vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn safe_field_missing_cranelift() {
check_missing("--run-cranelift");
}
const PRESENT_FIELD: &str = "f j:t>R t t;r=jpar! j;~fmt \"{}\" r.?present";
fn check_present(engine: &str) {
assert_eq!(
run(engine, PRESENT_FIELD, "f", &[r#"{"present":42}"#]),
"42",
"engine={engine}"
);
}
#[test]
fn safe_field_present_tree() {
check_present("--run-tree");
}
#[test]
fn safe_field_present_vm() {
check_present("--run-vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn safe_field_present_cranelift() {
check_present("--run-cranelift");
}
const CHAINED_MISSING: &str = "f j:t>R t t;r=jpar! j;~fmt \"{}\" r.?outer.?inner";
fn check_chained_missing(engine: &str) {
assert_eq!(
run(engine, CHAINED_MISSING, "f", &[r#"{"other":1}"#]),
"nil",
"engine={engine}"
);
}
#[test]
fn safe_field_chained_missing_tree() {
check_chained_missing("--run-tree");
}
#[test]
fn safe_field_chained_missing_vm() {
check_chained_missing("--run-vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn safe_field_chained_missing_cranelift() {
check_chained_missing("--run-cranelift");
}
const CHAINED_PRESENT: &str = "f j:t>R t t;r=jpar! j;~fmt \"{}\" r.?outer.?inner";
fn check_chained_present(engine: &str) {
assert_eq!(
run(
engine,
CHAINED_PRESENT,
"f",
&[r#"{"outer":{"inner":"x"}}"#]
),
"x",
"engine={engine}"
);
}
#[test]
fn safe_field_chained_present_tree() {
check_chained_present("--run-tree");
}
#[test]
fn safe_field_chained_present_vm() {
check_chained_present("--run-vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn safe_field_chained_present_cranelift() {
check_chained_present("--run-cranelift");
}
const NIL_OBJECT: &str = "f j:t>R t t;r=jpar! j;v=r.?missing;~fmt \"{}\" v.?anything";
fn check_nil_object(engine: &str) {
assert_eq!(
run(engine, NIL_OBJECT, "f", &[r#"{"a":1}"#]),
"nil",
"engine={engine}"
);
}
#[test]
fn safe_field_nil_object_tree() {
check_nil_object("--run-tree");
}
#[test]
fn safe_field_nil_object_vm() {
check_nil_object("--run-vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn safe_field_nil_object_cranelift() {
check_nil_object("--run-cranelift");
}
const STRICT_MISSING: &str = "f j:t>R t t;r=jpar! j;vb=r.missing;~vb";
#[test]
fn strict_field_missing_still_errors_tree() {
let err = run_err("--run-tree", STRICT_MISSING, "f", &[r#"{"a":1}"#]);
assert!(
err.contains("ILO-R005") && err.contains("missing"),
"stderr: {err}"
);
}
#[test]
fn strict_field_missing_still_errors_vm() {
let err = run_err("--run-vm", STRICT_MISSING, "f", &[r#"{"a":1}"#]);
assert!(
err.contains("ILO-R005") && err.contains("missing"),
"stderr: {err}"
);
}
#[test]
fn safe_field_on_typed_list_caught_by_verifier() {
let out = ilo()
.args(["f>n;xs=[1,2,3];xs.?name??99", "--run-tree", "f"])
.output()
.expect("failed to run ilo");
assert!(
!out.status.success(),
"verifier should reject .?field on a typed list"
);
let err = String::from_utf8_lossy(&out.stderr);
assert!(err.contains("ILO-T018"), "expected ILO-T018, stderr: {err}");
}
#[test]
fn safe_field_typo_on_static_record_still_errors() {
let src = "type pt{x:n;y:n}\nf>n;p=pt x:1 y:2;p.?z";
let out = ilo()
.args([src, "--run-tree", "f"])
.output()
.expect("failed to run ilo");
assert!(
!out.status.success(),
"expected verify error for typo on static record; stdout={} stderr={}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
let err = String::from_utf8_lossy(&out.stderr);
assert!(
err.contains("ILO-T019") && err.contains("'z'"),
"stderr: {err}"
);
}