Skip to content

Commit 7720767

Browse files
hovinenbcopybara-github
authored andcommitted
Have edit_list return an enum which indicates when the left and right values are completely unrelated.
Previously, the fallback option when the edit distance between the two arguments to `edit_list` exceeded the maximum was to return `ExtraLeft` and `ExtraRight` values and ignore any common values. This would be a problem when using the `contains`, `starts_with`, and `ends_with` matchers, since the edit list could easily exceed the maximum on large inputs. This change replaces the output of `edit_list` with an enum which explicitly indicates whether a list of edits could be found. If it cannot, then the output generator in `eq_matcher` suppresses the diff output entirely. PiperOrigin-RevId: 539567235
1 parent ce9b9d8 commit 7720767

File tree

2 files changed

+122
-73
lines changed

2 files changed

+122
-73
lines changed

googletest/src/matcher_support/edit_distance.rs

Lines changed: 116 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,55 @@ use std::fmt::Debug;
2121
/// quadratically increasing its worst-case runtime.
2222
const MAX_DISTANCE: i32 = 25;
2323

24-
/// Compute the edit list of `left` and `right`.
24+
/// The difference between two inputs as produced by [`edit_list`].
25+
#[derive(Debug)]
26+
pub(crate) enum Difference<T> {
27+
/// No differences were detected at all.
28+
Equal,
29+
30+
/// At most [`MAX_DISTANCE`] edits are required to convert one input to the
31+
/// other.
32+
///
33+
/// Contains the list of [`Edit`] to perform the transformation.
34+
Editable(Vec<Edit<T>>),
35+
36+
/// More than [`MAX_DISTANCE`] edits are required to convert one input to
37+
/// the other.
38+
///
39+
/// The inputs are therefore considered unrelated and no edit list is
40+
/// provided.
41+
Unrelated,
42+
}
43+
44+
/// An edit operation on two sequences of `T`.
45+
#[derive(Debug, Clone)]
46+
pub(crate) enum Edit<T> {
47+
/// An extra `T` was added to the left sequence.
48+
ExtraLeft(T),
49+
50+
/// An extra `T` was added to the right sequence.
51+
ExtraRight(T),
52+
53+
/// An element was added to each sequence.
54+
Both(T),
55+
}
56+
57+
/// Computes the edit list of `left` and `right`.
2558
///
26-
/// This returns a vec of [`Edit`] which can be applied to `left` to obtain
27-
/// `right`. See <https://en.wikipedia.org/wiki/Edit_distance> for more
28-
/// information.
59+
/// If `left` and `right` are equal, then this returns [`Difference::Equal`]. If
60+
/// they are different but have an
61+
/// [edit distance](https://en.wikipedia.org/wiki/Edit_distance)
62+
/// of at most [`MAX_DISTANCE`], this returns [`Difference::Editable`] with the
63+
/// sequence of [`Edit`] which can be applied to `left` to obtain `right`.
64+
/// Otherwise this returns [`Difference::Unrelated`].
2965
///
30-
/// It uses [Myers Algorithm](https://neil.fraser.name/writing/diff/myers.pdf)
31-
/// with a maximum edit distance of [`MAX_DISTANCE`]. If more than
32-
/// [`MAX_DISTANCE`] insertions or deletions are required to convert `left` to
33-
/// `right`, it returns a default fallback edit list which deletes all items
34-
/// from `left` and inserts all items in `right`.
66+
/// This uses [Myers Algorithm](https://neil.fraser.name/writing/diff/myers.pdf)
67+
/// with a maximum edit distance of [`MAX_DISTANCE`]. Thus the worst-case
68+
/// runtime is linear in both the input length and [`MAX_DISTANCE`].
3569
pub(crate) fn edit_list<T: PartialEq + Copy>(
3670
left: impl IntoIterator<Item = T>,
3771
right: impl IntoIterator<Item = T>,
38-
) -> Vec<Edit<T>> {
72+
) -> Difference<T> {
3973
let left: Vec<_> = left.into_iter().collect();
4074
let right: Vec<_> = right.into_iter().collect();
4175

@@ -106,7 +140,11 @@ pub(crate) fn edit_list<T: PartialEq + Copy>(
106140

107141
// If we have exhausted both inputs, we are done.
108142
if left_endpoint == left.len() && right_endpoint == right.len() {
109-
return path.edits;
143+
return if path.edits.iter().any(|v| !matches!(v, Edit::Both(_))) {
144+
Difference::Editable(path.edits)
145+
} else {
146+
Difference::Equal
147+
};
110148
}
111149

112150
path.left_endpoint = left_endpoint;
@@ -116,11 +154,7 @@ pub(crate) fn edit_list<T: PartialEq + Copy>(
116154
paths_last = paths_current;
117155
}
118156

119-
// Fallback when the distance is too large: assume the two are completely
120-
// different.
121-
let mut result: Vec<_> = left.iter().map(|t| Edit::ExtraLeft(*t)).collect();
122-
result.extend(right.iter().map(|t| Edit::ExtraRight(*t)));
123-
result
157+
Difference::Unrelated
124158
}
125159

126160
fn index_of_k(k: i32, k_min: i32) -> usize {
@@ -150,62 +184,55 @@ impl<T: Clone> Path<T> {
150184
}
151185
}
152186

153-
/// An edit operation on two sequences of `T`.
154-
#[derive(Debug, Clone)]
155-
pub(crate) enum Edit<T> {
156-
/// An extra `T` was added to the left sequence.
157-
ExtraLeft(T),
158-
/// An extra `T` was added to the right sequence.
159-
ExtraRight(T),
160-
/// An element was added to each sequence.
161-
Both(T),
162-
}
163-
164187
#[cfg(test)]
165188
mod tests {
166189
use super::*;
167190
use crate::prelude::*;
168-
use quickcheck::{quickcheck, Arbitrary};
191+
use quickcheck::{quickcheck, Arbitrary, TestResult};
169192

170193
#[test]
171-
fn returns_single_edit_when_strings_are_equal() -> Result<()> {
194+
fn returns_equal_when_strings_are_equal() -> Result<()> {
172195
let result = edit_list(["A string"], ["A string"]);
173-
verify_that!(result, elements_are![matches_pattern!(Edit::Both(eq("A string")))])
196+
verify_that!(result, matches_pattern!(Difference::Equal))
174197
}
175198

176199
#[test]
177200
fn returns_sequence_of_two_common_parts() -> Result<()> {
178201
let result = edit_list(["A string (1)", "A string (2)"], ["A string (1)", "A string (2)"]);
179-
verify_that!(
180-
result,
181-
elements_are![
182-
matches_pattern!(Edit::Both(eq("A string (1)"))),
183-
matches_pattern!(Edit::Both(eq("A string (2)")))
184-
]
185-
)
202+
verify_that!(result, matches_pattern!(Difference::Equal))
186203
}
187204

188205
#[test]
189206
fn returns_extra_left_when_only_left_has_content() -> Result<()> {
190207
let result = edit_list(["A string"], []);
191-
verify_that!(result, elements_are![matches_pattern!(Edit::ExtraLeft(eq("A string"))),])
208+
verify_that!(
209+
result,
210+
matches_pattern!(Difference::Editable(elements_are![matches_pattern!(
211+
Edit::ExtraLeft(eq("A string"))
212+
)]))
213+
)
192214
}
193215

194216
#[test]
195217
fn returns_extra_right_when_only_right_has_content() -> Result<()> {
196218
let result = edit_list([], ["A string"]);
197-
verify_that!(result, elements_are![matches_pattern!(Edit::ExtraRight(eq("A string"))),])
219+
verify_that!(
220+
result,
221+
matches_pattern!(Difference::Editable(elements_are![matches_pattern!(
222+
Edit::ExtraRight(eq("A string"))
223+
)]))
224+
)
198225
}
199226

200227
#[test]
201228
fn returns_extra_left_followed_by_extra_right_with_two_unequal_strings() -> Result<()> {
202229
let result = edit_list(["A string"], ["Another string"]);
203230
verify_that!(
204231
result,
205-
elements_are![
232+
matches_pattern!(Difference::Editable(elements_are![
206233
matches_pattern!(Edit::ExtraLeft(eq("A string"))),
207234
matches_pattern!(Edit::ExtraRight(eq("Another string"))),
208-
]
235+
]))
209236
)
210237
}
211238

@@ -214,12 +241,12 @@ mod tests {
214241
let result = edit_list(["A string", "A string"], ["Another string", "Another string"]);
215242
verify_that!(
216243
result,
217-
elements_are![
244+
matches_pattern!(Difference::Editable(elements_are![
218245
matches_pattern!(Edit::ExtraLeft(eq("A string"))),
219246
matches_pattern!(Edit::ExtraRight(eq("Another string"))),
220247
matches_pattern!(Edit::ExtraLeft(eq("A string"))),
221248
matches_pattern!(Edit::ExtraRight(eq("Another string"))),
222-
]
249+
]))
223250
)
224251
}
225252

@@ -228,11 +255,11 @@ mod tests {
228255
let result = edit_list(["Common part", "Left only"], ["Common part", "Right only"]);
229256
verify_that!(
230257
result,
231-
elements_are![
258+
matches_pattern!(Difference::Editable(elements_are![
232259
matches_pattern!(Edit::Both(eq("Common part"))),
233260
matches_pattern!(Edit::ExtraLeft(eq("Left only"))),
234261
matches_pattern!(Edit::ExtraRight(eq("Right only"))),
235-
]
262+
]))
236263
)
237264
}
238265

@@ -241,10 +268,10 @@ mod tests {
241268
let result = edit_list(["Common part", "Left only"], ["Common part"]);
242269
verify_that!(
243270
result,
244-
elements_are![
271+
matches_pattern!(Difference::Editable(elements_are![
245272
matches_pattern!(Edit::Both(eq("Common part"))),
246273
matches_pattern!(Edit::ExtraLeft(eq("Left only"))),
247-
]
274+
]))
248275
)
249276
}
250277

@@ -253,10 +280,10 @@ mod tests {
253280
let result = edit_list(["Common part"], ["Common part", "Right only"]);
254281
verify_that!(
255282
result,
256-
elements_are![
283+
matches_pattern!(Difference::Editable(elements_are![
257284
matches_pattern!(Edit::Both(eq("Common part"))),
258285
matches_pattern!(Edit::ExtraRight(eq("Right only"))),
259-
]
286+
]))
260287
)
261288
}
262289

@@ -265,11 +292,11 @@ mod tests {
265292
let result = edit_list(["Left only", "Common part"], ["Right only", "Common part"]);
266293
verify_that!(
267294
result,
268-
elements_are![
295+
matches_pattern!(Difference::Editable(elements_are![
269296
matches_pattern!(Edit::ExtraLeft(eq("Left only"))),
270297
matches_pattern!(Edit::ExtraRight(eq("Right only"))),
271298
matches_pattern!(Edit::Both(eq("Common part"))),
272-
]
299+
]))
273300
)
274301
}
275302

@@ -282,13 +309,13 @@ mod tests {
282309
);
283310
verify_that!(
284311
result,
285-
elements_are![
312+
matches_pattern!(Difference::Editable(elements_are![
286313
matches_pattern!(Edit::ExtraLeft(eq("Left only (1)"))),
287314
matches_pattern!(Edit::ExtraRight(eq("Right only (1)"))),
288315
matches_pattern!(Edit::Both(eq("Common part"))),
289316
matches_pattern!(Edit::ExtraLeft(eq("Left only (2)"))),
290317
matches_pattern!(Edit::ExtraRight(eq("Right only (2)"))),
291-
]
318+
]))
292319
)
293320
}
294321

@@ -301,12 +328,12 @@ mod tests {
301328
);
302329
verify_that!(
303330
result,
304-
elements_are![
331+
matches_pattern!(Difference::Editable(elements_are![
305332
matches_pattern!(Edit::Both(eq("Common part (1)"))),
306333
matches_pattern!(Edit::ExtraLeft(eq("Left only"))),
307334
matches_pattern!(Edit::ExtraRight(eq("Right only"))),
308335
matches_pattern!(Edit::Both(eq("Common part (2)"))),
309-
]
336+
]))
310337
)
311338
}
312339

@@ -319,11 +346,11 @@ mod tests {
319346
);
320347
verify_that!(
321348
result,
322-
elements_are![
349+
matches_pattern!(Difference::Editable(elements_are![
323350
matches_pattern!(Edit::Both(eq("Common part (1)"))),
324351
matches_pattern!(Edit::ExtraLeft(eq("Left only"))),
325352
matches_pattern!(Edit::Both(eq("Common part (2)"))),
326-
]
353+
]))
327354
)
328355
}
329356

@@ -336,37 +363,59 @@ mod tests {
336363
);
337364
verify_that!(
338365
result,
339-
elements_are![
366+
matches_pattern!(Difference::Editable(elements_are![
340367
matches_pattern!(Edit::Both(eq("Common part (1)"))),
341368
matches_pattern!(Edit::ExtraRight(eq("Right only"))),
342369
matches_pattern!(Edit::Both(eq("Common part (2)"))),
343-
]
370+
]))
344371
)
345372
}
346373

347374
#[test]
348-
fn returns_rewrite_fallback_when_maximum_distance_exceeded() -> Result<()> {
375+
fn returns_unrelated_when_maximum_distance_exceeded() -> Result<()> {
349376
let result = edit_list(0..=20, 20..40);
350-
verify_that!(result, not(contains(matches_pattern!(Edit::Both(anything())))))
377+
verify_that!(result, matches_pattern!(Difference::Unrelated))
351378
}
352379

353380
quickcheck! {
354381
fn edit_list_edits_left_to_right(
355382
left: Vec<Alphabet>,
356383
right: Vec<Alphabet>
357-
) -> bool {
358-
let edit_list = edit_list(left.clone(), right.clone());
359-
apply_edits_to_left(&edit_list, &left) == right
384+
) -> TestResult {
385+
match edit_list(left.clone(), right.clone()) {
386+
Difference::Equal => TestResult::from_bool(left == right),
387+
Difference::Editable(edit_list) => {
388+
TestResult::from_bool(apply_edits_to_left(&edit_list, &left) == right)
389+
}
390+
Difference::Unrelated => {
391+
if left == right {
392+
TestResult::failed()
393+
} else {
394+
TestResult::discard()
395+
}
396+
}
397+
}
360398
}
361399
}
362400

363401
quickcheck! {
364402
fn edit_list_edits_right_to_left(
365403
left: Vec<Alphabet>,
366404
right: Vec<Alphabet>
367-
) -> bool {
368-
let edit_list = edit_list(left.clone(), right.clone());
369-
apply_edits_to_right(&edit_list, &right) == left
405+
) -> TestResult {
406+
match edit_list(left.clone(), right.clone()) {
407+
Difference::Equal => TestResult::from_bool(left == right),
408+
Difference::Editable(edit_list) => {
409+
TestResult::from_bool(apply_edits_to_right(&edit_list, &right) == left)
410+
}
411+
Difference::Unrelated => {
412+
if left == right {
413+
TestResult::failed()
414+
} else {
415+
TestResult::discard()
416+
}
417+
}
418+
}
370419
}
371420
}
372421

googletest/src/matchers/eq_matcher.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,13 @@ pub(super) fn create_diff(expected_debug: &str, actual_debug: &str) -> Cow<'stat
124124
// line-by-line diff.
125125
return "".into();
126126
}
127-
let edit_list = edit_distance::edit_list(actual_debug.lines(), expected_debug.lines());
128-
129-
if edit_list.is_empty() {
130-
return "No difference found between debug strings.".into();
127+
match edit_distance::edit_list(actual_debug.lines(), expected_debug.lines()) {
128+
edit_distance::Difference::Equal => "No difference found between debug strings.".into(),
129+
edit_distance::Difference::Editable(edit_list) => {
130+
format!("\nDifference:{}", edit_list_summary(&edit_list)).into()
131+
}
132+
edit_distance::Difference::Unrelated => "".into(),
131133
}
132-
133-
format!("\nDifference:{}", edit_list_summary(&edit_list)).into()
134134
}
135135

136136
fn edit_list_summary(edit_list: &[edit_distance::Edit<&str>]) -> String {

0 commit comments

Comments
 (0)