blob: 450d7f7e47887d94b3ffaf23ce5ab0945206bdd7 [file] [log] [blame]
use super::*;
macro_rules! diff_list {
() => {
Solution {
text1: Range::empty(),
text2: Range::empty(),
diffs: Vec::new(),
utf8: true,
}
};
($($kind:ident($text:literal)),+ $(,)?) => {{
macro_rules! text1 {
(Insert, $s:literal) => { "" };
(Delete, $s:literal) => { $s };
(Equal, $s:literal) => { $s };
}
macro_rules! text2 {
(Insert, $s:literal) => { $s };
(Delete, $s:literal) => { "" };
(Equal, $s:literal) => { $s };
}
let text1 = concat!($(text1!($kind, $text)),*);
let text2 = concat!($(text2!($kind, $text)),*);
let (_i, _j) = (&mut 0, &mut 0);
macro_rules! range {
(Insert, $s:literal) => {
Diff::Insert(range(text2, _j, $s))
};
(Delete, $s:literal) => {
Diff::Delete(range(text1, _i, $s))
};
(Equal, $s:literal) => {
Diff::Equal(range(text1, _i, $s), range(text2, _j, $s))
};
}
Solution {
text1: Range::new(text1, ..),
text2: Range::new(text2, ..),
diffs: vec![$(range!($kind, $text)),*],
utf8: true,
}
}};
}
fn range<'a>(doc: &'a str, offset: &mut usize, text: &str) -> Range<'a> {
let range = Range {
doc,
offset: *offset,
len: text.len(),
};
*offset += text.len();
range
}
macro_rules! assert_diffs {
([$($kind:ident($text:literal)),* $(,)?], $solution:ident, $msg:expr $(,)?) => {
let expected = &[$(Chunk::$kind($text)),*];
assert!(
same_diffs(expected, &$solution.diffs),
concat!($msg, "\nexpected={:#?}\nactual={:#?}"),
expected, $solution.diffs,
);
};
}
fn same_diffs(expected: &[Chunk], actual: &[Diff]) -> bool {
expected.len() == actual.len()
&& expected.iter().zip(actual).all(|pair| match pair {
(Chunk::Insert(expected), Diff::Insert(actual)) => *expected == str(*actual),
(Chunk::Delete(expected), Diff::Delete(actual)) => *expected == str(*actual),
(Chunk::Equal(expected), Diff::Equal(actual1, actual2)) => {
*expected == str(*actual1) && *expected == str(*actual2)
}
(_, _) => false,
})
}
#[test]
fn test_common_prefix() {
let text1 = Range::new("abc", ..);
let text2 = Range::new("xyz", ..);
assert_eq!(0, common_prefix_bytes(text1, text2), "Null case");
let text1 = Range::new("1234abcdef", ..);
let text2 = Range::new("1234xyz", ..);
assert_eq!(4, common_prefix_bytes(text1, text2), "Non-null case");
let text1 = Range::new("1234", ..);
let text2 = Range::new("1234xyz", ..);
assert_eq!(4, common_prefix_bytes(text1, text2), "Whole case");
}
#[test]
fn test_common_suffix() {
let text1 = Range::new("abc", ..);
let text2 = Range::new("xyz", ..);
assert_eq!(0, common_suffix(text1, text2), "Null case");
assert_eq!(0, common_suffix_bytes(text1, text2), "Null case");
let text1 = Range::new("abcdef1234", ..);
let text2 = Range::new("xyz1234", ..);
assert_eq!(4, common_suffix(text1, text2), "Non-null case");
assert_eq!(4, common_suffix_bytes(text1, text2), "Non-null case");
let text1 = Range::new("1234", ..);
let text2 = Range::new("xyz1234", ..);
assert_eq!(4, common_suffix(text1, text2), "Whole case");
assert_eq!(4, common_suffix_bytes(text1, text2), "Whole case");
}
#[test]
fn test_common_overlap() {
let text1 = Range::empty();
let text2 = Range::new("abcd", ..);
assert_eq!(0, common_overlap(text1, text2), "Null case");
let text1 = Range::new("abc", ..);
let text2 = Range::new("abcd", ..);
assert_eq!(3, common_overlap(text1, text2), "Whole case");
let text1 = Range::new("123456", ..);
let text2 = Range::new("abcd", ..);
assert_eq!(0, common_overlap(text1, text2), "No overlap");
let text1 = Range::new("123456xxx", ..);
let text2 = Range::new("xxxabcd", ..);
assert_eq!(3, common_overlap(text1, text2), "Overlap");
// Some overly clever languages (C#) may treat ligatures as equal to their
// component letters. E.g. U+FB01 == 'fi'
let text1 = Range::new("fi", ..);
let text2 = Range::new("\u{fb01}i", ..);
assert_eq!(0, common_overlap(text1, text2), "Unicode");
}
#[test]
fn test_cleanup_merge() {
let mut solution = diff_list![];
cleanup_merge(&mut solution);
assert_diffs!([], solution, "Null case");
let mut solution = diff_list![Equal("a"), Delete("b"), Insert("c")];
cleanup_merge(&mut solution);
assert_diffs!(
[Equal("a"), Delete("b"), Insert("c")],
solution,
"No change case",
);
let mut solution = diff_list![Equal("a"), Equal("b"), Equal("c")];
cleanup_merge(&mut solution);
assert_diffs!([Equal("abc")], solution, "Merge equalities");
let mut solution = diff_list![Delete("a"), Delete("b"), Delete("c")];
cleanup_merge(&mut solution);
assert_diffs!([Delete("abc")], solution, "Merge deletions");
let mut solution = diff_list![Insert("a"), Insert("b"), Insert("c")];
cleanup_merge(&mut solution);
assert_diffs!([Insert("abc")], solution, "Merge insertions");
let mut solution = diff_list![
Delete("a"),
Insert("b"),
Delete("c"),
Insert("d"),
Equal("e"),
Equal("f"),
];
cleanup_merge(&mut solution);
assert_diffs!(
[Delete("ac"), Insert("bd"), Equal("ef")],
solution,
"Merge interweave",
);
let mut solution = diff_list![Delete("a"), Insert("abc"), Delete("dc")];
cleanup_merge(&mut solution);
assert_diffs!(
[Equal("a"), Delete("d"), Insert("b"), Equal("c")],
solution,
"Prefix and suffix detection",
);
let mut solution = diff_list![
Equal("x"),
Delete("a"),
Insert("abc"),
Delete("dc"),
Equal("y"),
];
cleanup_merge(&mut solution);
assert_diffs!(
[Equal("xa"), Delete("d"), Insert("b"), Equal("cy")],
solution,
"Prefix and suffix detection with equalities",
);
let mut solution = diff_list![Equal("a"), Insert("ba"), Equal("c")];
cleanup_merge(&mut solution);
assert_diffs!([Insert("ab"), Equal("ac")], solution, "Slide edit left");
let mut solution = diff_list![Equal("c"), Insert("ab"), Equal("a")];
cleanup_merge(&mut solution);
assert_diffs!([Equal("ca"), Insert("ba")], solution, "Slide edit right");
let mut solution = diff_list![
Equal("a"),
Delete("b"),
Equal("c"),
Delete("ac"),
Equal("x"),
];
cleanup_merge(&mut solution);
assert_diffs!(
[Delete("abc"), Equal("acx")],
solution,
"Slide edit left recursive",
);
let mut solution = diff_list![
Equal("x"),
Delete("ca"),
Equal("c"),
Delete("b"),
Equal("a"),
];
cleanup_merge(&mut solution);
assert_diffs!(
[Equal("xca"), Delete("cba")],
solution,
"Slide edit right recursive",
);
let mut solution = diff_list![Delete("b"), Insert("ab"), Equal("c")];
cleanup_merge(&mut solution);
assert_diffs!([Insert("a"), Equal("bc")], solution, "Empty range");
let mut solution = diff_list![Equal(""), Insert("a"), Equal("b")];
cleanup_merge(&mut solution);
assert_diffs!([Insert("a"), Equal("b")], solution, "Empty equality");
}
#[test]
fn test_cleanup_semantic_lossless() {
let mut solution = diff_list![];
cleanup_semantic_lossless(&mut solution);
assert_diffs!([], solution, "Null case");
let mut solution = diff_list![
Equal("AAA\r\n\r\nBBB"),
Insert("\r\nDDD\r\n\r\nBBB"),
Equal("\r\nEEE"),
];
cleanup_semantic_lossless(&mut solution);
assert_diffs!(
[
Equal("AAA\r\n\r\n"),
Insert("BBB\r\nDDD\r\n\r\n"),
Equal("BBB\r\nEEE"),
],
solution,
"Blank lines",
);
let mut solution = diff_list![Equal("AAA\r\nBBB"), Insert(" DDD\r\nBBB"), Equal(" EEE")];
cleanup_semantic_lossless(&mut solution);
assert_diffs!(
[Equal("AAA\r\n"), Insert("BBB DDD\r\n"), Equal("BBB EEE")],
solution,
"Line boundaries",
);
let mut solution = diff_list![Equal("The c"), Insert("ow and the c"), Equal("at.")];
cleanup_semantic_lossless(&mut solution);
assert_diffs!(
[Equal("The "), Insert("cow and the "), Equal("cat.")],
solution,
"Word boundaries",
);
let mut solution = diff_list![Equal("The-c"), Insert("ow-and-the-c"), Equal("at.")];
cleanup_semantic_lossless(&mut solution);
assert_diffs!(
[Equal("The-"), Insert("cow-and-the-"), Equal("cat.")],
solution,
"Alphanumeric boundaries",
);
let mut solution = diff_list![Equal("a"), Delete("a"), Equal("ax")];
cleanup_semantic_lossless(&mut solution);
assert_diffs!([Delete("a"), Equal("aax")], solution, "Hitting the start");
let mut solution = diff_list![Equal("xa"), Delete("a"), Equal("a")];
cleanup_semantic_lossless(&mut solution);
assert_diffs!([Equal("xaa"), Delete("a")], solution, "Hitting the end");
let mut solution = diff_list![Equal("The xxx. The "), Insert("zzz. The "), Equal("yyy.")];
cleanup_semantic_lossless(&mut solution);
assert_diffs!(
[Equal("The xxx."), Insert(" The zzz."), Equal(" The yyy.")],
solution,
"Sentence boundaries",
);
}
#[test]
fn test_cleanup_semantic() {
let mut solution = diff_list![];
cleanup_semantic(&mut solution);
assert_diffs!([], solution, "Null case");
let mut solution = diff_list![Delete("ab"), Insert("cd"), Equal("12"), Delete("e")];
cleanup_semantic(&mut solution);
assert_diffs!(
[Delete("ab"), Insert("cd"), Equal("12"), Delete("e")],
solution,
"No elimination #1",
);
let mut solution = diff_list![Delete("abc"), Insert("ABC"), Equal("1234"), Delete("wxyz")];
cleanup_semantic(&mut solution);
assert_diffs!(
[Delete("abc"), Insert("ABC"), Equal("1234"), Delete("wxyz")],
solution,
"No elimination #2",
);
let mut solution = diff_list![Delete("a"), Equal("b"), Delete("c")];
cleanup_semantic(&mut solution);
assert_diffs!([Delete("abc"), Insert("b")], solution, "Simple elimination",);
let mut solution = diff_list![
Delete("ab"),
Equal("cd"),
Delete("e"),
Equal("f"),
Insert("g"),
];
cleanup_semantic(&mut solution);
assert_diffs!(
[Delete("abcdef"), Insert("cdfg")],
solution,
"Backpass elimination",
);
let mut solution = diff_list![
Insert("1"),
Equal("A"),
Delete("B"),
Insert("2"),
Equal("_"),
Insert("1"),
Equal("A"),
Delete("B"),
Insert("2"),
];
cleanup_semantic(&mut solution);
assert_diffs!(
[Delete("AB_AB"), Insert("1A2_1A2")],
solution,
"Multiple elimination",
);
let mut solution = diff_list![Equal("The c"), Delete("ow and the c"), Equal("at.")];
cleanup_semantic(&mut solution);
assert_diffs!(
[Equal("The "), Delete("cow and the "), Equal("cat.")],
solution,
"Word boundaries",
);
let mut solution = diff_list![Delete("abcxx"), Insert("xxdef")];
cleanup_semantic(&mut solution);
assert_diffs!(
[Delete("abcxx"), Insert("xxdef")],
solution,
"No overlap elimination",
);
let mut solution = diff_list![Delete("abcxxx"), Insert("xxxdef")];
cleanup_semantic(&mut solution);
assert_diffs!(
[Delete("abc"), Equal("xxx"), Insert("def")],
solution,
"Overlap elimination",
);
let mut solution = diff_list![Delete("xxxabc"), Insert("defxxx")];
cleanup_semantic(&mut solution);
assert_diffs!(
[Insert("def"), Equal("xxx"), Delete("abc")],
solution,
"Reverse overlap elimination",
);
let mut solution = diff_list![
Delete("abcd1212"),
Insert("1212efghi"),
Equal("----"),
Delete("A3"),
Insert("3BC"),
];
cleanup_semantic(&mut solution);
assert_diffs!(
[
Delete("abcd"),
Equal("1212"),
Insert("efghi"),
Equal("----"),
Delete("A"),
Equal("3"),
Insert("BC"),
],
solution,
"Two overlap eliminations",
);
}
#[test]
fn test_bisect() {
let text1 = Range::new("cat", ..);
let text2 = Range::new("map", ..);
let solution = Solution {
text1,
text2,
diffs: bisect(text1, text2),
utf8: false,
};
assert_diffs!(
[
Delete("c"),
Insert("m"),
Equal("a"),
Delete("t"),
Insert("p"),
],
solution,
"Normal",
);
}
#[test]
fn test_main() {
let solution = main(Range::empty(), Range::empty());
assert_diffs!([], solution, "Null case");
let solution = main(Range::new("abc", ..), Range::new("abc", ..));
assert_diffs!([Equal("abc")], solution, "Equality");
let solution = main(Range::new("abc", ..), Range::new("ab123c", ..));
assert_diffs!(
[Equal("ab"), Insert("123"), Equal("c")],
solution,
"Simple insertion",
);
let solution = main(Range::new("a123bc", ..), Range::new("abc", ..));
assert_diffs!(
[Equal("a"), Delete("123"), Equal("bc")],
solution,
"Simple deletion",
);
let solution = main(Range::new("abc", ..), Range::new("a123b456c", ..));
assert_diffs!(
[
Equal("a"),
Insert("123"),
Equal("b"),
Insert("456"),
Equal("c"),
],
solution,
"Two insertions",
);
let solution = main(Range::new("a123b456c", ..), Range::new("abc", ..));
assert_diffs!(
[
Equal("a"),
Delete("123"),
Equal("b"),
Delete("456"),
Equal("c"),
],
solution,
"Two deletions",
);
let solution = main(Range::new("a", ..), Range::new("b", ..));
assert_diffs!([Delete("a"), Insert("b")], solution, "Simple case #1");
let solution = main(
Range::new("Apples are a fruit.", ..),
Range::new("Bananas are also fruit.", ..),
);
assert_diffs!(
[
Delete("Apple"),
Insert("Banana"),
Equal("s are a"),
Insert("lso"),
Equal(" fruit."),
],
solution,
"Simple case #2",
);
let solution = main(Range::new("ax\t", ..), Range::new("\u{0680}x\000", ..));
assert_diffs!(
[
Delete("a"),
Insert("\u{0680}"),
Equal("x"),
Delete("\t"),
Insert("\000"),
],
solution,
"Simple case #3",
);
let solution = main(Range::new("1ayb2", ..), Range::new("abxab", ..));
assert_diffs!(
[
Delete("1"),
Equal("a"),
Delete("y"),
Equal("b"),
Delete("2"),
Insert("xab"),
],
solution,
"Overlap #1",
);
let solution = main(Range::new("abcy", ..), Range::new("xaxcxabc", ..));
assert_diffs!(
[Insert("xaxcx"), Equal("abc"), Delete("y")],
solution,
"Overlap #2",
);
let solution = main(
Range::new("ABCDa=bcd=efghijklmnopqrsEFGHIJKLMNOefg", ..),
Range::new("a-bcd-efghijklmnopqrs", ..),
);
assert_diffs!(
[
Delete("ABCD"),
Equal("a"),
Delete("="),
Insert("-"),
Equal("bcd"),
Delete("="),
Insert("-"),
Equal("efghijklmnopqrs"),
Delete("EFGHIJKLMNOefg"),
],
solution,
"Overlap #3",
);
let solution = main(
Range::new("a [[Pennsylvania]] and [[New", ..),
Range::new(" and [[Pennsylvania]]", ..),
);
assert_diffs!(
[
Insert(" "),
Equal("a"),
Insert("nd"),
Equal(" [[Pennsylvania]]"),
Delete(" and [[New"),
],
solution,
"Large equality",
);
}