Skip to content

Commit d9717c4

Browse files
hovinenbcopybara-github
authored andcommitted
Improve support for printing diffs with the starts_with matcher.
Previously, every additional element of `right` not present in `left` would contribute to the edit distance. When using `starts_with`, the maximum edit distance could easily be exceeded and the diff output suppressed. This (re-)introduces an enum `Mode` for configuring the behaviour of `edit_list`. There are two modes: `Exact`, which corresponds to current behaviour, and `Prefix`, where the edit list is returned as soon as `left` is exhausted, even if `right` has more elements. Thus additional elements beyond the prefix from `starts_with` no longer contribute to the edit distance, raising the likelihood of printing a diff. Follow-up CLs will make use of `Mode` to also better support the `ends_with` and `contains_substring` matchers. PiperOrigin-RevId: 540585576
1 parent 7720767 commit d9717c4

File tree

4 files changed

+197
-21
lines changed

4 files changed

+197
-21
lines changed

googletest/src/matcher_support/edit_distance.rs

Lines changed: 95 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,27 @@ pub(crate) enum Edit<T> {
5252

5353
/// An element was added to each sequence.
5454
Both(T),
55+
56+
/// Additional (unlisted) elements are present in the left sequence.
57+
///
58+
/// This is only output in the mode [`Mode::Prefix`]. Its presence precludes
59+
/// reconstructing the left sequence from the right sequence.
60+
AdditionalLeft,
61+
}
62+
63+
/// Controls the termination condition of [`edit_list`].
64+
pub(crate) enum Mode {
65+
/// Indicates that the two arguments are intended to be equal.
66+
///
67+
/// The entire edit list to transform between `left` and `right` is
68+
/// returned.
69+
Exact,
70+
71+
/// Indicates that `right` is inteded to be a prefix of `left`.
72+
///
73+
/// Any additional parts of `left` after the prefix `right` are omitted from
74+
/// the output.
75+
Prefix,
5576
}
5677

5778
/// Computes the edit list of `left` and `right`.
@@ -69,6 +90,7 @@ pub(crate) enum Edit<T> {
6990
pub(crate) fn edit_list<T: PartialEq + Copy>(
7091
left: impl IntoIterator<Item = T>,
7192
right: impl IntoIterator<Item = T>,
93+
mode: Mode,
7294
) -> Difference<T> {
7395
let left: Vec<_> = left.into_iter().collect();
7496
let right: Vec<_> = right.into_iter().collect();
@@ -151,6 +173,22 @@ pub(crate) fn edit_list<T: PartialEq + Copy>(
151173
path.right_endpoint = right_endpoint;
152174
paths_current.push(path);
153175
}
176+
177+
if matches!(mode, Mode::Prefix) {
178+
if let Some(path) = paths_current
179+
.iter_mut()
180+
.filter(|p| p.right_endpoint == right.len())
181+
.max_by(|p1, p2| p1.edits.len().cmp(&p2.edits.len()))
182+
{
183+
path.edits.push(Edit::AdditionalLeft);
184+
return if path.edits.iter().any(|v| !matches!(v, Edit::Both(_))) {
185+
Difference::Editable(std::mem::take(&mut path.edits))
186+
} else {
187+
Difference::Equal
188+
};
189+
}
190+
}
191+
154192
paths_last = paths_current;
155193
}
156194

@@ -192,19 +230,23 @@ mod tests {
192230

193231
#[test]
194232
fn returns_equal_when_strings_are_equal() -> Result<()> {
195-
let result = edit_list(["A string"], ["A string"]);
233+
let result = edit_list(["A string"], ["A string"], Mode::Exact);
196234
verify_that!(result, matches_pattern!(Difference::Equal))
197235
}
198236

199237
#[test]
200238
fn returns_sequence_of_two_common_parts() -> Result<()> {
201-
let result = edit_list(["A string (1)", "A string (2)"], ["A string (1)", "A string (2)"]);
239+
let result = edit_list(
240+
["A string (1)", "A string (2)"],
241+
["A string (1)", "A string (2)"],
242+
Mode::Exact,
243+
);
202244
verify_that!(result, matches_pattern!(Difference::Equal))
203245
}
204246

205247
#[test]
206248
fn returns_extra_left_when_only_left_has_content() -> Result<()> {
207-
let result = edit_list(["A string"], []);
249+
let result = edit_list(["A string"], [], Mode::Exact);
208250
verify_that!(
209251
result,
210252
matches_pattern!(Difference::Editable(elements_are![matches_pattern!(
@@ -215,7 +257,7 @@ mod tests {
215257

216258
#[test]
217259
fn returns_extra_right_when_only_right_has_content() -> Result<()> {
218-
let result = edit_list([], ["A string"]);
260+
let result = edit_list([], ["A string"], Mode::Exact);
219261
verify_that!(
220262
result,
221263
matches_pattern!(Difference::Editable(elements_are![matches_pattern!(
@@ -226,7 +268,7 @@ mod tests {
226268

227269
#[test]
228270
fn returns_extra_left_followed_by_extra_right_with_two_unequal_strings() -> Result<()> {
229-
let result = edit_list(["A string"], ["Another string"]);
271+
let result = edit_list(["A string"], ["Another string"], Mode::Exact);
230272
verify_that!(
231273
result,
232274
matches_pattern!(Difference::Editable(elements_are![
@@ -238,7 +280,8 @@ mod tests {
238280

239281
#[test]
240282
fn interleaves_extra_left_and_extra_right_when_multiple_lines_differ() -> Result<()> {
241-
let result = edit_list(["A string", "A string"], ["Another string", "Another string"]);
283+
let result =
284+
edit_list(["A string", "A string"], ["Another string", "Another string"], Mode::Exact);
242285
verify_that!(
243286
result,
244287
matches_pattern!(Difference::Editable(elements_are![
@@ -252,7 +295,8 @@ mod tests {
252295

253296
#[test]
254297
fn returns_common_part_plus_difference_when_there_is_common_prefix() -> Result<()> {
255-
let result = edit_list(["Common part", "Left only"], ["Common part", "Right only"]);
298+
let result =
299+
edit_list(["Common part", "Left only"], ["Common part", "Right only"], Mode::Exact);
256300
verify_that!(
257301
result,
258302
matches_pattern!(Difference::Editable(elements_are![
@@ -265,7 +309,7 @@ mod tests {
265309

266310
#[test]
267311
fn returns_common_part_plus_extra_left_when_left_has_extra_suffix() -> Result<()> {
268-
let result = edit_list(["Common part", "Left only"], ["Common part"]);
312+
let result = edit_list(["Common part", "Left only"], ["Common part"], Mode::Exact);
269313
verify_that!(
270314
result,
271315
matches_pattern!(Difference::Editable(elements_are![
@@ -277,7 +321,7 @@ mod tests {
277321

278322
#[test]
279323
fn returns_common_part_plus_extra_right_when_right_has_extra_suffix() -> Result<()> {
280-
let result = edit_list(["Common part"], ["Common part", "Right only"]);
324+
let result = edit_list(["Common part"], ["Common part", "Right only"], Mode::Exact);
281325
verify_that!(
282326
result,
283327
matches_pattern!(Difference::Editable(elements_are![
@@ -289,7 +333,8 @@ mod tests {
289333

290334
#[test]
291335
fn returns_difference_plus_common_part_when_there_is_common_suffix() -> Result<()> {
292-
let result = edit_list(["Left only", "Common part"], ["Right only", "Common part"]);
336+
let result =
337+
edit_list(["Left only", "Common part"], ["Right only", "Common part"], Mode::Exact);
293338
verify_that!(
294339
result,
295340
matches_pattern!(Difference::Editable(elements_are![
@@ -306,6 +351,7 @@ mod tests {
306351
let result = edit_list(
307352
["Left only (1)", "Common part", "Left only (2)"],
308353
["Right only (1)", "Common part", "Right only (2)"],
354+
Mode::Exact,
309355
);
310356
verify_that!(
311357
result,
@@ -325,6 +371,7 @@ mod tests {
325371
let result = edit_list(
326372
["Common part (1)", "Left only", "Common part (2)"],
327373
["Common part (1)", "Right only", "Common part (2)"],
374+
Mode::Exact,
328375
);
329376
verify_that!(
330377
result,
@@ -343,6 +390,7 @@ mod tests {
343390
let result = edit_list(
344391
["Common part (1)", "Left only", "Common part (2)"],
345392
["Common part (1)", "Common part (2)"],
393+
Mode::Exact,
346394
);
347395
verify_that!(
348396
result,
@@ -360,6 +408,7 @@ mod tests {
360408
let result = edit_list(
361409
["Common part (1)", "Common part (2)"],
362410
["Common part (1)", "Right only", "Common part (2)"],
411+
Mode::Exact,
363412
);
364413
verify_that!(
365414
result,
@@ -371,9 +420,36 @@ mod tests {
371420
)
372421
}
373422

423+
#[test]
424+
fn skips_extra_parts_on_left_at_end_in_prefix_mode() -> Result<()> {
425+
let result =
426+
edit_list(["Common part", "Left only"], ["Right only", "Common part"], Mode::Prefix);
427+
verify_that!(
428+
result,
429+
matches_pattern!(Difference::Editable(not(contains(matches_pattern!(
430+
Edit::ExtraLeft(eq("Left only"))
431+
)))))
432+
)
433+
}
434+
435+
#[test]
436+
fn does_not_skip_extra_parts_on_left_in_prefix_mode_at_end_when_they_are_in_common()
437+
-> Result<()> {
438+
let result =
439+
edit_list(["Left only", "Common part"], ["Right only", "Common part"], Mode::Prefix);
440+
verify_that!(
441+
result,
442+
matches_pattern!(Difference::Editable(elements_are![
443+
matches_pattern!(Edit::ExtraLeft(eq("Left only"))),
444+
matches_pattern!(Edit::ExtraRight(eq("Right only"))),
445+
matches_pattern!(Edit::Both(eq("Common part"))),
446+
]))
447+
)
448+
}
449+
374450
#[test]
375451
fn returns_unrelated_when_maximum_distance_exceeded() -> Result<()> {
376-
let result = edit_list(0..=20, 20..40);
452+
let result = edit_list(0..=20, 20..40, Mode::Exact);
377453
verify_that!(result, matches_pattern!(Difference::Unrelated))
378454
}
379455

@@ -382,7 +458,7 @@ mod tests {
382458
left: Vec<Alphabet>,
383459
right: Vec<Alphabet>
384460
) -> TestResult {
385-
match edit_list(left.clone(), right.clone()) {
461+
match edit_list(left.clone(), right.clone(), Mode::Exact) {
386462
Difference::Equal => TestResult::from_bool(left == right),
387463
Difference::Editable(edit_list) => {
388464
TestResult::from_bool(apply_edits_to_left(&edit_list, &left) == right)
@@ -403,7 +479,7 @@ mod tests {
403479
left: Vec<Alphabet>,
404480
right: Vec<Alphabet>
405481
) -> TestResult {
406-
match edit_list(left.clone(), right.clone()) {
482+
match edit_list(left.clone(), right.clone(), Mode::Exact) {
407483
Difference::Equal => TestResult::from_bool(left == right),
408484
Difference::Editable(edit_list) => {
409485
TestResult::from_bool(apply_edits_to_right(&edit_list, &right) == left)
@@ -450,6 +526,9 @@ mod tests {
450526
assert_that!(left_iter.next(), some(eq(value)));
451527
result.push(*value);
452528
}
529+
Edit::AdditionalLeft => {
530+
fail!("Unexpected Edit::AdditionalLeft").unwrap();
531+
}
453532
}
454533
}
455534
assert_that!(left_iter.next(), none());
@@ -474,6 +553,9 @@ mod tests {
474553
assert_that!(right_iter.next(), some(eq(value)));
475554
result.push(*value);
476555
}
556+
Edit::AdditionalLeft => {
557+
fail!("Unexpected Edit::AdditionalLeft").unwrap();
558+
}
477559
}
478560
}
479561
assert_that!(right_iter.next(), none());

googletest/src/matchers/eq_deref_of_matcher.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
use crate::{
1616
matcher::{Matcher, MatcherResult},
17+
matcher_support::edit_distance,
1718
matchers::eq_matcher::create_diff,
1819
};
1920
use std::{fmt::Debug, marker::PhantomData, ops::Deref};
@@ -87,7 +88,11 @@ where
8788
format!(
8889
"which {}{}",
8990
&self.describe(self.matches(actual)),
90-
create_diff(&format!("{:#?}", self.expected.deref()), &format!("{:#?}", actual))
91+
create_diff(
92+
&format!("{:#?}", self.expected.deref()),
93+
&format!("{:#?}", actual),
94+
edit_distance::Mode::Exact,
95+
)
9196
)
9297
}
9398
}

googletest/src/matchers/eq_matcher.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,22 +109,27 @@ impl<A: Debug + ?Sized, T: PartialEq<A> + Debug> Matcher for EqMatcher<A, T> {
109109
// actually return None and unwrap() should not panic.
110110
&to_display_output(&expected_debug).unwrap(),
111111
&to_display_output(&actual_debug).unwrap(),
112+
edit_distance::Mode::Exact,
112113
)
113114
} else {
114-
create_diff(&expected_debug, &actual_debug)
115+
create_diff(&expected_debug, &actual_debug, edit_distance::Mode::Exact)
115116
};
116117

117118
format!("which {description}{diff}")
118119
}
119120
}
120121

121-
pub(super) fn create_diff(expected_debug: &str, actual_debug: &str) -> Cow<'static, str> {
122+
pub(super) fn create_diff(
123+
expected_debug: &str,
124+
actual_debug: &str,
125+
diff_mode: edit_distance::Mode,
126+
) -> Cow<'static, str> {
122127
if actual_debug.lines().count() < 2 {
123128
// If the actual debug is only one line, then there is no point in doing a
124129
// line-by-line diff.
125130
return "".into();
126131
}
127-
match edit_distance::edit_list(actual_debug.lines(), expected_debug.lines()) {
132+
match edit_distance::edit_list(actual_debug.lines(), expected_debug.lines(), diff_mode) {
128133
edit_distance::Difference::Equal => "No difference found between debug strings.".into(),
129134
edit_distance::Difference::Editable(edit_list) => {
130135
format!("\nDifference:{}", edit_list_summary(&edit_list)).into()
@@ -143,13 +148,14 @@ fn edit_list_summary(edit_list: &[edit_distance::Edit<&str>]) -> String {
143148
common_line_buffer.push(*left);
144149
continue;
145150
}
146-
edit_distance::Edit::ExtraLeft(left) => ('+', left),
147-
edit_distance::Edit::ExtraRight(right) => ('-', right),
151+
edit_distance::Edit::ExtraLeft(left) => ("+", *left),
152+
edit_distance::Edit::ExtraRight(right) => ("-", *right),
153+
edit_distance::Edit::AdditionalLeft => ("<---- remaining lines omitted ---->", ""),
148154
};
149155
summary.push_str(&compress_common_lines(std::mem::take(&mut common_line_buffer)));
150156

151157
summary.push('\n');
152-
summary.push(start);
158+
summary.push_str(start);
153159
summary.push_str(line);
154160
}
155161
summary.push_str(&compress_common_lines(common_line_buffer));

0 commit comments

Comments
 (0)