Skip to content

Commit 1d2bb64

Browse files
authored
Merge pull request #368 from epage/unordered
fix(filter): When checking unordered arrays, check normalized values
2 parents e457cc5 + bfbb99f commit 1d2bb64

File tree

1 file changed

+106
-80
lines changed

1 file changed

+106
-80
lines changed

crates/snapbox/src/filter/pattern.rs

Lines changed: 106 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -272,34 +272,7 @@ fn normalize_value_to_unordered_redactions(
272272
*act = normalize_str_to_unordered_redactions(act, exp, substitutions);
273273
}
274274
(Array(act), Array(exp)) => {
275-
let mut actual_values = std::mem::take(act);
276-
let mut expected_values = exp.clone();
277-
let mut elided = false;
278-
expected_values.retain(|expected_value| {
279-
let mut matched = false;
280-
if expected_value == VALUE_WILDCARD {
281-
matched = true;
282-
elided = true;
283-
} else {
284-
actual_values.retain(|actual_value| {
285-
if !matched && actual_value == expected_value {
286-
matched = true;
287-
false
288-
} else {
289-
true
290-
}
291-
});
292-
}
293-
if matched {
294-
act.push(expected_value.clone());
295-
}
296-
!matched
297-
});
298-
if !elided {
299-
for actual_value in actual_values {
300-
act.push(actual_value);
301-
}
302-
}
275+
*act = normalize_array_to_unordered_redactions(act, exp, substitutions);
303276
}
304277
(Object(act), Object(exp)) => {
305278
let has_key_wildcard =
@@ -324,6 +297,55 @@ fn normalize_value_to_unordered_redactions(
324297
}
325298
}
326299

300+
#[cfg(feature = "structured-data")]
301+
fn normalize_array_to_unordered_redactions(
302+
actual: &[serde_json::Value],
303+
expected: &[serde_json::Value],
304+
substitutions: &Redactions,
305+
) -> Vec<serde_json::Value> {
306+
if actual == expected {
307+
return actual.to_owned();
308+
}
309+
310+
let mut normalized: Vec<serde_json::Value> = Vec::new();
311+
let mut actual_values = actual.to_owned();
312+
let mut expected_values = expected.to_owned();
313+
let mut elided = false;
314+
expected_values.retain(|expected_value| {
315+
let mut matched = false;
316+
if expected_value == VALUE_WILDCARD {
317+
matched = true;
318+
elided = true;
319+
} else {
320+
actual_values.retain(|actual_value| {
321+
let mut normalized_actual_value = actual_value.clone();
322+
normalize_value_to_unordered_redactions(
323+
&mut normalized_actual_value,
324+
expected_value,
325+
substitutions,
326+
);
327+
if !matched && normalized_actual_value == *expected_value {
328+
matched = true;
329+
false
330+
} else {
331+
true
332+
}
333+
});
334+
}
335+
if matched {
336+
normalized.push(expected_value.clone());
337+
}
338+
!matched
339+
});
340+
if !elided {
341+
for actual_value in actual_values {
342+
normalized.push(actual_value);
343+
}
344+
}
345+
346+
normalized
347+
}
348+
327349
fn normalize_str_to_unordered_redactions(
328350
actual: &str,
329351
expected: &str,
@@ -454,120 +476,124 @@ fn normalize_value_to_redactions(
454476

455477
#[cfg(feature = "structured-data")]
456478
fn normalize_array_to_redactions(
457-
input: &[serde_json::Value],
458-
pattern: &[serde_json::Value],
479+
actual: &[serde_json::Value],
480+
expected: &[serde_json::Value],
459481
redactions: &Redactions,
460482
) -> Vec<serde_json::Value> {
461-
if input == pattern {
462-
return input.to_vec();
483+
if actual == expected {
484+
return actual.to_vec();
463485
}
464486

465487
let mut normalized: Vec<serde_json::Value> = Vec::new();
466-
let mut input_index = 0;
467-
let mut pattern = pattern.iter().peekable();
468-
while let Some(pattern_elem) = pattern.next() {
469-
if pattern_elem == VALUE_WILDCARD {
470-
let Some(next_pattern_elem) = pattern.peek() else {
488+
let mut actual_index = 0;
489+
let mut expected = expected.iter().peekable();
490+
while let Some(expected_elem) = expected.next() {
491+
if expected_elem == VALUE_WILDCARD {
492+
let Some(next_expected_elem) = expected.peek() else {
471493
// Stop as elide consumes to end
472-
normalized.push(pattern_elem.clone());
473-
input_index = input.len();
494+
normalized.push(expected_elem.clone());
495+
actual_index = actual.len();
474496
break;
475497
};
476-
let Some(index_offset) = input[input_index..].iter().position(|next_input_elem| {
477-
let mut next_input_elem = next_input_elem.clone();
478-
normalize_value_to_redactions(&mut next_input_elem, next_pattern_elem, redactions);
479-
next_input_elem == **next_pattern_elem
498+
let Some(index_offset) = actual[actual_index..].iter().position(|next_actual_elem| {
499+
let mut next_actual_elem = next_actual_elem.clone();
500+
normalize_value_to_redactions(
501+
&mut next_actual_elem,
502+
next_expected_elem,
503+
redactions,
504+
);
505+
next_actual_elem == **next_expected_elem
480506
}) else {
481507
// Give up as we can't find where the elide ends
482508
break;
483509
};
484-
normalized.push(pattern_elem.clone());
485-
input_index += index_offset;
510+
normalized.push(expected_elem.clone());
511+
actual_index += index_offset;
486512
} else {
487-
let Some(input_elem) = input.get(input_index) else {
513+
let Some(actual_elem) = actual.get(actual_index) else {
488514
// Give up as we have no more content to check
489515
break;
490516
};
491517

492-
input_index += 1;
493-
let mut normalized_elem = input_elem.clone();
494-
normalize_value_to_redactions(&mut normalized_elem, pattern_elem, redactions);
518+
actual_index += 1;
519+
let mut normalized_elem = actual_elem.clone();
520+
normalize_value_to_redactions(&mut normalized_elem, expected_elem, redactions);
495521
normalized.push(normalized_elem);
496522
}
497523
}
498524

499-
normalized.extend(input[input_index..].iter().cloned());
525+
normalized.extend(actual[actual_index..].iter().cloned());
500526
normalized
501527
}
502528

503-
fn normalize_str_to_redactions(input: &str, pattern: &str, redactions: &Redactions) -> String {
504-
if input == pattern {
505-
return input.to_owned();
529+
fn normalize_str_to_redactions(actual: &str, expected: &str, redactions: &Redactions) -> String {
530+
if actual == expected {
531+
return actual.to_owned();
506532
}
507533

508534
let mut normalized: Vec<&str> = Vec::new();
509-
let mut input_index = 0;
510-
let input_lines: Vec<_> = crate::utils::LinesWithTerminator::new(input).collect();
511-
let mut pattern_lines = crate::utils::LinesWithTerminator::new(pattern).peekable();
512-
while let Some(pattern_line) = pattern_lines.next() {
513-
if is_line_elide(pattern_line) {
514-
let Some(next_pattern_line) = pattern_lines.peek() else {
535+
let mut actual_index = 0;
536+
let actual_lines: Vec<_> = crate::utils::LinesWithTerminator::new(actual).collect();
537+
let mut expected_lines = crate::utils::LinesWithTerminator::new(expected).peekable();
538+
while let Some(expected_line) = expected_lines.next() {
539+
if is_line_elide(expected_line) {
540+
let Some(next_expected_line) = expected_lines.peek() else {
515541
// Stop as elide consumes to end
516-
normalized.push(pattern_line);
517-
input_index = input_lines.len();
542+
normalized.push(expected_line);
543+
actual_index = actual_lines.len();
518544
break;
519545
};
520546
let Some(index_offset) =
521-
input_lines[input_index..]
547+
actual_lines[actual_index..]
522548
.iter()
523-
.position(|next_input_line| {
524-
line_matches(next_input_line, next_pattern_line, redactions)
549+
.position(|next_actual_line| {
550+
line_matches(next_actual_line, next_expected_line, redactions)
525551
})
526552
else {
527553
// Give up as we can't find where the elide ends
528554
break;
529555
};
530-
normalized.push(pattern_line);
531-
input_index += index_offset;
556+
normalized.push(expected_line);
557+
actual_index += index_offset;
532558
} else {
533-
let Some(input_line) = input_lines.get(input_index) else {
559+
let Some(actual_line) = actual_lines.get(actual_index) else {
534560
// Give up as we have no more content to check
535561
break;
536562
};
537563

538-
if line_matches(input_line, pattern_line, redactions) {
539-
input_index += 1;
540-
normalized.push(pattern_line);
564+
if line_matches(actual_line, expected_line, redactions) {
565+
actual_index += 1;
566+
normalized.push(expected_line);
541567
} else {
542568
// Skip this line and keep processing
543-
input_index += 1;
544-
normalized.push(input_line);
569+
actual_index += 1;
570+
normalized.push(actual_line);
545571
}
546572
}
547573
}
548574

549-
normalized.extend(input_lines[input_index..].iter().copied());
575+
normalized.extend(actual_lines[actual_index..].iter().copied());
550576
normalized.join("")
551577
}
552578

553579
fn is_line_elide(line: &str) -> bool {
554580
line == "...\n" || line == "..."
555581
}
556582

557-
fn line_matches(mut input: &str, pattern: &str, redactions: &Redactions) -> bool {
558-
if input == pattern {
583+
fn line_matches(mut actual: &str, expected: &str, redactions: &Redactions) -> bool {
584+
if actual == expected {
559585
return true;
560586
}
561587

562-
let pattern = redactions.clear(pattern);
563-
let mut sections = pattern.split("[..]").peekable();
588+
let expected = redactions.clear(expected);
589+
let mut sections = expected.split("[..]").peekable();
564590
while let Some(section) = sections.next() {
565-
if let Some(remainder) = input.strip_prefix(section) {
591+
if let Some(remainder) = actual.strip_prefix(section) {
566592
if let Some(next_section) = sections.peek() {
567593
if next_section.is_empty() {
568-
input = "";
594+
actual = "";
569595
} else if let Some(restart_index) = remainder.find(next_section) {
570-
input = &remainder[restart_index..];
596+
actual = &remainder[restart_index..];
571597
}
572598
} else {
573599
return remainder.is_empty();

0 commit comments

Comments
 (0)