Skip to content

Commit 4f5ed44

Browse files
committed
TestAssertionFailure: retain full error chain in message
so far the conversion from an `Error` to a `TestAssertionFailure` meant that all information about the source errors were lost. often, the `Display` implementation for `Error` implementations does not include the source information since that should instead be retrieved via `source`. while this has not yet been made into an official API guideline (see [rust-lang/api-guidelines#210]) this is nevertheless being followed. various crates like `anyhow` or `eyere` take care of pretty-printing the error chain on failure, however this does not work with `googletest` since `googletest::Result` has `TestAssertionFailure` as the error type which swallows any `Error` and only keeps the message. to resolve this a simple error chain implementation is added to the `From` implementation which pretty-prints the error chain. example: ``` Error: test3 Caused by: 1: test2 2: test1 ``` fixes google#657 [rust-lang/api-guidelines#210]: rust-lang/api-guidelines#210
1 parent 75ed7ee commit 4f5ed44

File tree

1 file changed

+46
-1
lines changed

1 file changed

+46
-1
lines changed

googletest/src/internal/test_outcome.rs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,21 @@ impl Debug for TestAssertionFailure {
236236
impl<T: std::error::Error> From<T> for TestAssertionFailure {
237237
#[track_caller]
238238
fn from(value: T) -> Self {
239-
TestAssertionFailure::create(format!("{value}"))
239+
// print the full error chain, not just the first error.
240+
let mut description = String::new();
241+
description.push_str(&format!("Error: {value}\n"));
242+
let mut source = value.source();
243+
if source.is_some() {
244+
description.push_str("\nCaused by:");
245+
let mut i = 1;
246+
while let Some(e) = source {
247+
description.push_str(&format!("\n{i}: {e}"));
248+
source = e.source();
249+
i += 1;
250+
}
251+
}
252+
253+
TestAssertionFailure::create(description)
240254
}
241255
}
242256

@@ -246,3 +260,34 @@ impl From<TestAssertionFailure> for proptest::test_runner::TestCaseError {
246260
proptest::test_runner::TestCaseError::Fail(format!("{value}").into())
247261
}
248262
}
263+
264+
#[cfg(test)]
265+
mod tests {
266+
use crate::internal::test_outcome::TestAssertionFailure;
267+
use std::fmt::{Debug, Display, Formatter};
268+
269+
#[test]
270+
fn error_chain_from_error() {
271+
#[derive(Debug)]
272+
struct CustomError {
273+
message: String,
274+
source: Option<Box<CustomError>>,
275+
}
276+
impl Display for CustomError {
277+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
278+
f.write_str(&self.message)
279+
}
280+
}
281+
impl std::error::Error for CustomError {
282+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
283+
self.source.as_ref().map(|e| e.as_ref() as _)
284+
}
285+
}
286+
287+
let source1 = CustomError { message: "test1".to_string(), source: None };
288+
let source2 = CustomError { message: "test2".to_string(), source: Some(source1.into()) };
289+
let error = CustomError { message: "test3".to_string(), source: Some(source2.into()) };
290+
let assertion_failure = TestAssertionFailure::from(error);
291+
assert_eq!(assertion_failure.description, "Error: test3\n\nCaused by:\n1: test2\n2: test1");
292+
}
293+
}

0 commit comments

Comments
 (0)