[red-knot] Print non-string panic payloads and (sometimes) backtraces (#15363)

More refinements to the panic messages for failing mdtests to mimic the
output of the default panic hook more closely:

- We now print out `Box<dyn Any>` if the panic payload is not a string
(which is typically the case for salsa panics).
- We now include the panic's backtrace if you set the `RUST_BACKTRACE`
environment variable.
This commit is contained in:
Douglas Creager 2025-01-08 18:12:16 -05:00 committed by GitHub
parent b6562ed57e
commit 5f5eb7c0dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 38 additions and 12 deletions

View file

@ -141,10 +141,23 @@ fn run_test(db: &mut db::Db, test: &parser::MarkdownTest) -> Result<(), Failures
Ok(type_diagnostics) => type_diagnostics, Ok(type_diagnostics) => type_diagnostics,
Err(info) => { Err(info) => {
let mut by_line = matcher::FailuresByLine::default(); let mut by_line = matcher::FailuresByLine::default();
by_line.push( let mut messages = vec![];
OneIndexed::from_zero_indexed(0), match info.location {
info.info.split('\n').map(String::from).collect(), Some(location) => messages.push(format!("panicked at {location}")),
); None => messages.push("panicked at unknown location".to_string()),
};
match info.payload {
Some(payload) => messages.push(payload),
// Mimic the default panic hook's rendering of the panic payload if it's
// not a string.
None => messages.push("Box<dyn Any>".to_string()),
};
if let Some(backtrace) = info.backtrace {
if std::env::var("RUST_BACKTRACE").is_ok() {
messages.extend(backtrace.to_string().split('\n').map(String::from));
}
}
by_line.push(OneIndexed::from_zero_indexed(0), messages);
return Some(FileFailures { return Some(FileFailures {
backtick_offset: test_file.backtick_offset, backtick_offset: test_file.backtick_offset,
by_line, by_line,

View file

@ -1,20 +1,27 @@
use std::cell::Cell; use std::cell::Cell;
use std::panic::Location;
use std::sync::OnceLock; use std::sync::OnceLock;
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct PanicError { pub struct PanicError {
pub info: String, pub location: Option<String>,
pub payload: Option<String>,
pub backtrace: Option<std::backtrace::Backtrace>, pub backtrace: Option<std::backtrace::Backtrace>,
} }
impl std::fmt::Display for PanicError { impl std::fmt::Display for PanicError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{}", self.info)?; writeln!(f, "panicked at")?;
if let Some(backtrace) = &self.backtrace { if let Some(location) = &self.location {
writeln!(f, "Backtrace: {backtrace}") write!(f, " {location}")?;
} else {
Ok(())
} }
if let Some(payload) = &self.payload {
write!(f, ":\n{payload}")?;
}
if let Some(backtrace) = &self.backtrace {
writeln!(f, "\nBacktrace: {backtrace}")?;
}
Ok(())
} }
} }
@ -34,11 +41,17 @@ fn install_hook() {
if !should_capture { if !should_capture {
return (*prev)(info); return (*prev)(info);
} }
let info = info.to_string(); let payload = if let Some(s) = info.payload().downcast_ref::<&str>() {
Some(s.to_string())
} else {
info.payload().downcast_ref::<String>().cloned()
};
let location = info.location().map(Location::to_string);
let backtrace = std::backtrace::Backtrace::force_capture(); let backtrace = std::backtrace::Backtrace::force_capture();
LAST_PANIC.with(|cell| { LAST_PANIC.with(|cell| {
cell.set(Some(PanicError { cell.set(Some(PanicError {
info, payload,
location,
backtrace: Some(backtrace), backtrace: Some(backtrace),
})); }));
}); });