use std::process::Command;
fn synth() -> &'static str {
env!("CARGO_BIN_EXE_synth")
}
const FIXTURE_WAT: &str = r#"(module
(func (export "mix") (param i32 i32) (result i32)
local.get 0
i32.const 255
i32.and
local.get 1
i32.const 3
i32.shl
i32.add
i32.const 65536
i32.sub
i32.const 1
i32.add))
"#;
struct Artifacts {
dir: std::path::PathBuf,
stdout: String,
report: serde_json::Value,
}
fn run_verify_with_report(tag: &str) -> Artifacts {
let dir = std::env::temp_dir().join(format!(
"synth_verify_report_935_{}_{}",
tag,
std::process::id()
));
std::fs::create_dir_all(&dir).expect("create temp dir");
let wat = dir.join("fixture.wat");
let elf = dir.join("fixture.elf");
let report_path = dir.join("report.json");
std::fs::write(&wat, FIXTURE_WAT).expect("write fixture wat");
let compile = Command::new(synth())
.args([
"compile",
wat.to_str().unwrap(),
"-o",
elf.to_str().unwrap(),
"--all-exports",
])
.output()
.expect("run synth compile");
assert!(
compile.status.success(),
"compile failed:\n{}\n{}",
String::from_utf8_lossy(&compile.stdout),
String::from_utf8_lossy(&compile.stderr)
);
let verify = Command::new(synth())
.args([
"verify",
wat.to_str().unwrap(),
elf.to_str().unwrap(),
"--emit-verify-report",
report_path.to_str().unwrap(),
])
.output()
.expect("run synth verify");
let stdout = String::from_utf8_lossy(&verify.stdout).into_owned();
assert!(
verify.status.success(),
"verify failed:\n{}\n{}",
stdout,
String::from_utf8_lossy(&verify.stderr)
);
let report: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string(&report_path).expect("sidecar must be written"),
)
.expect("sidecar must be valid JSON");
Artifacts {
dir,
stdout,
report,
}
}
#[test]
fn declines_are_reported_with_counts_reasons_and_rocq_join_keys() {
let a = run_verify_with_report("declines");
assert!(
a.stdout.contains("declined"),
"summary must mention declines:\n{}",
a.stdout
);
assert!(
a.stdout.contains("Module rule inventory:"),
"module-level inventory line missing:\n{}",
a.stdout
);
assert!(
a.stdout.contains("I32Const × 4: register-operation"),
"i32.const decline (count 4) not reported:\n{}",
a.stdout
);
assert_eq!(a.report["schema"], "synth-verify-v1");
let rules = a.report["functions"][0]["rules"]
.as_array()
.expect("rules array");
let find = |name: &str| {
rules
.iter()
.find(|r| r["rule"] == name)
.unwrap_or_else(|| panic!("rule {name} missing from inventory"))
};
let konst = find("I32Const");
assert_eq!(konst["status"], "declined");
assert_eq!(konst["reason"], "register-operation");
assert_eq!(konst["rocq_theorem"], "i32_const_correct");
assert_eq!(konst["count"], 4, "i32.const occurrence count must be real");
let lget = find("LocalGet");
assert_eq!(lget["status"], "declined");
assert_eq!(lget["reason"], "register-operation");
assert_eq!(lget["rocq_theorem"], "local_get_correct");
assert_eq!(lget["count"], 2);
let shl = find("I32Shl");
assert_eq!(shl["status"], "declined");
assert_eq!(shl["reason"], "immediate-shift-encoding");
let and = find("I32And");
assert_eq!(and["status"], "verified");
assert_eq!(and["smt_rule"], "i32.and → AND");
let _ = std::fs::remove_dir_all(&a.dir);
}
#[test]
fn summary_denominator_includes_declines_and_is_internally_consistent() {
let a = run_verify_with_report("denominator");
let s = &a.report["summary"];
let verified = s["verified"].as_u64().unwrap();
let failed = s["failed"].as_u64().unwrap();
let unknown = s["unknown"].as_u64().unwrap();
let declined = s["declined"].as_u64().unwrap();
let kinds = s["applied_rule_kinds"].as_u64().unwrap();
assert!(
declined > 0,
"fixture has known declines; a zero declined count is the #935 vacuity"
);
assert_eq!(
kinds,
verified + failed + unknown + declined,
"applied_rule_kinds must equal the sum of all statuses"
);
let mut per_fn_declined = 0u64;
let mut per_fn_total = 0u64;
for f in a.report["functions"].as_array().unwrap() {
for r in f["rules"].as_array().unwrap() {
per_fn_total += 1;
if r["status"] == "declined" {
per_fn_declined += 1;
}
}
}
assert_eq!(declined, per_fn_declined);
assert_eq!(kinds, per_fn_total);
let by_reason = &s["declined_by_reason"];
assert!(
by_reason["register-operation"]["instructions"]
.as_u64()
.unwrap()
>= 6,
"register-operation must count the 4 consts + 2 local.gets"
);
let _ = std::fs::remove_dir_all(&a.dir);
}