#![cfg(feature = "fancy")]
#![cfg_attr(
feature = "unstable-error-generic-member-access",
feature(error_generic_member_access)
)]
use std::process::Command;
use oopsie::oopsie;
const TRIGGER_ENV: &str = "OOPSIE_PANIC_HOOK_TEST_TRIGGER";
const PANIC_MESSAGE: &str = "integration boom";
#[test]
fn panic_child() {
if std::env::var_os(TRIGGER_ENV).is_none() {
return;
}
oopsie::install_panic_hook();
panic!("{PANIC_MESSAGE}");
}
fn run_panicking_child(child_test: &str, extra_env: &[(&str, &str)]) -> String {
let exe = std::env::current_exe().expect("locate test binary");
let mut cmd = Command::new(exe);
cmd.arg(child_test)
.args(["--exact", "--nocapture", "--test-threads=1"])
.env(TRIGGER_ENV, "1");
for (key, value) in extra_env {
cmd.env(key, value);
}
let output = cmd.output().expect("spawn child test process");
let stderr = strip_ansi_escapes::strip(&output.stderr);
let stderr = String::from_utf8_lossy(&stderr).into_owned();
assert!(
!output.status.success(),
"child process should have panicked\n--- stderr ---\n{stderr}"
);
stderr
}
#[test]
fn renders_header_message_and_hint_without_backtrace() {
let stderr = run_panicking_child("panic_child", &[("NO_COLOR", "1"), ("RUST_BACKTRACE", "0")]);
assert!(
stderr.contains("The application panicked (crashed)."),
"missing panic header\n{stderr}"
);
assert!(
stderr.contains(&format!("Message: {PANIC_MESSAGE}")),
"missing panic message\n{stderr}"
);
assert!(stderr.contains("Location:"), "missing location\n{stderr}");
assert!(
stderr.contains("RUST_BACKTRACE=1"),
"missing backtrace hint\n{stderr}"
);
assert!(
!stderr.contains(" BACKTRACE "),
"unexpected backtrace banner\n{stderr}"
);
}
#[test]
fn renders_backtrace_with_user_frame_on_top_and_no_panic_plumbing() {
let stderr = run_panicking_child("panic_child", &[("NO_COLOR", "1"), ("RUST_BACKTRACE", "1")]);
assert!(
stderr.contains("The application panicked (crashed)."),
"missing panic header\n{stderr}"
);
assert!(
stderr.contains(" BACKTRACE "),
"missing backtrace banner\n{stderr}"
);
assert!(
stderr.contains("panic_child"),
"panic site frame missing from backtrace\n{stderr}"
);
for plumbing in [
"core::panicking",
"rust_begin_unwind",
"__rust_end_short_backtrace",
"panic_with_hook",
] {
assert!(
!stderr.contains(plumbing),
"panic plumbing `{plumbing}` leaked into backtrace\n{stderr}"
);
}
}
#[oopsie(traced)]
#[oopsie("never constructed: {message}")]
pub struct NeverError {
message: String,
}
#[test]
fn panic_child_in_run() {
if std::env::var_os(TRIGGER_ENV).is_none() {
return;
}
let _report = oopsie::Report::run(|| -> Result<(), NeverError> {
panic!("{PANIC_MESSAGE}");
});
}
#[test]
fn report_run_panic_backtrace_ends_at_run_boundary() {
let stderr = run_panicking_child(
"panic_child_in_run",
&[("NO_COLOR", "1"), ("RUST_BACKTRACE", "1")],
);
assert!(
stderr.contains(" BACKTRACE "),
"missing backtrace banner\n{stderr}"
);
assert!(
stderr.contains("panic_child_in_run"),
"panic site frame missing\n{stderr}"
);
let boundary_lines: Vec<_> = stderr
.lines()
.filter(|line| line.contains("::report::"))
.collect();
assert!(
boundary_lines.len() == 1 && boundary_lines[0].contains("closure"),
"exactly the run-closure boundary frame may be visible, got {boundary_lines:?}\n{stderr}"
);
assert!(
!stderr.contains("lang_start"),
"runtime tail leaked\n{stderr}"
);
assert!(
!stderr.contains("catch_unwind"),
"catch_unwind cluster leaked\n{stderr}"
);
}
#[test]
fn full_backtrace_still_bypasses_marker_stripping() {
let stderr = run_panicking_child(
"panic_child_in_run",
&[("NO_COLOR", "1"), ("RUST_BACKTRACE", "full")],
);
assert!(
stderr.contains("catch_unwind"),
"full mode must show the unwind plumbing the marker hides\n{stderr}"
);
}