#![cfg(feature = "std")]
#![allow(clippy::unwrap_used, reason = "test code")]
#![allow(clippy::std_instead_of_core, reason = "test code uses std")]
#![allow(clippy::collection_is_never_read, reason = "tests retain handles to keep chunks alive")]
use std::alloc::Layout;
use std::ptr::NonNull;
use std::sync::{Arc, Mutex};
use allocator_api2::alloc::{AllocError, Allocator, Global};
use multitude::Arena;
const CHUNK_ALIGN: usize = 65_536;
type RequestLog = Arc<Mutex<Vec<(usize, usize)>>>;
#[derive(Clone)]
struct Recorder {
requests: RequestLog,
}
unsafe impl Allocator for Recorder {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
self.requests.lock().unwrap().push((layout.size(), layout.align()));
Global.allocate(layout)
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
unsafe { Global.deallocate(ptr, layout) };
}
}
fn recorder() -> (Recorder, RequestLog) {
let requests = Arc::new(Mutex::new(Vec::new()));
(
Recorder {
requests: Arc::clone(&requests),
},
requests,
)
}
fn chunk_sizes(log: &RequestLog) -> Vec<usize> {
log.lock()
.unwrap()
.iter()
.filter(|(_, align)| *align >= CHUNK_ALIGN)
.map(|(size, _)| *size)
.collect()
}
#[test]
fn small_chunk_is_not_inflated_to_chunk_align() {
let (rec, log) = recorder();
{
let arena = Arena::new_in(rec);
let a = arena.alloc_arc(7_u32);
assert_eq!(*a, 7);
let chunks = chunk_sizes(&log);
assert!(
!chunks.is_empty(),
"expected at least one chunk allocation, got {:?}",
log.lock().unwrap()
);
for size in &chunks {
assert!(
*size < CHUNK_ALIGN,
"a chunk for a single small Arc was allocated at {size} B (>= {CHUNK_ALIGN}); the size was inflated to the base alignment"
);
}
}
}
#[cfg(feature = "stats")]
#[test]
fn total_bytes_allocated_matches_real_chunk_footprint() {
let (rec, log) = recorder();
let arena = Arena::new_in(rec);
let _a = arena.alloc_arc(123_u64);
let real_chunk_bytes: usize = chunk_sizes(&log).iter().sum();
assert!(real_chunk_bytes > 0, "expected a chunk allocation, got {:?}", log.lock().unwrap());
let tracked = arena.stats().total_bytes_allocated;
assert_eq!(
tracked, real_chunk_bytes as u64,
"total_bytes_allocated ({tracked}) must equal the real chunk bytes requested from the allocator ({real_chunk_bytes})"
);
}
#[cfg(feature = "stats")]
#[test]
fn total_bytes_allocated_matches_oversized_chunk_footprint() {
let (rec, log) = recorder();
let arena = Arena::new_in(rec);
let data = vec![0xABu8; 70_001];
let _a = arena.alloc_slice_copy_arc(&data);
let real_chunk_bytes: usize = chunk_sizes(&log).iter().sum();
assert!(
real_chunk_bytes > 70_001,
"expected an oversized chunk allocation, got {:?}",
log.lock().unwrap()
);
assert_eq!(real_chunk_bytes % 8, 0, "the oversized allocation size should be value-aligned");
let tracked = arena.stats().total_bytes_allocated;
assert_eq!(
tracked, real_chunk_bytes as u64,
"total_bytes_allocated ({tracked}) must equal the real (rounded) oversized chunk footprint ({real_chunk_bytes})"
);
}
#[test]
fn chunk_sizes_track_size_classes() {
let (rec, log) = recorder();
let arena = Arena::new_in(rec);
let mut handles = Vec::new();
for i in 0..2_000_u32 {
handles.push(arena.alloc_arc(i));
}
let chunks = chunk_sizes(&log);
assert!(chunks.len() >= 2, "expected multiple chunks, got sizes {chunks:?}");
assert!(
chunks.iter().any(|&s| s < CHUNK_ALIGN),
"every chunk was allocated at {CHUNK_ALIGN} B: sizes {chunks:?}"
);
for &s in &chunks {
assert!(s <= CHUNK_ALIGN, "chunk size {s} exceeds max chunk bytes");
assert!(s.is_power_of_two(), "chunk size {s} is not a power-of-two class size");
}
}