use nexus_slab::Slot;
use nexus_slab::bounded::Slab as BoundedSlab;
use nexus_slab::unbounded::Slab as UnboundedSlab;
use proptest::prelude::*;
use std::collections::HashSet;
#[test]
fn bounded_value_integrity_random() {
let slab = unsafe { BoundedSlab::<u64>::with_capacity(50) };
let mut rng = proptest::test_runner::TestRng::deterministic_rng(
proptest::test_runner::RngAlgorithm::ChaCha,
);
let mut slots: Vec<(Slot<u64>, u64)> = Vec::new();
for _ in 0..500 {
let action: u8 = rng.random_range(0..10);
match action {
0..=5 => {
let value: u64 = rng.random();
if let Ok(slot) = slab.try_alloc(value) {
slots.push((slot, value));
}
}
6..=7 => {
if let Some((slot, _)) = slots.pop() {
slab.free(slot);
}
}
8 => {
if !slots.is_empty() {
let idx = rng.random_range(0..slots.len());
let (slot, _) = slots.swap_remove(idx);
slab.free(slot);
}
}
9 => {
if !slots.is_empty() {
let idx = rng.random_range(0..slots.len());
let new_value: u64 = rng.random();
*slots[idx].0 = new_value;
slots[idx].1 = new_value;
}
}
_ => unreachable!(),
}
for (slot, expected) in &slots {
assert_eq!(**slot, *expected);
}
}
for (slot, _) in slots {
slab.free(slot);
}
}
#[test]
fn bounded_capacity_never_exceeded_1() {
let slab = unsafe { BoundedSlab::<u64>::with_capacity(1) };
let mut slots = Vec::new();
for i in 0..200 {
if let Ok(slot) = slab.try_alloc(i) {
slots.push(slot);
}
}
assert!(slots.len() <= 1);
for slot in slots {
slab.free(slot);
}
}
#[test]
fn bounded_capacity_never_exceeded_10() {
let slab = unsafe { BoundedSlab::<u64>::with_capacity(10) };
let mut slots = Vec::new();
for i in 0..200 {
if let Ok(slot) = slab.try_alloc(i) {
slots.push(slot);
}
}
assert!(slots.len() <= 10);
for slot in slots {
slab.free(slot);
}
}
#[test]
fn bounded_capacity_never_exceeded_50() {
let slab = unsafe { BoundedSlab::<u64>::with_capacity(50) };
let mut slots = Vec::new();
for i in 0..200 {
if let Ok(slot) = slab.try_alloc(i) {
slots.push(slot);
}
}
assert!(slots.len() <= 50);
for slot in slots {
slab.free(slot);
}
}
#[test]
fn bounded_fill_drain_integrity() {
let slab = unsafe { BoundedSlab::<u64>::with_capacity(20) };
for cycle in 0..10 {
let slots: Vec<_> = (0..20)
.map(|i| slab.alloc((cycle * 20 + i) as u64))
.collect();
for (i, slot) in slots.iter().enumerate() {
assert_eq!(**slot, (cycle * 20 + i) as u64);
}
for slot in slots {
slab.free(slot);
}
}
}
#[test]
fn unbounded_value_integrity_random() {
let slab = unsafe { UnboundedSlab::<u64>::with_chunk_capacity(8) };
let mut rng = proptest::test_runner::TestRng::deterministic_rng(
proptest::test_runner::RngAlgorithm::ChaCha,
);
let mut slots: Vec<(Slot<u64>, u64)> = Vec::new();
for _ in 0..500 {
let action: u8 = rng.random_range(0..10);
match action {
0..=5 => {
let value: u64 = rng.random();
slots.push((slab.alloc(value), value));
}
6..=7 => {
if let Some((slot, _)) = slots.pop() {
slab.free(slot);
}
}
8 => {
if !slots.is_empty() {
let idx = rng.random_range(0..slots.len());
let (slot, _) = slots.swap_remove(idx);
slab.free(slot);
}
}
9 => {
if !slots.is_empty() {
let idx = rng.random_range(0..slots.len());
let new_value: u64 = rng.random();
*slots[idx].0 = new_value;
slots[idx].1 = new_value;
}
}
_ => unreachable!(),
}
for (slot, expected) in &slots {
assert_eq!(**slot, *expected);
}
}
for (slot, _) in slots {
slab.free(slot);
}
}
#[test]
fn unbounded_growth_maintains_integrity() {
let slab = unsafe { UnboundedSlab::<u64>::with_chunk_capacity(8) };
for count in [10, 50, 100, 200] {
let slots: Vec<_> = (0..count).map(|i| slab.alloc(i as u64)).collect();
for (i, slot) in slots.iter().enumerate() {
assert_eq!(**slot, i as u64);
}
assert!(slab.capacity() >= count);
for slot in slots {
slab.free(slot);
}
}
}
#[test]
fn freelist_no_duplicates() {
let slab = unsafe { BoundedSlab::<u64>::with_capacity(20) };
let mut rng = proptest::test_runner::TestRng::deterministic_rng(
proptest::test_runner::RngAlgorithm::ChaCha,
);
let mut slots: Vec<Slot<u64>> = Vec::new();
let mut seen_ptrs = HashSet::new();
for _ in 0..200 {
let should_insert = rng.random_bool(0.6) || slots.is_empty();
if should_insert {
if let Ok(slot) = slab.try_alloc(0) {
let ptr = slot.as_ptr();
for existing in &slots {
assert_ne!(ptr, existing.as_ptr(), "Duplicate slot returned!");
}
slots.push(slot);
seen_ptrs.insert(ptr as usize);
}
} else if !slots.is_empty() {
let idx = rng.random_range(0..slots.len());
let slot = slots.swap_remove(idx);
slab.free(slot);
}
}
assert!(!seen_ptrs.is_empty());
for slot in slots {
slab.free(slot);
}
}
#[test]
fn freelist_reuses_freed_slots() {
let slab = unsafe { BoundedSlab::<u64>::with_capacity(10) };
let mut slots: Vec<Slot<u64>> = Vec::new();
let mut freed_ptrs: Vec<*mut nexus_slab::SlotCell<u64>> = Vec::new();
for i in 0..100 {
let should_insert = i % 3 != 2 || slots.is_empty();
if should_insert {
if let Ok(slot) = slab.try_alloc(0) {
let ptr = slot.as_ptr();
if let Some(expected) = freed_ptrs.pop() {
assert_eq!(ptr, expected, "Expected LIFO reuse");
}
slots.push(slot);
}
} else if !slots.is_empty() {
let slot = slots.pop().unwrap();
freed_ptrs.push(slot.as_ptr());
slab.free(slot);
}
}
for slot in slots {
slab.free(slot);
}
}
use std::cell::Cell;
thread_local! {
static DROP_COUNTER: Cell<usize> = const { Cell::new(0) };
}
pub struct Counted;
impl Drop for Counted {
fn drop(&mut self) {
DROP_COUNTER.with(|c| c.set(c.get() + 1));
}
}
fn reset_counter() {
DROP_COUNTER.with(|c| c.set(0));
}
fn get_counter() -> usize {
DROP_COUNTER.with(Cell::get)
}
#[test]
fn drop_count_matches_inserts() {
reset_counter();
let slab = unsafe { BoundedSlab::<Counted>::with_capacity(100) };
let count = 50;
{
let slots: Vec<_> = (0..count).map(|_| slab.alloc(Counted)).collect();
for slot in slots {
slab.free(slot);
}
}
assert_eq!(get_counter(), count);
}
#[test]
fn into_inner_prevents_drop() {
reset_counter();
let slab = unsafe { BoundedSlab::<Counted>::with_capacity(100) };
let count = 20;
let slots: Vec<_> = (0..count).map(|_| slab.alloc(Counted)).collect();
let half = count / 2;
let mut values = Vec::new();
let mut remaining_slots = Vec::new();
for (i, slot) in slots.into_iter().enumerate() {
if i < half {
let value = slab.take(slot);
values.push(value);
} else {
remaining_slots.push(slot);
}
}
assert_eq!(get_counter(), 0);
for slot in remaining_slots {
slab.free(slot);
}
assert_eq!(get_counter(), count - half);
drop(values);
assert_eq!(get_counter(), count);
}