Skip to content

Commit

Permalink
Merge pull request #260 from epage/initial
Browse files Browse the repository at this point in the history
fix(snap): Ensure we normalize to the intended format
  • Loading branch information
epage authored Feb 22, 2024
2 parents 99fc297 + 2dd664f commit 7ac4423
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 45 deletions.
4 changes: 2 additions & 2 deletions crates/snapbox/src/assert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ impl Assert {
) -> (crate::Data, crate::Data) {
let expected = expected.normalize(NormalizeNewlines);
// On `expected` being an error, make a best guess
let format = expected.format();
let format = expected.intended_format();

actual = actual.coerce_to(format).normalize(NormalizeNewlines);

Expand All @@ -147,7 +147,7 @@ impl Assert {
) -> (crate::Data, crate::Data) {
let expected = expected.normalize(NormalizeNewlines);
// On `expected` being an error, make a best guess
let format = expected.format();
let format = expected.intended_format();
actual = actual.coerce_to(format);

if self.normalize_paths {
Expand Down
144 changes: 106 additions & 38 deletions crates/snapbox/src/data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ macro_rules! file {
/// ```
///
/// 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]] };
Expand Down Expand Up @@ -108,7 +110,7 @@ pub struct Data {

#[derive(Clone, Debug)]
pub(crate) enum DataInner {
Error(crate::Error),
Error(DataError),
Binary(Vec<u8>),
Text(String),
#[cfg(feature = "json")]
Expand All @@ -133,8 +135,12 @@ impl Data {
DataInner::Json(raw.into()).into()
}

fn error(raw: impl Into<crate::Error>) -> Self {
DataInner::Error(raw.into()).into()
fn error(raw: impl Into<crate::Error>, intended: DataFormat) -> Self {
DataError {
error: raw.into(),
intended,
}
.into()
}

/// Empty test data
Expand All @@ -151,50 +157,25 @@ impl Data {
self.with_source(path.into())
}

/// Load test data from a file
/// Load `expected` data from a file
pub fn read_from(path: &std::path::Path, data_format: Option<DataFormat>) -> Self {
match Self::try_read_from(path, data_format) {
Ok(data) => data,
Err(err) => Self::error(err).with_path(path),
Err(err) => Self::error(err, data_format.unwrap_or_default()).with_path(path),
}
}

/// Load test data from a file
/// Load `expected` data from a file
pub fn try_read_from(
path: &std::path::Path,
data_format: Option<DataFormat>,
) -> Result<Self, crate::Error> {
let data =
std::fs::read(path).map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
let data = Self::binary(data);
let data = match data_format {
Some(df) => match df {
DataFormat::Error => Self::error("unknown error"),
DataFormat::Binary => {
let data = std::fs::read(path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
Self::binary(data)
}
DataFormat::Text => {
let data = std::fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
Self::text(data)
}
#[cfg(feature = "json")]
DataFormat::Json => {
let data = std::fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
Self::json(serde_json::from_str::<serde_json::Value>(&data).unwrap())
}
#[cfg(feature = "term-svg")]
DataFormat::TermSvg => {
let data = std::fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
Self::from(DataInner::TermSvg(data))
}
},
Some(df) => data.is(df),
None => {
let data = std::fs::read(path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
let data = Self::binary(data);

let file_name = path
.file_name()
.and_then(|e| e.to_str())
Expand Down Expand Up @@ -273,7 +254,7 @@ impl Data {

pub fn to_bytes(&self) -> Result<Vec<u8>, crate::Error> {
match &self.inner {
DataInner::Error(err) => Err(err.clone()),
DataInner::Error(err) => Err(err.error.clone()),
DataInner::Binary(data) => Ok(data.clone()),
DataInner::Text(data) => Ok(data.clone().into_bytes()),
#[cfg(feature = "json")]
Expand All @@ -285,9 +266,63 @@ impl Data {
}
}

/// Initialize `Self` as [`format`][DataFormat] or [`Error`][DataFormat::Error]
///
/// This is generally used for `expected` data
pub fn is(self, format: DataFormat) -> Self {
match self.try_is(format) {
Ok(new) => new,
Err(err) => Self::error(err, format),
}
}

fn try_is(self, format: DataFormat) -> Result<Self, crate::Error> {
let original = self.format();
let source = self.source;
let inner = match (self.inner, format) {
(DataInner::Error(inner), _) => DataInner::Error(inner),
(DataInner::Binary(inner), DataFormat::Binary) => DataInner::Binary(inner),
(DataInner::Text(inner), DataFormat::Text) => DataInner::Text(inner),
#[cfg(feature = "json")]
(DataInner::Json(inner), DataFormat::Json) => DataInner::Json(inner),
#[cfg(feature = "term-svg")]
(DataInner::TermSvg(inner), DataFormat::TermSvg) => DataInner::TermSvg(inner),
(DataInner::Binary(inner), _) => {
let inner = String::from_utf8(inner).map_err(|_err| "invalid UTF-8".to_owned())?;
Self::text(inner).try_is(format)?.inner
}
#[cfg(feature = "json")]
(DataInner::Text(inner), DataFormat::Json) => {
let inner = serde_json::from_str::<serde_json::Value>(&inner)
.map_err(|err| err.to_string())?;
DataInner::Json(inner)
}
#[cfg(feature = "term-svg")]
(DataInner::Text(inner), DataFormat::TermSvg) => DataInner::TermSvg(inner),
(inner, DataFormat::Binary) => {
let remake: Self = inner.into();
DataInner::Binary(remake.to_bytes().expect("error case handled"))
}
// This variant is already covered unless structured data is enabled
#[cfg(feature = "structured-data")]
(inner, DataFormat::Text) => {
if let Some(str) = Data::from(inner).render() {
DataInner::Text(str)
} else {
return Err(format!("cannot convert {original:?} to {format:?}").into());
}
}
(_, _) => return Err(format!("cannot convert {original:?} to {format:?}").into()),
};
Ok(Self { inner, source })
}

/// Convert `Self` to [`format`][DataFormat] if possible
///
/// This is generally used on `actual` data to make it match `expected`
pub fn coerce_to(self, format: DataFormat) -> Self {
let mut data = match (self.inner, format) {
(DataInner::Error(inner), _) => Self::error(inner),
(DataInner::Error(inner), _) => inner.into(),
(inner, DataFormat::Error) => inner.into(),
(DataInner::Binary(inner), DataFormat::Binary) => Self::binary(inner),
(DataInner::Text(inner), DataFormat::Text) => Self::text(inner),
Expand Down Expand Up @@ -368,6 +403,18 @@ impl Data {
}
}

pub(crate) fn intended_format(&self) -> DataFormat {
match &self.inner {
DataInner::Error(DataError { intended, .. }) => *intended,
DataInner::Binary(_) => DataFormat::Binary,
DataInner::Text(_) => DataFormat::Text,
#[cfg(feature = "json")]
DataInner::Json(_) => DataFormat::Json,
#[cfg(feature = "term-svg")]
DataInner::TermSvg(_) => DataFormat::TermSvg,
}
}

pub(crate) fn relevant(&self) -> Option<&str> {
match &self.inner {
DataInner::Error(_) => None,
Expand All @@ -390,10 +437,19 @@ impl From<DataInner> for Data {
}
}

impl From<DataError> for Data {
fn from(inner: DataError) -> Self {
Data {
inner: DataInner::Error(inner),
source: None,
}
}
}

impl std::fmt::Display for Data {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.inner {
DataInner::Error(err) => err.fmt(f),
DataInner::Error(data) => data.fmt(f),
DataInner::Binary(data) => String::from_utf8_lossy(data).fmt(f),
DataInner::Text(data) => data.fmt(f),
#[cfg(feature = "json")]
Expand Down Expand Up @@ -424,6 +480,18 @@ impl PartialEq for Data {
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct DataError {
error: crate::Error,
intended: DataFormat,
}

impl std::fmt::Display for DataError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.error.fmt(f)
}
}

#[cfg(feature = "term-svg")]
fn text_elem(svg: &str) -> Option<&str> {
let open_elem_start_idx = svg.find("<text")?;
Expand Down
6 changes: 3 additions & 3 deletions crates/snapbox/src/data/normalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub struct NormalizeNewlines;
impl Normalize for NormalizeNewlines {
fn normalize(&self, data: Data) -> Data {
let mut new = match data.inner {
DataInner::Error(err) => Data::error(err),
DataInner::Error(err) => err.into(),
DataInner::Binary(bin) => Data::binary(bin),
DataInner::Text(text) => {
let lines = crate::utils::normalize_lines(&text);
Expand All @@ -36,7 +36,7 @@ pub struct NormalizePaths;
impl Normalize for NormalizePaths {
fn normalize(&self, data: Data) -> Data {
let mut new = match data.inner {
DataInner::Error(err) => Data::error(err),
DataInner::Error(err) => err.into(),
DataInner::Binary(bin) => Data::binary(bin),
DataInner::Text(text) => {
let lines = crate::utils::normalize_paths(&text);
Expand Down Expand Up @@ -76,7 +76,7 @@ impl<'a> NormalizeMatches<'a> {
impl Normalize for NormalizeMatches<'_> {
fn normalize(&self, data: Data) -> Data {
let mut new = match data.inner {
DataInner::Error(err) => Data::error(err),
DataInner::Error(err) => err.into(),
DataInner::Binary(bin) => Data::binary(bin),
DataInner::Text(text) => {
let lines = self
Expand Down
10 changes: 10 additions & 0 deletions crates/snapbox/src/data/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ impl Inline {
self
}

/// Initialize `Self` as [`format`][crate::data::DataFormat] or [`Error`][crate::data::DataFormat::Error]
///
/// This is generally used for `expected` data
pub fn is(self, format: super::DataFormat) -> super::Data {
let data: super::Data = self.into();
data.is(format)
}

/// Deprecated, replaced with [`Inline::is`]
#[deprecated(since = "0.5.2", note = "Replaced with `Inline::is`")]
pub fn coerce_to(self, format: super::DataFormat) -> super::Data {
let data: super::Data = self.into();
data.coerce_to(format)
Expand Down
4 changes: 2 additions & 2 deletions crates/snapbox/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ impl PathDiff {
crate::Data::read_from(&expected_path, None).normalize(NormalizeNewlines);

actual = actual
.coerce_to(expected.format())
.coerce_to(expected.intended_format())
.normalize(NormalizeNewlines);

if expected != actual {
Expand Down Expand Up @@ -268,7 +268,7 @@ impl PathDiff {
crate::Data::read_from(&expected_path, None).normalize(NormalizeNewlines);

actual = actual
.coerce_to(expected.format())
.coerce_to(expected.intended_format())
.normalize(NormalizePaths)
.normalize(NormalizeNewlines)
.normalize(NormalizeMatches::new(substitutions, &expected));
Expand Down

0 comments on commit 7ac4423

Please sign in to comment.