use std::alloc::{GlobalAlloc, Layout, System};
use std::cell::Cell;
use std::io::Write;
use std::sync::atomic::{AtomicUsize, Ordering};
static ALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
thread_local! {
static RECORDING: Cell<bool> = const { Cell::new(false) };
}
struct CountingAllocator;
unsafe impl GlobalAlloc for CountingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
if RECORDING.with(Cell::get) {
ALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
}
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
}
#[global_allocator]
static ALLOCATOR: CountingAllocator = CountingAllocator;
fn count_allocations(body: impl FnOnce()) -> usize {
RECORDING.with(|r| r.set(false));
ALLOC_COUNT.store(0, Ordering::Relaxed);
RECORDING.with(|r| r.set(true));
body();
RECORDING.with(|r| r.set(false));
ALLOC_COUNT.load(Ordering::Relaxed)
}
#[test]
fn test_plain_write_path_is_allocation_free() {
let mut buffer: Vec<u8> = Vec::with_capacity(256);
let line = "deploying release artifacts to the staging environment";
writeln!(buffer, "{line}").expect("write to buffer");
buffer.clear();
let allocations = count_allocations(|| {
writeln!(buffer, "{line}").expect("write to buffer");
});
assert_eq!(
allocations, 0,
"the plain output path allocated {allocations} time(s); it must be allocation-free"
);
assert_eq!(buffer, format!("{line}\n").into_bytes());
}
#[test]
fn test_counter_detects_allocations() {
let allocations = count_allocations(|| {
let v: Vec<u8> = Vec::with_capacity(64);
std::hint::black_box(v);
});
assert!(
allocations >= 1,
"counting allocator failed to observe an allocation"
);
}