use rusty_alloc::alloc::{free, malloc, usable_size};
use rusty_alloc::init;
#[test]
fn csprng_streams_differ_per_heap() {
let h1 = init::create_heap(0, true, -1);
let h2 = init::create_heap(0, true, -1);
unsafe {
let a: Vec<usize> = (0..8)
.map(|_| (*(*h1).heap.get()).rng.next_usize())
.collect();
let b: Vec<usize> = (0..8)
.map(|_| (*(*h2).heap.get()).rng.next_usize())
.collect();
assert_ne!(a, b, "per-heap CSPRNG streams collided");
init::heap_destroy(h1);
init::heap_destroy(h2);
}
}
#[test]
fn free_list_survives_heavy_churn() {
let mut live: Vec<(*mut u8, usize, u8)> = Vec::new();
let mut state = 0x5EC0_0DE5_1234_5678u64;
let mut rng = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
let iters = if cfg!(miri) { 500 } else { 50_000 };
for i in 0..iters {
if !live.is_empty() && (live.len() > 800 || rng() % 2 == 0) {
let idx = (rng() as usize) % live.len();
let (p, size, tag) = live.swap_remove(idx);
unsafe {
assert_eq!(p.read(), tag);
assert_eq!(p.add(size - 1).read(), tag);
free(p);
}
} else {
let size = 8 + (rng() as usize) % 3000;
let p = malloc(size);
assert!(!p.is_null());
let tag = (i as u8) | 1;
unsafe {
assert!(usable_size(p) >= size);
p.write(tag);
p.add(size - 1).write(tag);
}
live.push((p, size, tag));
}
}
for (p, size, tag) in live.drain(..) {
unsafe {
assert_eq!(p.read(), tag);
assert_eq!(p.add(size - 1).read(), tag);
free(p);
}
}
}
#[test]
#[cfg(not(miri))] fn guarded_objects_are_protected() {
let h = init::create_heap(0, true, -1);
unsafe {
(*(*h).heap.get()).guarded_set_size_bound(1, 64 * 1024);
(*(*h).heap.get()).guarded_set_sample_rate(1, 0x1234);
let before = (*(*h).heap.get()).stats.guarded;
let mut ps = Vec::new();
for size in [64usize, 500, 4096, 20_000] {
let p = rusty_alloc::alloc::heap_malloc(h, size);
assert!(!p.is_null());
core::ptr::write_bytes(p, 0xA7, size);
assert_eq!(p.read(), 0xA7);
assert_eq!(p.add(size - 1).read(), 0xA7);
assert_eq!(
(p.addr() + size) % rusty_alloc::os::page_size(),
0,
"guarded object not right-aligned against its guard page"
);
ps.push(p);
}
let after = (*(*h).heap.get()).stats.guarded;
assert!(
after - before >= 4,
"guarded sampling did not fire: {before} → {after}"
);
for p in ps {
free(p);
}
init::heap_destroy(h);
}
}
#[test]
fn purge_returns_memory() {
rusty_alloc::options::set(15, 10); let before = rusty_alloc::alloc::stats().purges;
let mut ps = Vec::new();
for _ in 0..24 {
let p = malloc(600 * 1024); assert!(!p.is_null());
unsafe { core::ptr::write_bytes(p, 1, 600 * 1024) };
ps.push(p);
}
for p in ps {
unsafe { free(p) };
}
rusty_alloc::alloc::collect(true);
let after = rusty_alloc::alloc::stats().purges;
rusty_alloc::options::set(15, -1); assert!(after > before, "no spans purged: {before} → {after}");
}