From a570f5fe654ac96424ed64bf753f312ac22cb088 Mon Sep 17 00:00:00 2001 From: Tom Milligan Date: Sun, 7 May 2023 14:40:55 +0100 Subject: [PATCH] config: support custom configuration of diff --- pretty_assertions/src/config.rs | 29 +++++++ pretty_assertions/src/lib.rs | 102 +++++++++++++++++++--- pretty_assertions/src/printer.rs | 139 ++++++++++++++++++++++-------- pretty_assertions/tests/macros.rs | 30 +++++++ 4 files changed, 252 insertions(+), 48 deletions(-) create mode 100644 pretty_assertions/src/config.rs diff --git a/pretty_assertions/src/config.rs b/pretty_assertions/src/config.rs new file mode 100644 index 0000000..cda2705 --- /dev/null +++ b/pretty_assertions/src/config.rs @@ -0,0 +1,29 @@ +/// Symbols used to indicate removed and added lines. +#[derive(Clone, Copy)] +pub enum LineSymbol { + /// Use '<' and '>' + Arrow, + /// Use '-' and '+' + Sign, +} + +impl Default for LineSymbol { + fn default() -> Self { + Self::Arrow + } +} + +/// Configuration object to pass to supported macros with `assert_eq!(config = config, ...)` +#[derive(Clone, Copy, Default)] +pub struct Config { + _private: (), + pub(crate) line_symbol: LineSymbol, +} + +impl Config { + /// Set the symbols used to indicate removed and added lines. + pub fn line_symbol(mut self, value: LineSymbol) -> Self { + self.line_symbol = value; + self + } +} diff --git a/pretty_assertions/src/lib.rs b/pretty_assertions/src/lib.rs index be97c9a..9a2f504 100644 --- a/pretty_assertions/src/lib.rs +++ b/pretty_assertions/src/lib.rs @@ -82,8 +82,12 @@ extern crate alloc; use core::fmt::{self, Debug, Display}; +/// Configuration options to customise the output format. +pub mod config; mod printer; +use config::Config; + #[cfg(windows)] use ctor::*; #[cfg(windows)] @@ -110,6 +114,7 @@ where { left: &'a TLeft, right: &'a TRight, + config: Option<&'a Config>, } impl<'a, TLeft, TRight> Comparison<'a, TLeft, TRight> @@ -121,7 +126,16 @@ where /// /// Expensive diffing is deferred until calling `Debug::fmt`. pub fn new(left: &'a TLeft, right: &'a TRight) -> Comparison<'a, TLeft, TRight> { - Comparison { left, right } + Comparison { + left, + right, + config: None, + } + } + + fn config(mut self, config: Option<&'a Config>) -> Self { + self.config = config; + self } } @@ -134,9 +148,10 @@ where // To diff arbitary types, render them as debug strings let left_debug = format!("{:#?}", self.left); let right_debug = format!("{:#?}", self.right); + let config = self.config.cloned().unwrap_or_default().into(); // And then diff the debug output - printer::write_header(f)?; - printer::write_lines(f, &left_debug, &right_debug) + printer::write_header(f, &config)?; + printer::write_lines(f, &left_debug, &right_debug, &config) } } @@ -184,6 +199,7 @@ where { left: &'a TLeft, right: &'a TRight, + config: Option<&'a Config>, } impl<'a, TLeft, TRight> StrComparison<'a, TLeft, TRight> @@ -195,7 +211,16 @@ where /// /// Expensive diffing is deferred until calling `Debug::fmt`. pub fn new(left: &'a TLeft, right: &'a TRight) -> StrComparison<'a, TLeft, TRight> { - StrComparison { left, right } + StrComparison { + left, + right, + config: None, + } + } + + fn config(mut self, config: Option<&'a Config>) -> Self { + self.config = config; + self } } @@ -205,8 +230,9 @@ where 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()) + let config = self.config.cloned().unwrap_or_default().into(); + printer::write_header(f, &config)?; + printer::write_lines(f, self.left.as_ref(), self.right.as_ref(), &config) } } @@ -225,19 +251,67 @@ where /// /// let a = 3; /// let b = 1 + 2; +/// +/// // Simple use /// assert_eq!(a, b); /// +/// // Custom message on failure /// assert_eq!(a, b, "we are testing addition with {} and {}", a, b); /// ``` +/// +/// # Configuration +/// +/// You can provide an optional `config = ...` argument to customize the output format. +/// +/// See the [`config::Config`] docs for available options. +/// +/// ``` +/// use pretty_assertions::assert_eq; +/// use pretty_assertions::config::{Config, LineSymbol}; +/// +/// // Uses '+' and '-' symbols, instead of '<' and '>' +/// let config = Config::default().line_symbol(LineSymbol::Sign); +/// assert_eq!(config = config, 42, 42); +/// ``` +/// +/// If you'd like for all your assertions to use the same settings, we recommend +/// defining a helper macro like: +/// +/// ``` +/// use pretty_assertions::assert_eq; +/// use pretty_assertions::config::{Config, LineSymbol}; +/// +/// macro_rules! assert_eq { +/// ($left:expr, $right:expr) => ({ +/// let config = Config::default().line_symbol(LineSymbol::Sign); +/// ::pretty_assertions::assert_eq!(config = config, $left, $right); +/// }); +/// } +/// +/// // Will use your custom settings +/// assert_eq!(42, 42); +/// ``` #[macro_export] macro_rules! assert_eq { + (config = $config:expr, $left:expr, $right:expr$(,)?) => ({ + use ::core::borrow::Borrow; + use ::core::option::Option::Some; + $crate::assert_eq!(@ Some($config.borrow()), $left, $right, "", ""); + }); + (config = $config:expr, $left:expr, $right:expr, $($arg:tt)*) => ({ + use ::core::borrow::Borrow; + use ::core::option::Option::Some; + $crate::assert_eq!(@ Some($config.borrow()), $left, $right, ": ", $($arg)+); + }); ($left:expr, $right:expr$(,)?) => ({ - $crate::assert_eq!(@ $left, $right, "", ""); + use ::core::option::Option::None; + $crate::assert_eq!(@ None, $left, $right, "", ""); }); ($left:expr, $right:expr, $($arg:tt)*) => ({ - $crate::assert_eq!(@ $left, $right, ": ", $($arg)+); + use ::core::option::Option::None; + $crate::assert_eq!(@ None, $left, $right, ": ", $($arg)+); }); - (@ $left:expr, $right:expr, $maybe_colon:expr, $($arg:tt)*) => ({ + (@ $config:expr, $left:expr, $right:expr, $maybe_colon:expr, $($arg:tt)*) => ({ match (&($left), &($right)) { (left_val, right_val) => { if !(*left_val == *right_val) { @@ -248,7 +322,7 @@ macro_rules! assert_eq { \n", $maybe_colon, format_args!($($arg)*), - (left_val, right_val).create_comparison() + (left_val, right_val, $config).create_comparison() ) } } @@ -446,21 +520,21 @@ pub mod private { fn create_comparison(self) -> Self::Comparison; } - impl<'a, T, U> CreateComparison for &'a (T, U) { + impl<'a, T, U> CreateComparison for &'a (T, U, Option<&'a crate::Config>) { type Comparison = crate::Comparison<'a, T, U>; fn create_comparison(self) -> Self::Comparison { - crate::Comparison::new(&self.0, &self.1) + crate::Comparison::new(&self.0, &self.1).config(self.2) } } - impl<'a, T, U> CreateComparison for (&'a T, &'a U) + impl<'a, T, U> CreateComparison for (&'a T, &'a U, Option<&'a crate::Config>) 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) + crate::StrComparison::new(self.0, self.1).config(self.2) } } } diff --git a/pretty_assertions/src/printer.rs b/pretty_assertions/src/printer.rs index 24c25f0..d1ae7ed 100644 --- a/pretty_assertions/src/printer.rs +++ b/pretty_assertions/src/printer.rs @@ -1,3 +1,4 @@ +use crate::config::{Config, LineSymbol}; #[cfg(feature = "alloc")] use alloc::format; use core::fmt; @@ -12,17 +13,35 @@ macro_rules! paint { ) } -const SIGN_RIGHT: char = '>'; // + > → -const SIGN_LEFT: char = '<'; // - < ← +pub(crate) struct PrinterConfig { + line_symbols: LineSymbols, +} + +struct LineSymbols { + removed: char, + added: char, +} + +impl From for PrinterConfig { + fn from(other: Config) -> Self { + let (removed, added) = match other.line_symbol { + LineSymbol::Arrow => ('<', '>'), + LineSymbol::Sign => ('-', '+'), + }; + Self { + line_symbols: LineSymbols { removed, added }, + } + } +} /// Present the diff output for two mutliline strings in a pretty, colorised manner. -pub(crate) fn write_header(f: &mut fmt::Formatter) -> fmt::Result { +pub(crate) fn write_header(f: &mut fmt::Formatter, config: &PrinterConfig) -> fmt::Result { writeln!( f, "{} {} / {} :", Style::new(Unset).bold().paint("Diff"), - Red.paint(format!("{} left", SIGN_LEFT)), - Green.paint(format!("right {}", SIGN_RIGHT)) + Red.paint(format!("{} left", config.line_symbols.removed)), + Green.paint(format!("right {}", config.line_symbols.added)) ) } @@ -30,15 +49,23 @@ pub(crate) fn write_header(f: &mut fmt::Formatter) -> fmt::Result { /// /// It can be formatted as a whole chunk by calling `flush`, or the inner value /// obtained with `take` for further processing (such as an inline diff). -#[derive(Default)] struct LatentDeletion<'a> { // The most recent deleted line we've seen value: Option<&'a str>, // The number of deleted lines we've seen, including the current value count: usize, + line_symbol_removed: char, } impl<'a> LatentDeletion<'a> { + fn new(line_symbol_removed: char) -> Self { + Self { + value: None, + count: 0, + line_symbol_removed, + } + } + /// Set the chunk value. fn set(&mut self, value: &'a str) { self.value = Some(value); @@ -62,7 +89,7 @@ impl<'a> LatentDeletion<'a> { /// without seeing another deletion. Therefore the line in the middle was something else). fn flush(&mut self, f: &mut TWrite) -> fmt::Result { if let Some(value) = self.value { - paint!(f, Red, "{}{}", SIGN_LEFT, value)?; + paint!(f, Red, "{}{}", self.line_symbol_removed, value)?; writeln!(f)?; self.value = None; } else { @@ -82,11 +109,12 @@ pub(crate) fn write_lines( f: &mut TWrite, left: &str, right: &str, + config: &PrinterConfig, ) -> fmt::Result { let diff = ::diff::lines(left, right); let mut changes = diff.into_iter().peekable(); - let mut previous_deletion = LatentDeletion::default(); + let mut previous_deletion = LatentDeletion::new(config.line_symbols.removed); while let Some(change) = changes.next() { match (change, changes.peek()) { @@ -103,16 +131,16 @@ pub(crate) fn write_lines( // If we're being followed by more insertions, don't inline diff (::diff::Result::Right(inserted), Some(::diff::Result::Right(_))) => { previous_deletion.flush(f)?; - paint!(f, Green, "{}{}", SIGN_RIGHT, inserted)?; + paint!(f, Green, "{}{}", config.line_symbols.added, inserted)?; writeln!(f)?; } // Otherwise, check if we need to inline diff with the previous line (if it was a deletion) (::diff::Result::Right(inserted), _) => { if let Some(deleted) = previous_deletion.take() { - write_inline_diff(f, deleted, inserted)?; + write_inline_diff(f, deleted, inserted, config)?; } else { previous_deletion.flush(f)?; - paint!(f, Green, "{}{}", SIGN_RIGHT, inserted)?; + paint!(f, Green, "{}{}", config.line_symbols.added, inserted)?; writeln!(f)?; } } @@ -175,14 +203,19 @@ where /// The given strings should not have a trailing newline. /// /// The output of this function will be two lines, each with a trailing newline. -fn write_inline_diff(f: &mut TWrite, left: &str, right: &str) -> fmt::Result { +fn write_inline_diff( + f: &mut TWrite, + left: &str, + right: &str, + config: &PrinterConfig, +) -> fmt::Result { let diff = ::diff::chars(left, right); let mut writer = InlineWriter::new(f); // Print the left string on one line, with differences highlighted let light = Style::new(Red); let heavy = Style::new(Red).bg(Fixed(52)).bold(); - writer.write_with_style(&SIGN_LEFT, light)?; + writer.write_with_style(&config.line_symbols.removed, light)?; for change in diff.iter() { match change { ::diff::Result::Both(value, _) => writer.write_with_style(value, light)?, @@ -195,7 +228,7 @@ fn write_inline_diff(f: &mut TWrite, left: &str, right: &str // Print the right string on one line, with differences highlighted let light = Style::new(Green); let heavy = Style::new(Green).bg(Fixed(22)).bold(); - writer.write_with_style(&SIGN_RIGHT, light)?; + writer.write_with_style(&config.line_symbols.added, light)?; for change in diff.iter() { match change { ::diff::Result::Both(value, _) => writer.write_with_style(value, light)?, @@ -222,16 +255,28 @@ mod test { const GREEN_HEAVY: &str = "\u{1b}[1;48;5;22;32m"; const RESET: &str = "\u{1b}[0m"; + const V1_CONFIG: PrinterConfig = PrinterConfig { + line_symbols: LineSymbols { + removed: '<', + added: '>', + }, + }; + /// Given that both of our diff printing functions have the same /// type signature, we can reuse the same test code for them. /// /// This could probably be nicer with traits! - fn check_printer(printer: TPrint, left: &str, right: &str, expected: &str) - where - TPrint: Fn(&mut String, &str, &str) -> fmt::Result, + fn check_printer( + printer: TPrint, + left: &str, + right: &str, + expected: &str, + config: &PrinterConfig, + ) where + TPrint: Fn(&mut String, &str, &str, &PrinterConfig) -> fmt::Result, { let mut actual = String::new(); - printer(&mut actual, left, right).expect("printer function failed"); + printer(&mut actual, left, right, config).expect("printer function failed"); // Cannot use IO without stdlib #[cfg(feature = "std")] @@ -261,7 +306,33 @@ mod test { reset = RESET, ); - check_printer(write_inline_diff, left, right, &expected); + check_printer(write_inline_diff, left, right, &expected, &V1_CONFIG); + } + + #[test] + fn write_inline_diff_empty_git() { + let left = ""; + let right = ""; + let expected = format!( + "{red_light}-{reset}\n\ + {green_light}+{reset}\n", + red_light = RED_LIGHT, + green_light = GREEN_LIGHT, + reset = RESET, + ); + + check_printer( + write_inline_diff, + left, + right, + &expected, + &PrinterConfig { + line_symbols: LineSymbols { + removed: '-', + added: '+', + }, + }, + ); } #[test] @@ -277,7 +348,7 @@ mod test { reset = RESET, ); - check_printer(write_inline_diff, left, right, &expected); + check_printer(write_inline_diff, left, right, &expected, &V1_CONFIG); } #[test] @@ -293,7 +364,7 @@ mod test { reset = RESET, ); - check_printer(write_inline_diff, left, right, &expected); + check_printer(write_inline_diff, left, right, &expected, &V1_CONFIG); } #[test] @@ -310,7 +381,7 @@ mod test { reset = RESET, ); - check_printer(write_inline_diff, left, right, &expected); + check_printer(write_inline_diff, left, right, &expected, &V1_CONFIG); } /// If one of our strings is empty, it should not be shown at all in the output. @@ -324,7 +395,7 @@ mod test { reset = RESET, ); - check_printer(write_lines, left, right, &expected); + check_printer(write_lines, left, right, &expected, &V1_CONFIG); } /// Realistic multiline struct diffing case. @@ -368,7 +439,7 @@ mod test { reset = RESET, ); - check_printer(write_lines, left, right, &expected); + check_printer(write_lines, left, right, &expected, &V1_CONFIG); } /// Relistic multiple line chunks @@ -394,7 +465,7 @@ Caravaggio"#; reset = RESET, ); - check_printer(write_lines, left, right, &expected); + check_printer(write_lines, left, right, &expected, &V1_CONFIG); } /// Single deletion line, multiple insertions - no inline diffing. @@ -413,7 +484,7 @@ Caravaggio"#; reset = RESET, ); - check_printer(write_lines, left, right, &expected); + check_printer(write_lines, left, right, &expected, &V1_CONFIG); } /// Multiple deletion, single insertion - no inline diffing. @@ -432,7 +503,7 @@ Cabbage"#; reset = RESET, ); - check_printer(write_lines, left, right, &expected); + check_printer(write_lines, left, right, &expected, &V1_CONFIG); } /// Regression test for multiline highlighting issue @@ -474,7 +545,7 @@ Cabbage"#; reset = RESET, ); - check_printer(write_lines, left, right, &expected); + check_printer(write_lines, left, right, &expected, &V1_CONFIG); } mod write_lines_edge_newlines { @@ -498,7 +569,7 @@ Cabbage"#; reset = RESET, ); - check_printer(write_lines, left, right, &expected); + check_printer(write_lines, left, right, &expected, &V1_CONFIG); } #[test] @@ -519,7 +590,7 @@ Cabbage"#; reset = RESET, ); - check_printer(write_lines, left, right, &expected); + check_printer(write_lines, left, right, &expected, &V1_CONFIG); } #[test] @@ -536,7 +607,7 @@ Cabbage"#; reset = RESET, ); - check_printer(write_lines, left, right, &expected); + check_printer(write_lines, left, right, &expected, &V1_CONFIG); } #[test] @@ -553,7 +624,7 @@ Cabbage"#; reset = RESET, ); - check_printer(write_lines, left, right, &expected); + check_printer(write_lines, left, right, &expected, &V1_CONFIG); } #[test] @@ -570,7 +641,7 @@ Cabbage"#; reset = RESET, ); - check_printer(write_lines, left, right, &expected); + check_printer(write_lines, left, right, &expected, &V1_CONFIG); } /// Regression test for double abort @@ -594,7 +665,7 @@ Cabbage"#; reset = RESET, ); - check_printer(write_lines, left, right, &expected); + check_printer(write_lines, left, right, &expected, &V1_CONFIG); } } } diff --git a/pretty_assertions/tests/macros.rs b/pretty_assertions/tests/macros.rs index 0ef251f..0f3c13e 100644 --- a/pretty_assertions/tests/macros.rs +++ b/pretty_assertions/tests/macros.rs @@ -124,6 +124,36 @@ mod assert_eq { #[test] #[should_panic(expected = r#"assertion failed: `(left == right)` +Diff - left / right + : +-666 ++999 + +"#)] + fn fails_with_config() { + use ::core::default::Default; + let config = ::pretty_assertions::config::Config::default() + .line_symbol(::pretty_assertions::config::LineSymbol::Sign); + ::pretty_assertions::assert_eq!(config = config, 666, 999); + } + + #[test] + #[should_panic(expected = r#"assertion failed: `(left == right)` + +Diff - left / right + : +-666 ++999 + +"#)] + fn fails_with_config_reference() { + use ::core::default::Default; + let config = ::pretty_assertions::config::Config::default() + .line_symbol(::pretty_assertions::config::LineSymbol::Sign); + ::pretty_assertions::assert_eq!(config = &config, 666, 999); + } + + #[test] + #[should_panic(expected = r#"assertion failed: `(left == right)` + Diff < left / right > : <666 >999