Skip to content

Commit

Permalink
fix(filter): Move from data to filter
Browse files Browse the repository at this point in the history
Cherry-pick fe2fb1e (assert-rs#288)
Cherry-pick 79e3167 (assert-rs#285)
  • Loading branch information
epage committed May 15, 2024
1 parent 1cbf588 commit 4990ecf
Show file tree
Hide file tree
Showing 6 changed files with 561 additions and 528 deletions.
4 changes: 2 additions & 2 deletions crates/snapbox/src/data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ macro_rules! str {
/// This provides conveniences for tracking the intended format (binary vs text).
#[derive(Clone, Debug)]
pub struct Data {
inner: DataInner,
source: Option<DataSource>,
pub(crate) inner: DataInner,
pub(crate) source: Option<DataSource>,
}

#[derive(Clone, Debug)]
Expand Down
188 changes: 8 additions & 180 deletions crates/snapbox/src/data/normalize.rs
Original file line number Diff line number Diff line change
@@ -1,196 +1,24 @@
//! Normalize `actual` or `expected` [`Data`]
//!
//! This can be done for
//! - Making snapshots consistent across platforms or conditional compilation
//! - Focusing snapshots on the characteristics of the data being tested
#![allow(deprecated)]

use super::Data;
use super::DataInner;

pub trait Normalize {
fn normalize(&self, data: Data) -> Data;
}
pub use crate::filter::Normalize;

#[deprecated(since = "0.5.11", note = "Replaced with `filter::NormalizeNewlines")]
pub struct NormalizeNewlines;
impl Normalize for NormalizeNewlines {
fn normalize(&self, data: Data) -> Data {
let source = data.source;
let inner = match data.inner {
DataInner::Error(err) => DataInner::Error(err),
DataInner::Binary(bin) => DataInner::Binary(bin),
DataInner::Text(text) => {
let lines = crate::utils::normalize_lines(&text);
DataInner::Text(lines)
}
#[cfg(feature = "json")]
DataInner::Json(value) => {
let mut value = value;
normalize_value(&mut value, crate::utils::normalize_lines);
DataInner::Json(value)
}
#[cfg(feature = "term-svg")]
DataInner::TermSvg(text) => {
let lines = crate::utils::normalize_lines(&text);
DataInner::TermSvg(lines)
}
};
Data { inner, source }
crate::filter::NormalizeNewlines.normalize(data)
}
}

#[deprecated(since = "0.5.11", note = "Replaced with `filter::NormalizePaths")]
pub struct NormalizePaths;
impl Normalize for NormalizePaths {
fn normalize(&self, data: Data) -> Data {
let source = data.source;
let inner = match data.inner {
DataInner::Error(err) => DataInner::Error(err),
DataInner::Binary(bin) => DataInner::Binary(bin),
DataInner::Text(text) => {
let lines = crate::utils::normalize_paths(&text);
DataInner::Text(lines)
}
#[cfg(feature = "json")]
DataInner::Json(value) => {
let mut value = value;
normalize_value(&mut value, crate::utils::normalize_paths);
DataInner::Json(value)
}
#[cfg(feature = "term-svg")]
DataInner::TermSvg(text) => {
let lines = crate::utils::normalize_paths(&text);
DataInner::TermSvg(lines)
}
};
Data { inner, source }
}
}

pub struct NormalizeMatches<'a> {
substitutions: &'a crate::Substitutions,
pattern: &'a Data,
}

impl<'a> NormalizeMatches<'a> {
pub fn new(substitutions: &'a crate::Substitutions, pattern: &'a Data) -> Self {
NormalizeMatches {
substitutions,
pattern,
}
}
}

impl Normalize for NormalizeMatches<'_> {
fn normalize(&self, data: Data) -> Data {
let source = data.source;
let inner = match data.inner {
DataInner::Error(err) => DataInner::Error(err),
DataInner::Binary(bin) => DataInner::Binary(bin),
DataInner::Text(text) => {
if let Some(pattern) = self.pattern.render() {
let lines = self.substitutions.normalize(&text, &pattern);
DataInner::Text(lines)
} else {
DataInner::Text(text)
}
}
#[cfg(feature = "json")]
DataInner::Json(value) => {
let mut value = value;
if let DataInner::Json(exp) = &self.pattern.inner {
normalize_value_matches(&mut value, exp, self.substitutions);
}
DataInner::Json(value)
}
#[cfg(feature = "term-svg")]
DataInner::TermSvg(text) => {
if let Some(pattern) = self.pattern.render() {
let lines = self.substitutions.normalize(&text, &pattern);
DataInner::TermSvg(lines)
} else {
DataInner::TermSvg(text)
}
}
};
Data { inner, source }
crate::filter::NormalizePaths.normalize(data)
}
}

#[cfg(feature = "structured-data")]
fn normalize_value(value: &mut serde_json::Value, op: fn(&str) -> String) {
match value {
serde_json::Value::String(str) => {
*str = op(str);
}
serde_json::Value::Array(arr) => {
for value in arr.iter_mut() {
normalize_value(value, op)
}
}
serde_json::Value::Object(obj) => {
for (_, value) in obj.iter_mut() {
normalize_value(value, op)
}
}
_ => {}
}
}

#[cfg(feature = "structured-data")]
fn normalize_value_matches(
actual: &mut serde_json::Value,
expected: &serde_json::Value,
substitutions: &crate::Substitutions,
) {
use serde_json::Value::*;

const VALUE_WILDCARD: &str = "{...}";

match (actual, expected) {
(act, String(exp)) if exp == VALUE_WILDCARD => {
*act = serde_json::json!(VALUE_WILDCARD);
}
(String(act), String(exp)) => {
*act = substitutions.normalize(act, exp);
}
(Array(act), Array(exp)) => {
let mut sections = exp.split(|e| e == VALUE_WILDCARD).peekable();
let mut processed = 0;
while let Some(expected_subset) = sections.next() {
// Process all values in the current section
if !expected_subset.is_empty() {
let actual_subset = &mut act[processed..processed + expected_subset.len()];
for (a, e) in actual_subset.iter_mut().zip(expected_subset) {
normalize_value_matches(a, e, substitutions);
}
processed += expected_subset.len();
}

if let Some(next_section) = sections.peek() {
// If the next section has nothing in it, replace from processed to end with
// a single "{...}"
if next_section.is_empty() {
act.splice(processed.., vec![String(VALUE_WILDCARD.to_owned())]);
processed += 1;
} else {
let first = next_section.first().unwrap();
// Replace everything up until the value we are looking for with
// a single "{...}".
if let Some(index) = act.iter().position(|v| v == first) {
act.splice(processed..index, vec![String(VALUE_WILDCARD.to_owned())]);
processed += 1;
} else {
// If we cannot find the value we are looking for return early
break;
}
}
}
}
}
(Object(act), Object(exp)) => {
for (a, e) in act.iter_mut().zip(exp).filter(|(a, e)| a.0 == e.0) {
normalize_value_matches(a.1, e.1, substitutions)
}
}
(_, _) => {}
}
}
#[deprecated(since = "0.5.11", note = "Replaced with `filter::NormalizeMatches")]
pub type NormalizeMatches<'a> = crate::filter::NormalizeMatches<'a>;
Loading

0 comments on commit 4990ecf

Please sign in to comment.