diff --git a/crates/snapbox/src/assert.rs b/crates/snapbox/src/assert.rs index f0267c07..1694d8a8 100644 --- a/crates/snapbox/src/assert.rs +++ b/crates/snapbox/src/assert.rs @@ -21,17 +21,18 @@ use crate::Action; /// .matches_path(actual, "tests/fixtures/help_output_is_clean.txt"); /// ``` #[derive(Clone, Debug)] -pub struct Assert { +pub struct Assert<'a> { action: Action, action_var: Option, normalize_paths: bool, substitutions: crate::Substitutions, pub(crate) palette: crate::report::Palette, pub(crate) data_format: Option, + message: Option>, } /// # Assertions -impl Assert { +impl<'a> Assert<'a> { pub fn new() -> Self { Default::default() } @@ -56,7 +57,7 @@ impl Assert { fn eq_inner(&self, expected: crate::Data, actual: crate::Data) { let (pattern, actual) = self.normalize_eq(Ok(expected), actual); if let Err(desc) = pattern.and_then(|p| self.try_verify(&p, &actual, None, None)) { - panic!("{}: {}", self.palette.error("Eq failed"), desc); + panic!("{}: {}", self.error_message("Eq failed"), desc); } } @@ -87,7 +88,7 @@ impl Assert { fn matches_inner(&self, pattern: crate::Data, actual: crate::Data) { let (pattern, actual) = self.normalize_match(Ok(pattern), actual); if let Err(desc) = pattern.and_then(|p| self.try_verify(&p, &actual, None, None)) { - panic!("{}: {}", self.palette.error("Match failed"), desc); + panic!("{}: {}", self.error_message("Match failed"), desc); } } @@ -287,11 +288,35 @@ impl Assert { Ok(()) } } + + fn error_message( + &self, + default_message: &'static str, + ) -> crate::report::Styled> { + self.palette.error(match self.message { + Some(custom_message) => ErrorMessage::Formatted(custom_message), + None => ErrorMessage::Static(default_message), + }) + } +} + +enum ErrorMessage<'a> { + Static(&'static str), + Formatted(std::fmt::Arguments<'a>), +} + +impl std::fmt::Display for ErrorMessage<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ErrorMessage::Static(err) => write!(f, "{err}"), + ErrorMessage::Formatted(err) => write!(f, "{err}"), + } + } } /// # Directory Assertions #[cfg(feature = "path")] -impl Assert { +impl Assert<'_> { #[track_caller] pub fn subset_eq( &self, @@ -452,7 +477,7 @@ impl Assert { } /// # Customize Behavior -impl Assert { +impl<'a> Assert<'a> { /// Override the color palette pub fn palette(mut self, palette: crate::report::Palette) -> Self { self.palette = palette; @@ -474,6 +499,14 @@ impl Assert { self } + /// Override the panic message. + /// + /// Currently only supported by `eq`, `eq_path`, `matches` and `matches_path`. + pub fn message(mut self, args: std::fmt::Arguments<'a>) -> Self { + self.message = Some(args); + self + } + /// Override the default [`Substitutions`][crate::Substitutions] pub fn substitutions(mut self, substitutions: crate::Substitutions) -> Self { self.substitutions = substitutions; @@ -505,7 +538,7 @@ impl Assert { } } -impl Default for Assert { +impl Default for Assert<'_> { fn default() -> Self { Self { action: Default::default(), @@ -514,6 +547,7 @@ impl Default for Assert { substitutions: Default::default(), palette: crate::report::Palette::color(), data_format: Default::default(), + message: Default::default(), } .substitutions(crate::Substitutions::with_exe()) } diff --git a/crates/snapbox/src/cmd.rs b/crates/snapbox/src/cmd.rs index 8529852f..50faebbe 100644 --- a/crates/snapbox/src/cmd.rs +++ b/crates/snapbox/src/cmd.rs @@ -5,16 +5,16 @@ use anstream::panic; /// Process spawning for testing of non-interactive commands #[derive(Debug)] -pub struct Command { +pub struct Command<'a> { cmd: std::process::Command, stdin: Option, timeout: Option, _stderr_to_stdout: bool, - config: crate::Assert, + config: crate::Assert<'a>, } /// # Builder API -impl Command { +impl<'a> Command<'a> { pub fn new(program: impl AsRef) -> Self { Self { cmd: std::process::Command::new(program), @@ -37,7 +37,7 @@ impl Command { } /// Customize the assertion behavior - pub fn with_assert(mut self, config: crate::Assert) -> Self { + pub fn with_assert(mut self, config: crate::Assert<'a>) -> Self { self.config = config; self } @@ -275,7 +275,7 @@ impl Command { } /// # Run Command -impl Command { +impl<'a> Command<'a> { /// Run the command and assert on the results /// /// ```rust @@ -288,7 +288,7 @@ impl Command { /// .stdout_eq("42"); /// ``` #[track_caller] - pub fn assert(self) -> OutputAssert { + pub fn assert(self) -> OutputAssert<'a> { let config = self.config.clone(); match self.output() { Ok(output) => OutputAssert::new(output).with_assert(config), @@ -424,7 +424,7 @@ where }) } -impl From for Command { +impl From for Command<'_> { fn from(cmd: std::process::Command) -> Self { Self::from_std(cmd) } @@ -435,12 +435,12 @@ impl From for Command { /// Create an `OutputAssert` through the [`Command::assert`]. /// /// [`Output`]: std::process::Output -pub struct OutputAssert { +pub struct OutputAssert<'a> { output: std::process::Output, - config: crate::Assert, + config: crate::Assert<'a>, } -impl OutputAssert { +impl<'a> OutputAssert<'a> { /// Create an `Assert` for a given [`Output`]. /// /// [`Output`]: std::process::Output @@ -452,7 +452,7 @@ impl OutputAssert { } /// Customize the assertion behavior - pub fn with_assert(mut self, config: crate::Assert) -> Self { + pub fn with_assert(mut self, config: crate::Assert<'a>) -> Self { self.config = config; self } diff --git a/crates/snapbox/src/lib.rs b/crates/snapbox/src/lib.rs index 7084c15d..436bb960 100644 --- a/crates/snapbox/src/lib.rs +++ b/crates/snapbox/src/lib.rs @@ -244,3 +244,14 @@ pub fn assert_subset_matches( .action_env(DEFAULT_ACTION_ENV) .subset_matches(pattern_root, actual_root); } + +/// A [`std::assert_eq`] compatible wrapper around the [`Assert`] API. +#[macro_export] +macro_rules! assert_eq { + ($left:expr, $right:expr $(,)?) => { + Assert::new().eq($left, $right); + }; + ($left:expr, $right:expr, $($arg:tt)+) => { + Assert::new().message(format_args!($($arg)+)).eq($left, $right); + }; +} diff --git a/src/schema.rs b/src/schema.rs index 7d7cbfb7..0e6a58f9 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -806,7 +806,10 @@ impl Env { self.remove.extend(other.remove.iter().cloned()); } - pub(crate) fn apply(&self, mut command: snapbox::cmd::Command) -> snapbox::cmd::Command { + pub(crate) fn apply<'a>( + &self, + mut command: snapbox::cmd::Command<'a>, + ) -> snapbox::cmd::Command<'a> { if !self.inherit() { command = command.env_clear(); }