use std::io::{self, Write};
use std::panic;
use std::sync::Once;
static INIT: Once = Once::new();
pub fn setup_panic_handler() {
INIT.call_once(|| {
let original_hook = panic::take_hook();
panic::set_hook(Box::new(move |panic_info| {
use crossterm::event::DisableMouseCapture;
use crossterm::execute;
use crossterm::terminal::{LeaveAlternateScreen, disable_raw_mode};
let _ = disable_raw_mode();
let _ = execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture);
let _ = io::stdout().flush();
original_hook(panic_info);
let _ = disable_raw_mode();
let _ = execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture);
let _ = io::stderr().flush();
let _ = io::stdout().flush();
}));
});
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
#[test]
fn test_setup_panic_handler_idempotent() {
setup_panic_handler();
setup_panic_handler();
setup_panic_handler();
}
#[test]
fn test_setup_panic_handler_thread_safety() {
let handles: Vec<_> = (0..10)
.map(|_| {
thread::spawn(|| {
setup_panic_handler();
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
}
#[test]
fn test_catch_panic_after_setup() {
setup_panic_handler();
let result = std::panic::catch_unwind(|| 42);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 42);
let result = std::panic::catch_unwind(|| {
panic!("test panic");
});
assert!(result.is_err());
}
#[test]
fn test_panic_payload_preserved() {
setup_panic_handler();
let result = std::panic::catch_unwind(|| {
panic!("preserved message");
});
assert!(result.is_err());
let err = result.unwrap_err();
let msg = err.downcast_ref::<&str>();
assert!(msg.is_some());
assert_eq!(*msg.unwrap(), "preserved message");
}
#[test]
fn test_multiple_setups_from_different_threads() {
let call_count = Arc::new(AtomicUsize::new(0));
let handles: Vec<_> = (0..5)
.map(|_| {
let count = call_count.clone();
thread::spawn(move || {
setup_panic_handler();
count.fetch_add(1, Ordering::SeqCst);
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
assert_eq!(call_count.load(Ordering::SeqCst), 5);
}
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn prop_setup_panic_handler_idempotent(
call_count in 1usize..=20,
) {
for _ in 0..call_count {
setup_panic_handler();
}
let result = std::panic::catch_unwind(|| 42);
prop_assert!(result.is_ok(), "catch_unwind should work after setup");
prop_assert_eq!(result.unwrap(), 42);
}
#[test]
fn prop_panic_payload_preserved_after_multiple_setups(
call_count in 1usize..=10,
panic_value in 1i32..1000,
) {
for _ in 0..call_count {
setup_panic_handler();
}
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
std::panic::panic_any(panic_value);
}));
prop_assert!(result.is_err(), "Panic should be caught");
let err = result.unwrap_err();
let value = err.downcast_ref::<i32>();
prop_assert!(value.is_some(), "Panic payload should be i32");
prop_assert_eq!(*value.unwrap(), panic_value, "Panic value should be preserved");
}
#[test]
fn prop_concurrent_setup_completes(
thread_count in 1usize..=10,
) {
let completed = Arc::new(AtomicUsize::new(0));
let handles: Vec<_> = (0..thread_count)
.map(|_| {
let completed = completed.clone();
std::thread::spawn(move || {
setup_panic_handler();
completed.fetch_add(1, Ordering::SeqCst);
})
})
.collect();
for handle in handles {
handle.join().expect("Thread should complete without panic");
}
prop_assert_eq!(
completed.load(Ordering::SeqCst),
thread_count,
"All threads should complete"
);
}
}
}