#![allow(clippy::unwrap_used, clippy::panic, clippy::arithmetic_side_effects)]
use std::alloc::{GlobalAlloc, Layout, System};
use std::cell::Cell;
use pragma_base::{BaseError, HandlePool};
struct Failing;
thread_local! {
static FAIL: Cell<bool> = const { Cell::new(false) };
}
fn failing() -> bool {
FAIL.try_with(Cell::get).unwrap_or(false)
}
unsafe impl GlobalAlloc for Failing {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
if failing() {
return std::ptr::null_mut();
}
unsafe { System.alloc(layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
if failing() {
return std::ptr::null_mut();
}
unsafe { System.realloc(ptr, layout, new_size) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
}
#[global_allocator]
static GLOBAL: Failing = Failing;
fn without_memory<R>(f: impl FnOnce() -> R) -> R {
FAIL.with(|fail| fail.set(true));
let result = f();
FAIL.with(|fail| fail.set(false));
result
}
#[test]
fn growth_without_memory_is_an_error_and_pool_stays_usable() {
let mut pool: HandlePool<u64> = HandlePool::new();
assert!(matches!(
without_memory(|| pool.insert(1)),
Err(BaseError::OutOfMemory { slots: 0, .. })
));
assert!(pool.is_empty());
let handles: Vec<_> = (0..100).map(|v| pool.insert(v).unwrap()).collect();
let reused = without_memory(|| {
let value = pool.remove(handles[10]).unwrap();
let reused = pool.insert(value + 1000).unwrap();
(value, reused)
});
assert_eq!(reused.0, 10);
assert_eq!(pool.get(reused.1), Some(&1010));
let error = without_memory(|| (0..1_000_000_u64).find_map(|v| pool.insert(v).err()));
let Some(BaseError::OutOfMemory { slots, .. }) = error else {
panic!("expected OutOfMemory, got {error:?}");
};
assert_eq!(slots, pool.len());
assert_eq!(pool.get(handles[0]), Some(&0));
assert!(pool.insert(7).is_ok());
}