#![allow(clippy::expect_used)]
use std::path::PathBuf;
use lean_rs::LeanRuntime;
use lean_rs_host::{LeanCapabilities, LeanHost, LeanSession, SessionPool};
fn fixture_lake_root() -> PathBuf {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let workspace = manifest_dir
.parent()
.and_then(std::path::Path::parent)
.expect("crates/<name>/ lives two directories beneath the workspace root");
workspace.join("fixtures").join("lean")
}
fn runtime() -> &'static LeanRuntime {
LeanRuntime::init().expect("Lean runtime initialisation must succeed")
}
fn fixture_host() -> LeanHost<'static> {
LeanHost::from_lake_project(runtime(), fixture_lake_root()).expect("host opens cleanly")
}
fn session_over_handles<'lean, 'c>(caps: &'c LeanCapabilities<'lean, 'c>) -> LeanSession<'lean, 'c> {
caps.session(&["LeanRsFixture.Handles"])
.expect("session imports cleanly")
}
fn iters(default: usize) -> usize {
std::env::var("LEAN_RS_LEAK_LOOP_ITERS")
.ok()
.and_then(|raw| raw.parse::<usize>().ok())
.unwrap_or(default)
}
#[test]
fn session_create_drop_loop_small() {
let host = fixture_host();
let caps = host
.load_capabilities("lean_rs_fixture", "LeanRsFixture")
.expect("load caps");
for _ in 0..iters(8) {
let session = session_over_handles(&caps);
drop(session);
}
}
#[test]
#[ignore = "long-running leak surface; run under sanitizer CI"]
fn session_create_drop_loop_long() {
let host = fixture_host();
let caps = host
.load_capabilities("lean_rs_fixture", "LeanRsFixture")
.expect("load caps");
let n = iters(512);
for _ in 0..n {
let session = session_over_handles(&caps);
drop(session);
}
}
#[test]
fn pool_acquire_release_loop_small() {
let host = fixture_host();
let caps = host
.load_capabilities("lean_rs_fixture", "LeanRsFixture")
.expect("load caps");
let pool = SessionPool::with_capacity(runtime(), 4);
let imports = ["LeanRsFixture.Handles"];
let n = iters(16);
for _ in 0..n {
let sess = pool.acquire(&caps, &imports).expect("acquire from warm pool");
drop(sess);
}
let stats = pool.stats();
let n_u64 = u64::try_from(n).expect("loop count fits u64");
assert_eq!(stats.acquired, n_u64, "every iteration accounted for");
assert_eq!(
stats.released_to_pool, n_u64,
"every release fits under the four-slot capacity"
);
assert_eq!(
stats.released_dropped, 0,
"pool capacity is comfortable for one live session"
);
assert_eq!(
stats.imports_performed, 1,
"after the first acquire every subsequent one must reuse the cached env"
);
assert_eq!(
stats.reused,
n_u64 - 1,
"n acquires, one fresh import, n - 1 cache hits"
);
}
#[test]
#[ignore = "long-running pool leak surface; run under sanitizer CI"]
fn pool_acquire_release_loop_long() {
let host = fixture_host();
let caps = host
.load_capabilities("lean_rs_fixture", "LeanRsFixture")
.expect("load caps");
let pool = SessionPool::with_capacity(runtime(), 4);
let imports = ["LeanRsFixture.Handles"];
let n = iters(2048);
for _ in 0..n {
let sess = pool.acquire(&caps, &imports).expect("acquire from warm pool");
drop(sess);
}
let stats = pool.stats();
let n_u64 = u64::try_from(n).expect("loop count fits u64");
assert_eq!(stats.acquired, n_u64);
assert_eq!(stats.imports_performed, 1);
assert_eq!(stats.reused, n_u64 - 1);
}
#[test]
fn pool_overflow_eviction_loop_small() {
let host = fixture_host();
let caps = host
.load_capabilities("lean_rs_fixture", "LeanRsFixture")
.expect("load caps");
let pool = SessionPool::with_capacity(runtime(), 1);
let imports = ["LeanRsFixture.Handles"];
let n = iters(4);
for _ in 0..n {
let s1 = pool.acquire(&caps, &imports).expect("acquire #1");
let s2 = pool.acquire(&caps, &imports).expect("acquire #2");
drop(s1);
drop(s2);
}
let stats = pool.stats();
let n_u64 = u64::try_from(n).expect("loop count fits u64");
assert_eq!(stats.acquired, n_u64 * 2);
assert!(
stats.released_to_pool >= n_u64,
"at least one release per iteration fits the pool",
);
assert!(
stats.released_dropped >= n_u64,
"at least one release per iteration overflows",
);
}