diff --git a/pretty_assertions/src/lib.rs b/pretty_assertions/src/lib.rs index beead99..062e5ee 100644 --- a/pretty_assertions/src/lib.rs +++ b/pretty_assertions/src/lib.rs @@ -141,6 +141,76 @@ where } } +/// A comparison of two strings. +/// +/// In contrast to [`Comparison`], which uses the [`core::fmt::Debug`] representation, +/// `StrComparison` uses the string values directly, resulting in multi-line output for multiline strings. +/// +/// ``` +/// use pretty_assertions::StrComparison; +/// +/// print!("{}", StrComparison::new("foo\nbar", "foo\nbaz")); +/// ``` +/// +/// ## Value type bounds +/// +/// Any value that can be referenced as a [`str`] via [`AsRef`] may be used: +/// +/// ``` +/// use pretty_assertions::StrComparison; +/// +/// #[derive(PartialEq)] +/// struct MyString(String); +/// +/// impl AsRef for MyString { +/// fn as_ref(&self) -> &str { +/// &self.0 +/// } +/// } +/// +/// print!( +/// "{}", +/// StrComparison::new( +/// &MyString("foo\nbar".to_owned()), +/// &MyString("foo\nbaz".to_owned()), +/// ), +/// ); +/// ``` +/// +/// The values may have different types, although in practice they are usually the same. +pub struct StrComparison<'a, TLeft, TRight> +where + TLeft: ?Sized, + TRight: ?Sized, +{ + left: &'a TLeft, + right: &'a TRight, +} + +impl<'a, TLeft, TRight> StrComparison<'a, TLeft, TRight> +where + TLeft: AsRef + ?Sized, + TRight: AsRef + ?Sized, +{ + /// Store two values to be compared in future. + /// + /// Expensive diffing is deferred until calling `Debug::fmt`. + pub fn new(left: &'a TLeft, right: &'a TRight) -> StrComparison<'a, TLeft, TRight> { + StrComparison { left, right } + } +} + +impl<'a, TLeft, TRight> Display for StrComparison<'a, TLeft, TRight> +where + TLeft: AsRef + ?Sized, + TRight: AsRef + ?Sized, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + printer::write_header(f)?; + printer::write_lines(f, self.left.as_ref(), self.right.as_ref()) + } +} + /// Asserts that two expressions are equal to each other (using [`PartialEq`]). /// /// On panic, this macro will print a diff derived from [`Debug`] representation of @@ -186,6 +256,51 @@ macro_rules! assert_eq { }); } +/// Asserts that two expressions are equal to each other (using [`PartialEq`]). +/// +/// On panic, this macro will print a diff derived from each value's [`str`] representation. +/// See [`StrComparison`] for further details. +/// +/// This is a drop in replacement for [`core::assert_eq!`]. +/// You can provide a custom panic message if desired. +/// +/// # Examples +/// +/// ``` +/// use pretty_assertions::assert_str_eq; +/// +/// let a = "foo\nbar"; +/// let b = ["foo", "bar"].join("\n"); +/// assert_str_eq!(a, b); +/// +/// assert_str_eq!(a, b, "we are testing concatenation with {} and {}", a, b); +/// ``` +#[macro_export] +macro_rules! assert_str_eq { + ($left:expr, $right:expr$(,)?) => ({ + $crate::assert_str_eq!(@ $left, $right, "", ""); + }); + ($left:expr, $right:expr, $($arg:tt)*) => ({ + $crate::assert_str_eq!(@ $left, $right, ": ", $($arg)+); + }); + (@ $left:expr, $right:expr, $maybe_semicolon:expr, $($arg:tt)*) => ({ + match (&($left), &($right)) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + ::core::panic!("assertion failed: `(left == right)`{}{}\ + \n\ + \n{}\ + \n", + $maybe_semicolon, + format_args!($($arg)*), + $crate::StrComparison::new(left_val, right_val) + ) + } + } + } + }); +} + /// Asserts that two expressions are not equal to each other (using [`PartialEq`]). /// /// On panic, this macro will print the values of the expressions with their diff --git a/pretty_assertions/tests/macros.rs b/pretty_assertions/tests/macros.rs index 3c34f02..81e0331 100644 --- a/pretty_assertions/tests/macros.rs +++ b/pretty_assertions/tests/macros.rs @@ -4,6 +4,70 @@ #[cfg(feature = "alloc")] extern crate alloc; +#[allow(clippy::eq_op)] +mod assert_str_eq { + use ::core::{cmp::PartialEq, convert::AsRef}; + + #[cfg(feature = "alloc")] + use ::alloc::string::{String, ToString}; + #[cfg(feature = "std")] + use ::std::string::{String, ToString}; + + #[test] + fn passes_str() { + let a = "some value"; + ::pretty_assertions::assert_str_eq!(a, a); + } + + #[test] + fn passes_string() { + let a: String = "some value".to_string(); + ::pretty_assertions::assert_str_eq!(a, a); + } + + #[test] + fn passes_comparable_types() { + let s0: &'static str = "foo"; + let s1: String = "foo".to_string(); + ::pretty_assertions::assert_str_eq!(s0, s1); + } + + #[test] + fn passes_as_ref_types() { + #[derive(PartialEq)] + struct MyString(String); + + impl AsRef for MyString { + fn as_ref(&self) -> &str { + &self.0 + } + } + + impl PartialEq for MyString { + fn eq(&self, other: &String) -> bool { + &self.0 == other + } + } + + let s0 = MyString("foo".to_string()); + let s1 = "foo".to_string(); + ::pretty_assertions::assert_str_eq!(s0, s1); + } + + #[test] + #[should_panic(expected = r#"assertion failed: `(left == right)` + +Diff < left / right > : + foo +baz + +"#)] + fn fails_foo() { + ::pretty_assertions::assert_str_eq!("foo\nbar", "foo\nbaz"); + } +} + #[allow(clippy::eq_op)] mod assert_eq { #[cfg(feature = "alloc")]