459 lines
14 KiB
Rust
459 lines
14 KiB
Rust
//! # Pretty Assertions
|
|
//!
|
|
//! When writing tests in Rust, you'll probably use `assert_eq!(a, b)` _a lot_.
|
|
//!
|
|
//! If such a test fails, it will present all the details of `a` and `b`.
|
|
//! But you have to spot the differences yourself, which is not always straightforward,
|
|
//! like here:
|
|
//!
|
|
//! 
|
|
//!
|
|
//! Wouldn't that task be _much_ easier with a colorful diff?
|
|
//!
|
|
//! 
|
|
//!
|
|
//! Yep — and you only need **one line of code** to make it happen:
|
|
//!
|
|
//! ```rust
|
|
//! use pretty_assertions::{assert_eq, assert_ne};
|
|
//! ```
|
|
//!
|
|
//! <details>
|
|
//! <summary>Show the example behind the screenshots above.</summary>
|
|
//!
|
|
//! ```rust,should_panic
|
|
//! // 1. add the `pretty_assertions` dependency to `Cargo.toml`.
|
|
//! // 2. insert this line at the top of each module, as needed
|
|
//! use pretty_assertions::{assert_eq, assert_ne};
|
|
//!
|
|
//! #[derive(Debug, PartialEq)]
|
|
//! struct Foo {
|
|
//! lorem: &'static str,
|
|
//! ipsum: u32,
|
|
//! dolor: Result<String, String>,
|
|
//! }
|
|
//!
|
|
//! let x = Some(Foo { lorem: "Hello World!", ipsum: 42, dolor: Ok("hey".to_string())});
|
|
//! let y = Some(Foo { lorem: "Hello Wrold!", ipsum: 42, dolor: Ok("hey ho!".to_string())});
|
|
//!
|
|
//! assert_eq!(x, y);
|
|
//! ```
|
|
//! </details>
|
|
//!
|
|
//! ## Tip
|
|
//!
|
|
//! Specify it as [`[dev-dependencies]`](http://doc.crates.io/specifying-dependencies.html#development-dependencies)
|
|
//! and it will only be used for compiling tests, examples, and benchmarks.
|
|
//! This way the compile time of `cargo build` won't be affected!
|
|
//!
|
|
//! Also add `#[cfg(test)]` to your `use` statements, like this:
|
|
//!
|
|
//! ```rust
|
|
//! #[cfg(test)]
|
|
//! use pretty_assertions::{assert_eq, assert_ne};
|
|
//! ```
|
|
//!
|
|
//! ## Note
|
|
//!
|
|
//! * Since `Rust 2018` edition, you need to declare
|
|
//! `use pretty_assertions::{assert_eq, assert_ne};` per module.
|
|
//! Before you would write `#[macro_use] extern crate pretty_assertions;`.
|
|
//! * The replacement is only effective in your own crate, not in other libraries
|
|
//! you include.
|
|
//! * `assert_ne` is also switched to multi-line presentation, but does _not_ show
|
|
//! a diff.
|
|
//!
|
|
//! ## Features
|
|
//!
|
|
//! Features provided by the crate are:
|
|
//!
|
|
//! - `std`: Use the Rust standard library. Enabled by default.
|
|
//! Exactly one of `std` and `alloc` is required.
|
|
//! - `alloc`: Use the `alloc` crate.
|
|
//! Exactly one of `std` and `alloc` is required.
|
|
//! - `unstable`: opt-in to unstable features that may not follow Semantic Versioning.
|
|
//! The implementation behind this feature is subject to change without warning between patch versions.
|
|
|
|
#![cfg_attr(not(feature = "std"), no_std)]
|
|
#![deny(clippy::all, missing_docs, unsafe_code)]
|
|
|
|
#[cfg(feature = "alloc")]
|
|
#[macro_use]
|
|
extern crate alloc;
|
|
use core::fmt::{self, Debug, Display};
|
|
|
|
mod printer;
|
|
|
|
/// A comparison of two values.
|
|
///
|
|
/// Where both values implement `Debug`, the comparison can be displayed as a pretty diff.
|
|
///
|
|
/// ```
|
|
/// use pretty_assertions::Comparison;
|
|
///
|
|
/// print!("{}", Comparison::new(&123, &134));
|
|
/// ```
|
|
///
|
|
/// The values may have different types, although in practice they are usually the same.
|
|
pub struct Comparison<'a, TLeft, TRight>
|
|
where
|
|
TLeft: ?Sized,
|
|
TRight: ?Sized,
|
|
{
|
|
left: &'a TLeft,
|
|
right: &'a TRight,
|
|
}
|
|
|
|
impl<'a, TLeft, TRight> Comparison<'a, TLeft, TRight>
|
|
where
|
|
TLeft: ?Sized,
|
|
TRight: ?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) -> Comparison<'a, TLeft, TRight> {
|
|
Comparison { left, right }
|
|
}
|
|
}
|
|
|
|
impl<'a, TLeft, TRight> Display for Comparison<'a, TLeft, TRight>
|
|
where
|
|
TLeft: Debug + ?Sized,
|
|
TRight: Debug + ?Sized,
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
// To diff arbitary types, render them as debug strings
|
|
let left_debug = format!("{:#?}", self.left);
|
|
let right_debug = format!("{:#?}", self.right);
|
|
// And then diff the debug output
|
|
printer::write_header(f)?;
|
|
printer::write_lines(f, &left_debug, &right_debug)
|
|
}
|
|
}
|
|
|
|
/// 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
|
|
/// each value.
|
|
///
|
|
/// This is a drop in replacement for [`core::assert_eq!`].
|
|
/// You can provide a custom panic message if desired.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use pretty_assertions::assert_eq;
|
|
///
|
|
/// let a = 3;
|
|
/// let b = 1 + 2;
|
|
/// assert_eq!(a, b);
|
|
///
|
|
/// assert_eq!(a, b, "we are testing addition with {} and {}", a, b);
|
|
/// ```
|
|
#[macro_export]
|
|
macro_rules! assert_eq {
|
|
($left:expr, $right:expr$(,)?) => ({
|
|
$crate::assert_eq!(@ $left, $right, "", "");
|
|
});
|
|
($left:expr, $right:expr, $($arg:tt)*) => ({
|
|
$crate::assert_eq!(@ $left, $right, ": ", $($arg)+);
|
|
});
|
|
(@ $left:expr, $right:expr, $maybe_colon:expr, $($arg:tt)*) => ({
|
|
match (&($left), &($right)) {
|
|
(left_val, right_val) => {
|
|
if !(*left_val == *right_val) {
|
|
use $crate::private::CreateComparison;
|
|
::core::panic!("assertion failed: `(left == right)`{}{}\
|
|
\n\
|
|
\n{}\
|
|
\n",
|
|
$maybe_colon,
|
|
format_args!($($arg)*),
|
|
(left_val, right_val).create_comparison()
|
|
)
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/// 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_colon: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_colon,
|
|
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
|
|
/// [`Debug`] representations.
|
|
///
|
|
/// This is a drop in replacement for [`core::assert_ne!`].
|
|
/// You can provide a custom panic message if desired.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use pretty_assertions::assert_ne;
|
|
///
|
|
/// let a = 3;
|
|
/// let b = 2;
|
|
/// assert_ne!(a, b);
|
|
///
|
|
/// assert_ne!(a, b, "we are testing that the values are not equal");
|
|
/// ```
|
|
#[macro_export]
|
|
macro_rules! assert_ne {
|
|
($left:expr, $right:expr$(,)?) => ({
|
|
$crate::assert_ne!(@ $left, $right, "", "");
|
|
});
|
|
($left:expr, $right:expr, $($arg:tt)+) => ({
|
|
$crate::assert_ne!(@ $left, $right, ": ", $($arg)+);
|
|
});
|
|
(@ $left:expr, $right:expr, $maybe_colon:expr, $($arg:tt)+) => ({
|
|
match (&($left), &($right)) {
|
|
(left_val, right_val) => {
|
|
if *left_val == *right_val {
|
|
::core::panic!("assertion failed: `(left != right)`{}{}\
|
|
\n\
|
|
\nBoth sides:\
|
|
\n{:#?}\
|
|
\n\
|
|
\n",
|
|
$maybe_colon,
|
|
format_args!($($arg)+),
|
|
left_val
|
|
)
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Asserts that a value matches a pattern.
|
|
///
|
|
/// On panic, this macro will print a diff derived from [`Debug`] representation of
|
|
/// the value, and a string representation of the pattern.
|
|
///
|
|
/// This is a drop in replacement for [`core::assert_matches::assert_matches!`].
|
|
/// You can provide a custom panic message if desired.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use pretty_assertions::assert_matches;
|
|
///
|
|
/// let a = Some(3);
|
|
/// assert_matches!(a, Some(_));
|
|
///
|
|
/// assert_matches!(a, Some(value) if value > 2, "we are testing {:?} with a pattern", a);
|
|
/// ```
|
|
///
|
|
/// # Features
|
|
///
|
|
/// Requires the `unstable` feature to be enabled.
|
|
///
|
|
/// **Please note:** implementation under the `unstable` feature may be changed between
|
|
/// patch versions without warning.
|
|
#[cfg(feature = "unstable")]
|
|
#[macro_export]
|
|
macro_rules! assert_matches {
|
|
($left:expr, $( $pattern:pat )|+ $( if $guard: expr )? $(,)?) => ({
|
|
match $left {
|
|
$( $pattern )|+ $( if $guard )? => {}
|
|
ref left_val => {
|
|
$crate::assert_matches!(
|
|
@
|
|
left_val,
|
|
::core::stringify!($($pattern)|+ $(if $guard)?),
|
|
"",
|
|
""
|
|
);
|
|
}
|
|
}
|
|
});
|
|
($left:expr, $( $pattern:pat )|+ $( if $guard: expr )?, $($arg:tt)+) => ({
|
|
match $left {
|
|
$( $pattern )|+ $( if $guard )? => {}
|
|
ref left_val => {
|
|
$crate::assert_matches!(
|
|
@
|
|
left_val,
|
|
::core::stringify!($($pattern)|+ $(if $guard)?),
|
|
": ",
|
|
$($arg)+
|
|
);
|
|
}
|
|
}
|
|
|
|
});
|
|
(@ $left:expr, $right:expr, $maybe_colon:expr, $($arg:tt)*) => ({
|
|
match (&($left), &($right)) {
|
|
(left_val, right_val) => {
|
|
// Use the Display implementation to display the pattern,
|
|
// as using Debug would add another layer of quotes to the output.
|
|
struct Pattern<'a>(&'a str);
|
|
impl ::core::fmt::Debug for Pattern<'_> {
|
|
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
|
|
::core::fmt::Display::fmt(self.0, f)
|
|
}
|
|
}
|
|
|
|
::core::panic!("assertion failed: `(left matches right)`{}{}\
|
|
\n\
|
|
\n{}\
|
|
\n",
|
|
$maybe_colon,
|
|
format_args!($($arg)*),
|
|
$crate::Comparison::new(left_val, &Pattern(right_val))
|
|
)
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Not public API. Used by the expansion of this crate's assert macros.
|
|
#[doc(hidden)]
|
|
pub mod private {
|
|
#[cfg(feature = "alloc")]
|
|
use alloc::string::String;
|
|
|
|
pub trait CompareAsStrByDefault: AsRef<str> {}
|
|
impl CompareAsStrByDefault for str {}
|
|
impl CompareAsStrByDefault for String {}
|
|
impl<T: CompareAsStrByDefault + ?Sized> CompareAsStrByDefault for &T {}
|
|
|
|
pub trait CreateComparison {
|
|
type Comparison;
|
|
fn create_comparison(self) -> Self::Comparison;
|
|
}
|
|
|
|
impl<'a, T, U> CreateComparison for &'a (T, U) {
|
|
type Comparison = crate::Comparison<'a, T, U>;
|
|
fn create_comparison(self) -> Self::Comparison {
|
|
crate::Comparison::new(&self.0, &self.1)
|
|
}
|
|
}
|
|
|
|
impl<'a, T, U> CreateComparison for (&'a T, &'a U)
|
|
where
|
|
T: CompareAsStrByDefault + ?Sized,
|
|
U: CompareAsStrByDefault + ?Sized,
|
|
{
|
|
type Comparison = crate::StrComparison<'a, T, U>;
|
|
fn create_comparison(self) -> Self::Comparison {
|
|
crate::StrComparison::new(self.0, self.1)
|
|
}
|
|
}
|
|
}
|