use super::super::*;
use mnemosyne_core::policy::StandardPolicy;
#[test]
fn adversarial_fragmentation_alternating_classes() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let mut alloc = ThreadAllocator::<DefaultBackend>::new();
const ROUNDS: usize = 200;
const CLASS_SIZES: [usize; 3] = [16, 32, 64];
let mut pinned: [std::vec::Vec<*mut u8>; 3] = [
std::vec::Vec::new(),
std::vec::Vec::new(),
std::vec::Vec::new(),
];
let mut freed: [std::vec::Vec<*mut u8>; 3] = [
std::vec::Vec::new(),
std::vec::Vec::new(),
std::vec::Vec::new(),
];
for round in 0..ROUNDS {
for (class_idx, &size) in CLASS_SIZES.iter().enumerate() {
let ptr = unsafe { alloc.alloc::<StandardPolicy>(size) };
assert!(
!ptr.is_null(),
"allocation failed at round {round}, class {class_idx}, size {size}"
);
unsafe {
for i in 0..size {
*ptr.add(i) = (round ^ class_idx) as u8;
}
}
if round % 4 == 0 {
pinned[class_idx].push(ptr);
} else {
freed[class_idx].push(ptr);
}
}
}
let stats_after_alloc = alloc.stats();
assert!(
stats_after_alloc.current_thread_owned_segments >= 1,
"must own at least one segment after allocation phase"
);
for ptrs in freed.iter() {
for &ptr in ptrs {
unsafe {
crate::thread_free::<StandardPolicy, DefaultBackend>(ptr);
}
}
}
for (class_idx, ptrs) in pinned.iter().enumerate() {
for (i, &ptr) in ptrs.iter().enumerate() {
let size = CLASS_SIZES[class_idx];
let expected_pattern = ((i * 4) ^ class_idx) as u8;
for byte_idx in 0..size {
let actual = unsafe { *ptr.add(byte_idx) };
assert_eq!(
actual, expected_pattern,
"pinned pointer corruption: class={class_idx}, pin_index={i}, byte={byte_idx}, \
expected={expected_pattern:#x}, got={actual:#x}"
);
}
}
}
for (class_idx, &size) in CLASS_SIZES.iter().enumerate() {
let new_ptrs: std::vec::Vec<*mut u8> = (0..50)
.map(|i| {
let ptr = unsafe { alloc.alloc::<StandardPolicy>(size) };
assert!(
!ptr.is_null(),
"post-fragmentation allocation failed: class={class_idx}, index={i}"
);
ptr
})
.collect();
let mut sorted = new_ptrs.clone();
sorted.sort_unstable_by_key(|p| *p as usize);
sorted.dedup();
assert_eq!(
sorted.len(),
new_ptrs.len(),
"duplicate pointers returned under fragmentation: class={class_idx}"
);
for ptr in new_ptrs {
unsafe {
crate::thread_free::<StandardPolicy, DefaultBackend>(ptr);
}
}
}
for ptrs in pinned {
for ptr in ptrs {
unsafe {
crate::thread_free::<StandardPolicy, DefaultBackend>(ptr);
}
}
}
}
#[test]
fn adversarial_single_class_checkerboard() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let mut alloc = ThreadAllocator::<DefaultBackend>::new();
const SIZE: usize = 32;
const COUNT: usize = 128;
let mut all_ptrs = std::vec::Vec::with_capacity(COUNT);
for i in 0..COUNT {
let ptr = unsafe { alloc.alloc::<StandardPolicy>(SIZE) };
assert!(!ptr.is_null(), "checkerboard alloc {i} failed");
unsafe {
*ptr = i as u8;
}
all_ptrs.push(ptr);
}
for i in (0..COUNT).step_by(2) {
unsafe {
crate::thread_free::<StandardPolicy, DefaultBackend>(all_ptrs[i]);
}
}
let mut reuse_ptrs = std::vec::Vec::with_capacity(COUNT / 2);
for i in 0..(COUNT / 2) {
let ptr = unsafe { alloc.alloc::<StandardPolicy>(SIZE) };
assert!(
!ptr.is_null(),
"reuse allocation {i} failed after checkerboard free"
);
reuse_ptrs.push(ptr);
}
let live_set: std::collections::HashSet<usize> = all_ptrs[1..COUNT]
.iter()
.step_by(2)
.map(|p| *p as usize)
.collect();
for &ptr in &reuse_ptrs {
assert!(
!live_set.contains(&(ptr as usize)),
"reuse pointer {ptr:p} collides with a live pinned block"
);
}
let mut sorted = reuse_ptrs.clone();
sorted.sort_unstable_by_key(|p| *p as usize);
sorted.dedup();
assert_eq!(
sorted.len(),
reuse_ptrs.len(),
"duplicate pointers in reuse phase"
);
for ptr in reuse_ptrs {
unsafe {
crate::thread_free::<StandardPolicy, DefaultBackend>(ptr);
}
}
for i in (1..COUNT).step_by(2) {
unsafe {
crate::thread_free::<StandardPolicy, DefaultBackend>(all_ptrs[i]);
}
}
}
#[test]
fn adversarial_mixed_class_page_recycling() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let mut alloc = ThreadAllocator::<DefaultBackend>::new();
const SIZES: [usize; 4] = [16, 32, 48, 64];
const WAVE_SIZE: usize = 50;
let mut waves: [std::vec::Vec<*mut u8>; 4] = [
std::vec::Vec::new(),
std::vec::Vec::new(),
std::vec::Vec::new(),
std::vec::Vec::new(),
];
for class_idx in 0..4 {
for _ in 0..WAVE_SIZE {
let ptr = unsafe { alloc.alloc::<StandardPolicy>(SIZES[class_idx]) };
assert!(!ptr.is_null());
unsafe {
core::ptr::write_bytes(ptr, (class_idx * 17) as u8, SIZES[class_idx]);
}
waves[class_idx].push(ptr);
}
}
for class_idx in [0, 2] {
for ptr in waves[class_idx].drain(..) {
unsafe {
crate::thread_free::<StandardPolicy, DefaultBackend>(ptr);
}
}
}
let segments_before = alloc.stats().fresh_segments;
for class_idx in [0, 2] {
for _ in 0..WAVE_SIZE {
let ptr = unsafe { alloc.alloc::<StandardPolicy>(SIZES[class_idx]) };
assert!(!ptr.is_null(), "wave 2 alloc failed for class {class_idx}");
waves[class_idx].push(ptr);
}
}
let segments_after = alloc.stats().fresh_segments;
let new_segments = segments_after - segments_before;
assert!(
new_segments <= 2,
"too many fresh segments for recycling: {new_segments} (expected <= 2)"
);
for class_idx in [1, 3] {
for &ptr in &waves[class_idx] {
for byte_idx in 0..SIZES[class_idx] {
let actual = unsafe { *ptr.add(byte_idx) };
assert_eq!(
actual,
(class_idx * 17) as u8,
"wave 1 survivor corruption at class {class_idx}, byte {byte_idx}"
);
}
}
}
for ptrs in waves {
for ptr in ptrs {
unsafe {
crate::thread_free::<StandardPolicy, DefaultBackend>(ptr);
}
}
}
}