use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
static DIAG_SERIAL: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn install_is_idempotent() {
let _serial = DIAG_SERIAL
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
mlxrs::diagnostics::install();
mlxrs::diagnostics::install();
mlxrs::diagnostics::install();
}
#[test]
fn panic_hook_chains_previous() {
let _serial = DIAG_SERIAL
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let harness_hook = std::panic::take_hook();
let prior_ran = Arc::new(AtomicBool::new(false));
let prior_ran_in_hook = Arc::clone(&prior_ran);
std::panic::set_hook(Box::new(move |_info| {
prior_ran_in_hook.store(true, Ordering::SeqCst);
}));
mlxrs::diagnostics::install();
let res = std::panic::catch_unwind(|| {
panic!("intentional panic to exercise the chained hook");
});
assert!(res.is_err(), "catch_unwind should have caught the panic");
std::panic::set_hook(harness_hook);
assert!(
prior_ran.load(Ordering::SeqCst),
"diagnostics::install() clobbered the previously-installed panic hook \
instead of chaining it"
);
}
const SIGABRT_CHILD_ENV: &str = "MLXRS_DIAG_SIGABRT_CHILD";
#[test]
fn sigabrt_is_not_swallowed() {
if std::env::var_os(SIGABRT_CHILD_ENV).is_some() {
mlxrs::diagnostics::install();
unsafe {
libc::raise(libc::SIGABRT);
}
std::process::exit(0);
}
use std::{os::unix::process::ExitStatusExt, process::Command};
let exe = std::env::current_exe().expect("current_exe");
let output = Command::new(exe)
.args([
"--exact",
"sigabrt_is_not_swallowed",
"--nocapture",
"--test-threads=1",
])
.env(SIGABRT_CHILD_ENV, "1")
.output()
.expect("spawn child test binary");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("mlxrs: process aborted (SIGABRT)"),
"child stderr missing the fixed mlxrs SIGABRT diagnostic message; \
stderr was:\n{stderr}"
);
assert_eq!(
output.status.signal(),
Some(libc::SIGABRT),
"child did not terminate via SIGABRT (abort was swallowed); \
status: {:?}, stderr:\n{stderr}",
output.status
);
}
const SIGABRT_CHAIN_ENV: &str = "MLXRS_DIAG_SIGABRT_CHAIN_CHILD";
extern "C" fn prior_sigabrt_handler(_sig: libc::c_int) {
const SENTINEL: &[u8] = b"PRIORHANDLER\n";
unsafe {
libc::write(2, SENTINEL.as_ptr() as *const libc::c_void, SENTINEL.len());
libc::_exit(42);
}
}
#[test]
fn sigabrt_chains_previous_handler() {
if std::env::var_os(SIGABRT_CHAIN_ENV).is_some() {
let mut act: libc::sigaction = unsafe { std::mem::zeroed() };
act.sa_sigaction = prior_sigabrt_handler as *const () as libc::sighandler_t;
unsafe {
libc::sigemptyset(&mut act.sa_mask);
libc::sigaction(libc::SIGABRT, &act, std::ptr::null_mut());
}
mlxrs::diagnostics::install();
unsafe {
libc::raise(libc::SIGABRT);
}
std::process::exit(0);
}
use std::process::Command;
let exe = std::env::current_exe().expect("current_exe");
let output = Command::new(exe)
.args([
"--exact",
"sigabrt_chains_previous_handler",
"--nocapture",
"--test-threads=1",
])
.env(SIGABRT_CHAIN_ENV, "1")
.output()
.expect("spawn child test binary");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("mlxrs: process aborted (SIGABRT)"),
"child stderr missing the fixed mlxrs SIGABRT diagnostic message \
(mlxrs handler did not run); stderr was:\n{stderr}"
);
assert!(
stderr.contains("PRIORHANDLER"),
"child stderr missing the prior handler's sentinel — the previous \
SIGABRT disposition was clobbered or read as garbage instead of being \
captured and chained; stderr was:\n{stderr}"
);
}