fix(graphical): fix nested error wrapping (#358)

Fixes: https://github.com/zkat/miette/issues/333
This commit is contained in:
Brooks Rady 2024-03-27 16:20:06 -07:00 committed by GitHub
parent ca646f3119
commit 3eabbcebf1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 200 additions and 72 deletions

View File

@ -6,35 +6,9 @@ use crate::{Diagnostic, LabeledSpan, Report, SourceCode};
use crate as miette; use crate as miette;
#[repr(transparent)]
pub(crate) struct DisplayError<M>(pub(crate) M);
#[repr(transparent)] #[repr(transparent)]
pub(crate) struct MessageError<M>(pub(crate) M); pub(crate) struct MessageError<M>(pub(crate) M);
pub(crate) struct NoneError;
impl<M> Debug for DisplayError<M>
where
M: Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl<M> Display for DisplayError<M>
where
M: Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl<M> StdError for DisplayError<M> where M: Display + 'static {}
impl<M> Diagnostic for DisplayError<M> where M: Display + 'static {}
impl<M> Debug for MessageError<M> impl<M> Debug for MessageError<M>
where where
M: Display + Debug, M: Display + Debug,
@ -56,21 +30,6 @@ where
impl<M> StdError for MessageError<M> where M: Display + Debug + 'static {} impl<M> StdError for MessageError<M> where M: Display + Debug + 'static {}
impl<M> Diagnostic for MessageError<M> where M: Display + Debug + 'static {} impl<M> Diagnostic for MessageError<M> where M: Display + Debug + 'static {}
impl Debug for NoneError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt("Option was None", f)
}
}
impl Display for NoneError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt("Option was None", f)
}
}
impl StdError for NoneError {}
impl Diagnostic for NoneError {}
#[repr(transparent)] #[repr(transparent)]
pub(crate) struct BoxedError(pub(crate) Box<dyn Diagnostic + Send + Sync>); pub(crate) struct BoxedError(pub(crate) Box<dyn Diagnostic + Send + Sync>);

View File

@ -1,7 +1,7 @@
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use owo_colors::{OwoColorize, Style, StyledList}; use owo_colors::{OwoColorize, Style, StyledList};
use unicode_width::UnicodeWidthChar; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
use crate::diagnostic_chain::{DiagnosticChain, ErrorKind}; use crate::diagnostic_chain::{DiagnosticChain, ErrorKind};
use crate::handlers::theme::*; use crate::handlers::theme::*;
@ -225,7 +225,7 @@ impl GraphicalReportHandler {
self.render_related(f, diagnostic, src)?; self.render_related(f, diagnostic, src)?;
if let Some(footer) = &self.footer { if let Some(footer) = &self.footer {
writeln!(f)?; writeln!(f)?;
let width = self.termwidth.saturating_sub(4); let width = self.termwidth.saturating_sub(2);
let mut opts = textwrap::Options::new(width) let mut opts = textwrap::Options::new(width)
.initial_indent(" ") .initial_indent(" ")
.subsequent_indent(" ") .subsequent_indent(" ")
@ -265,7 +265,6 @@ impl GraphicalReportHandler {
); );
write!(header, "{}", link)?; write!(header, "{}", link)?;
writeln!(f, "{}", header)?; writeln!(f, "{}", header)?;
writeln!(f)?;
} else if let Some(code) = diagnostic.code() { } else if let Some(code) = diagnostic.code() {
write!(header, "{}", code.style(severity_style),)?; write!(header, "{}", code.style(severity_style),)?;
if self.links == LinkStyle::Text && diagnostic.url().is_some() { if self.links == LinkStyle::Text && diagnostic.url().is_some() {
@ -273,8 +272,8 @@ impl GraphicalReportHandler {
write!(header, " ({})", url.style(self.theme.styles.link))?; write!(header, " ({})", url.style(self.theme.styles.link))?;
} }
writeln!(f, "{}", header)?; writeln!(f, "{}", header)?;
writeln!(f)?;
} }
writeln!(f)?;
Ok(()) Ok(())
} }
@ -354,8 +353,12 @@ impl GraphicalReportHandler {
inner_renderer.footer = None; inner_renderer.footer = None;
// Cause chains are already flattened, so don't double-print the nested error // Cause chains are already flattened, so don't double-print the nested error
inner_renderer.with_cause_chain = false; inner_renderer.with_cause_chain = false;
// Since everything from here on is indented, shrink the virtual terminal
inner_renderer.termwidth -= rest_indent.width();
inner_renderer.render_report(&mut inner, diag)?; inner_renderer.render_report(&mut inner, diag)?;
// If there was no header, remove the leading newline
let inner = inner.trim_start_matches('\n');
writeln!(f, "{}", self.wrap(&inner, opts))?; writeln!(f, "{}", self.wrap(&inner, opts))?;
} }
ErrorKind::StdError(err) => { ErrorKind::StdError(err) => {
@ -370,7 +373,7 @@ impl GraphicalReportHandler {
fn render_footer(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result { fn render_footer(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
if let Some(help) = diagnostic.help() { if let Some(help) = diagnostic.help() {
let width = self.termwidth.saturating_sub(4); let width = self.termwidth.saturating_sub(2);
let initial_indent = " help: ".style(self.theme.styles.help).to_string(); let initial_indent = " help: ".style(self.theme.styles.help).to_string();
let mut opts = textwrap::Options::new(width) let mut opts = textwrap::Options::new(width)
.initial_indent(&initial_indent) .initial_indent(&initial_indent)
@ -398,8 +401,8 @@ impl GraphicalReportHandler {
let mut inner_renderer = self.clone(); let mut inner_renderer = self.clone();
// Re-enable the printing of nested cause chains for related errors // Re-enable the printing of nested cause chains for related errors
inner_renderer.with_cause_chain = true; inner_renderer.with_cause_chain = true;
writeln!(f)?;
for rel in related { for rel in related {
writeln!(f)?;
match rel.severity() { match rel.severity() {
Some(Severity::Error) | None => write!(f, "Error: ")?, Some(Severity::Error) | None => write!(f, "Error: ")?,
Some(Severity::Warning) => write!(f, "Warning: ")?, Some(Severity::Warning) => write!(f, "Warning: ")?,

View File

@ -6,12 +6,14 @@ fn related() {
#[derive(Error, Debug, Diagnostic)] #[derive(Error, Debug, Diagnostic)]
#[error("welp")] #[error("welp")]
#[diagnostic(code(foo::bar::baz))] #[diagnostic(code(foo::bar::baz))]
#[allow(dead_code)]
struct Foo { struct Foo {
#[related] #[related]
related: Vec<Baz>, related: Vec<Baz>,
} }
#[derive(Error, Debug, Diagnostic)] #[derive(Error, Debug, Diagnostic)]
#[allow(dead_code)]
enum Bar { enum Bar {
#[error("variant1")] #[error("variant1")]
#[diagnostic(code(foo::bar::baz))] #[diagnostic(code(foo::bar::baz))]
@ -29,6 +31,7 @@ fn related() {
#[derive(Error, Debug, Diagnostic)] #[derive(Error, Debug, Diagnostic)]
#[error("welp2")] #[error("welp2")]
#[allow(dead_code)]
struct Baz; struct Baz;
} }
@ -37,6 +40,7 @@ fn related_report() {
#[derive(Error, Debug, Diagnostic)] #[derive(Error, Debug, Diagnostic)]
#[error("welp")] #[error("welp")]
#[diagnostic(code(foo::bar::baz))] #[diagnostic(code(foo::bar::baz))]
#[allow(dead_code)]
struct Foo { struct Foo {
#[related] #[related]
related: Vec<Report>, related: Vec<Report>,
@ -288,6 +292,7 @@ fn test_snippet_named_struct() {
#[derive(Debug, Diagnostic, Error)] #[derive(Debug, Diagnostic, Error)]
#[error("welp")] #[error("welp")]
#[diagnostic(code(foo::bar::baz))] #[diagnostic(code(foo::bar::baz))]
#[allow(dead_code)]
struct Foo<'a> { struct Foo<'a> {
#[source_code] #[source_code]
src: &'a str, src: &'a str,
@ -310,6 +315,7 @@ fn test_snippet_unnamed_struct() {
#[derive(Debug, Diagnostic, Error)] #[derive(Debug, Diagnostic, Error)]
#[error("welp")] #[error("welp")]
#[diagnostic(code(foo::bar::baz))] #[diagnostic(code(foo::bar::baz))]
#[allow(dead_code)]
struct Foo<'a>( struct Foo<'a>(
#[source_code] &'a str, #[source_code] &'a str,
#[label("{0}")] SourceSpan, #[label("{0}")] SourceSpan,

View File

@ -59,14 +59,15 @@ fn word_wrap_options() -> Result<(), MietteError> {
let out = let out =
fmt_report_with_settings(Report::msg("abcdefghijklmnopqrstuvwxyz"), |handler| handler); fmt_report_with_settings(Report::msg("abcdefghijklmnopqrstuvwxyz"), |handler| handler);
let expected = " × abcdefghijklmnopqrstuvwxyz\n".to_string(); let expected = "\n × abcdefghijklmnopqrstuvwxyz\n".to_string();
assert_eq!(expected, out); assert_eq!(expected, out);
// A long word can break with a smaller width // A long word can break with a smaller width
let out = fmt_report_with_settings(Report::msg("abcdefghijklmnopqrstuvwxyz"), |handler| { let out = fmt_report_with_settings(Report::msg("abcdefghijklmnopqrstuvwxyz"), |handler| {
handler.with_width(10) handler.with_width(10)
}); });
let expected = r#" × abcd let expected = r#"
× abcd
efgh efgh
ijkl ijkl
mnop mnop
@ -81,7 +82,7 @@ fn word_wrap_options() -> Result<(), MietteError> {
let out = fmt_report_with_settings(Report::msg("abcdefghijklmnopqrstuvwxyz"), |handler| { let out = fmt_report_with_settings(Report::msg("abcdefghijklmnopqrstuvwxyz"), |handler| {
handler.with_width(10).with_break_words(false) handler.with_width(10).with_break_words(false)
}); });
let expected = " × abcdefghijklmnopqrstuvwxyz\n".to_string(); let expected = "\n × abcdefghijklmnopqrstuvwxyz\n".to_string();
assert_eq!(expected, out); assert_eq!(expected, out);
// Breaks should start at the boundary of each word if possible // Breaks should start at the boundary of each word if possible
@ -89,7 +90,8 @@ fn word_wrap_options() -> Result<(), MietteError> {
Report::msg("12 123 1234 12345 123456 1234567 1234567890"), Report::msg("12 123 1234 12345 123456 1234567 1234567890"),
|handler| handler.with_width(10), |handler| handler.with_width(10),
); );
let expected = r#" × 12 let expected = r#"
× 12
123 123
1234 1234
1234 1234
@ -110,7 +112,8 @@ fn word_wrap_options() -> Result<(), MietteError> {
Report::msg("12 123 1234 12345 123456 1234567 1234567890"), Report::msg("12 123 1234 12345 123456 1234567 1234567890"),
|handler| handler.with_width(10).with_break_words(false), |handler| handler.with_width(10).with_break_words(false),
); );
let expected = r#" × 12 let expected = r#"
× 12
123 123
1234 1234
12345 12345
@ -126,7 +129,8 @@ fn word_wrap_options() -> Result<(), MietteError> {
Report::msg("a-b a-b-c a-b-c-d a-b-c-d-e a-b-c-d-e-f a-b-c-d-e-f-g a-b-c-d-e-f-g-h"), Report::msg("a-b a-b-c a-b-c-d a-b-c-d-e a-b-c-d-e-f a-b-c-d-e-f-g a-b-c-d-e-f-g-h"),
|handler| handler.with_width(10).with_break_words(false), |handler| handler.with_width(10).with_break_words(false),
); );
let expected = r#" × a-b let expected = r#"
× a-b
a-b- a-b-
c a- c a-
b-c- b-c-
@ -158,7 +162,8 @@ fn word_wrap_options() -> Result<(), MietteError> {
.with_word_splitter(textwrap::WordSplitter::NoHyphenation) .with_word_splitter(textwrap::WordSplitter::NoHyphenation)
}, },
); );
let expected = r#" × a-b let expected = r#"
× a-b
a-b-c a-b-c
a-b-c-d a-b-c-d
a-b-c-d-e a-b-c-d-e
@ -174,7 +179,8 @@ fn word_wrap_options() -> Result<(), MietteError> {
Report::msg("a/b a/b/c a/b/c/d a/b/c/d/e a/b/c/d/e/f a/b/c/d/e/f/g a/b/c/d/e/f/g/h"), Report::msg("a/b a/b/c a/b/c/d a/b/c/d/e a/b/c/d/e/f a/b/c/d/e/f/g a/b/c/d/e/f/g/h"),
|handler| handler.with_width(10).with_break_words(false), |handler| handler.with_width(10).with_break_words(false),
); );
let expected = r#" × a/b let expected = r#"
× a/b
a/b/ a/b/
c a/ c a/
b/c/ b/c/
@ -206,7 +212,8 @@ fn word_wrap_options() -> Result<(), MietteError> {
.with_word_separator(textwrap::WordSeparator::AsciiSpace) .with_word_separator(textwrap::WordSeparator::AsciiSpace)
}, },
); );
let expected = r#" × a/b let expected = r#"
× a/b
a/b/c a/b/c
a/b/c/d a/b/c/d
a/b/c/d/e a/b/c/d/e
@ -227,7 +234,8 @@ fn wrap_option() -> Result<(), MietteError> {
Report::msg("abc def ghi jkl mno pqr stu vwx yz abc def ghi jkl mno pqr stu vwx yz"), Report::msg("abc def ghi jkl mno pqr stu vwx yz abc def ghi jkl mno pqr stu vwx yz"),
|handler| handler.with_width(15), |handler| handler.with_width(15),
); );
let expected = r#" × abc def let expected = r#"
× abc def
ghi jkl ghi jkl
mno pqr mno pqr
stu vwx stu vwx
@ -246,7 +254,7 @@ fn wrap_option() -> Result<(), MietteError> {
|handler| handler.with_width(15).with_wrap_lines(false), |handler| handler.with_width(15).with_wrap_lines(false),
); );
let expected = let expected =
" × abc def ghi jkl mno pqr stu vwx yz abc def ghi jkl mno pqr stu vwx yz\n".to_string(); "\n × abc def ghi jkl mno pqr stu vwx yz abc def ghi jkl mno pqr stu vwx yz\n".to_string();
assert_eq!(expected, out); assert_eq!(expected, out);
// Then, user-defined new lines should be preserved wrapping is disabled // Then, user-defined new lines should be preserved wrapping is disabled
@ -254,7 +262,8 @@ fn wrap_option() -> Result<(), MietteError> {
Report::msg("abc def ghi jkl mno pqr stu vwx yz\nabc def ghi jkl mno pqr stu vwx yz\nabc def ghi jkl mno pqr stu vwx yz"), Report::msg("abc def ghi jkl mno pqr stu vwx yz\nabc def ghi jkl mno pqr stu vwx yz\nabc def ghi jkl mno pqr stu vwx yz"),
|handler| handler.with_width(15).with_wrap_lines(false), |handler| handler.with_width(15).with_wrap_lines(false),
); );
let expected = r#" × abc def ghi jkl mno pqr stu vwx yz let expected = r#"
× abc def ghi jkl mno pqr stu vwx yz
abc def ghi jkl mno pqr stu vwx yz abc def ghi jkl mno pqr stu vwx yz
abc def ghi jkl mno pqr stu vwx yz abc def ghi jkl mno pqr stu vwx yz
"# "#
@ -264,6 +273,143 @@ fn wrap_option() -> Result<(), MietteError> {
Ok(()) Ok(())
} }
#[test]
fn wrapping_nested_errors() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)]
#[error("This is the parent error, the error withhhhh the children, kiddos, pups, as it were, and so on...")]
#[diagnostic(
code(mama::error),
help(
"try doing it better next time? I mean, you could have also done better thisssss time, but no?"
)
)]
struct MamaError {
#[diagnostic_source]
baby: BabyError,
}
#[derive(Debug, Diagnostic, Error)]
#[error("Wah wah: I may be small, but I'll cause a proper bout of trouble — justt try wrapping this mess of a line, buddo!")]
#[diagnostic(
code(baby::error),
help(
"it cannot be helped... woulddddddd you really want to get rid of an error that's so cute?"
)
)]
struct BabyError;
let err = MamaError { baby: BabyError };
let out = fmt_report_with_settings(err.into(), |handler| handler.with_width(50));
let expected = r#"mama::error
× This is the parent error, the error withhhhh
the children, kiddos, pups, as it were, and
so on...
baby::error
× Wah wah: I may be small, but I'll
cause a proper bout of trouble justt
try wrapping this mess of a line,
buddo!
help: it cannot be helped... woulddddddd
you really want to get rid of an
error that's so cute?
help: try doing it better next time? I mean,
you could have also done better thisssss
time, but no?
"#;
assert_eq!(expected, out);
Ok(())
}
#[test]
fn wrapping_related_errors() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)]
#[error("This is the parent error, the error withhhhh the children, kiddos, pups, as it were, and so on...")]
#[diagnostic(
code(mama::error),
help(
"try doing it better next time? I mean, you could have also done better thisssss time, but no?"
)
)]
struct MamaError {
#[diagnostic_source]
baby: BrotherError,
}
#[derive(Debug, Diagnostic, Error)]
#[error("Welcome to the brother-error brotherhood — where all of the wee baby errors join into a formidable force")]
#[diagnostic(code(brother::error))]
struct BrotherError {
#[related]
brethren: Vec<Box<dyn Diagnostic + Send + Sync>>,
}
#[derive(Debug, Diagnostic, Error)]
#[error("Wah wah: I may be small, but I'll cause a proper bout of trouble — justt try wrapping this mess of a line, buddo!")]
#[diagnostic(help(
"it cannot be helped... woulddddddd you really want to get rid of an error that's so cute?"
))]
struct BabyError;
#[derive(Debug, Diagnostic, Error)]
#[error("Wah wah: I may be small, but I'll cause a proper bout of trouble — justt try wrapping this mess of a line, buddo!")]
#[diagnostic(severity(Warning))]
struct BabyWarning;
#[derive(Debug, Diagnostic, Error)]
#[error("Wah wah: I may be small, but I'll cause a proper bout of trouble — justt try wrapping this mess of a line, buddo!")]
#[diagnostic(severity(Advice))]
struct BabyAdvice;
let err = MamaError {
baby: BrotherError {
brethren: vec![BabyError.into(), BabyWarning.into(), BabyAdvice.into()],
},
};
let out = fmt_report_with_settings(err.into(), |handler| handler.with_width(50));
let expected = r#"mama::error
× This is the parent error, the error withhhhh
the children, kiddos, pups, as it were, and
so on...
brother::error
× Welcome to the brother-error
brotherhood where all of the wee
baby errors join into a formidable
force
Error:
× Wah wah: I may be small, but I'll
cause a proper bout of trouble justt
try wrapping this mess of a line,
buddo!
help: it cannot be helped... woulddddddd
you really want to get rid of an
error that's so cute?
Warning:
Wah wah: I may be small, but I'll
cause a proper bout of trouble justt
try wrapping this mess of a line,
buddo!
Advice:
Wah wah: I may be small, but I'll
cause a proper bout of trouble justt
try wrapping this mess of a line,
buddo!
help: try doing it better next time? I mean,
you could have also done better thisssss
time, but no?
"#;
assert_eq!(expected, out);
Ok(())
}
#[test] #[test]
fn empty_source() -> Result<(), MietteError> { fn empty_source() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)] #[derive(Debug, Diagnostic, Error)]
@ -326,7 +472,8 @@ if true {
let out = fmt_report(err.into()); let out = fmt_report(err.into());
println!("Error: {}", out); println!("Error: {}", out);
let expected = r#" × oops! let expected = r#"
× oops!
[issue:1:1] [issue:1:1]
1 if true { 1 if true {
2 a 2 a
@ -361,7 +508,8 @@ fn single_line_highlight_span_full_line() {
let out = fmt_report(err.into()); let out = fmt_report(err.into());
println!("Error: {}", out); println!("Error: {}", out);
let expected = r#" × oops! let expected = r#"
× oops!
[issue:2:1] [issue:2:1]
1 source 1 source
2 text 2 text
@ -1411,7 +1559,6 @@ Error: oops::my::bad
2 text 2 text
help: try doing it better next time? help: try doing it better next time?
"# "#
.trim_start() .trim_start()
.to_string(); .to_string();
@ -1578,6 +1725,7 @@ Error: oops::my::related::error
2 text 2 text
help: try doing it better next time? help: try doing it better next time?
Warning: oops::my::related::warning Warning: oops::my::related::warning
oops! oops!
@ -1588,6 +1736,7 @@ Warning: oops::my::related::warning
2 text 2 text
help: try doing it better next time? help: try doing it better next time?
Advice: oops::my::related::advice Advice: oops::my::related::advice
oops! oops!
@ -1623,7 +1772,8 @@ fn zero_length_eol_span() {
let out = fmt_report(err.into()); let out = fmt_report(err.into());
println!("Error: {}", out); println!("Error: {}", out);
let expected = r#" × oops! let expected = r#"
× oops!
[issue:2:1] [issue:2:1]
1 this is the first line 1 this is the first line
2 this is the second line 2 this is the second line
@ -1657,7 +1807,8 @@ fn primary_label() {
println!("Error: {}", out); println!("Error: {}", out);
// line 2 should be the primary, not line 1 // line 2 should be the primary, not line 1
let expected = r#" × oops! let expected = r#"
× oops!
[issue:2:2] [issue:2:2]
1 this is the first line 1 this is the first line
· ·
@ -1808,7 +1959,8 @@ fn syntax_highlighter() {
GraphicalReportHandler::new_themed(GraphicalTheme::unicode()) GraphicalReportHandler::new_themed(GraphicalTheme::unicode())
.render_report(&mut out, &err) .render_report(&mut out, &err)
.unwrap(); .unwrap();
let expected = r#" × This is an error let expected = r#"
× This is an error
[hello_world:2:5] [hello_world:2:5]
1 fn main() { 1 fn main() {
2 println!("Hello, World!"); 2 println!("Hello, World!");
@ -1862,7 +2014,8 @@ fn syntax_highlighter_on_real_file() {
.unwrap(); .unwrap();
let expected = format!( let expected = format!(
r#" × This is an error r#"
× This is an error
[{filename}:{l2}:{CO}] [{filename}:{l2}:{CO}]
{l1} {l1}
{l2} let (filename, line) = (file!(), line!() as usize); {l2} let (filename, line) = (file!(), line!() as usize);

View File

@ -104,7 +104,8 @@ fn test_diagnostic_source_pass_extra_info() {
.render_report(&mut out, &diag) .render_report(&mut out, &diag)
.unwrap(); .unwrap();
println!("Error: {}", out); println!("Error: {}", out);
let expected = r#" × TestError let expected = r#"
× TestError
× A complex error happened × A complex error happened
[1:2] [1:2]
1 Hello 1 Hello
@ -138,7 +139,8 @@ fn test_diagnostic_source_is_output() {
.unwrap(); .unwrap();
println!("{}", out); println!("{}", out);
let expected = r#" × TestError let expected = r#"
× TestError
× A complex error happened × A complex error happened
1 right here 1 right here
@ -186,7 +188,8 @@ fn test_nested_diagnostic_source_is_output() {
.unwrap(); .unwrap();
println!("{}", out); println!("{}", out);
let expected = r#" × A nested error happened let expected = r#"
× A nested error happened
× TestError × TestError
× A complex error happened × A complex error happened
@ -252,10 +255,12 @@ fn test_nested_cause_chains_for_related_errors_are_output() {
.unwrap(); .unwrap();
println!("{}", out); println!("{}", out);
let expected = r#" × A nested error happened let expected = r#"
× A nested error happened
× A multi-error happened × A multi-error happened
Error: × A nested error happened Error:
× A nested error happened
× TestError × TestError
× A complex error happened × A complex error happened
@ -271,7 +276,9 @@ fn test_nested_cause_chains_for_related_errors_are_output() {
· ·
· here · here
Error: × A complex error happened
Error:
× A complex error happened
1 You're actually a mess 1 You're actually a mess
· ·