use std::hint::black_box;
use std::thread;
use alloc_tracker::Session;
use crate::report_total_bytes;
#[test]
#[cfg_attr(miri, ignore)] fn multithreaded_allocations_show_span_differences() {
const NUM_WORKER_THREADS: u32 = 4;
const ALLOCATIONS_PER_THREAD: u32 = 50;
const MAIN_THREAD_ALLOCATIONS: u32 = 10;
const TEST_ITERATIONS: usize = 3;
let session = Session::new().no_stdout().no_file();
let spawn_workers = || {
let handles: Vec<_> = (0..NUM_WORKER_THREADS)
.map(|thread_id| {
thread::spawn(move || {
for i in 0..ALLOCATIONS_PER_THREAD {
let size = ((thread_id + 1) * 100 + i) as usize;
let data = vec![42_u8; size];
black_box(data);
}
})
})
.collect();
for i in 0..MAIN_THREAD_ALLOCATIONS {
#[expect(
clippy::cast_possible_truncation,
reason = "small test values will not truncate"
)]
let data = vec![i as u8; 100];
black_box(data);
}
for handle in handles {
handle.join().unwrap();
}
};
{
let process_op = session.operation("process_multithreaded");
for _ in 0..TEST_ITERATIONS {
let _span = process_op.measure_process().iterations(1);
spawn_workers();
}
}
let process_total = report_total_bytes(&session, "process_multithreaded");
{
let thread_op = session.operation("thread_multithreaded");
for _ in 0..TEST_ITERATIONS {
let _span = thread_op.measure_thread().iterations(1);
spawn_workers();
}
}
let thread_total = report_total_bytes(&session, "thread_multithreaded");
assert!(process_total > 0);
assert!(thread_total > 0);
assert!(process_total > thread_total * 2);
}
#[test]
#[cfg_attr(miri, ignore)] fn mixed_span_types_in_multithreaded_context() {
const ITERATIONS: usize = 3;
let session = Session::new().no_stdout().no_file();
let mixed_op = session.operation("mixed_multithreaded");
for iteration in 1..=ITERATIONS {
if iteration % 2 == 0 {
let _span = mixed_op.measure_process().iterations(1);
let handle = thread::spawn(|| {
let data = vec![0_u8; 500];
black_box(data);
});
let data = vec![0_u8; 100];
black_box(data);
handle.join().unwrap();
} else {
let _span = mixed_op.measure_thread().iterations(1);
let handle = thread::spawn(|| {
let data = vec![0_u8; 500];
black_box(data);
});
let data = vec![0_u8; 100];
black_box(data);
handle.join().unwrap();
}
}
let total = report_total_bytes(&session, "mixed_multithreaded");
assert!(total > 0);
}
#[test]
#[cfg_attr(miri, ignore)] fn process_report_includes_allocations_from_multiple_threads() {
const THREAD_A_ALLOCS: usize = 40;
const THREAD_B_ALLOCS: usize = 25;
const SIZE_A: usize = 128; const SIZE_B: usize = 256;
let session = Session::new().no_stdout().no_file();
{
let op = session.operation("two_thread_process");
let _span = op.measure_process().iterations(1);
let handle_a = thread::spawn(|| {
let mut total = 0_usize;
for _ in 0..THREAD_A_ALLOCS {
let v = vec![0_u8; SIZE_A];
total += v.len();
black_box(&v);
}
total
});
let handle_b = thread::spawn(|| {
let mut total = 0_usize;
for _ in 0..THREAD_B_ALLOCS {
let v = vec![1_u8; SIZE_B];
black_box(&v);
total += v.len();
}
total
});
let main_alloc = vec![2_u8; 64];
black_box(&main_alloc);
let a_bytes = handle_a.join().unwrap();
let b_bytes = handle_b.join().unwrap();
assert_eq!(a_bytes, THREAD_A_ALLOCS * SIZE_A);
assert_eq!(b_bytes, THREAD_B_ALLOCS * SIZE_B);
}
let report = session.to_report();
let operations: Vec<_> = report.operations().collect();
assert_eq!(operations.len(), 1);
let (_name, op) = operations.first().unwrap();
let total = op.total_bytes_allocated();
let min_expected = (THREAD_A_ALLOCS * SIZE_A + THREAD_B_ALLOCS * SIZE_B) as u64;
assert!(total >= min_expected);
assert!(total >= (THREAD_A_ALLOCS * SIZE_A) as u64);
assert!(total >= (THREAD_B_ALLOCS * SIZE_B) as u64);
}