use std::path::PathBuf;
use std::process::{Command, Output};
fn synth() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_synth"))
}
fn workdir(tag: &str) -> PathBuf {
let d = std::env::temp_dir().join(format!("synth-952-{tag}"));
std::fs::create_dir_all(&d).expect("temp dir");
d
}
fn compile(dir: &std::path::Path, wat: &str, out_name: &str, extra: &[&str]) -> Output {
let src = dir.join("m.wat");
std::fs::write(&src, wat).expect("write wat");
let obj = dir.join(out_name);
let mut c = Command::new(synth());
c.args([
"compile",
src.to_str().unwrap(),
"-b",
"arm",
"-t",
"cortex-m3",
"--all-exports",
"--relocatable",
"-o",
obj.to_str().unwrap(),
]);
c.args(extra);
c.output().expect("run synth compile")
}
fn stderr(o: &Output) -> String {
String::from_utf8_lossy(&o.stderr).into_owned()
}
const REQUESTED_EXPORT_DECLINED: &str = r#"(module
(func $g (param i32 i64) (result i32) (local.get 0))
(func (export "f") (result i32) (call $g (i32.const 7) (i64.const 9))))
"#;
const HELPER_DECLINE_NO_DANGLE: &str = r#"(module
(func $hard (result f64) (f64.sqrt (f64.const 2.0)))
(func (export "f") (result i32) (call $hard) (drop) (i32.const 1))
(func (export "ok") (result i32) (i32.const 1)))
"#;
#[test]
fn declined_requested_export_exits_nonzero() {
let dir = workdir("red");
let out = compile(&dir, REQUESTED_EXPORT_DECLINED, "ctrl.o", &[]);
let err = stderr(&out);
assert!(
err.contains("skipping function 'f'"),
"fixture must decline 'f' specifically (the anchor this test relies \
on) — got:\n{err}"
);
assert!(
!out.status.success(),
"#952: a compile that declines a REQUESTED export ('f', the module's \
sole export) must exit non-zero — a build gating on `$?` must not \
accept an object silently missing its public entry point. \
stderr:\n{err}"
);
}
#[test]
fn skipped_helper_without_dangling_reference_still_exits_zero() {
let dir = workdir("negctrl");
let out = compile(
&dir,
HELPER_DECLINE_NO_DANGLE,
"ctrl.o",
&["--allow-skipped-exports"],
);
let err = stderr(&out);
assert!(
err.contains("skipping function 'func_0'") && err.contains("were skipped"),
"fixture must actually skip the non-exported helper for this control \
to mean anything — got:\n{err}"
);
assert!(
out.status.success(),
"negative control: a decline that leaves NO dangling reference from \
retained code must still exit 0 under --allow-skipped-exports — \
only a declined REQUESTED export (#952) or a retained function \
relocating against a declined one (#1102) may fail the build. \
stderr:\n{err}"
);
assert!(
dir.join("ctrl.o").exists(),
"control object was not emitted"
);
}
#[test]
fn allow_skipped_exports_restores_exit_zero() {
let dir = workdir("allow");
let out = compile(
&dir,
REQUESTED_EXPORT_DECLINED,
"ctrl.o",
&["--allow-skipped-exports"],
);
let err = stderr(&out);
assert!(
err.contains("skipping function 'f'"),
"the decline must still happen (and still warn) under the opt-out — \
only the EXIT CODE changes. stderr:\n{err}"
);
assert!(
out.status.success(),
"--allow-skipped-exports must restore exit 0 on a declined requested \
export. stderr:\n{err}"
);
}