#![cfg(feature = "alloc-core")]
use std::alloc::Layout;
use std::ptr;
use sefer_alloc::{AllocCore, SegmentLayout};
#[test]
fn m1_small_allocations_are_aligned_and_writable() {
let mut a = AllocCore::new().unwrap();
for align in [1usize, 2, 4, 8, 16] {
for size in [1usize, 7, 16, 100, 1024, 4096] {
let layout = Layout::from_size_align(size, align).unwrap();
let ptr = a.alloc(layout);
assert!(!ptr.is_null(), "alloc({:?}) returned null", layout);
assert_eq!(
(ptr as usize) % align,
0,
"ptr {ptr:#p} not aligned to {align}"
);
unsafe {
for b in 0..size {
ptr.add(b).write(0x5C);
}
for b in 0..size {
assert_eq!(ptr.add(b).read(), 0x5C, "byte {b} not writable/readable");
}
}
}
}
}
#[test]
fn m1_large_allocations_are_aligned_and_writable() {
let mut a = AllocCore::new().unwrap();
let big = SegmentLayout::SMALL_MAX + SegmentLayout::PAGE;
let layout = Layout::from_size_align(big, 4096).unwrap();
let ptr = a.alloc(layout);
assert!(!ptr.is_null(), "large alloc returned null");
assert_eq!((ptr as usize) % 4096, 0, "large ptr not page-aligned");
unsafe {
ptr::write_bytes(ptr, 0x33, big);
assert_eq!(ptr.add(0).read(), 0x33);
assert_eq!(ptr.add(big - 1).read(), 0x33);
}
}
#[test]
fn m1_alloc_zeroed_is_all_zero() {
let mut a = AllocCore::new().unwrap();
let layout = Layout::from_size_align(999, 8).unwrap();
let ptr = a.alloc_zeroed(layout);
assert!(!ptr.is_null());
unsafe {
for b in 0..999 {
assert_eq!(ptr.add(b).read(), 0, "byte {b} not zero");
}
}
}
#[test]
fn m2_double_free_is_noop() {
let mut a = AllocCore::new().unwrap();
let layout = Layout::from_size_align(64, 8).unwrap();
let ptr = a.alloc(layout);
a.dealloc(ptr, layout);
a.dealloc(ptr, layout);
let ptr2 = a.alloc(layout);
assert!(!ptr2.is_null());
}
#[test]
fn m2_foreign_pointer_dealloc_is_noop() {
let mut a = AllocCore::new().unwrap();
let stack_var: u64 = 0xDEAD_BEEF;
let foreign_ptr = &stack_var as *const u64 as *mut u8;
let layout = Layout::from_size_align(8, 8).unwrap();
a.dealloc(foreign_ptr, layout);
let ptr = a.alloc(layout);
assert!(!ptr.is_null());
}
#[test]
fn m3_simultaneous_allocations_do_not_overlap() {
let mut a = AllocCore::new().unwrap();
let layout = Layout::from_size_align(256, 8).unwrap();
let mut ptrs = Vec::new();
for _ in 0..64 {
let ptr = a.alloc(layout);
assert!(!ptr.is_null());
ptrs.push((ptr as usize, 256));
}
for i in 0..ptrs.len() {
for j in (i + 1)..ptrs.len() {
let (pa, sa) = ptrs[i];
let (pb, sb) = ptrs[j];
assert!(
pa + sa <= pb || pb + sb <= pa,
"allocations {i} and {j} overlap"
);
}
}
for (i, &(p, _)) in ptrs.iter().enumerate() {
unsafe {
ptr::write_bytes(p as *mut u8, i as u8, 256);
}
}
for (i, &(p, _)) in ptrs.iter().enumerate() {
unsafe {
for b in 0..256 {
assert_eq!(
(p as *const u8).add(b).read(),
i as u8,
"alloc {i} byte {b} clobbered"
);
}
}
}
}
#[test]
fn m4_size_class_satisfies_size_and_align() {
let mut a = AllocCore::new().unwrap();
for align in [1usize, 2, 4, 8, 16] {
for size in [1usize, 15, 31, 63, 127, 255, 511, 1023, 2047] {
let layout = Layout::from_size_align(size, align).unwrap();
let ptr = a.alloc(layout);
assert!(!ptr.is_null());
assert_eq!((ptr as usize) % align, 0, "size={size} align={align}");
}
}
}
#[test]
fn m4_large_alignment_uses_dedicated_segment() {
let mut a = AllocCore::new().unwrap();
let align = 4096;
let layout = Layout::from_size_align(32, align).unwrap();
let ptr = a.alloc(layout);
assert!(!ptr.is_null());
assert_eq!((ptr as usize) % align, 0);
}
#[test]
fn segment_of_finds_our_segment_base() {
let mut a = AllocCore::new().unwrap();
let layout = Layout::from_size_align(48, 8).unwrap();
let ptr = a.alloc(layout);
let base = SegmentLayout::segment_base_of(ptr as usize);
assert_eq!(base % SegmentLayout::SEGMENT, 0);
assert!(
(ptr as usize) >= base && (ptr as usize) < base + SegmentLayout::SEGMENT,
"ptr not within its computed segment"
);
}
#[test]
fn m5_churn_keeps_allocator_consistent() {
let mut a = AllocCore::new().unwrap();
let layout = Layout::from_size_align(64, 8).unwrap();
for _ in 0..10_000 {
let ptr = a.alloc(layout);
assert!(!ptr.is_null());
a.dealloc(ptr, layout);
}
}
#[test]
fn realloc_preserves_prefix_bytes() {
let mut a = AllocCore::new().unwrap();
let initial = 128;
let layout = Layout::from_size_align(initial, 8).unwrap();
let ptr = a.alloc(layout);
unsafe {
for b in 0..initial {
ptr.add(b).write((b as u8).wrapping_mul(7));
}
}
let new_ptr = a.realloc(ptr, layout, 512);
assert!(!new_ptr.is_null());
unsafe {
for b in 0..initial {
assert_eq!(
new_ptr.add(b).read(),
(b as u8).wrapping_mul(7),
"byte {b} not preserved across realloc grow"
);
}
}
let new_layout = Layout::from_size_align(512, 8).unwrap();
let shrunk = a.realloc(new_ptr, new_layout, 32);
assert!(!shrunk.is_null());
unsafe {
for b in 0..32 {
assert_eq!(
shrunk.add(b).read(),
(b as u8).wrapping_mul(7),
"byte {b} not preserved across realloc shrink"
);
}
}
}
#[test]
fn free_list_reuses_freed_blocks() {
let mut a = AllocCore::new().unwrap();
let layout = Layout::from_size_align(64, 8).unwrap();
let mut ptrs = Vec::new();
for _ in 0..256 {
ptrs.push(a.alloc(layout));
}
for p in &ptrs {
a.dealloc(*p, layout);
}
for _ in 0..256 {
let p = a.alloc(layout);
assert!(!p.is_null());
}
}
#[test]
fn many_large_allocs_then_free() {
let mut a = AllocCore::new().unwrap();
let mut ptrs = Vec::new();
for i in 0..20usize {
let size = 50_000 * (i + 1);
let layout = Layout::from_size_align(size, 4096).unwrap();
let p = a.alloc(layout);
assert!(!p.is_null(), "large alloc {i} failed");
ptrs.push((p, layout));
}
for (p, l) in &ptrs {
a.dealloc(*p, *l);
}
}