Skip to content

Commit 5c51f41

Browse files
committed
[nextest-runner] write our own DisplayErrorChain
Wasn't overly satisfied with the upstream one, in particular with issues around indentation.
1 parent 3da5f5d commit 5c51f41

File tree

6 files changed

+147
-23
lines changed

6 files changed

+147
-23
lines changed

Cargo.lock

Lines changed: 0 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ clap = { version = "4.5.21", features = ["derive"] }
4747
console-subscriber = "0.4.1"
4848
dialoguer = "0.11.0"
4949
debug-ignore = "1.0.5"
50-
display-error-chain = "0.2.2"
5150
duct = "0.13.7"
5251
dunce = "1.0.5"
5352
enable-ansi-support = "0.2.1"

nextest-runner/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ config.workspace = true
2626
cfg-if.workspace = true
2727
chrono.workspace = true
2828
debug-ignore.workspace = true
29-
display-error-chain.workspace = true
3029
duct.workspace = true
3130
future-queue.workspace = true
3231
futures.workspace = true

nextest-runner/src/config/scripts.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -610,10 +610,9 @@ mod tests {
610610
use super::*;
611611
use crate::{
612612
config::{test_helpers::*, ConfigExperimental, NextestConfig, ToolConfigFile},
613-
errors::{ConfigParseErrorKind, UnknownConfigScriptError},
613+
errors::{ConfigParseErrorKind, DisplayErrorChain, UnknownConfigScriptError},
614614
};
615615
use camino_tempfile::tempdir;
616-
use display_error_chain::DisplayErrorChain;
617616
use indoc::indoc;
618617
use maplit::btreeset;
619618
use test_case::test_case;

nextest-runner/src/errors.rs

Lines changed: 144 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,13 @@ pub(crate) enum SetupScriptError {
346346
#[derive(Clone, Debug)]
347347
pub struct ErrorList<T>(pub Vec<T>);
348348

349+
impl<T> ErrorList<T> {
350+
/// Returns true if the error list is empty.
351+
pub fn is_empty(&self) -> bool {
352+
self.0.is_empty()
353+
}
354+
}
355+
349356
impl<T: std::error::Error> fmt::Display for ErrorList<T> {
350357
fn fmt(&self, mut f: &mut fmt::Formatter) -> fmt::Result {
351358
// If a single error occurred, pretend that this is just that.
@@ -356,15 +363,8 @@ impl<T: std::error::Error> fmt::Display for ErrorList<T> {
356363
// Otherwise, list all errors.
357364
writeln!(f, "{} errors occurred:", self.0.len())?;
358365
for error in &self.0 {
359-
writeln!(f, "- {}", error)?;
360-
// Also display the chain of causes here, since we can't return a single error in the
361-
// causes section below.
362-
let mut indent = IndentWriter::new(" ", f);
363-
let mut cause = error.source();
364-
while let Some(cause_error) = cause {
365-
writeln!(indent, "Caused by: {}", cause_error)?;
366-
cause = cause_error.source();
367-
}
366+
let mut indent = IndentWriter::new_skip_initial(" ", f);
367+
writeln!(indent, "* {}", DisplayErrorChain(error))?;
368368
f = indent.into_inner();
369369
}
370370
Ok(())
@@ -383,6 +383,44 @@ impl<T: std::error::Error> std::error::Error for ErrorList<T> {
383383
}
384384
}
385385

386+
/// A wrapper type to display a chain of errors with internal indentation.
387+
///
388+
/// This is similar to the display-error-chain crate, but uses IndentWriter
389+
/// internally to ensure that subsequent lines are also nested.
390+
pub(crate) struct DisplayErrorChain<E>(E);
391+
392+
impl<E: std::error::Error> DisplayErrorChain<E> {
393+
pub(crate) fn new(error: E) -> Self {
394+
Self(error)
395+
}
396+
}
397+
398+
impl<E> fmt::Display for DisplayErrorChain<E>
399+
where
400+
E: std::error::Error,
401+
{
402+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
403+
write!(f, "{}", self.0)?;
404+
405+
let Some(mut cause) = self.0.source() else {
406+
return Ok(());
407+
};
408+
409+
write!(f, "\n caused by:")?;
410+
411+
let mut indent = IndentWriter::new_skip_initial(" ", f);
412+
loop {
413+
write!(indent, "\n- {}", cause)?;
414+
415+
let Some(next_cause) = cause.source() else {
416+
break Ok(());
417+
};
418+
419+
cause = next_cause;
420+
}
421+
}
422+
}
423+
386424
/// An error was returned during the process of managing a child process.
387425
#[derive(Clone, Debug, Error)]
388426
#[non_exhaustive]
@@ -1757,3 +1795,100 @@ mod self_update_errors {
17571795

17581796
#[cfg(feature = "self-update")]
17591797
pub use self_update_errors::*;
1798+
1799+
#[cfg(test)]
1800+
mod tests {
1801+
use super::*;
1802+
1803+
#[test]
1804+
fn display_error_chain() {
1805+
let err1 = StringError::new("err1", None);
1806+
1807+
insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&err1)), @"err1");
1808+
1809+
let err2 = StringError::new("err2", Some(err1));
1810+
let err3 = StringError::new("err3\nerr3 line 2", Some(err2));
1811+
1812+
insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&err3)), @r"
1813+
err3
1814+
err3 line 2
1815+
caused by:
1816+
- err2
1817+
- err1
1818+
");
1819+
}
1820+
1821+
#[test]
1822+
fn display_error_list() {
1823+
let err1 = StringError::new("err1", None);
1824+
1825+
let error_list = ErrorList(vec![err1.clone()]);
1826+
insta::assert_snapshot!(format!("{}", error_list), @"err1");
1827+
insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"err1");
1828+
1829+
let err2 = StringError::new("err2", Some(err1));
1830+
let err3 = StringError::new("err3", Some(err2));
1831+
1832+
let error_list = ErrorList(vec![err3.clone()]);
1833+
insta::assert_snapshot!(format!("{}", error_list), @"err3");
1834+
insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @r"
1835+
err3
1836+
caused by:
1837+
- err2
1838+
- err1
1839+
");
1840+
1841+
let err4 = StringError::new("err4", None);
1842+
let err5 = StringError::new("err5", Some(err4));
1843+
let err6 = StringError::new("err6\nerr6 line 2", Some(err5));
1844+
1845+
let error_list = ErrorList(vec![err3, err6]);
1846+
1847+
insta::assert_snapshot!(format!("{}", error_list), @r"
1848+
2 errors occurred:
1849+
* err3
1850+
caused by:
1851+
- err2
1852+
- err1
1853+
* err6
1854+
err6 line 2
1855+
caused by:
1856+
- err5
1857+
- err4
1858+
");
1859+
insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @r"
1860+
2 errors occurred:
1861+
* err3
1862+
caused by:
1863+
- err2
1864+
- err1
1865+
* err6
1866+
err6 line 2
1867+
caused by:
1868+
- err5
1869+
- err4
1870+
");
1871+
}
1872+
1873+
#[derive(Clone, Debug, Error)]
1874+
struct StringError {
1875+
message: String,
1876+
#[source]
1877+
source: Option<Box<StringError>>,
1878+
}
1879+
1880+
impl StringError {
1881+
fn new(message: impl Into<String>, source: Option<StringError>) -> Self {
1882+
Self {
1883+
message: message.into(),
1884+
source: source.map(Box::new),
1885+
}
1886+
}
1887+
}
1888+
1889+
impl fmt::Display for StringError {
1890+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1891+
write!(f, "{}", self.message)
1892+
}
1893+
}
1894+
}

nextest-runner/src/runner.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ use crate::{
1313
},
1414
double_spawn::DoubleSpawnInfo,
1515
errors::{
16-
ChildError, ConfigureHandleInheritanceError, ErrorList, RunTestError, SetupScriptError,
17-
TestRunnerBuildError, TestRunnerExecuteErrors,
16+
ChildError, ConfigureHandleInheritanceError, DisplayErrorChain, ErrorList, RunTestError,
17+
SetupScriptError, TestRunnerBuildError, TestRunnerExecuteErrors,
1818
},
1919
list::{TestExecuteContext, TestInstance, TestInstanceId, TestList},
2020
reporter::{
@@ -28,7 +28,6 @@ use crate::{
2828
};
2929
use async_scoped::TokioScope;
3030
use chrono::{DateTime, FixedOffset, Local};
31-
use display_error_chain::DisplayErrorChain;
3231
use future_queue::StreamExt;
3332
use futures::prelude::*;
3433
use nextest_metadata::{FilterMatch, MismatchReason};

0 commit comments

Comments
 (0)