use std::path::{Path, PathBuf};
use std::process::Command;
fn synth_binary() -> PathBuf {
let mut path = std::env::current_exe()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap()
.to_path_buf();
path.push("synth");
path
}
fn workspace_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap()
.to_path_buf()
}
fn wast_dir() -> PathBuf {
workspace_root().join("tests").join("wast")
}
fn compile_wast(wast_file: &Path) -> PathBuf {
let stem = wast_file.file_stem().unwrap().to_str().unwrap();
let output = std::env::temp_dir().join(format!("synth_test_{}.elf", stem));
let result = Command::new(synth_binary())
.args([
"compile",
wast_file.to_str().unwrap(),
"--all-exports",
"--cortex-m",
"-o",
output.to_str().unwrap(),
])
.output()
.expect("Failed to run synth binary");
let stdout = String::from_utf8_lossy(&result.stdout);
let stderr = String::from_utf8_lossy(&result.stderr);
assert!(
result.status.success(),
"synth compile failed for {}:\nstdout: {}\nstderr: {}",
wast_file.display(),
stdout,
stderr,
);
assert!(
output.exists(),
"Output ELF not created: {}",
output.display()
);
let data = std::fs::read(&output).unwrap();
assert!(data.len() > 52, "ELF file too small: {} bytes", data.len());
assert_eq!(&data[0..4], b"\x7fELF", "Not a valid ELF file");
assert_eq!(data[18], 0x28, "Not an ARM ELF (e_machine)");
output
}
#[test]
fn compile_i32_arithmetic() {
compile_wast(&wast_dir().join("i32_arithmetic.wast"));
}
#[test]
fn compile_i32_compare() {
compile_wast(&wast_dir().join("i32_compare.wast"));
}
#[test]
fn compile_i32_eq_simple() {
compile_wast(&wast_dir().join("i32_eq_simple.wast"));
}
#[test]
fn compile_i32_shift() {
compile_wast(&wast_dir().join("i32_shift.wast"));
}
#[test]
fn compile_i32_rotate() {
compile_wast(&wast_dir().join("i32_rotate.wast"));
}
#[test]
fn compile_i32_bitcount() {
compile_wast(&wast_dir().join("i32_bitcount.wast"));
}
#[test]
fn compile_i32_div() {
compile_wast(&wast_dir().join("i32_div.wast"));
}
#[test]
fn compile_i32_rem() {
compile_wast(&wast_dir().join("i32_rem.wast"));
}
#[test]
fn compile_i32_memory() {
compile_wast(&wast_dir().join("i32_memory.wast"));
}
#[test]
fn compile_control_select() {
compile_wast(&wast_dir().join("control_select.wast"));
}
#[test]
fn compile_control_if() {
compile_wast(&wast_dir().join("control_if.wast"));
}
#[test]
fn compile_control_loop() {
compile_wast(&wast_dir().join("control_loop.wast"));
}
#[test]
fn compile_control_nested_select() {
compile_wast(&wast_dir().join("control_nested_select.wast"));
}
#[test]
fn compile_control_br_if_select() {
compile_wast(&wast_dir().join("control_br_if_select.wast"));
}
#[test]
fn compile_control_nested_loop() {
compile_wast(&wast_dir().join("control_nested_loop.wast"));
}
#[test]
fn compile_control_factorial() {
compile_wast(&wast_dir().join("control_factorial.wast"));
}
#[test]
fn compile_control_loop_if() {
compile_wast(&wast_dir().join("control_loop_if.wast"));
}
#[test]
fn compile_i64_arithmetic() {
compile_wast(&wast_dir().join("i64_arithmetic.wast"));
}
#[test]
fn compile_i64_compare() {
compile_wast(&wast_dir().join("i64_compare.wast"));
}
#[test]
fn compile_i64_shift() {
compile_wast(&wast_dir().join("i64_shift.wast"));
}
#[test]
fn compile_i64_mul() {
compile_wast(&wast_dir().join("i64_mul.wast"));
}
#[test]
fn compile_i64_div() {
compile_wast(&wast_dir().join("i64_div.wast"));
}
#[test]
fn compile_import_call_produces_relocatable_elf() {
let wat = workspace_root()
.join("tests")
.join("integration")
.join("import_call.wat");
assert!(wat.exists(), "import_call.wat not found: {}", wat.display());
let output = std::env::temp_dir().join("synth_test_import_call.o");
let result = Command::new(synth_binary())
.args([
"compile",
wat.to_str().unwrap(),
"--no-optimize",
"-o",
output.to_str().unwrap(),
])
.output()
.expect("Failed to run synth binary");
let stdout = String::from_utf8_lossy(&result.stdout);
let stderr = String::from_utf8_lossy(&result.stderr);
assert!(
result.status.success(),
"synth compile failed:\nstdout: {}\nstderr: {}",
stdout,
stderr,
);
let data = std::fs::read(&output).unwrap();
assert_eq!(&data[0..4], b"\x7fELF", "Not a valid ELF file");
assert_eq!(data[4], 1, "Expected 32-bit ELF");
assert_eq!(data[5], 1, "Expected little-endian ELF");
let e_type = u16::from_le_bytes([data[16], data[17]]);
assert_eq!(e_type, 1, "Expected ET_REL (1), got {}", e_type);
assert_eq!(data[18], 0x28, "Expected ARM architecture");
let meld_sym = b"__meld_dispatch_import";
let has_meld_sym = data
.windows(meld_sym.len())
.any(|w| w == meld_sym.as_slice());
assert!(
has_meld_sym,
"__meld_dispatch_import symbol not found in ELF"
);
let call_log = b"call_log";
let has_call_log = data
.windows(call_log.len())
.any(|w| w == call_log.as_slice());
assert!(has_call_log, "call_log function symbol not found in ELF");
let rel_text = b".rel.text";
let has_rel_text = data
.windows(rel_text.len())
.any(|w| w == rel_text.as_slice());
assert!(has_rel_text, ".rel.text section not found in ELF");
let import_table = b".meld_import_table";
let has_import_table = data
.windows(import_table.len())
.any(|w| w == import_table.as_slice());
assert!(
has_import_table,
".meld_import_table section not found in ELF"
);
let has_env = data.windows(3).any(|w| w == b"env");
assert!(has_env, "Import module name 'env' not found in metadata");
assert!(
stdout.contains("Relocations:") || stdout.contains("relocatable"),
"Output should mention relocations:\n{}",
stdout
);
}
#[test]
fn compile_with_relocatable_flag_forces_et_rel() {
let wast_file = wast_dir().join("i32_arithmetic.wast");
assert!(wast_file.exists(), "i32_arithmetic.wast missing");
let output = std::env::temp_dir().join("synth_test_relocatable.o");
let result = Command::new(synth_binary())
.args([
"compile",
wast_file.to_str().unwrap(),
"--all-exports",
"--cortex-m",
"--relocatable",
"-o",
output.to_str().unwrap(),
])
.output()
.expect("Failed to run synth binary");
let stderr = String::from_utf8_lossy(&result.stderr);
let stdout = String::from_utf8_lossy(&result.stdout);
assert!(
result.status.success(),
"synth compile --relocatable failed:\nstdout: {}\nstderr: {}",
stdout,
stderr,
);
assert!(output.exists(), "output not created");
let data = std::fs::read(&output).unwrap();
assert_eq!(&data[0..4], b"\x7fELF", "not an ELF");
let e_type = u16::from_le_bytes([data[16], data[17]]);
assert_eq!(
e_type, 1,
"--relocatable should produce ET_REL (1), got {}",
e_type
);
}
#[test]
fn compile_without_relocatable_flag_produces_et_exec_for_no_imports() {
let wast_file = wast_dir().join("i32_arithmetic.wast");
let output = std::env::temp_dir().join("synth_test_no_relocatable.elf");
let result = Command::new(synth_binary())
.args([
"compile",
wast_file.to_str().unwrap(),
"--all-exports",
"--cortex-m",
"-o",
output.to_str().unwrap(),
])
.output()
.expect("Failed to run synth binary");
assert!(
result.status.success(),
"default compile (no --relocatable) failed: stderr={}",
String::from_utf8_lossy(&result.stderr),
);
let data = std::fs::read(&output).unwrap();
let e_type = u16::from_le_bytes([data[16], data[17]]);
assert_eq!(
e_type, 2,
"default (no --relocatable, no imports) should be ET_EXEC (2)"
);
}
#[test]
fn all_wast_files_present() {
let dir = wast_dir();
assert!(
dir.exists(),
"WAST test directory missing: {}",
dir.display()
);
let files: Vec<_> = std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().is_some_and(|ext| ext == "wast"))
.collect();
assert!(
files.len() >= 20,
"Expected at least 20 WAST files, found {}",
files.len()
);
}