use proptest::prelude::*;
use rusty_alloc::alloc;
fn cfg() -> ProptestConfig {
ProptestConfig {
failure_persistence: None,
cases: if cfg!(miri) { 4 } else { 256 },
..ProptestConfig::default()
}
}
fn any_size() -> impl Strategy<Value = usize> {
prop_oneof![
5 => 0usize..1024, 3 => 1024usize..65_536, 2 => 65_536usize..1_000_000, 1 => 1_000_000usize..(if cfg!(miri) { 1_200_000 } else { 8_000_000 }), ]
}
proptest! {
#![proptest_config(cfg())]
#[test]
fn usable_size_is_at_least_requested_and_stable(size in any_size()) {
let p = alloc::malloc(size);
prop_assert!(!p.is_null(), "malloc({size}) returned null");
unsafe {
let u1 = alloc::usable_size(p);
let u2 = alloc::usable_size(p);
prop_assert!(u1 >= size, "usable_size {u1} < requested {size}");
prop_assert_eq!(u1, u2, "usable_size changed while the block was live");
alloc::free(p);
}
}
#[test]
fn zalloc_is_zero_across_the_whole_usable_extent(size in 1usize..200_000) {
let dirty = alloc::malloc(size);
if !dirty.is_null() {
unsafe {
core::ptr::write_bytes(dirty, 0xDD, size);
alloc::free(dirty);
}
}
let p = alloc::zalloc(size);
prop_assert!(!p.is_null());
unsafe {
let u = alloc::usable_size(p);
for i in [0, size / 2, size - 1, u - 1] {
prop_assert_eq!(*p.add(i), 0, "zalloc byte {} was not zero", i);
}
alloc::free(p);
}
}
#[test]
fn aligned_blocks_are_aligned(size in 1usize..100_000, shift in 0u32..13) {
let align = 1usize << shift;
let p = alloc::malloc_aligned(size, align);
prop_assert!(!p.is_null(), "malloc_aligned({size}, {align}) returned null");
prop_assert_eq!(p.addr() % align, 0, "block not {}-aligned", align);
unsafe {
prop_assert!(alloc::usable_size(p) >= size);
alloc::free(p);
}
}
#[test]
fn realloc_preserves_the_prefix(old in 1usize..80_000, new in 1usize..80_000) {
let p = alloc::malloc(old);
prop_assert!(!p.is_null());
unsafe {
core::ptr::write_bytes(p, 0x3C, old);
let np = alloc::realloc(p, new);
prop_assert!(!np.is_null());
let keep = old.min(new);
for i in [0, keep / 2, keep - 1] {
prop_assert_eq!(*np.add(i), 0x3C, "realloc lost prefix byte {}", i);
}
prop_assert!(alloc::usable_size(np) >= new);
alloc::free(np);
}
}
#[test]
fn live_blocks_never_overlap(sizes in prop::collection::vec(1usize..8_000, 1..64)) {
let mut live: Vec<(*mut u8, usize, u8)> = Vec::new();
for (i, &n) in sizes.iter().enumerate() {
let p = alloc::malloc(n);
if p.is_null() {
continue;
}
let tag = (i as u8).wrapping_add(1);
unsafe { core::ptr::write_bytes(p, tag, n) };
live.push((p, n, tag));
}
for &(p, n, tag) in &live {
unsafe {
for i in [0, n / 2, n - 1] {
prop_assert_eq!(*p.add(i), tag, "block contents overlapped");
}
}
}
for (p, _, _) in live {
unsafe { alloc::free(p) };
}
}
#[test]
fn good_size_is_idempotent_and_never_shrinks(size in 0usize..2_000_000) {
let g = rusty_alloc::bins::good_size(size);
prop_assert!(g >= size, "good_size({size}) = {g} shrank the request");
prop_assert_eq!(rusty_alloc::bins::good_size(g), g, "good_size is not idempotent");
}
#[test]
fn usable_size_agrees_with_good_size(size in 1usize..100_000) {
let g = rusty_alloc::bins::good_size(size);
let p = alloc::malloc(size);
prop_assert!(!p.is_null());
unsafe {
let u = alloc::usable_size(p);
prop_assert!(
u >= g,
"usable_size {u} is below the {g} that good_size promised for a {size}-byte request"
);
alloc::free(p);
}
}
#[test]
fn zero_and_null_edge_cases(_seed in 0u8..8) {
unsafe { alloc::free(core::ptr::null_mut()) };
let p = alloc::malloc(0);
prop_assert!(!p.is_null(), "malloc(0) must return a unique freeable pointer");
unsafe { alloc::free(p) };
}
}