use std::panic;
use std::sync::atomic::{AtomicUsize, Ordering};
#[global_allocator]
static GLOBAL: alloc_chaos::ChaosAllocator = alloc_chaos::ChaosAllocator::system();
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum BuildError {
OutOfMemory,
}
#[derive(Debug, PartialEq, Eq)]
struct Packet {
header: Vec<u8>,
body: Vec<u8>,
}
fn reserve_zeroed(len: usize) -> Result<Vec<u8>, BuildError> {
let mut bytes = Vec::new();
bytes
.try_reserve_exact(len)
.map_err(|_| BuildError::OutOfMemory)?;
bytes.resize(len, 0);
Ok(bytes)
}
fn build_packet() -> Result<Packet, BuildError> {
Ok(Packet {
header: reserve_zeroed(16)?,
body: reserve_zeroed(256)?,
})
}
fn checked_packet_builder() {
match build_packet() {
Ok(packet) => {
assert_eq!(packet.header.len(), 16);
assert_eq!(packet.body.len(), 256);
}
Err(BuildError::OutOfMemory) => {}
}
}
fn main() {
strict_success();
bounded_diagnostic_run();
range_diagnostic_run();
reproduce_one_failure_and_show_metadata();
unstable_baseline_diagnostic();
mishandled_oom_diagnostic();
}
fn strict_success() {
let report = alloc_chaos::check(checked_packet_builder);
print_report("strict exhaustive check", &report);
report.assert_success();
}
fn bounded_diagnostic_run() {
let report = alloc_chaos::Check::new()
.max_failures(1)
.stop_on_failure(true)
.run(checked_packet_builder);
print_report("bounded diagnostic run", &report);
assert!(report.is_truncated());
assert!(!report.is_success());
}
fn range_diagnostic_run() {
let report = alloc_chaos::Check::new()
.failure_range(1..2)
.run(checked_packet_builder);
print_report("selected failure range", &report);
assert!(report.is_truncated());
assert_eq!(report.tested_failures(), 1);
assert!(!report.is_success());
}
fn reproduce_one_failure_and_show_metadata() {
let full_report = alloc_chaos::check(checked_packet_builder);
full_report.assert_success();
let target = full_report
.attempts()
.first()
.map(alloc_chaos::Attempt::target_allocation)
.expect("packet builder should allocate during the baseline run");
let report = alloc_chaos::Check::new()
.only_failure(target)
.run(checked_packet_builder);
print_report("single-target reproduction", &report);
print_allocation_metadata(&report);
assert!(report.is_truncated());
assert_eq!(report.tested_failures(), 1);
assert!(report.attempts()[0].is_success());
assert!(!report.is_success());
}
fn unstable_baseline_diagnostic() {
let runs = AtomicUsize::new(0);
let report = alloc_chaos::Check::new().stability_runs(2).run(|| {
if runs.fetch_add(1, Ordering::SeqCst) == 0 {
checked_packet_builder();
}
});
print_report("unstable baseline", &report);
assert!(!report.baseline_is_stable());
assert!(report.attempts().is_empty());
assert!(!report.is_success());
}
fn mishandled_oom_diagnostic() {
with_quiet_expected_panics(|| {
let report = alloc_chaos::Check::new()
.max_failures(1)
.run(|| match build_packet() {
Ok(packet) => assert_eq!(packet.body.len(), 256),
Err(BuildError::OutOfMemory) => panic!("allocation failure was not handled"),
});
print_report("mishandled OOM path", &report);
assert!(report.first_failure().is_some());
assert!(!report.is_success());
});
}
fn print_report(title: &str, report: &alloc_chaos::Report) {
println!("\n=== {title} ===");
println!("{report}");
}
fn print_allocation_metadata(report: &alloc_chaos::Report) {
for attempt in report.attempts() {
if let Some(allocation) = attempt.injected_allocation() {
println!(
"metadata: target #{} -> {} size={} align={} new_size={:?}",
allocation.index(),
allocation.operation(),
allocation.size(),
allocation.align(),
allocation.new_size(),
);
}
}
}
fn with_quiet_expected_panics<R>(f: impl FnOnce() -> R) -> R {
let previous_hook = panic::take_hook();
panic::set_hook(Box::new(|_| {}));
let result = panic::catch_unwind(panic::AssertUnwindSafe(f));
panic::set_hook(previous_hook);
match result {
Ok(value) => value,
Err(payload) => panic::resume_unwind(payload),
}
}