#![cfg(feature = "golden")]
use std::path::PathBuf;
use std::process::Command;
fn ilo() -> Command {
Command::new(env!("CARGO_BIN_EXE_ilo"))
}
fn conformance_dir() -> PathBuf {
let manifest = env!("CARGO_MANIFEST_DIR");
PathBuf::from(manifest)
.join("conformance")
.join("diagnostics")
}
fn explain_json(code: &str) -> String {
let out = ilo()
.args(["explain", code, "--json"])
.output()
.unwrap_or_else(|e| panic!("failed to spawn ilo explain {code}: {e}"));
assert!(
out.status.success(),
"ilo explain {code} --json failed: {}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8(out.stdout)
.unwrap_or_else(|_| panic!("non-UTF8 output from ilo explain {code}"))
}
fn normalise_json(raw: &str, label: &str) -> serde_json::Value {
serde_json::from_str(raw)
.unwrap_or_else(|e| panic!("invalid JSON from {label}: {e}\nRaw output:\n{raw}"))
}
fn golden_path(code: &str) -> PathBuf {
conformance_dir().join(format!("{code}.expected.json"))
}
fn assert_golden(code: &str) {
let actual_raw = explain_json(code);
let actual: serde_json::Value = normalise_json(&actual_raw, &format!("ilo explain {code}"));
let bless = std::env::args().any(|a| a == "--bless")
|| std::env::var("ILO_GOLDEN_BLESS").as_deref() == Ok("1");
let path = golden_path(code);
if bless {
let pretty = serde_json::to_string_pretty(&actual)
.unwrap_or_else(|e| panic!("failed to serialise {code}: {e}"));
std::fs::write(&path, format!("{pretty}\n"))
.unwrap_or_else(|e| panic!("failed to write golden {}: {e}", path.display()));
println!("blessed {}", path.display());
return;
}
let expected_raw = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("missing golden file {}: {e}\nRun `cargo test --features golden -- --bless` to create it", path.display()));
let expected: serde_json::Value = normalise_json(&expected_raw, &format!("{}", path.display()));
if actual != expected {
let actual_pretty = serde_json::to_string_pretty(&actual).unwrap();
let expected_pretty = serde_json::to_string_pretty(&expected).unwrap();
let diff_lines: Vec<String> = expected_pretty
.lines()
.zip(actual_pretty.lines())
.enumerate()
.filter(|(_, (e, a))| e != a)
.map(|(i, (e, a))| format!(" line {}: expected {e:?} got {a:?}", i + 1))
.collect();
panic!(
"golden mismatch for {code}:\n{}\n\nRun `cargo test --features golden -- --bless` to update the golden file.",
diff_lines.join("\n")
);
}
}
#[test]
fn golden_ilo_l001() {
assert_golden("ILO-L001");
}
#[test]
fn golden_ilo_l002() {
assert_golden("ILO-L002");
}
#[test]
fn golden_ilo_l003() {
assert_golden("ILO-L003");
}
#[test]
fn golden_ilo_p001() {
assert_golden("ILO-P001");
}
#[test]
fn golden_ilo_p002() {
assert_golden("ILO-P002");
}
#[test]
fn golden_ilo_p003() {
assert_golden("ILO-P003");
}
#[test]
fn golden_ilo_p004() {
assert_golden("ILO-P004");
}
#[test]
fn golden_ilo_p005() {
assert_golden("ILO-P005");
}
#[test]
fn golden_ilo_t001() {
assert_golden("ILO-T001");
}
#[test]
fn golden_ilo_t002() {
assert_golden("ILO-T002");
}
#[test]
fn golden_ilo_t003() {
assert_golden("ILO-T003");
}
#[test]
fn golden_ilo_t004() {
assert_golden("ILO-T004");
}
#[test]
fn golden_ilo_t005() {
assert_golden("ILO-T005");
}
#[test]
fn golden_ilo_t006() {
assert_golden("ILO-T006");
}
#[test]
fn golden_ilo_t007() {
assert_golden("ILO-T007");
}
#[test]
fn golden_ilo_t008() {
assert_golden("ILO-T008");
}
#[test]
fn golden_ilo_t009() {
assert_golden("ILO-T009");
}
#[test]
fn golden_ilo_r001() {
assert_golden("ILO-R001");
}
#[test]
fn golden_ilo_r003() {
assert_golden("ILO-R003");
}
#[test]
fn golden_ilo_r005() {
assert_golden("ILO-R005");
}
#[test]
fn provenance_surface_is_valid() {
let manifest = env!("CARGO_MANIFEST_DIR");
let path = PathBuf::from(manifest)
.join("conformance")
.join("provenance-surface.json");
let raw = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("missing {}: {e}", path.display()));
let v: serde_json::Value = serde_json::from_str(&raw)
.unwrap_or_else(|e| panic!("invalid JSON in provenance-surface.json: {e}"));
assert_eq!(
v["schemaVersion"], 1,
"provenance-surface.json must have schemaVersion: 1"
);
let features = v["features"].as_array().expect("features must be an array");
assert!(!features.is_empty(), "features array must not be empty");
for (i, f) in features.iter().enumerate() {
assert!(
f["feature"].is_string(),
"features[{i}].feature must be a string"
);
assert!(
f["compiler_path"].is_string(),
"features[{i}].compiler_path must be a string"
);
assert!(
f["function"].is_string(),
"features[{i}].function must be a string"
);
assert!(
f["error_codes"].is_array(),
"features[{i}].error_codes must be an array"
);
}
}
#[test]
fn all_golden_files_have_required_keys() {
let dir = conformance_dir();
let entries: Vec<_> = std::fs::read_dir(&dir)
.unwrap_or_else(|e| panic!("cannot read {}: {e}", dir.display()))
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().and_then(|x| x.to_str()) == Some("json"))
.collect();
assert!(
!entries.is_empty(),
"no golden files found in conformance/diagnostics/"
);
for entry in entries {
let path = entry.path();
let raw = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
let v: serde_json::Value = serde_json::from_str(&raw)
.unwrap_or_else(|e| panic!("invalid JSON in {}: {e}", path.display()));
let name = path.display().to_string();
assert_eq!(v["schemaVersion"], 1, "{name}: schemaVersion must be 1");
assert!(v["code"].is_string(), "{name}: code must be a string");
assert!(v["phase"].is_string(), "{name}: phase must be a string");
assert!(v["short"].is_string(), "{name}: short must be a string");
assert!(v["long"].is_string(), "{name}: long must be a string");
}
}