use super::super::page::unlink_page_from_list_raw;
use super::super::*;
use core::ptr::NonNull;
use mnemosyne_core::policy::StandardPolicy;
use mnemosyne_core::types::Page;
#[test]
fn owned_segment_list_is_doubly_linked_and_unlinks_in_place() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let mut alloc = ThreadAllocator::<DefaultBackend>::new();
let mut storage: [std::boxed::Box<core::mem::MaybeUninit<Segment>>; 3] = [
std::boxed::Box::new(core::mem::MaybeUninit::uninit()),
std::boxed::Box::new(core::mem::MaybeUninit::uninit()),
std::boxed::Box::new(core::mem::MaybeUninit::uninit()),
];
let mut seg = [core::ptr::null_mut::<Segment>(); 3];
for (i, slot) in storage.iter_mut().enumerate() {
let ptr = slot.as_mut_ptr();
unsafe {
Segment::initialize(ptr, core::ptr::null_mut(), 0);
assert!(
Segment::owner_allocator(ptr).is_null(),
"initialized segment owner_allocator must start null"
);
alloc.push_owned_segment::<StandardPolicy>(ptr);
}
seg[i] = ptr;
assert_eq!(
unsafe { Segment::owner_allocator(ptr) },
(&mut alloc as *mut ThreadAllocator<DefaultBackend>).cast(),
"owned segment must cache the owning allocator pointer"
);
assert_eq!(
alloc.owned_segment_count,
i + 1,
"owned_segment_count must track each owned-list insertion"
);
}
assert_eq!(
alloc.owned_segments_head, seg[2],
"head must be last pushed"
);
unsafe {
assert!((*seg[2]).prev_owned_segment.is_null());
assert_eq!((*seg[2]).next_owned_segment, seg[1]);
assert_eq!((*seg[1]).prev_owned_segment, seg[2]);
assert_eq!((*seg[1]).next_owned_segment, seg[0]);
assert_eq!((*seg[0]).prev_owned_segment, seg[1]);
assert!((*seg[0]).next_owned_segment.is_null());
}
unsafe { alloc.unlink_owned_segment(seg[1]) };
assert_eq!(
alloc.owned_segment_count, 2,
"unlinking the middle segment must decrement owned_segment_count"
);
assert_eq!(alloc.owned_segments_head, seg[2]);
unsafe {
assert_eq!((*seg[2]).next_owned_segment, seg[0]);
assert_eq!((*seg[0]).prev_owned_segment, seg[2]);
assert!((*seg[1]).prev_owned_segment.is_null());
assert!((*seg[1]).next_owned_segment.is_null());
}
unsafe { alloc.unlink_owned_segment(seg[2]) };
assert_eq!(
alloc.owned_segment_count, 1,
"unlinking the head segment must decrement owned_segment_count"
);
assert_eq!(alloc.owned_segments_head, seg[0]);
unsafe { assert!((*seg[0]).prev_owned_segment.is_null()) };
unsafe { alloc.unlink_owned_segment(seg[0]) };
assert_eq!(
alloc.owned_segment_count, 0,
"unlinking the final segment must clear owned_segment_count"
);
assert!(alloc.owned_segments_head.is_null(), "list must be empty");
}
#[test]
fn unlink_page_from_list_splices_and_reports_membership() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let p0 = std::boxed::Box::into_raw(std::boxed::Box::new(Page::new()));
let p1 = std::boxed::Box::into_raw(std::boxed::Box::new(Page::new()));
let p2 = std::boxed::Box::into_raw(std::boxed::Box::new(Page::new()));
let (n0, n1, n2) = unsafe {
let n0 = NonNull::new_unchecked(p0);
let n1 = NonNull::new_unchecked(p1);
let n2 = NonNull::new_unchecked(p2);
(*p0).next_page = Some(n1);
(*p0).prev_page = None;
(*p1).next_page = Some(n2);
(*p1).prev_page = Some(n0);
(*p2).next_page = None;
(*p2).prev_page = Some(n1);
(n0, n1, n2)
};
let mut head = Some(n0);
unsafe { unlink_page_from_list_raw(n1, &mut head) };
assert_eq!(head, Some(n0));
unsafe {
assert_eq!((*p0).next_page, Some(n2));
assert_eq!((*p0).prev_page, None);
assert_eq!((*p2).prev_page, Some(n0));
assert_eq!((*p2).next_page, None);
assert_eq!((*p1).next_page, None);
assert_eq!((*p1).prev_page, None);
}
unsafe { unlink_page_from_list_raw(n0, &mut head) };
assert_eq!(head, Some(n2));
unsafe {
assert_eq!((*p2).prev_page, None);
assert_eq!((*p2).next_page, None);
assert_eq!((*p0).next_page, None);
assert_eq!((*p0).prev_page, None);
}
unsafe { unlink_page_from_list_raw(n2, &mut head) };
assert!(head.is_none());
unsafe {
drop(std::boxed::Box::from_raw(p0));
drop(std::boxed::Box::from_raw(p1));
drop(std::boxed::Box::from_raw(p2));
}
}
#[test]
fn unlink_full_page_reports_found_status_without_mutating_missing_page() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let mut alloc = ThreadAllocator::<DefaultBackend>::new();
let class =
mnemosyne_core::size_class::size_to_class(16).expect("16 bytes is a small allocation");
let mut listed = Page::new();
let mut missing = Page::new();
listed.list_state = 2; let listed_ptr = NonNull::from(&mut listed);
let missing_ptr = NonNull::from(&mut missing);
alloc.full_pages[class] = Some(listed_ptr);
let removed_missing = unsafe { alloc.unlink_full_page(missing_ptr.as_ptr(), class) };
assert!(
!removed_missing,
"unlink_full_page reported removal for a page outside the full list"
);
assert_eq!(
alloc.full_pages[class].map(NonNull::as_ptr),
Some(listed_ptr.as_ptr())
);
assert_eq!(missing.next_page, None);
let removed_listed = unsafe { alloc.unlink_full_page(listed_ptr.as_ptr(), class) };
assert!(
removed_listed,
"unlink_full_page did not report removal for the listed page"
);
assert_eq!(alloc.full_pages[class], None);
assert_eq!(listed.next_page, None);
}