pub mod common;
use std::{cell, mem};
use common::{
Order,
alloc::{
AlignedZst,
owner::{self, Aligned},
system::Tracker,
},
};
use o3::alloc::{self, array};
#[global_allocator]
static ALLOCATOR: Tracker = Tracker;
#[test]
fn defaults_preserve_owners() {
let (built, cold) = Tracker::measure(|| {
let values: Result<Box<[Box<u16>]>, alloc::Error> = alloc::BoxSliceExt::try_default(3);
values
});
let bytes = 3 * (mem::size_of::<Box<u16>>() + mem::size_of::<u16>());
assert_eq!(cold, [4, bytes, 0, 0]);
let mut values = built.expect("outer slice and three default owners");
*values[0] = 7;
let ((), freed) = Tracker::measure(move || {
assert_eq!((*values[0], *values[1], *values[2]), (7, 0, 0));
});
assert_eq!(freed, [0, 0, 4, bytes]);
let (failed, activity) = Tracker::measure(|| {
let values: Result<Box<[Box<u16>]>, alloc::Error> =
Tracker::fail_after(0, || alloc::BoxSliceExt::try_default(3));
values
});
assert!(matches!(failed, Err(alloc::Error::Exhausted)));
assert_eq!(activity, [0; 4]);
}
#[test]
fn slice_keeps_borrows() {
let source = [String::from("left"), String::from("right")];
let borrowed: Result<Box<[&str]>, _> =
alloc::BoxSliceExt::try_from_fn(source.len(), |index| source[index].as_str());
let borrowed = (borrowed).expect("the two-element borrowed slice must allocate");
assert_eq!(&*borrowed, ["left", "right"]);
}
#[test]
fn aligned_layouts_match() {
let (built, cold) = Tracker::measure(|| {
let boxed: Result<Box<Aligned>, alloc::Exhausted> = alloc::BoxExt::try_new(Aligned(7));
let slice: Result<Box<[Aligned]>, alloc::Error> =
alloc::BoxSliceExt::try_from_fn(3, |index| Aligned(index as u8));
(boxed, slice)
});
let bytes = 4 * mem::size_of::<Aligned>();
assert_eq!(cold, [2, bytes, 0, 0]);
let boxed = built.0.expect("aligned box allocation");
let slice = built.1.expect("aligned slice allocation");
assert_eq!(boxed.0, 7);
assert_eq!(
slice.iter().map(|entry| entry.0).collect::<Vec<_>>(),
[0, 1, 2]
);
assert_eq!(
boxed.as_ref() as *const Aligned as usize % mem::align_of::<Aligned>(),
0
);
assert_eq!(slice.as_ptr() as usize % mem::align_of::<Aligned>(), 0);
let ((), freed) = Tracker::measure(|| drop((boxed, slice)));
assert_eq!(freed, [0, 0, 2, bytes]);
}
#[test]
fn zsts_allocate_nothing() {
let (built, activity) = Tracker::measure(|| {
let boxed: Result<Box<AlignedZst>, alloc::Exhausted> = alloc::BoxExt::try_new(AlignedZst);
let slice: Result<Box<[AlignedZst]>, alloc::Error> =
alloc::BoxSliceExt::try_repeat(8, AlignedZst);
let empty: Result<Box<[Aligned]>, alloc::Error> =
alloc::BoxSliceExt::try_repeat(0, Aligned(0));
let values: Result<Vec<AlignedZst>, alloc::Error> = alloc::VecExt::try_new(usize::MAX);
(boxed, slice, empty, values)
});
assert_eq!(activity, [0; 4]);
let boxed = built.0.expect("zero-sized box");
let slice = built.1.expect("zero-sized slice");
let empty = built.2.expect("empty slice");
let values = built.3.expect("zero-sized vector");
assert_eq!(
boxed.as_ref() as *const AlignedZst as usize % mem::align_of::<AlignedZst>(),
0
);
assert_eq!(slice.as_ptr() as usize % mem::align_of::<AlignedZst>(), 0);
assert_eq!(empty.as_ptr() as usize % mem::align_of::<Aligned>(), 0);
assert_eq!(values.as_ptr().addr() % mem::align_of::<AlignedZst>(), 0);
assert_eq!(values.capacity(), usize::MAX);
assert_eq!((slice.len(), empty.len(), values.len()), (8, 0, 0));
}
#[test]
fn array_uninit_keeps_rows() {
let bytes = 3 * mem::size_of::<u32>();
let (rows, cold) = Tracker::measure(|| {
let rows: Result<Box<[mem::MaybeUninit<u32>; 3]>, alloc::Exhausted> =
alloc::BoxArrayExt::try_uninit();
rows
});
assert_eq!(cold, [1, bytes, 0, 0]);
let mut rows = rows.expect("three uninitialized rows must allocate");
rows[0].write(7);
rows[2].write(9);
let (first, last) = unsafe { (rows[0].assume_init(), rows[2].assume_init()) };
assert_eq!((first, last), (7, 9));
let ((), freed) = Tracker::measure(move || {
let released = rows;
assert_eq!(released.len(), 3);
});
assert_eq!(freed, [0, 0, 1, bytes]);
}
#[test]
fn array_uninit_failure() {
let (failed, activity) = Tracker::measure(|| {
let rows: Result<Box<[mem::MaybeUninit<u8>; 4]>, alloc::Exhausted> =
Tracker::fail_after(0, alloc::BoxArrayExt::try_uninit);
rows
});
assert!(matches!(failed, Err(alloc::Exhausted)));
assert_eq!(activity, [0; 4]);
}
#[test]
fn copy_keeps_layout() {
let source = [Aligned(3), Aligned(7)];
let (copied, cold) = Tracker::measure(|| {
let copied: Result<Box<[Aligned]>, alloc::Exhausted> =
alloc::BoxSliceExt::try_copy(&source);
copied
});
assert_eq!(cold, [1, mem::size_of_val(&source), 0, 0]);
let copied = (copied).expect("copy must allocate the exact source layout");
let ((), freed) = Tracker::measure(move || {
assert_eq!((copied[0].0, copied[1].0), (3, 7));
assert_eq!(copied.as_ptr() as usize % mem::align_of::<Aligned>(), 0);
assert_ne!(copied.as_ptr(), source.as_ptr());
});
assert_eq!(freed, [0, 0, 1, mem::size_of_val(&source)]);
assert_eq!(
mem::size_of::<Result<Box<[Aligned]>, alloc::Exhausted>>(),
mem::size_of::<Box<[Aligned]>>()
);
}
#[test]
fn copy_keeps_borrows() {
let source = [String::from("left"), String::from("right")];
let values = [source[0].as_str(), source[1].as_str()];
let copied: Result<Box<[&str]>, alloc::Exhausted> = alloc::BoxSliceExt::try_copy(&values);
let copied = (copied).expect("copy must retain the borrowed elements");
assert_eq!(&*copied, values);
assert_eq!(copied[0].as_ptr(), source[0].as_ptr());
assert_eq!(copied[1].as_ptr(), source[1].as_ptr());
}
#[test]
fn copy_allocates_no_zsts() {
let zsts = [AlignedZst; usize::MAX];
let (copied, activity) = Tracker::measure(|| {
let zsts: Result<Box<[AlignedZst]>, alloc::Exhausted> = alloc::BoxSliceExt::try_copy(&zsts);
let empty: Result<Box<[Aligned]>, alloc::Exhausted> = alloc::BoxSliceExt::try_copy(&[]);
(zsts, empty)
});
assert_eq!(activity, [0; 4]);
let zsts = copied.0.expect("zero-sized copy");
let empty = copied.1.expect("empty copy");
assert_eq!((zsts.len(), empty.len()), (usize::MAX, 0));
assert_eq!(zsts.as_ptr() as usize % mem::align_of::<AlignedZst>(), 0);
assert_eq!(empty.as_ptr() as usize % mem::align_of::<Aligned>(), 0);
}
#[test]
fn copy_failure_is_inert() {
let source = [3u64, 7];
let (copied, activity) = Tracker::measure(|| {
Tracker::fail_after(0, || {
let copied: Result<Box<[u64]>, alloc::Exhausted> =
alloc::BoxSliceExt::try_copy(&source);
copied
})
});
assert!(matches!(copied, Err(alloc::Exhausted)));
assert_eq!(activity, [0; 4]);
assert_eq!(source, [3, 7]);
}
#[test]
fn init_failure_is_inert() {
let mut calls = 0;
let mut init = || {
calls += 1;
7_u16
};
let (results, activity) = Tracker::measure(|| {
let overflow: Result<Box<[u16]>, alloc::Error> =
alloc::BoxSliceExt::try_init(usize::MAX, &mut init);
let failed: Result<Box<[u16]>, alloc::Error> =
Tracker::fail_after(0, || alloc::BoxSliceExt::try_init(2, &mut init));
(overflow, failed)
});
assert!(matches!(results.0, Err(alloc::Error::Overflow)));
assert!(matches!(results.1, Err(alloc::Error::Exhausted)));
assert_eq!(calls, 0);
assert_eq!(activity, [0; 4]);
}
#[test]
fn init_releases_owners() {
let drops = cell::Cell::new(Some(0));
let mut calls = 0;
let ((), activity) = Tracker::measure(|| {
let values: Result<Box<[owner::Owner<'_>]>, _> =
alloc::BoxSliceExt::try_from_fn(4, |index| {
calls += 1;
owner::Owner {
drops: &drops,
value: Box::new(index + 1),
}
});
let values = values.expect("four initialized owners");
assert_eq!(values.len(), 4);
});
assert_eq!(calls, 4);
assert_eq!(drops.get(), Some(1234));
let bytes = 4 * (mem::size_of::<owner::Owner<'_>>() + mem::size_of::<usize>());
assert_eq!(activity, [5, bytes, 5, bytes]);
}
#[test]
fn init_counts_zsts() {
let mut calls = 0;
let (built, activity) = Tracker::measure(|| {
let mut init = |index| {
assert_eq!(index, calls);
calls += 1;
AlignedZst
};
let empty: Result<Box<[AlignedZst]>, alloc::Error> =
alloc::BoxSliceExt::try_from_fn(0, &mut init);
let values: Result<Box<[AlignedZst]>, alloc::Error> =
alloc::BoxSliceExt::try_from_fn(8, init);
(empty, values)
});
assert_eq!(calls, 8);
assert_eq!(activity, [0; 4]);
let empty = built.0.expect("empty initialization");
let values = built.1.expect("zero-sized initialization");
assert_eq!((empty.len(), values.len()), (0, 8));
assert_eq!(values.as_ptr() as usize % mem::align_of::<AlignedZst>(), 0);
}
#[test]
fn block_owns_elements() {
let drops = cell::Cell::new(Some(0));
let (block, activity) = Tracker::measure(|| {
array::Block::try_new(3_usize, Order(&drops, 4))
.map(|block| block.init(|index| Order(&drops, index as u64 + 1)))
});
let mut block = block.expect("initialized element allocation");
let bytes = 3 * mem::size_of::<Order<'_>>();
assert_eq!(activity, [1, bytes, 0, 0]);
assert_eq!(*block.extent(), 3);
assert_eq!(block.state().1, 4);
block[1].1 = 5;
let ((), released) = Tracker::measure(move || {
assert_eq!(block.iter().map(|value| value.1).sum::<u64>(), 9);
});
assert_eq!(drops.get(), Some(1534));
assert_eq!(released, [0, 0, 1, bytes]);
}
#[test]
fn block_zero_size() {
let mut calls = 0;
let ((), activity) = Tracker::measure(|| {
let empty = array::Block::try_new(0_usize, ())
.expect("empty allocation")
.init(|_| {
calls += 1;
0_u64
});
assert!(empty.is_empty());
assert_eq!(calls, 0);
let zst = array::Block::try_new(7_usize, ())
.expect("zero-sized allocation")
.init(|index| {
assert_eq!(index, calls);
calls += 1;
AlignedZst
});
assert_eq!(zst.len(), 7);
assert_eq!(zst.as_ptr().addr() % mem::align_of::<AlignedZst>(), 0);
});
assert_eq!(calls, 7);
assert_eq!(activity, [0; 4]);
}
#[test]
fn block_vec_owners() {
let drops = cell::Cell::new(Some(0));
let block = array::Block::try_new(3_usize, ())
.expect("owner allocation")
.init(|index| Order(&drops, index as u64 + 1));
let pointer = block.as_ptr();
let (values, transfer) = Tracker::measure(|| block.into_vec());
assert_eq!(transfer, [0; 4]);
assert_eq!(values.as_ptr(), pointer);
assert_eq!((values.len(), values.capacity()), (3, 3));
assert_eq!(drops.get(), Some(0));
let ((), released) = Tracker::measure(|| drop(values));
assert_eq!(drops.get(), Some(123));
assert_eq!(released, [0, 0, 1, 3 * mem::size_of::<Order<'_>>()]);
}
#[test]
fn block_vec_layouts() {
let block = array::Block::try_new(3_usize, ())
.expect("aligned allocation")
.init(|index| Aligned(index as u8));
let pointer = block.as_ptr();
let (values, transfer) = Tracker::measure(|| block.into_vec());
assert_eq!(transfer, [0; 4]);
assert_eq!(values.as_ptr(), pointer);
assert_eq!(values.as_ptr().addr() % mem::align_of::<Aligned>(), 0);
assert_eq!((values.len(), values.capacity()), (3, 3));
for (index, value) in values.iter().enumerate() {
assert_eq!(value.0, index as u8);
}
let ((), released) = Tracker::measure(|| drop(values));
assert_eq!(released, [0, 0, 1, 3 * mem::size_of::<Aligned>()]);
let ((), activity) = Tracker::measure(|| {
let empty = array::Block::try_new(0_usize, ())
.expect("empty backing")
.repeat_with(|| Aligned(0));
let pointer = empty.as_ptr();
let empty = empty.into_vec();
assert_eq!(empty.as_ptr(), pointer);
assert_eq!((empty.len(), empty.capacity()), (0, 0));
let zst = array::Block::try_new(7_usize, ())
.expect("zero-sized backing")
.repeat_with(|| AlignedZst);
let pointer = zst.as_ptr();
let zst = zst.into_vec();
assert_eq!(zst.as_ptr(), pointer);
assert_eq!(zst.as_ptr().addr() % mem::align_of::<AlignedZst>(), 0);
assert_eq!((zst.len(), zst.capacity()), (7, usize::MAX));
let maximum = array::Block::<mem::MaybeUninit<AlignedZst>, usize>::try_new(usize::MAX, ())
.expect("maximum zero-sized backing")
.into_vec();
assert_eq!(
(maximum.len(), maximum.capacity()),
(usize::MAX, usize::MAX)
);
assert_eq!(maximum.as_ptr().addr() % mem::align_of::<AlignedZst>(), 0);
});
assert_eq!(activity, [0; 4]);
}
#[test]
fn slice_plan() {
let plan = {
let source = [Aligned(3), Aligned(7)];
array::Plan::from_slice(&source)
};
assert!(matches!(
Tracker::fail_after(0, || plan.try_vec()),
Err(alloc::Exhausted)
));
let (values, activity) = Tracker::measure(|| plan.try_vec());
let values = values.expect("retained source layout");
assert!(values.is_empty());
assert_eq!(values.capacity(), 2);
assert_eq!(values.as_ptr().addr() % mem::align_of::<Aligned>(), 0);
let bytes = 2 * mem::size_of::<Aligned>();
assert_eq!(activity, [1, bytes, 0, 0]);
let ((), released) = Tracker::measure(|| drop(values));
assert_eq!(released, [0, 0, 1, bytes]);
}