#![cfg(all(feature = "alloc-core", feature = "alloc-decommit"))]
use core::alloc::Layout;
use sefer_alloc::AllocCore;
const MIB: usize = 1024 * 1024;
fn layout(mib: usize) -> Layout {
Layout::from_size_align(mib * MIB, 8).unwrap()
}
fn assert_used_bytes_invariant(ac: &AllocCore) {
let slots = ac.dbg_large_cache_slot_sizes();
let expected: usize = slots.iter().filter_map(|s| *s).sum();
assert_eq!(
ac.dbg_large_cache_used(),
expected,
"large_cache_used_bytes invariant violated: counter={}, slot_sum={}",
ac.dbg_large_cache_used(),
expected
);
}
#[test]
fn budget_none_caches_any_size() {
let mut ac = AllocCore::new().expect("primordial");
ac.dbg_set_large_cache_budget(None);
let l = layout(100); let ptr1 = ac.alloc(l);
if ptr1.is_null() {
eprintln!("OOM allocating 100 MiB — skip test (machine too small)");
return;
}
assert_eq!(ac.dbg_large_cache_used(), 0);
assert_used_bytes_invariant(&ac);
ac.dealloc(ptr1, l);
assert!(
ac.dbg_large_cache_used() > 0,
"100 MiB span should be cached when budget is None"
);
assert_used_bytes_invariant(&ac);
let ptr2 = ac.alloc(l);
assert!(!ptr2.is_null(), "re-alloc after cache deposit must succeed");
assert_eq!(ac.dbg_large_cache_used(), 0);
assert_used_bytes_invariant(&ac);
unsafe {
ptr2.write(0xAB);
assert_eq!(ptr2.read(), 0xAB);
}
ac.dealloc(ptr2, l);
}
#[test]
fn budget_set_evicts_when_full() {
let mut ac = AllocCore::new().expect("primordial");
ac.dbg_set_large_cache_budget(None);
let l = layout(4);
let p1 = ac.alloc(l);
if p1.is_null() {
eprintln!("OOM — skip budget_set_evicts_when_full");
return;
}
ac.dealloc(p1, l);
assert_used_bytes_invariant(&ac);
let one_span = ac.dbg_large_cache_used();
assert!(one_span > 0, "first dealloc must cache the span");
ac.dbg_set_large_cache_budget(Some(one_span));
let p2 = ac.alloc(l);
if p2.is_null() {
eprintln!("OOM — skip budget_set_evicts_when_full (p2)");
return;
}
assert_used_bytes_invariant(&ac);
ac.dealloc(p2, l);
assert_used_bytes_invariant(&ac);
assert_eq!(
ac.dbg_large_cache_used(),
one_span,
"used_bytes should equal one_span after depositing p2"
);
let p3 = ac.alloc(l);
if p3.is_null() {
assert_used_bytes_invariant(&ac);
return;
}
assert_eq!(ac.dbg_large_cache_used(), 0);
assert_used_bytes_invariant(&ac);
ac.dealloc(p3, l);
assert_used_bytes_invariant(&ac);
assert!(
ac.dbg_large_cache_used() <= one_span,
"used_bytes {} must not exceed budget {}",
ac.dbg_large_cache_used(),
one_span
);
}
#[test]
fn budget_too_small_releases_immediately() {
const BUDGET: usize = 10 * MIB; const SPAN: usize = 100;
let mut ac = AllocCore::new().expect("primordial");
ac.dbg_set_large_cache_budget(Some(BUDGET));
let l = layout(SPAN);
let ptr = ac.alloc(l);
if ptr.is_null() {
eprintln!("OOM — skip budget_too_small_releases_immediately");
return;
}
unsafe { ptr.write(0xCD) };
ac.dealloc(ptr, l);
assert_eq!(
ac.dbg_large_cache_used(),
0,
"span larger than budget must be released to the OS, not cached"
);
assert_used_bytes_invariant(&ac);
let slots = ac.dbg_large_cache_slot_sizes();
assert!(slots.iter().all(|s| s.is_none()), "all slots must be empty");
}
#[test]
fn config_sets_budget() {
use sefer_alloc::LargeCacheConfig;
let cfg = LargeCacheConfig::new().budget_bytes(256 * MIB);
let mut ac = AllocCore::new_with_config(cfg).expect("primordial");
let l = layout(4); let p1 = ac.alloc(l);
if p1.is_null() {
eprintln!("OOM — skip config_sets_budget");
return;
}
ac.dealloc(p1, l);
assert_used_bytes_invariant(&ac);
let used = ac.dbg_large_cache_used();
assert!(
used > 0,
"span should be cached when budget (256 MiB) is larger than span (~8 MiB)"
);
let p2 = ac.alloc(l);
assert!(!p2.is_null());
assert_eq!(
ac.dbg_large_cache_used(),
0,
"cache hit must remove the span from used_bytes"
);
assert_used_bytes_invariant(&ac);
ac.dealloc(p2, l);
assert_used_bytes_invariant(&ac);
assert!(
ac.dbg_large_cache_used() <= 256 * MIB,
"used_bytes must not exceed the 256 MiB budget"
);
}
#[test]
fn large_cache_used_bytes_invariant() {
let mut ac = AllocCore::new().expect("primordial");
ac.dbg_set_large_cache_budget(None);
assert_used_bytes_invariant(&ac);
let l4 = layout(4);
let l8 = layout(8);
let p4 = ac.alloc(l4);
assert_used_bytes_invariant(&ac);
if p4.is_null() {
eprintln!("OOM — skip large_cache_used_bytes_invariant");
return;
}
let p8 = ac.alloc(l8);
assert_used_bytes_invariant(&ac);
if !p8.is_null() {
ac.dealloc(p4, l4);
assert_used_bytes_invariant(&ac);
ac.dealloc(p8, l8);
assert_used_bytes_invariant(&ac);
let p4b = ac.alloc(l4);
assert_used_bytes_invariant(&ac);
if !p4b.is_null() {
ac.dealloc(p4b, l4);
assert_used_bytes_invariant(&ac);
}
} else {
ac.dealloc(p4, l4);
assert_used_bytes_invariant(&ac);
}
assert_used_bytes_invariant(&ac);
}