Skip to content

Commit

Permalink
Add assert_str_eq macro for comparing "raw" strings
Browse files Browse the repository at this point in the history
As discussed in #24, pretty_assertion's `assert_eq` will escape line
breaks in multi-line strings, making diffs for them much harder to
read.

This commit introduces another variant, `assert_str_eq`, and the
associated `StrComparison`, which compares the raw strings, where
the newlines are not escaped.

Fixes #24

Co-authored-by: Tom Milligan <tom@reinfer.io>
  • Loading branch information
x3ro and tommilligan committed Feb 1, 2022
1 parent 88cd1be commit 5271543
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 0 deletions.
115 changes: 115 additions & 0 deletions pretty_assertions/src/lib.rs
Expand Up @@ -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<str> 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<str> + ?Sized,
TRight: AsRef<str> + ?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<str> + ?Sized,
TRight: AsRef<str> + ?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
Expand Down Expand Up @@ -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
Expand Down
64 changes: 64 additions & 0 deletions pretty_assertions/tests/macros.rs
Expand Up @@ -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<str> for MyString {
fn as_ref(&self) -> &str {
&self.0
}
}

impl PartialEq<String> 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
<bar
>baz
"#)]
fn fails_foo() {
::pretty_assertions::assert_str_eq!("foo\nbar", "foo\nbaz");
}
}

#[allow(clippy::eq_op)]
mod assert_eq {
#[cfg(feature = "alloc")]
Expand Down

0 comments on commit 5271543

Please sign in to comment.