use std::process::Command;
fn ilo() -> Command {
Command::new(env!("CARGO_BIN_EXE_ilo"))
}
fn run_args(args: &[&str]) -> (bool, String, String) {
let out = ilo()
.args(args)
.output()
.unwrap_or_else(|e| panic!("failed to spawn ilo: {e}"));
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
let stderr = String::from_utf8_lossy(&out.stderr).to_string();
(out.status.success(), stdout, stderr)
}
fn write_temp_ilo(content: &str) -> (tempfile::TempDir, std::path::PathBuf) {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("test.ilo");
std::fs::write(&path, content).expect("write temp ilo");
(dir, path)
}
#[test]
fn run_verb_runs_file() {
let (_dir, path) = write_temp_ilo("main>n;42");
let (ok, stdout, stderr) = run_args(&["run", path.to_str().unwrap()]);
assert!(ok, "ilo run <file> should succeed; stderr: {stderr}");
assert!(stdout.contains("42"), "stdout: {stdout}");
}
#[test]
fn run_verb_runs_inline_code() {
let (ok, stdout, stderr) = run_args(&["run", "main>n;42"]);
assert!(ok, "ilo run <code> should succeed; stderr: {stderr}");
assert!(stdout.contains("42"), "stdout: {stdout}");
}
#[test]
fn run_verb_forwards_args() {
let (_dir, path) = write_temp_ilo("main x:n y:n>n;+x y");
let (ok, stdout, stderr) = run_args(&["run", path.to_str().unwrap(), "10", "32"]);
assert!(ok, "ilo run <file> args should succeed; stderr: {stderr}");
assert!(stdout.contains("42"), "stdout: {stdout}");
}
#[test]
fn run_verb_no_args_prints_usage() {
let (ok, _stdout, stderr) = run_args(&["run"]);
assert!(!ok, "ilo run with no args should exit non-zero");
assert!(
stderr.contains("Usage: ilo run"),
"stderr should contain usage line; got: {stderr}"
);
assert!(
!stderr.contains("ILO-P020"),
"should not surface the lex-as-source parser error; got: {stderr}"
);
}
#[test]
fn check_verb_clean_file_succeeds() {
let (_dir, path) = write_temp_ilo("add a:n b:n>n;+a b main>n;add 1 2");
let (ok, stdout, stderr) = run_args(&["check", path.to_str().unwrap()]);
assert!(
ok,
"ilo check on a clean file should succeed; stdout: {stdout}; stderr: {stderr}"
);
assert!(
stderr.is_empty(),
"check on clean file should produce no stderr; got: {stderr}"
);
assert!(
stdout.is_empty(),
"check on clean file should produce no stdout; got: {stdout}"
);
}
#[test]
fn check_verb_inline_code() {
let (ok, _stdout, stderr) = run_args(&["check", "main>n;42"]);
assert!(
ok,
"ilo check on clean inline code should succeed; stderr: {stderr}"
);
}
#[test]
fn check_verb_type_error_exits_nonzero() {
let (_dir, path) = write_temp_ilo("f>n;\"hello\"");
let (ok, _stdout, stderr) = run_args(&["check", path.to_str().unwrap()]);
assert!(
!ok,
"ilo check on a type-error file should exit non-zero; stderr: {stderr}"
);
assert!(
!stderr.is_empty(),
"check on broken file should emit diagnostics"
);
}
#[test]
fn check_verb_parse_error_does_not_crash() {
let (_dir, path) = write_temp_ilo("f a:n");
let (ok, _stdout, stderr) = run_args(&["check", path.to_str().unwrap()]);
assert!(
!ok,
"ilo check on a syntactically broken file should exit non-zero"
);
assert!(
!stderr.is_empty(),
"check should emit some diagnostic; stderr: {stderr}"
);
}
#[test]
fn check_verb_json_flag_emits_json_diagnostics() {
let (_dir, path) = write_temp_ilo("f>n;\"hello\"");
let (ok, _stdout, stderr) = run_args(&["check", path.to_str().unwrap(), "--json"]);
assert!(!ok);
let first = stderr
.lines()
.find(|l| !l.trim().is_empty())
.expect("expected at least one diagnostic line");
let parsed: serde_json::Value =
serde_json::from_str(first).expect("check --json diagnostic should be valid JSON");
assert!(
parsed.get("code").is_some() || parsed.get("message").is_some(),
"diagnostic JSON should have code/message field; got: {parsed}"
);
}
#[test]
fn check_verb_strict_elevates_t032_to_exit_failure() {
let (_dir, path) = write_temp_ilo("f>n;fmt \"x={}\" 1;42");
let (ok_relaxed, _stdout, stderr_relaxed) = run_args(&["check", path.to_str().unwrap()]);
assert!(
ok_relaxed,
"bare `ilo check` should exit 0 on a warning-only file; stderr: {stderr_relaxed}"
);
assert!(
stderr_relaxed.contains("ILO-T032"),
"warning should still be emitted on stderr; got: {stderr_relaxed}"
);
let (ok_strict, _stdout, stderr_strict) =
run_args(&["check", path.to_str().unwrap(), "--strict"]);
assert!(
!ok_strict,
"`ilo check --strict` should exit non-zero when only warnings fire; stderr: {stderr_strict}"
);
assert!(
stderr_strict.contains("ILO-T032"),
"warning should still be emitted under --strict; got: {stderr_strict}"
);
}
#[test]
fn check_verb_strict_elevates_t033_to_exit_failure() {
let (_dir, path) = write_temp_ilo("f>n;m=mmap;mset m \"k\" 1;42");
let (ok_relaxed, _stdout, stderr_relaxed) = run_args(&["check", path.to_str().unwrap()]);
assert!(
ok_relaxed,
"bare `ilo check` should exit 0 on a warning-only file; stderr: {stderr_relaxed}"
);
assert!(
stderr_relaxed.contains("ILO-T033"),
"warning should still be emitted on stderr; got: {stderr_relaxed}"
);
let (ok_strict, _stdout, stderr_strict) =
run_args(&["check", path.to_str().unwrap(), "--strict"]);
assert!(
!ok_strict,
"`ilo check --strict` should exit non-zero on T033; stderr: {stderr_strict}"
);
assert!(
stderr_strict.contains("ILO-T033"),
"warning should still be emitted under --strict; got: {stderr_strict}"
);
}
#[test]
fn check_verb_strict_json_keeps_warning_severity() {
let (_dir, path) = write_temp_ilo("f>n;fmt \"x={}\" 1;42");
let (ok, _stdout, stderr) = run_args(&["check", path.to_str().unwrap(), "--strict", "--json"]);
assert!(
!ok,
"--strict --json should exit non-zero on warning-only file; stderr: {stderr}"
);
let first = stderr
.lines()
.find(|l| !l.trim().is_empty())
.expect("expected at least one diagnostic line");
let parsed: serde_json::Value =
serde_json::from_str(first).expect("check --json diagnostic should be valid JSON");
assert_eq!(
parsed.get("severity").and_then(|v| v.as_str()),
Some("warning"),
"JSON severity must stay 'warning' under --strict; got: {parsed}"
);
assert_eq!(
parsed.get("code").and_then(|v| v.as_str()),
Some("ILO-T032"),
"expected ILO-T032 diagnostic; got: {parsed}"
);
}
#[test]
fn check_verb_strict_clean_file_succeeds() {
let (_dir, path) = write_temp_ilo("main>n;42");
let (ok, _stdout, stderr) = run_args(&["check", path.to_str().unwrap(), "--strict"]);
assert!(
ok,
"`ilo check --strict` on a clean file should exit 0; stderr: {stderr}"
);
}
#[test]
fn check_verb_strict_error_file_still_exits_nonzero() {
let (_dir, path) = write_temp_ilo("f>n;\"hello\"");
let (ok, _stdout, stderr) = run_args(&["check", path.to_str().unwrap(), "--strict"]);
assert!(
!ok,
"`ilo check --strict` should exit 1 on an error file; stderr: {stderr}"
);
}
#[test]
fn check_verb_no_args_prints_usage() {
let (ok, _stdout, stderr) = run_args(&["check"]);
assert!(!ok);
assert!(
stderr.contains("Usage: ilo check"),
"stderr should contain usage line; got: {stderr}"
);
}
#[test]
fn build_verb_produces_binary() {
let (dir, path) = write_temp_ilo("main>n;42");
let out_path = dir.path().join("test-bin");
let (ok, _stdout, stderr) = run_args(&[
"build",
path.to_str().unwrap(),
"-o",
out_path.to_str().unwrap(),
]);
if !ok && stderr.contains("requires the 'cranelift' feature") {
return;
}
assert!(
ok,
"ilo build <file> -o <out> should succeed; stderr: {stderr}"
);
assert!(out_path.exists(), "binary should exist at {out_path:?}");
}
#[test]
fn build_verb_no_args_prints_usage() {
let (ok, _stdout, stderr) = run_args(&["build"]);
assert!(!ok);
assert!(
stderr.contains("Usage: ilo build"),
"stderr should contain usage line; got: {stderr}"
);
}
#[test]
fn positional_file_still_runs() {
let (_dir, path) = write_temp_ilo("main>n;7");
let (ok, stdout, stderr) = run_args(&[path.to_str().unwrap()]);
assert!(ok, "bare positional run should succeed; stderr: {stderr}");
assert!(stdout.contains("7"));
}
#[test]
fn positional_file_with_args_still_runs() {
let (_dir, path) = write_temp_ilo("main x:n y:n>n;+x y");
let (ok, stdout, stderr) = run_args(&[path.to_str().unwrap(), "3", "4"]);
assert!(
ok,
"positional file + args should succeed; stderr: {stderr}"
);
assert!(stdout.contains("7"));
}
#[test]
fn positional_file_with_func_still_runs() {
let (_dir, path) = write_temp_ilo("dbl x:n>n;+*x 2 0 main>n;dbl 21");
let (ok, stdout, stderr) = run_args(&[path.to_str().unwrap(), "dbl", "21"]);
assert!(
ok,
"positional file + func should succeed; stderr: {stderr}"
);
assert!(stdout.contains("42"));
}
#[test]
fn positional_compile_still_works() {
let (dir, path) = write_temp_ilo("main>n;42");
let out_path = dir.path().join("test-bin-compile");
let (ok, _stdout, stderr) = run_args(&[
"compile",
path.to_str().unwrap(),
"-o",
out_path.to_str().unwrap(),
]);
if !ok && stderr.contains("requires the 'cranelift' feature") {
return;
}
assert!(ok, "compile should still succeed; stderr: {stderr}");
assert!(out_path.exists());
}