//! Embedding-ABI M1 (M1 fieldtest spec_gap#2): `ail emit-ir //! --emit=staticlib` prints a `main`-free kernel's LLVM IR (the //! external `@` forwarders, no `@main`) instead of the //! executable-path `MissingEntryMain` rejection — the Decision-5 //! IR-readability affordance for the artefact M1 introduced. use ailang_test_support::workspace_root; use std::process::Command; fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") } #[test] fn emit_ir_staticlib_prints_kernel_forwarder_no_main() { let fixture = workspace_root().join("examples").join("embed_backtest_step.ail"); let out = Command::new(ail_bin()) .args(["emit-ir", fixture.to_str().unwrap(), "--emit=staticlib"]) .output().expect("ail emit-ir --emit=staticlib"); let stdout = String::from_utf8_lossy(&out.stdout); let stderr = String::from_utf8_lossy(&out.stderr); assert!(out.status.success(), "emit-ir --emit=staticlib on a main-free kernel must succeed; stderr={stderr}"); assert!(!stderr.contains("has no `main` def"), "must NOT hit the executable-path MissingEntryMain rejection; stderr={stderr}"); assert!(stdout.contains("@backtest_step("), "kernel IR must contain the external `(export \"backtest_step\")` forwarder; stdout={stdout}"); assert!(stdout.contains("@ail_embed_backtest_step_step"), "kernel IR must contain the internal mangled symbol the forwarder calls; stdout={stdout}"); assert!(!stdout.contains("@main("), "staticlib kernel IR must NOT contain an @main trampoline; stdout={stdout}"); } #[test] fn emit_ir_staticlib_requires_an_export() { // embed_noentry_baseline has no `(export …)` fn — symmetric with // build's zero-export guard (embed_staticlib_cli.rs). let fixture = workspace_root().join("examples").join("embed_noentry_baseline.ail"); let out = Command::new(ail_bin()) .args(["emit-ir", fixture.to_str().unwrap(), "--emit=staticlib"]) .output().expect("ail emit-ir --emit=staticlib"); assert!(!out.status.success(), "emit-ir --emit=staticlib with zero exports must fail"); assert!(String::from_utf8_lossy(&out.stderr) .contains("at least one `(export"), "expected the zero-export staticlib error; got {}", String::from_utf8_lossy(&out.stderr)); } #[test] fn emit_ir_default_exe_still_requires_main() { // Regression: --emit defaults to "exe"; emit-ir on a main-free // module WITHOUT --emit=staticlib must still hit MissingEntryMain // (the new staticlib branch must not alter the default path). let fixture = workspace_root().join("examples").join("embed_backtest_step.ail"); let out = Command::new(ail_bin()) .args(["emit-ir", fixture.to_str().unwrap()]) .output().expect("ail emit-ir"); assert!(!out.status.success(), "default-exe emit-ir on a main-free kernel must still fail"); assert!(String::from_utf8_lossy(&out.stderr) .contains("has no `main` def"), "default path must still surface MissingEntryMain; got {}", String::from_utf8_lossy(&out.stderr)); }