From e9dbf6859dc5229acf96d8b5dc0ab57420c5d488 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 22 Apr 2024 16:40:32 -0500 Subject: [PATCH 1/8] feat(data): Add IntoData for easier initialization --- crates/snapbox/src/data/mod.rs | 27 +++++++++++++++++++++++++++ crates/snapbox/src/lib.rs | 1 + 2 files changed, 28 insertions(+) diff --git a/crates/snapbox/src/data/mod.rs b/crates/snapbox/src/data/mod.rs index 6120acea..7486bb9e 100644 --- a/crates/snapbox/src/data/mod.rs +++ b/crates/snapbox/src/data/mod.rs @@ -42,6 +42,32 @@ impl ToDebug for D { } } +/// Modify `expected` data +pub trait IntoData: Sized { + /// Remove default [`filters`][crate::filters] from this `expected` result + fn raw(self) -> Data { + self.into_data().raw() + } + + /// Initialize as [`format`][DataFormat] or [`Error`][DataFormat::Error] + /// + /// This is generally used for `expected` data + fn is(self, format: DataFormat) -> Data { + self.into_data().is(format) + } + + fn into_data(self) -> Data; +} + +impl IntoData for D +where + D: Into, +{ + fn into_data(self) -> Data { + self.into() + } +} + /// Declare an expected value for an assert from a file /// /// This is relative to the source file the macro is run from @@ -140,6 +166,7 @@ pub(crate) enum DataInner { /// - [`str!`] for inline snapshots /// - [`file!`] for external snapshots /// - [`ToDebug`] for verifying a debug representation +/// - [`IntoData`] for modifying `expected` impl Data { /// Mark the data as binary (no post-processing) pub fn binary(raw: impl Into>) -> Self { diff --git a/crates/snapbox/src/lib.rs b/crates/snapbox/src/lib.rs index d9248896..3e09e27e 100644 --- a/crates/snapbox/src/lib.rs +++ b/crates/snapbox/src/lib.rs @@ -112,6 +112,7 @@ pub use action::Action; pub use action::DEFAULT_ACTION_ENV; pub use assert::Assert; pub use data::Data; +pub use data::IntoData; pub use data::ToDebug; pub use error::Error; pub use filters::Redactions; From c925d49789a62355f68d3e1d4de566ae31426efc Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 22 Apr 2024 16:42:35 -0500 Subject: [PATCH 2/8] feat(snap): Add a prelude --- crates/snapbox/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/snapbox/src/lib.rs b/crates/snapbox/src/lib.rs index 3e09e27e..21340b87 100644 --- a/crates/snapbox/src/lib.rs +++ b/crates/snapbox/src/lib.rs @@ -120,6 +120,12 @@ pub use snapbox_macros::debug; pub type Result = std::result::Result; +/// Easier access to common traits +pub mod prelude { + pub use crate::IntoData; + pub use crate::ToDebug; +} + /// Check if a value is the same as an expected value /// /// By default [`filters`] are applied, including: From 732fad9cf858401aaee0cfd8930edabb9247b454 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 22 Apr 2024 16:43:32 -0500 Subject: [PATCH 3/8] fix(data)!: Remove Inline::raw in favor of IntoData::raw --- crates/snapbox/src/data/runtime.rs | 2 +- crates/snapbox/src/data/source.rs | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/crates/snapbox/src/data/runtime.rs b/crates/snapbox/src/data/runtime.rs index a0208da4..132f7345 100644 --- a/crates/snapbox/src/data/runtime.rs +++ b/crates/snapbox/src/data/runtime.rs @@ -349,8 +349,8 @@ impl PathRuntime { mod tests { use super::*; use crate::assert_eq; + use crate::prelude::*; use crate::str; - use crate::ToDebug as _; #[test] fn test_format_patch() { diff --git a/crates/snapbox/src/data/source.rs b/crates/snapbox/src/data/source.rs index 26ad94c7..066ec7d5 100644 --- a/crates/snapbox/src/data/source.rs +++ b/crates/snapbox/src/data/source.rs @@ -97,13 +97,6 @@ impl Inline { data.is(format) } - /// Remove default [`filters`][crate::filters] from this `expected` result - pub fn raw(self) -> super::Data { - let mut data: super::Data = self.into(); - data.filters = super::FilterSet::empty().newlines(); - data - } - fn trimmed(&self) -> String { let mut data = self.data; if data.contains('\n') { From 00e746b545629048708e0046c2bab102ddd08052 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 22 Apr 2024 16:45:37 -0500 Subject: [PATCH 4/8] fix(data)!: Remove Inline::is in favor of IntoData::is --- crates/snapbox/src/data/mod.rs | 17 +++++++++++++---- crates/snapbox/src/data/source.rs | 18 ------------------ 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/crates/snapbox/src/data/mod.rs b/crates/snapbox/src/data/mod.rs index 7486bb9e..969d5680 100644 --- a/crates/snapbox/src/data/mod.rs +++ b/crates/snapbox/src/data/mod.rs @@ -52,6 +52,17 @@ pub trait IntoData: Sized { /// Initialize as [`format`][DataFormat] or [`Error`][DataFormat::Error] /// /// This is generally used for `expected` data + /// + /// ```rust + /// # #[cfg(feature = "json")] { + /// use snapbox::prelude::*; + /// use snapbox::str; + /// + /// let expected = str![[r#"{"hello": "world"}"#]] + /// .is(snapbox::data::DataFormat::Json); + /// assert_eq!(expected.format(), snapbox::data::DataFormat::Json); + /// # } + /// ``` fn is(self, format: DataFormat) -> Data { self.into_data().is(format) } @@ -106,6 +117,8 @@ macro_rules! file { /// Declare an expected value from within Rust source /// +/// Output type: [`Inline`], see [`IntoData`] for operations +/// /// ``` /// # use snapbox::str; /// str![[" @@ -113,10 +126,6 @@ macro_rules! file { /// "]]; /// str![r#"{"Foo": 92}"#]; /// ``` -/// -/// Leading indentation is stripped. -/// -/// See [`Inline::is`] for declaring the data to be of a certain format. #[macro_export] macro_rules! str { [$data:literal] => { $crate::str![[$data]] }; diff --git a/crates/snapbox/src/data/source.rs b/crates/snapbox/src/data/source.rs index 066ec7d5..8dd14697 100644 --- a/crates/snapbox/src/data/source.rs +++ b/crates/snapbox/src/data/source.rs @@ -79,24 +79,6 @@ pub struct Inline { } impl Inline { - /// Initialize `Self` as [`format`][crate::data::DataFormat] or [`Error`][crate::data::DataFormat::Error] - /// - /// This is generally used for `expected` data - /// - /// ```rust - /// # #[cfg(feature = "json")] { - /// use snapbox::str; - /// - /// let expected = str![[r#"{"hello": "world"}"#]] - /// .is(snapbox::data::DataFormat::Json); - /// assert_eq!(expected.format(), snapbox::data::DataFormat::Json); - /// # } - /// ``` - pub fn is(self, format: super::DataFormat) -> super::Data { - let data: super::Data = self.into(); - data.is(format) - } - fn trimmed(&self) -> String { let mut data = self.data; if data.contains('\n') { From e1cadb372d120f6b99cd87ece49e6c2cfdabff5e Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 22 Apr 2024 16:51:02 -0500 Subject: [PATCH 5/8] refactor(snap): Switch to IntoData for easier discoverability --- crates/snapbox/src/assert.rs | 7 ++++--- crates/snapbox/src/cmd.rs | 14 ++++++++------ crates/snapbox/src/lib.rs | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/crates/snapbox/src/assert.rs b/crates/snapbox/src/assert.rs index bf60cd70..152ba22e 100644 --- a/crates/snapbox/src/assert.rs +++ b/crates/snapbox/src/assert.rs @@ -7,6 +7,7 @@ use std::io::stderr; use crate::filters::{Filter as _, FilterNewlines, FilterPaths, FilterRedactions}; use crate::Action; +use crate::IntoData; /// Snapshot assertion against a file's contents /// @@ -61,9 +62,9 @@ impl Assert { /// Assert::new().eq(file!["output.txt"], actual); /// ``` #[track_caller] - pub fn eq(&self, expected: impl Into, actual: impl Into) { - let expected = expected.into(); - let actual = actual.into(); + pub fn eq(&self, expected: impl IntoData, actual: impl IntoData) { + let expected = expected.into_data(); + let actual = actual.into_data(); if let Err(err) = self.try_eq(expected, actual, Some(&"In-memory")) { err.panic(); } diff --git a/crates/snapbox/src/cmd.rs b/crates/snapbox/src/cmd.rs index 1df82fbb..5dd669da 100644 --- a/crates/snapbox/src/cmd.rs +++ b/crates/snapbox/src/cmd.rs @@ -3,6 +3,8 @@ #[cfg(feature = "color")] use anstream::panic; +use crate::IntoData; + /// Process spawning for testing of non-interactive commands #[derive(Debug)] pub struct Command { @@ -243,8 +245,8 @@ impl Command { /// .assert() /// .stdout_eq("42"); /// ``` - pub fn stdin(mut self, stream: impl Into) -> Self { - self.stdin = Some(stream.into()); + pub fn stdin(mut self, stream: impl IntoData) -> Self { + self.stdin = Some(stream.into_data()); self } @@ -621,8 +623,8 @@ impl OutputAssert { /// .stdout_eq(file!["stdout.log"]); /// ``` #[track_caller] - pub fn stdout_eq(self, expected: impl Into) -> Self { - let expected = expected.into(); + pub fn stdout_eq(self, expected: impl IntoData) -> Self { + let expected = expected.into_data(); self.stdout_eq_inner(expected) } @@ -671,8 +673,8 @@ impl OutputAssert { /// .stderr_eq(file!["stderr.log"]); /// ``` #[track_caller] - pub fn stderr_eq(self, expected: impl Into) -> Self { - let expected = expected.into(); + pub fn stderr_eq(self, expected: impl IntoData) -> Self { + let expected = expected.into_data(); self.stderr_eq_inner(expected) } diff --git a/crates/snapbox/src/lib.rs b/crates/snapbox/src/lib.rs index 21340b87..ef6f6ce8 100644 --- a/crates/snapbox/src/lib.rs +++ b/crates/snapbox/src/lib.rs @@ -152,7 +152,7 @@ pub mod prelude { /// assert_eq(file!["output.txt"], actual); /// ``` #[track_caller] -pub fn assert_eq(expected: impl Into, actual: impl Into) { +pub fn assert_eq(expected: impl IntoData, actual: impl IntoData) { Assert::new() .action_env(DEFAULT_ACTION_ENV) .eq(expected, actual); From bd2a8581bfdd053858f81e29fc8f6f770d5ed855 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 22 Apr 2024 16:52:25 -0500 Subject: [PATCH 6/8] refactor(data): Consolidate From impls --- crates/snapbox/src/data/mod.rs | 7 +++++++ crates/snapbox/src/data/source.rs | 9 +-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/snapbox/src/data/mod.rs b/crates/snapbox/src/data/mod.rs index 969d5680..954be351 100644 --- a/crates/snapbox/src/data/mod.rs +++ b/crates/snapbox/src/data/mod.rs @@ -666,6 +666,13 @@ impl<'s> From<&'s str> for Data { } } +impl From for super::Data { + fn from(inline: Inline) -> Self { + let trimmed = inline.trimmed(); + super::Data::text(trimmed).with_source(inline) + } +} + #[cfg(feature = "detect-encoding")] fn is_binary(data: &[u8]) -> bool { match content_inspector::inspect(data) { diff --git a/crates/snapbox/src/data/source.rs b/crates/snapbox/src/data/source.rs index 8dd14697..10e9b9d9 100644 --- a/crates/snapbox/src/data/source.rs +++ b/crates/snapbox/src/data/source.rs @@ -79,7 +79,7 @@ pub struct Inline { } impl Inline { - fn trimmed(&self) -> String { + pub(crate) fn trimmed(&self) -> String { let mut data = self.data; if data.contains('\n') { data = data.strip_prefix('\n').unwrap_or(data); @@ -95,13 +95,6 @@ impl std::fmt::Display for Inline { } } -impl From for super::Data { - fn from(inline: Inline) -> Self { - let trimmed = inline.trimmed(); - super::Data::text(trimmed).with_source(inline) - } -} - /// Position within Rust source code, see [`Inline`] #[doc(hidden)] #[derive(Clone, Debug, PartialEq, Eq)] From fb144107063f8f32d195baf4dc378002faec440f Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 22 Apr 2024 16:58:17 -0500 Subject: [PATCH 7/8] docs(data): Clarify supported types for IntoData --- crates/snapbox/src/data/mod.rs | 53 ++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/crates/snapbox/src/data/mod.rs b/crates/snapbox/src/data/mod.rs index 954be351..5c0450e1 100644 --- a/crates/snapbox/src/data/mod.rs +++ b/crates/snapbox/src/data/mod.rs @@ -42,7 +42,7 @@ impl ToDebug for D { } } -/// Modify `expected` data +/// Convert to [`Data`] with modifiers for `expected` data pub trait IntoData: Sized { /// Remove default [`filters`][crate::filters] from this `expected` result fn raw(self) -> Data { @@ -67,15 +67,56 @@ pub trait IntoData: Sized { self.into_data().is(format) } + /// Convert to [`Data`], applying defaults fn into_data(self) -> Data; } -impl IntoData for D -where - D: Into, -{ +impl IntoData for Data { fn into_data(self) -> Data { - self.into() + self + } +} + +impl IntoData for &'_ Data { + fn into_data(self) -> Data { + self.clone() + } +} + +impl IntoData for Vec { + fn into_data(self) -> Data { + Data::binary(self) + } +} + +impl IntoData for &'_ [u8] { + fn into_data(self) -> Data { + self.to_owned().into() + } +} + +impl IntoData for String { + fn into_data(self) -> Data { + Data::text(self) + } +} + +impl IntoData for &'_ String { + fn into_data(self) -> Data { + self.to_owned().into() + } +} + +impl IntoData for &'_ str { + fn into_data(self) -> Data { + self.to_owned().into() + } +} + +impl IntoData for Inline { + fn into_data(self) -> Data { + let trimmed = self.trimmed(); + super::Data::text(trimmed).with_source(self) } } From 8daebd32ec2734fdb42e84f55bed7048b89803c3 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 22 Apr 2024 17:01:09 -0500 Subject: [PATCH 8/8] fix(data): From From impls in favor of IntoData This reduces duplicated effort --- crates/snapbox/src/cmd.rs | 4 +-- crates/snapbox/src/data/mod.rs | 49 +++------------------------------- src/runner.rs | 5 ++-- 3 files changed, 8 insertions(+), 50 deletions(-) diff --git a/crates/snapbox/src/cmd.rs b/crates/snapbox/src/cmd.rs index 5dd669da..7dd26ed6 100644 --- a/crates/snapbox/src/cmd.rs +++ b/crates/snapbox/src/cmd.rs @@ -630,7 +630,7 @@ impl OutputAssert { #[track_caller] fn stdout_eq_inner(self, expected: crate::Data) -> Self { - let actual = crate::Data::from(self.output.stdout.as_slice()); + let actual = self.output.stdout.as_slice().into_data(); if let Err(err) = self.config.try_eq(expected, actual, Some(&"stdout")) { err.panic(); } @@ -680,7 +680,7 @@ impl OutputAssert { #[track_caller] fn stderr_eq_inner(self, expected: crate::Data) -> Self { - let actual = crate::Data::from(self.output.stderr.as_slice()); + let actual = self.output.stderr.as_slice().into_data(); if let Err(err) = self.config.try_eq(expected, actual, Some(&"stderr")) { err.panic(); } diff --git a/crates/snapbox/src/data/mod.rs b/crates/snapbox/src/data/mod.rs index 5c0450e1..c41f1e4a 100644 --- a/crates/snapbox/src/data/mod.rs +++ b/crates/snapbox/src/data/mod.rs @@ -91,7 +91,7 @@ impl IntoData for Vec { impl IntoData for &'_ [u8] { fn into_data(self) -> Data { - self.to_owned().into() + self.to_owned().into_data() } } @@ -103,13 +103,13 @@ impl IntoData for String { impl IntoData for &'_ String { fn into_data(self) -> Data { - self.to_owned().into() + self.to_owned().into_data() } } impl IntoData for &'_ str { fn into_data(self) -> Data { - self.to_owned().into() + self.to_owned().into_data() } } @@ -671,49 +671,6 @@ impl Default for Data { } } -impl<'d> From<&'d Data> for Data { - fn from(other: &'d Data) -> Self { - other.clone() - } -} - -impl From> for Data { - fn from(other: Vec) -> Self { - Self::binary(other) - } -} - -impl<'b> From<&'b [u8]> for Data { - fn from(other: &'b [u8]) -> Self { - other.to_owned().into() - } -} - -impl From for Data { - fn from(other: String) -> Self { - Self::text(other) - } -} - -impl<'s> From<&'s String> for Data { - fn from(other: &'s String) -> Self { - other.clone().into() - } -} - -impl<'s> From<&'s str> for Data { - fn from(other: &'s str) -> Self { - other.to_owned().into() - } -} - -impl From for super::Data { - fn from(inline: Inline) -> Self { - let trimmed = inline.trimmed(); - super::Data::text(trimmed).with_source(inline) - } -} - #[cfg(feature = "detect-encoding")] fn is_binary(data: &[u8]) -> bool { match content_inspector::inspect(data) { diff --git a/src/runner.rs b/src/runner.rs index 1adcb237..5449bbbb 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -15,6 +15,7 @@ use rayon::prelude::*; use snapbox::data::DataFormat; use snapbox::filters::{Filter as _, FilterNewlines, FilterPaths, FilterRedactions}; use snapbox::path::FileType; +use snapbox::IntoData; #[derive(Debug)] pub(crate) struct Runner { @@ -580,12 +581,12 @@ impl Output { self.spawn.status = SpawnStatus::Ok; self.stdout = Some(Stream { stream: Stdio::Stdout, - content: output.stdout.into(), + content: output.stdout.into_data(), status: StreamStatus::Ok, }); self.stderr = Some(Stream { stream: Stdio::Stderr, - content: output.stderr.into(), + content: output.stderr.into_data(), status: StreamStatus::Ok, }); self