use super::super::*;
use mnemosyne_core::constants::{PAGE_SHIFT, PAGES_PER_SEGMENT, SEGMENT_SIZE};
use mnemosyne_core::policy::StandardPolicy;
fn alloc_small(alloc: &mut ThreadAllocator<DefaultBackend>, size: usize) -> *mut u8 {
let ptr = unsafe { alloc.alloc::<StandardPolicy>(size) };
assert!(!ptr.is_null(), "alloc_small({size}) returned null");
ptr
}
fn block_stride_for(ptr: *mut u8) -> usize {
let ptr_val = ptr as usize;
let segment_addr = ptr_val & !(SEGMENT_SIZE - 1);
let segment = segment_addr as *mut mnemosyne_core::types::Segment;
let page_index = (ptr_val >> PAGE_SHIFT) & (PAGES_PER_SEGMENT - 1);
unsafe { (*segment).pages[page_index].block_size as usize }
}
#[test]
fn realloc_within_same_class_returns_same_ptr() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let mut alloc = ThreadAllocator::<DefaultBackend>::new();
let ptr = alloc_small(&mut alloc, 16);
let stride = block_stride_for(ptr);
let new_size = stride - 1;
let layout = core::alloc::Layout::from_size_align(16, 8).expect("layout valid");
let result =
unsafe { crate::thread_realloc::<StandardPolicy, DefaultBackend>(ptr, layout, new_size) };
assert_eq!(
result, ptr,
"realloc within same class must return the same pointer (in-place)"
);
let half = 16 / 2;
let result2 =
unsafe { crate::thread_realloc::<StandardPolicy, DefaultBackend>(ptr, layout, half) };
assert_eq!(
result2, ptr,
"realloc shrinking to >= 50% must return the same pointer"
);
unsafe { crate::thread_free::<StandardPolicy, DefaultBackend>(ptr) };
}
#[test]
fn realloc_shrink_below_half_allocates_new_block() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let mut alloc = ThreadAllocator::<DefaultBackend>::new();
let ptr = alloc_small(&mut alloc, 64);
unsafe {
core::ptr::write_bytes(ptr, 0xAB, 64);
}
let layout = core::alloc::Layout::from_size_align(64, 8).expect("layout valid");
let result =
unsafe { crate::thread_realloc::<StandardPolicy, DefaultBackend>(ptr, layout, 16) };
assert!(
!result.is_null(),
"shrink-below-half realloc must not return null"
);
assert_ne!(result, ptr, "shrink below 50% must allocate a new block");
let slice = unsafe { core::slice::from_raw_parts(result, 16) };
for (i, &byte) in slice.iter().enumerate() {
assert_eq!(
byte, 0xAB,
"data mismatch at byte {i}: expected 0xAB, got {byte}"
);
}
unsafe { crate::thread_free::<StandardPolicy, DefaultBackend>(result) };
}
#[test]
fn realloc_grow_to_different_class_allocates_new_block() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let mut alloc = ThreadAllocator::<DefaultBackend>::new();
let ptr = alloc_small(&mut alloc, 16);
unsafe {
core::ptr::write_bytes(ptr, 0xCD, 16);
}
let stride = block_stride_for(ptr);
let new_size = stride + 1;
let layout = core::alloc::Layout::from_size_align(16, 8).expect("layout valid");
let result =
unsafe { crate::thread_realloc::<StandardPolicy, DefaultBackend>(ptr, layout, new_size) };
assert!(
!result.is_null(),
"grow-beyond-class realloc must not return null"
);
assert_ne!(result, ptr, "grow beyond class must allocate a new block");
let slice = unsafe { core::slice::from_raw_parts(result, 16) };
for (i, &byte) in slice.iter().enumerate() {
assert_eq!(
byte, 0xCD,
"data mismatch at byte {i}: expected 0xCD, got {byte}"
);
}
unsafe { crate::thread_free::<StandardPolicy, DefaultBackend>(result) };
}
#[test]
fn realloc_null_ptr_allocates_fresh() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let layout = core::alloc::Layout::from_size_align(32, 8).expect("layout valid");
let result = unsafe {
crate::thread_realloc::<StandardPolicy, DefaultBackend>(core::ptr::null_mut(), layout, 32)
};
assert!(
!result.is_null(),
"realloc(null, 32) must return a valid pointer"
);
unsafe {
core::ptr::write_bytes(result, 0xEF, 32);
assert_eq!(*result, 0xEF);
}
unsafe { crate::thread_free::<StandardPolicy, DefaultBackend>(result) };
}
#[test]
fn realloc_null_ptr_zero_size_returns_null() {
let layout = core::alloc::Layout::from_size_align(32, 8).expect("layout valid");
let result = unsafe {
crate::thread_realloc::<StandardPolicy, DefaultBackend>(core::ptr::null_mut(), layout, 0)
};
assert!(result.is_null(), "realloc(null, 0) must return null");
}
#[test]
fn realloc_non_null_zero_size_frees_block() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let mut alloc = ThreadAllocator::<DefaultBackend>::new();
let ptr = alloc_small(&mut alloc, 32);
let layout = core::alloc::Layout::from_size_align(32, 8).expect("layout valid");
let result = unsafe { crate::thread_realloc::<StandardPolicy, DefaultBackend>(ptr, layout, 0) };
assert!(
result.is_null(),
"realloc(ptr, 0) must return null (free semantics)"
);
}
#[test]
fn realloc_grow_within_class_preserves_existing_data() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let mut alloc = ThreadAllocator::<DefaultBackend>::new();
let ptr = alloc_small(&mut alloc, 16);
unsafe {
for i in 0..16 {
*ptr.add(i) = (i as u8) ^ 0x55;
}
}
let stride = block_stride_for(ptr);
let new_size = stride - 1;
let layout = core::alloc::Layout::from_size_align(16, 8).expect("layout valid");
let result =
unsafe { crate::thread_realloc::<StandardPolicy, DefaultBackend>(ptr, layout, new_size) };
assert_eq!(result, ptr, "grow within same class must reuse the block");
for i in 0..16 {
let expected = (i as u8) ^ 0x55;
let actual = unsafe { *ptr.add(i) };
assert_eq!(
actual, expected,
"byte {i} corrupted: expected {expected:#x}, got {actual:#x}"
);
}
unsafe { crate::thread_free::<StandardPolicy, DefaultBackend>(ptr) };
}
#[test]
fn realloc_grow_beyond_class_copies_only_min_bytes() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let mut alloc = ThreadAllocator::<DefaultBackend>::new();
let ptr = alloc_small(&mut alloc, 16);
unsafe {
core::ptr::write_bytes(ptr, 0x77, 16);
}
let stride = block_stride_for(ptr);
let new_size = stride + 16;
let layout = core::alloc::Layout::from_size_align(16, 8).expect("layout valid");
let result =
unsafe { crate::thread_realloc::<StandardPolicy, DefaultBackend>(ptr, layout, new_size) };
assert!(!result.is_null());
for i in 0..16 {
let actual = unsafe { *result.add(i) };
assert_eq!(
actual, 0x77,
"byte {i} mismatch: expected 0x77, got {actual:#x}"
);
}
unsafe { crate::thread_free::<StandardPolicy, DefaultBackend>(result) };
}
#[test]
fn realloc_shrink_then_grow_stays_in_place() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let mut alloc = ThreadAllocator::<DefaultBackend>::new();
let ptr = alloc_small(&mut alloc, 32);
let layout = core::alloc::Layout::from_size_align(32, 8).expect("layout valid");
let shrunk =
unsafe { crate::thread_realloc::<StandardPolicy, DefaultBackend>(ptr, layout, 16) };
assert_eq!(shrunk, ptr, "shrink to 50% must be in-place");
let grew = unsafe { crate::thread_realloc::<StandardPolicy, DefaultBackend>(ptr, layout, 32) };
assert_eq!(grew, ptr, "grow back to original must be in-place");
unsafe { crate::thread_free::<StandardPolicy, DefaultBackend>(ptr) };
}
#[test]
fn realloc_repeated_grow_shrink_cycle() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let mut alloc = ThreadAllocator::<DefaultBackend>::new();
let ptr = alloc_small(&mut alloc, 8);
let mut current_size = 8usize;
let stride = block_stride_for(ptr);
for _ in 0..20 {
let target = stride - 8;
let layout = core::alloc::Layout::from_size_align(current_size, 8).expect("layout valid");
let result =
unsafe { crate::thread_realloc::<StandardPolicy, DefaultBackend>(ptr, layout, target) };
assert_eq!(
result, ptr,
"in-place grow failed during cycle (current={current_size}, target={target})"
);
current_size = target;
let half = current_size / 2;
let layout = core::alloc::Layout::from_size_align(current_size, 8).expect("layout valid");
let result =
unsafe { crate::thread_realloc::<StandardPolicy, DefaultBackend>(ptr, layout, half) };
assert_eq!(
result, ptr,
"in-place shrink failed during cycle (current={current_size}, target={half})"
);
current_size = half;
}
unsafe { crate::thread_free::<StandardPolicy, DefaultBackend>(ptr) };
}
#[test]
fn realloc_cross_thread_free_of_result() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let mut alloc = ThreadAllocator::<DefaultBackend>::new();
let ptr = alloc_small(&mut alloc, 32);
let layout = core::alloc::Layout::from_size_align(32, 8).expect("layout valid");
let result =
unsafe { crate::thread_realloc::<StandardPolicy, DefaultBackend>(ptr, layout, 128) };
assert!(!result.is_null());
assert_ne!(result, ptr);
let result_val = result as usize;
let handle = std::thread::spawn(move || unsafe {
crate::thread_free::<StandardPolicy, DefaultBackend>(result_val as *mut u8);
});
handle
.join()
.expect("cross-thread free of realloc result panicked");
}
#[test]
fn realloc_within_class_preserves_full_pattern() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let mut alloc = ThreadAllocator::<DefaultBackend>::new();
let ptr = alloc_small(&mut alloc, 32);
unsafe {
for i in 0..32 {
*ptr.add(i) = i as u8;
}
}
let stride = block_stride_for(ptr);
let layout = core::alloc::Layout::from_size_align(32, 8).expect("layout valid");
let result =
unsafe { crate::thread_realloc::<StandardPolicy, DefaultBackend>(ptr, layout, stride - 1) };
assert_eq!(result, ptr, "grow within class must be in-place");
for i in 0..32 {
let expected = i as u8;
let actual = unsafe { *ptr.add(i) };
assert_eq!(
actual, expected,
"byte {i} corrupted after in-place grow: expected {expected:#x}, got {actual:#x}"
);
}
unsafe { crate::thread_free::<StandardPolicy, DefaultBackend>(ptr) };
}
#[test]
fn realloc_grow_beyond_class_copies_full_old_data() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let mut alloc = ThreadAllocator::<DefaultBackend>::new();
let ptr = alloc_small(&mut alloc, 8);
unsafe {
for i in 0..8 {
*ptr.add(i) = 0xAA + i as u8;
}
}
let stride = block_stride_for(ptr);
let new_size = stride + 1;
let layout = core::alloc::Layout::from_size_align(8, 8).expect("layout valid");
let result =
unsafe { crate::thread_realloc::<StandardPolicy, DefaultBackend>(ptr, layout, new_size) };
assert!(!result.is_null());
assert_ne!(result, ptr);
for i in 0..8 {
let expected = 0xAA + i as u8;
let actual = unsafe { *result.add(i) };
assert_eq!(
actual, expected,
"byte {i} mismatch: expected {expected:#x}, got {actual:#x}"
);
}
unsafe { crate::thread_free::<StandardPolicy, DefaultBackend>(result) };
}