use std::process::Command;
fn ilo() -> Command {
Command::new(env!("CARGO_BIN_EXE_ilo"))
}
fn bench_json(source: &str, func: &str, args: &[&str]) -> String {
let mut cmd = ilo();
cmd.arg(source).arg("--bench").arg(func);
for a in args {
cmd.arg(a);
}
cmd.arg("--json");
let out = cmd.output().expect("spawn ilo");
assert!(
out.status.success(),
"ilo --bench --json exited non-zero: stderr={}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).to_string()
}
#[test]
fn bench_json_emits_engine_field_per_engine() {
let stdout = bench_json("add a:n b:n>n;+a b", "add", &["1", "2"]);
let lines: Vec<&str> = stdout.lines().filter(|l| !l.is_empty()).collect();
assert!(
!lines.is_empty(),
"expected JSON envelopes on stdout, got nothing. full stdout: {stdout}"
);
for line in &lines {
assert!(
line.contains("\"schemaVersion\":1"),
"missing schemaVersion in bench JSON line: {line}"
);
assert!(
line.starts_with('{') && line.ends_with('}'),
"bench JSON line not a single-line object: {line}"
);
assert!(
line.contains("\"engine\":\""),
"bench JSON line missing top-level engine field: {line}"
);
}
}
#[test]
fn bench_json_covers_tree_vm_jit() {
let stdout = bench_json("add a:n b:n>n;+a b", "add", &["1", "2"]);
let engines: Vec<String> = stdout
.lines()
.filter(|l| !l.is_empty())
.map(|line| {
let key = "\"engine\":\"";
let start = line.find(key).expect("engine field present") + key.len();
let rest = &line[start..];
let end = rest.find('"').expect("engine field terminator");
rest[..end].to_string()
})
.collect();
assert!(
engines.iter().any(|e| e == "tree"),
"expected an `engine: tree` record in bench JSON output. saw: {engines:?}"
);
assert!(
engines.iter().any(|e| e == "vm"),
"expected an `engine: vm` record in bench JSON output. saw: {engines:?}"
);
if cfg!(feature = "cranelift") {
assert!(
engines.iter().any(|e| e == "jit"),
"expected an `engine: jit` record (cranelift feature on). saw: {engines:?}"
);
}
let vm_count = engines.iter().filter(|e| e.as_str() == "vm").count();
assert_eq!(
vm_count, 2,
"expected exactly two `engine: vm` records (fresh + reusable). saw: {engines:?}"
);
let stdout_lower = stdout.to_lowercase();
assert!(
stdout_lower.contains("\"variant\":\"fresh\""),
"expected `variant: fresh` on one of the vm records. stdout: {stdout}"
);
assert!(
stdout_lower.contains("\"variant\":\"reusable\""),
"expected `variant: reusable` on one of the vm records. stdout: {stdout}"
);
}
#[test]
fn bench_json_records_carry_timing_fields() {
let stdout = bench_json("add a:n b:n>n;+a b", "add", &["1", "2"]);
for line in stdout.lines().filter(|l| !l.is_empty()) {
assert!(
line.contains("\"iterations\":"),
"bench JSON line missing iterations: {line}"
);
assert!(
line.contains("\"totalMs\":"),
"bench JSON line missing totalMs: {line}"
);
assert!(
line.contains("\"perCallNs\":"),
"bench JSON line missing perCallNs: {line}"
);
assert!(
line.contains("\"result\":\"3\""),
"bench JSON line missing/wrong result: {line}"
);
}
}
#[test]
fn bench_text_mode_unchanged() {
let out = ilo()
.arg("add a:n b:n>n;+a b")
.arg("--bench")
.arg("add")
.arg("1")
.arg("2")
.arg("--text")
.output()
.expect("spawn ilo");
assert!(out.status.success(), "ilo --bench --text failed");
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("Rust interpreter"),
"text mode missing Rust interpreter heading: {stdout}"
);
assert!(
stdout.contains("Register VM"),
"text mode missing Register VM heading: {stdout}"
);
assert!(
!stdout.contains("\"schemaVersion\""),
"text mode unexpectedly emitted JSON envelope: {stdout}"
);
}