use backtrace::{BytesOrWideString, PrintFmt};
use std::{
error::Error,
fmt::{self, Display},
};
use crate::TestError;
#[derive(Debug)]
pub(crate) struct PanicDetails {
pub backtrace: backtrace::Backtrace,
pub payload: Option<Box<dyn core::any::Any + Send>>,
pub location: Option<(String, u32, u32)>,
pub message: Option<String>,
pub backtrace_function_name: String,
pub test_id: u64,
}
#[derive(Debug)]
pub(crate) struct PanicError {
pub details: Option<PanicDetails>,
}
impl PanicError {
pub fn new(details: Option<PanicDetails>) -> Self { Self { details } }
}
impl Display for PanicError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.details {
Some(pd) => {
if let Some(msg) = &pd.message {
writeln!(f, "Panic: '{}'", msg)?;
}
},
None => {
write!(f, "no panic info")?;
},
}
Ok(())
}
}
impl Error for PanicError {}
impl TestError for PanicError {
fn fails(&self) -> bool { true }
fn exitcode(&self) -> (u8, u64) { (1, 1_000_000_000) }
fn additional_details(&self) -> Option<String> {
use std::fmt::Write;
self.details.as_ref().map(|pd| {
let mut buf = String::new();
if let Some(loc) = &pd.location {
writeln!(buf, "at {}:{}", loc.0, loc.1).unwrap();
}
let bt = FormatBacktrace(&pd.backtrace, Some(&pd.backtrace_function_name));
writeln!(buf, "Stacktrace:").unwrap();
write!(buf, "{:?}", &bt).unwrap();
buf
})
}
fn print_cli(&self) -> String {
use colored::Colorize;
use std::fmt::Write;
let mut buf = String::new();
match &self.details {
Some(pd) => {
if let Some(msg) = &pd.message {
writeln!(buf, "{}: '{}'", "Panic".bold(), msg.red()).unwrap();
}
if let Some(loc) = &pd.location {
writeln!(buf, "at {}:{}", loc.0.yellow(), loc.1.to_string().yellow()).unwrap();
}
let bt = FormatBacktrace(&pd.backtrace, Some(&pd.backtrace_function_name));
writeln!(buf, "{}:", "Stacktrace".italic()).unwrap();
write!(buf, "{:?}", &bt).unwrap();
},
None => {
write!(buf, "no panic info").unwrap();
},
}
buf
}
}
pub(crate) struct FormatBacktrace<'a>(pub(crate) &'a backtrace::Backtrace, pub(crate) Option<&'a str>);
impl<'a> fmt::Debug for FormatBacktrace<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
let frames = &self.0.frames();
let contains_string = self.1;
let begin = frames
.iter()
.position(|f| {
let begin = f
.symbols()
.first()
.map(|s| s.name().map(|n| n.as_str() == Some("rust_begin_unwind")));
begin.flatten().unwrap_or(false)
})
.unwrap_or(0);
let end = frames[begin..].iter().position(|f| {
let end = f.symbols().first().map(|s| {
s.name().map(|n| {
n.as_str().map(|s| match contains_string {
Some(cs) => s.contains(cs),
None => s == "main",
})
})
});
end.flatten().flatten().unwrap_or(false)
});
let frames = match end {
Some(end) => &frames[begin..end + begin + 1],
None => &frames[begin..],
};
let cwd = std::env::current_dir();
let mut print_path = move |fmt: &mut fmt::Formatter<'_>, path: BytesOrWideString<'_>| {
let path = path.into_path_buf();
if let Ok(cwd) = &cwd {
if let Ok(suffix) = path.strip_prefix(cwd) {
return fmt::Display::fmt(&suffix.display(), fmt);
}
}
fmt::Display::fmt(&path.display(), fmt)
};
let mut f = backtrace::BacktraceFmt::new(fmt, PrintFmt::Short, &mut print_path);
f.add_context()?;
for frame in frames {
f.frame().backtrace_frame(frame)?;
}
f.finish()?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use backtrace::Backtrace;
use super::*;
#[test]
fn stacktrace_test() {
let file = std::file!();
let column = std::column!();
let line = std::line!();
let trace = Backtrace::new();
let bt = FormatBacktrace(&trace, Some("stacktrace_test"));
let calculated_pos = format!("{}:{}:{}", file.trim_start_matches("rtest/"), line + 1, column - 1);
let printed = format!("{:?}", bt);
assert_eq!(
printed,
format!(" 0: rtest::runner::panic::tests::stacktrace_test\n at {calculated_pos}\n")
)
}
}