//! Pins that the #61 headline `series_sma` example is leak-clean under //! `--alloc=rc`. The residual `live=2` was #63 leg 3 (branch-consume- //! split); this pin closes that leak tail. //! //! Property protected: `series_sma` runs to completion under //! `AILANG_RC_STATS=1` with `live == 0` (equivalently `allocs == frees`). //! `series_sma_pin.rs` already pins stdout; this pin is the RC ledger, //! distinct. Goes green once the branch-consume-split leak is fixed. use std::path::Path; use std::process::Command; fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") } #[test] fn series_sma_is_leak_clean() { let manifest_dir = env!("CARGO_MANIFEST_DIR"); let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap(); let src = workspace.join("examples").join("series_sma.ail"); let tmp = std::env::temp_dir().join(format!( "ailang_series_sma_no_leak_pin_{}", std::process::id() )); std::fs::create_dir_all(&tmp).unwrap(); let out = tmp.join("bin"); let status = Command::new(ail_bin()) .args(["build", src.to_str().unwrap(), "--alloc=rc", "-o"]) .arg(&out) .status() .expect("ail build failed to run"); assert!( status.success(), "ail build --alloc=rc failed for series_sma.ail" ); let output = Command::new(&out) .env("AILANG_RC_STATS", "1") .output() .expect("execute binary"); assert!( output.status.success(), "binary exited non-zero: status {:?}", output.status ); let stderr = String::from_utf8(output.stderr).expect("stderr utf8"); let stats_line = stderr .lines() .find(|l| l.starts_with("ailang_rc_stats:")) .unwrap_or_else(|| { panic!("missing ailang_rc_stats line in stderr; stderr was:\n{stderr}") }); let mut allocs: Option = None; let mut frees: Option = None; let mut live: Option = None; for tok in stats_line.split_whitespace() { if let Some(v) = tok.strip_prefix("allocs=") { allocs = v.parse().ok(); } else if let Some(v) = tok.strip_prefix("frees=") { frees = v.parse().ok(); } else if let Some(v) = tok.strip_prefix("live=") { live = v.parse().ok(); } } let allocs = allocs.expect("missing allocs= field"); let frees = frees.expect("missing frees= field"); let live = live.expect("missing live= field"); assert_eq!( live, 0, "series_sma leaks {live} slab(s) (allocs={allocs} frees={frees}): the \ #61 residual live=2 was the #63 leg-3 branch-consume-split leak; if \ this is non-zero the leak-class drop is not firing on the series path." ); assert_eq!( allocs, frees, "alloc/free mismatch (allocs={allocs} frees={frees} live={live})" ); }