#![cfg(feature = "alloc-core")]
use std::alloc::Layout;
use std::collections::HashSet;
use sefer_alloc::{AllocCore, SegmentLayout};
fn class_for(core: &AllocCore, size: usize, align: usize) -> usize {
let layout = Layout::from_size_align(size, align).unwrap();
core.dbg_layout_class_for(layout)
.expect("expected a small class")
}
fn seg_base(ptr: *mut u8) -> usize {
SegmentLayout::segment_base_of(ptr as usize)
}
#[test]
fn t_refill_equiv_class0_n8() {
t_refill_equiv_inner(16, 8, 8);
}
#[test]
fn t_refill_equiv_class0_n16() {
t_refill_equiv_inner(16, 8, 16);
}
#[test]
fn t_refill_equiv_class0_n64() {
t_refill_equiv_inner(16, 8, 64);
}
#[test]
fn t_refill_equiv_medium_n16() {
t_refill_equiv_inner(256, 8, 16);
}
fn t_refill_equiv_inner(size: usize, align: usize, n: usize) {
let mut core = AllocCore::new().unwrap();
let c = class_for(&core, size, align);
let mut buf = vec![core::ptr::null_mut::<u8>(); n];
let got = core.refill_class(c, n, &mut buf);
assert_eq!(got, n, "refill_class returned {got}, expected {n} (OOM?)");
for (i, &ptr) in buf.iter().enumerate() {
assert!(!ptr.is_null(), "buf[{i}] is null after refill");
}
let unique: HashSet<usize> = buf.iter().map(|p| *p as usize).collect();
assert_eq!(unique.len(), n, "refill_class returned duplicate pointers");
let layout = Layout::from_size_align(size, align).unwrap();
for &ptr in &buf {
core.dealloc(ptr, layout);
}
let check = core.alloc(layout);
assert!(!check.is_null(), "alloc after round-trip returned null");
core.dealloc(check, layout);
}
#[test]
fn t_flush_equiv_class0_n16() {
t_flush_equiv_inner(16, 8, 16);
}
#[test]
fn t_flush_equiv_medium_n16() {
t_flush_equiv_inner(256, 8, 16);
}
fn t_flush_equiv_inner(size: usize, align: usize, n: usize) {
let mut core = AllocCore::new().unwrap();
let c = class_for(&core, size, align);
let mut buf = vec![core::ptr::null_mut::<u8>(); n];
let got = core.refill_class(c, n, &mut buf);
assert_eq!(got, n);
core.flush_class(c, &buf);
let mut buf2 = vec![core::ptr::null_mut::<u8>(); n];
let got2 = core.refill_class(c, n, &mut buf2);
assert_eq!(got2, n, "re-refill after flush returned {got2}");
for (i, &ptr) in buf2.iter().enumerate() {
assert!(!ptr.is_null(), "buf2[{i}] is null after re-refill");
}
let unique: HashSet<usize> = buf2.iter().map(|p| *p as usize).collect();
assert_eq!(unique.len(), n);
let layout = Layout::from_size_align(size, align).unwrap();
for &ptr in &buf2 {
core.dealloc(ptr, layout);
}
}
#[test]
fn t_refill_spans_segments() {
let mut core = AllocCore::new().unwrap();
let size = 1024usize;
let align = 8usize;
let n = 5000usize;
let c = class_for(&core, size, align);
let mut buf = vec![core::ptr::null_mut::<u8>(); n];
let got = core.refill_class(c, n, &mut buf);
assert_eq!(got, n, "refill_class returned {got}, expected {n}");
for (i, &ptr) in buf.iter().enumerate() {
assert!(!ptr.is_null(), "buf[{i}] is null");
}
let unique: HashSet<usize> = buf.iter().map(|p| *p as usize).collect();
assert_eq!(
unique.len(),
n,
"duplicate pointers in multi-segment refill"
);
let bases: HashSet<usize> = buf.iter().map(|&p| seg_base(p)).collect();
assert!(
bases.len() >= 2,
"expected >= 2 segment bases, got {} (all pointers in one segment?)",
bases.len(),
);
let layout = Layout::from_size_align(size, align).unwrap();
for &ptr in &buf {
core.dealloc(ptr, layout);
}
}
#[cfg(feature = "alloc-decommit")]
#[test]
fn t_flush_decommit() {
let mut core = AllocCore::new().unwrap();
let size = 1024usize;
let align = 8usize;
let c = class_for(&core, size, align);
let n = 12_000usize;
let mut buf = vec![core::ptr::null_mut::<u8>(); n];
let got = core.refill_class(c, n, &mut buf);
assert_eq!(got, n);
let mut by_base: std::collections::HashMap<usize, Vec<*mut u8>> =
std::collections::HashMap::new();
for &ptr in &buf {
by_base.entry(seg_base(ptr)).or_default().push(ptr);
}
assert!(
by_base.len() >= 3,
"need >= 3 segments for decommit test (primordial + small + small_cur), got {}",
by_base.len(),
);
let before = AllocCore::dbg_decommit_count();
core.flush_class(c, &buf);
let after = AllocCore::dbg_decommit_count();
assert!(
after > before,
"dbg_decommit_count did not increase after flushing all blocks \
(before={before}, after={after}, segments={}). \
Decommit should fire for empty non-current Small segments.",
by_base.len(),
);
}
#[test]
fn t_flush_null_defensive() {
let mut core = AllocCore::new().unwrap();
let c = class_for(&core, 16, 8);
let nulls = [core::ptr::null_mut::<u8>(); 4];
core.flush_class(c, &nulls);
let layout = Layout::from_size_align(16, 8).unwrap();
let ptr = core.alloc(layout);
assert!(!ptr.is_null());
core.dealloc(ptr, layout);
}
#[test]
fn t_refill_zero() {
let mut core = AllocCore::new().unwrap();
let c = class_for(&core, 16, 8);
let mut buf = [core::ptr::null_mut::<u8>(); 0];
let got = core.refill_class(c, 0, &mut buf);
assert_eq!(got, 0);
}
#[test]
fn t_flush_double_free_is_noop() {
let mut core = AllocCore::new().unwrap();
let c = class_for(&core, 64, 8);
let n = 8;
let mut buf = vec![core::ptr::null_mut::<u8>(); n];
let got = core.refill_class(c, n, &mut buf);
assert_eq!(got, n);
core.flush_class(c, &buf);
core.flush_class(c, &buf);
let layout = Layout::from_size_align(64, 8).unwrap();
let ptr = core.alloc(layout);
assert!(!ptr.is_null());
core.dealloc(ptr, layout);
}