require_relative "./helper"
require_relative "../lib/dimapa"
class DiffTest < Minitest::Test
def setup
@dmp = DiMaPa.new
end
def test_diff_common_prefix
# Detect any common prefix.
# Null case.
assert_equal(0, @dmp.diff_common_prefix("abc", "xyz"))
# Non-null case.
assert_equal(4, @dmp.diff_common_prefix("1234abcdef", "1234xyz"))
# Whole case.
assert_equal(4, @dmp.diff_common_prefix("1234", "1234xyz"))
end
def test_diff_common_suffix
# Detect any common suffix.
# Null case.
assert_equal(0, @dmp.diff_common_suffix("abc", "xyz"))
# Non-null case.
assert_equal(4, @dmp.diff_common_suffix("abcdef1234", "xyz1234"))
# Whole case.
assert_equal(4, @dmp.diff_common_suffix("1234", "xyz1234"))
end
def test_diff_common_overlap
# Detect any suffix/prefix overlap.
# Null case.
assert_equal(0, @dmp.diff_common_overlap("", "abcd"))
# Whole case.
assert_equal(3, @dmp.diff_common_overlap("abc", "abcd"))
# No overlap.
assert_equal(0, @dmp.diff_common_overlap("123456", "abcd"))
# Overlap.
assert_equal(3, @dmp.diff_common_overlap("123456xxx", "xxxabcd"))
# Unicode.
# Some overly clever languages (C#) may treat ligatures as equal to their
# component letters. E.g. U+FB01 == 'fi'
assert_equal(0, @dmp.diff_common_overlap("fi", '\ufb01i'))
end
def test_diff_half_match
# Detect a halfmatch.
@dmp.diff_timeout = 1
# No match.
assert_nil(@dmp.diff_half_match("1234567890", "abcdef"))
assert_nil(@dmp.diff_half_match("12345", "23"))
# Single Match.
assert_equal(
["12", "90", "a", "z", "345678"],
@dmp.diff_half_match("1234567890", "a345678z")
)
assert_equal(
["a", "z", "12", "90", "345678"],
@dmp.diff_half_match("a345678z", "1234567890")
)
assert_equal(
["abc", "z", "1234", "0", "56789"],
@dmp.diff_half_match("abc56789z", "1234567890")
)
assert_equal(
["a", "xyz", "1", "7890", "23456"],
@dmp.diff_half_match("a23456xyz", "1234567890")
)
# Multiple Matches.
assert_equal(
["12123", "123121", "a", "z", "1234123451234"],
@dmp.diff_half_match("121231234123451234123121", "a1234123451234z")
)
assert_equal(
["", "-=-=-=-=-=", "x", "", "x-=-=-=-=-=-=-="],
@dmp.diff_half_match("x-=-=-=-=-=-=-=-=-=-=-=-=", "xx-=-=-=-=-=-=-=")
)
assert_equal(
["-=-=-=-=-=", "", "", "y", "-=-=-=-=-=-=-=y"],
@dmp.diff_half_match("-=-=-=-=-=-=-=-=-=-=-=-=y", "-=-=-=-=-=-=-=yy")
)
# Non-optimal halfmatch.
# Optimal diff would be -q+x=H-i+e=lloHe+Hu=llo-Hew+y
# not -qHillo+x=HelloHe-w+Hulloy
assert_equal(
["qHillo", "w", "x", "Hulloy", "HelloHe"],
@dmp.diff_half_match("qHilloHelloHew", "xHelloHeHulloy")
)
# Optimal no halfmatch.
@dmp.diff_timeout = 0
assert_nil(@dmp.diff_half_match("qHilloHelloHew", "xHelloHeHulloy"))
end
def test_diff_lines_to_chars
# Convert lines down to characters.
assert_equal(
["\x01\x02\x01", "\x02\x01\x02", ["", "alpha\n", "beta\n"]],
@dmp.diff_lines_to_chars("alpha\nbeta\nalpha\n", "beta\nalpha\nbeta\n")
)
assert_equal(
["", "\x01\x02\x03\x03", ["", "alpha\r\n", "beta\r\n", "\r\n"]],
@dmp.diff_lines_to_chars("", "alpha\r\nbeta\r\n\r\n\r\n")
)
assert_equal(
["\x01", "\x02", ["", "a", "b"]],
@dmp.diff_lines_to_chars("a", "b")
)
# More than 256 to reveal any 8-bit limitations.
n = 300
line_list = (1..n).map { |x| x.to_s + "\n" }
char_list = (1..n).map { |x| x.chr(Encoding::UTF_8) }
assert_equal(n, line_list.length)
lines = line_list.join
chars = char_list.join
assert_equal(n, chars.length)
line_list.unshift("")
assert_equal([chars, "", line_list], @dmp.diff_lines_to_chars(lines, ""))
end
def test_diff_chars_to_lines
# Convert chars up to lines.
diffs = [[:equal, "\x01\x02\x01"], [:insert, "\x02\x01\x02"]]
@dmp.diff_chars_to_lines(diffs, ["", "alpha\n", "beta\n"])
assert_equal(
[[:equal, "alpha\nbeta\nalpha\n"], [:insert, "beta\nalpha\nbeta\n"]],
diffs
)
# More than 256 to reveal any 8-bit limitations.
n = 300
line_list = (1..n).map { |x| x.to_s + "\n" }
char_list = (1..n).map { |x| x.chr(Encoding::UTF_8) }
assert_equal(n, line_list.length)
lines = line_list.join
chars = char_list.join
assert_equal(n, chars.length)
line_list.unshift("")
diffs = [[:delete, chars]]
@dmp.diff_chars_to_lines(diffs, line_list)
assert_equal([[:delete, lines]], diffs)
end
def test_diff_cleanup_merge
# Cleanup a messy diff.
# Null case.
diffs = []
@dmp.diff_cleanup_merge(diffs)
assert_equal([], diffs)
# No change case.
diffs = [[:equal, "a"], [:delete, "b"], [:insert, "c"]]
@dmp.diff_cleanup_merge(diffs)
assert_equal([[:equal, "a"], [:delete, "b"], [:insert, "c"]], diffs)
# Merge equalities.
diffs = [[:equal, "a"], [:equal, "b"], [:equal, "c"]]
@dmp.diff_cleanup_merge(diffs)
assert_equal([[:equal, "abc"]], diffs)
# Merge deletions.
diffs = [[:delete, "a"], [:delete, "b"], [:delete, "c"]]
@dmp.diff_cleanup_merge(diffs)
assert_equal([[:delete, "abc"]], diffs)
# Merge insertions.
diffs = [[:insert, "a"], [:insert, "b"], [:insert, "c"]]
@dmp.diff_cleanup_merge(diffs)
assert_equal([[:insert, "abc"]], diffs)
# Merge interweave.
diffs = [
[:delete, "a"], [:insert, "b"], [:delete, "c"],
[:insert, "d"], [:equal, "e"], [:equal, "f"]
]
@dmp.diff_cleanup_merge(diffs)
assert_equal([[:delete, "ac"], [:insert, "bd"], [:equal, "ef"]], diffs)
# Prefix and suffix detection.
diffs = [[:delete, "a"], [:insert, "abc"], [:delete, "dc"]]
@dmp.diff_cleanup_merge(diffs)
assert_equal(
[[:equal, "a"], [:delete, "d"], [:insert, "b"], [:equal, "c"]],
diffs
)
# Prefix and suffix detection with equalities.
diffs = [
[:equal, "x"], [:delete, "a"], [:insert, "abc"],
[:delete, "dc"], [:equal, "y"]
]
@dmp.diff_cleanup_merge(diffs)
assert_equal(
[[:equal, "xa"], [:delete, "d"], [:insert, "b"], [:equal, "cy"]],
diffs
)
# Slide edit left.
diffs = [[:equal, "a"], [:insert, "ba"], [:equal, "c"]]
@dmp.diff_cleanup_merge(diffs)
assert_equal([[:insert, "ab"], [:equal, "ac"]], diffs)
# Slide edit right.
diffs = [[:equal, "c"], [:insert, "ab"], [:equal, "a"]]
@dmp.diff_cleanup_merge(diffs)
assert_equal([[:equal, "ca"], [:insert, "ba"]], diffs)
# Slide edit left recursive.
diffs = [
[:equal, "a"], [:delete, "b"], [:equal, "c"],
[:delete, "ac"], [:equal, "x"]
]
@dmp.diff_cleanup_merge(diffs)
assert_equal([[:delete, "abc"], [:equal, "acx"]], diffs)
# Slide edit right recursive.
diffs = [
[:equal, "x"], [:delete, "ca"], [:equal, "c"],
[:delete, "b"], [:equal, "a"]
]
@dmp.diff_cleanup_merge(diffs)
assert_equal([[:equal, "xca"], [:delete, "cba"]], diffs)
end
def test_diff_cleanup_semantic_lossless
# Slide diffs to match logical boundaries.
# Null case.
diffs = []
@dmp.diff_cleanup_semantic_lossless(diffs)
assert_equal([], diffs)
# Blank lines.
diffs = [
[:equal, "AAA\r\n\r\nBBB"],
[:insert, "\r\nDDD\r\n\r\nBBB"],
[:equal, "\r\nEEE"]
]
@dmp.diff_cleanup_semantic_lossless(diffs)
assert_equal([
[:equal, "AAA\r\n\r\n"],
[:insert, "BBB\r\nDDD\r\n\r\n"],
[:equal, "BBB\r\nEEE"]
],
diffs)
# Line boundaries.
diffs = [[:equal, "AAA\r\nBBB"], [:insert, " DDD\r\nBBB"], [:equal, " EEE"]]
@dmp.diff_cleanup_semantic_lossless(diffs)
assert_equal(
[[:equal, "AAA\r\n"], [:insert, "BBB DDD\r\n"], [:equal, "BBB EEE"]],
diffs
)
# Word boundaries.
diffs = [[:equal, "The c"], [:insert, "ow and the c"], [:equal, "at."]]
@dmp.diff_cleanup_semantic_lossless(diffs)
assert_equal(
[[:equal, "The "], [:insert, "cow and the "], [:equal, "cat."]],
diffs
)
# Alphanumeric boundaries.
diffs = [[:equal, "The-c"], [:insert, "ow-and-the-c"], [:equal, "at."]]
@dmp.diff_cleanup_semantic_lossless(diffs)
assert_equal(
[[:equal, "The-"], [:insert, "cow-and-the-"], [:equal, "cat."]],
diffs
)
# Hitting the start.
diffs = [[:equal, "a"], [:delete, "a"], [:equal, "ax"]]
@dmp.diff_cleanup_semantic_lossless(diffs)
assert_equal([[:delete, "a"], [:equal, "aax"]], diffs)
# Hitting the end.
diffs = [[:equal, "xa"], [:delete, "a"], [:equal, "a"]]
@dmp.diff_cleanup_semantic_lossless(diffs)
assert_equal([[:equal, "xaa"], [:delete, "a"]], diffs)
end
def test_diff_cleanup_semantic
# Cleanup semantically trivial equalities.
# Null case.
diffs = []
@dmp.diff_cleanup_semantic(diffs)
assert_equal([], diffs)
# No elimination #1.
diffs = [[:delete, "ab"], [:insert, "cd"], [:equal, "12"], [:delete, "e"]]
@dmp.diff_cleanup_semantic(diffs)
assert_equal(
[[:delete, "ab"], [:insert, "cd"], [:equal, "12"], [:delete, "e"]],
diffs
)
# No elimination #2.
diffs = [
[:delete, "abc"], [:insert, "ABC"],
[:equal, "1234"], [:delete, "wxyz"]
]
@dmp.diff_cleanup_semantic(diffs)
assert_equal(
[[:delete, "abc"], [:insert, "ABC"], [:equal, "1234"], [:delete, "wxyz"]],
diffs
)
# Simple elimination.
diffs = [[:delete, "a"], [:equal, "b"], [:delete, "c"]]
@dmp.diff_cleanup_semantic(diffs)
assert_equal([[:delete, "abc"], [:insert, "b"]], diffs)
# Backpass elimination.
diffs = [
[:delete, "ab"], [:equal, "cd"], [:delete, "e"],
[:equal, "f"], [:insert, "g"]
]
@dmp.diff_cleanup_semantic(diffs)
assert_equal([[:delete, "abcdef"], [:insert, "cdfg"]], diffs)
# Multiple eliminations.
diffs = [
[:insert, "1"], [:equal, "A"], [:delete, "B"],
[:insert, "2"], [:equal, "_"], [:insert, "1"],
[:equal, "A"], [:delete, "B"], [:insert, "2"]
]
@dmp.diff_cleanup_semantic(diffs)
assert_equal([[:delete, "AB_AB"], [:insert, "1A2_1A2"]], diffs)
# Word boundaries.
diffs = [[:equal, "The c"], [:delete, "ow and the c"], [:equal, "at."]]
@dmp.diff_cleanup_semantic(diffs)
assert_equal(
[[:equal, "The "], [:delete, "cow and the "], [:equal, "cat."]],
diffs
)
# No overlap elimination.
diffs = [[:delete, "abcxx"], [:insert, "xxdef"]]
@dmp.diff_cleanup_semantic(diffs)
assert_equal([[:delete, "abcxx"], [:insert, "xxdef"]], diffs)
# Overlap elimination.
diffs = [[:delete, "abcxxx"], [:insert, "xxxdef"]]
@dmp.diff_cleanup_semantic(diffs)
assert_equal([[:delete, "abc"], [:equal, "xxx"], [:insert, "def"]], diffs)
# Two overlap eliminations.
diffs = [
[:delete, "abcd1212"], [:insert, "1212efghi"], [:equal, "----"],
[:delete, "A3"], [:insert, "3BC"]
]
@dmp.diff_cleanup_semantic(diffs)
assert_equal([
[:delete, "abcd"], [:equal, "1212"], [:insert, "efghi"],
[:equal, "----"], [:delete, "A"], [:equal, "3"], [:insert, "BC"]
],
diffs)
end
def test_diff_cleanup_efficiency
# Cleanup operationally trivial equalities.
@dmp.diff_edit_cost = 4
# Null case.
diffs = []
@dmp.diff_cleanup_efficiency(diffs)
assert_equal([], diffs)
# No elimination.
diffs = [
[:delete, "ab"], [:insert, "12"], [:equal, "wxyz"],
[:delete, "cd"], [:insert, "34"]
]
@dmp.diff_cleanup_efficiency(diffs)
assert_equal([
[:delete, "ab"], [:insert, "12"], [:equal, "wxyz"],
[:delete, "cd"], [:insert, "34"]
],
diffs)
# Four-edit elimination.
diffs = [
[:delete, "ab"], [:insert, "12"], [:equal, "xyz"],
[:delete, "cd"], [:insert, "34"]
]
@dmp.diff_cleanup_efficiency(diffs)
assert_equal([[:delete, "abxyzcd"], [:insert, "12xyz34"]], diffs)
# Three-edit elimination.
diffs = [[:insert, "12"], [:equal, "x"], [:delete, "cd"], [:insert, "34"]]
@dmp.diff_cleanup_efficiency(diffs)
assert_equal([[:delete, "xcd"], [:insert, "12x34"]], diffs)
# Backpass elimination.
diffs = [
[:delete, "ab"], [:insert, "12"], [:equal, "xy"], [:insert, "34"],
[:equal, "z"], [:delete, "cd"], [:insert, "56"]
]
@dmp.diff_cleanup_efficiency(diffs)
assert_equal([[:delete, "abxyzcd"], [:insert, "12xy34z56"]], diffs)
# High cost elimination.
@dmp.diff_edit_cost = 5
diffs = [
[:delete, "ab"], [:insert, "12"], [:equal, "wxyz"],
[:delete, "cd"], [:insert, "34"]
]
@dmp.diff_cleanup_efficiency(diffs)
assert_equal([[:delete, "abwxyzcd"], [:insert, "12wxyz34"]], diffs)
@dmp.diff_edit_cost = 4
end
def test_diff_pretty_html
# Pretty print.
diffs = [[:equal, 'a\n'], [:delete, "b"], [:insert, "c&d"]]
assert_equal(
'a¶
<B>' \
'b</B>c&d',
@dmp.diff_pretty_html(diffs)
)
end
def test_diff_text
# Compute the source and destination texts.
diffs = [
[:equal, "jump"], [:delete, "s"], [:insert, "ed"], [:equal, " over "],
[:delete, "the"], [:insert, "a"], [:equal, " lazy"]
]
assert_equal("jumps over the lazy", @dmp.diff_text1(diffs))
assert_equal("jumped over a lazy", @dmp.diff_text2(diffs))
end
def test_diff_delta
# Convert a diff into delta string.
diffs = [
[:equal, "jump"], [:delete, "s"], [:insert, "ed"], [:equal, " over "],
[:delete, "the"], [:insert, "a"], [:equal, " lazy"], [:insert, "old dog"]
]
text1 = @dmp.diff_text1(diffs)
assert_equal("jumps over the lazy", text1)
delta = @dmp.diff_to_delta(diffs)
assert_equal("=4\t-1\t+ed\t=6\t-3\t+a\t=5\t+old dog", delta)
# Convert delta string into a diff.
assert_equal(diffs, @dmp.diff_from_delta(text1, delta))
# Generates error (19 != 20).
assert_raises ArgumentError do
@dmp.diff_from_delta(text1 + "x", delta)
end
# Generates error (19 != 18)
assert_raises ArgumentError do
@dmp.diff_from_delta(text1[1..-1], delta)
end
# Test deltas with special characters.
diffs = [
[:equal, "\u0680 \x00 \t %"],
[:delete, "\u0681 \x01 \n ^"],
[:insert, "\u0682 \x02 \\ |"]
]
text1 = @dmp.diff_text1(diffs)
assert_equal("\u0680 \x00 \t %\u0681 \x01 \n ^", text1)
delta = @dmp.diff_to_delta(diffs)
assert_equal("=7\t-7\t+%DA%82 %02 %5C %7C", delta)
# Convert delta string into a diff.
assert_equal(diffs, @dmp.diff_from_delta(text1, delta))
# Verify pool of unchanged characters.
diffs = [[:insert, "A-Z a-z 0-9 - _ . ! ~ * \' ( ) / ? : @ & = + $ , # "]]
text2 = @dmp.diff_text2(diffs)
assert_equal("A-Z a-z 0-9 - _ . ! ~ * \' ( ) / ? : @ & = + $ , # ", text2)
delta = @dmp.diff_to_delta(diffs)
assert_equal("+A-Z a-z 0-9 - _ . ! ~ * \' ( ) / ? : @ & = + $ , # ", delta)
# Convert delta string into a diff.
assert_equal(diffs, @dmp.diff_from_delta("", delta))
end
def test_diff_x_index
# Translate a location in text1 to text2.
# Translation on equality.
diffs = [[:delete, "a"], [:insert, "1234"], [:equal, "xyz"]]
assert_equal(5, @dmp.diff_x_index(diffs, 2))
# Translation on deletion.
diffs = [[:equal, "a"], [:delete, "1234"], [:equal, "xyz"]]
assert_equal(1, @dmp.diff_x_index(diffs, 3))
end
def test_diff_levenshtein
# Levenshtein with trailing equality.
diffs = [[:delete, "abc"], [:insert, "1234"], [:equal, "xyz"]]
assert_equal(4, @dmp.diff_levenshtein(diffs))
# Levenshtein with leading equality.
diffs = [[:equal, "xyz"], [:delete, "abc"], [:insert, "1234"]]
assert_equal(4, @dmp.diff_levenshtein(diffs))
# Levenshtein with middle equality.
diffs = [[:delete, "abc"], [:equal, "xyz"], [:insert, "1234"]]
assert_equal(7, @dmp.diff_levenshtein(diffs))
end
def test_diff_bisect
# Normal.
a = "cat"
b = "map"
# Since the resulting diff hasn't been normalized, it would be ok if
# the insertion and deletion pairs are swapped.
# If the order changes, tweak this test as required.
diffs = [
[:delete, "c"], [:insert, "m"], [:equal, "a"],
[:delete, "t"], [:insert, "p"]
]
assert_equal(diffs, @dmp.diff_bisect(a, b, nil))
# Timeout.
assert_equal(
[[:delete, "cat"], [:insert, "map"]],
@dmp.diff_bisect(a, b, Time.now - 1)
)
end
def test_diff_main
# Perform a trivial diff.
# Null case.
assert_equal([], @dmp.diff_main("", "", false))
# Equality.
assert_equal([[:equal, "abc"]], @dmp.diff_main("abc", "abc", false))
# Simple insertion.
assert_equal(
[[:equal, "ab"], [:insert, "123"], [:equal, "c"]],
@dmp.diff_main("abc", "ab123c", false)
)
# Simple deletion.
assert_equal(
[[:equal, "a"], [:delete, "123"], [:equal, "bc"]],
@dmp.diff_main("a123bc", "abc", false)
)
# Two insertions.
assert_equal([
[:equal, "a"], [:insert, "123"], [:equal, "b"],
[:insert, "456"], [:equal, "c"]
],
@dmp.diff_main("abc", "a123b456c", false))
# Two deletions.
assert_equal([
[:equal, "a"], [:delete, "123"], [:equal, "b"],
[:delete, "456"], [:equal, "c"]
],
@dmp.diff_main("a123b456c", "abc", false))
# Perform a real diff.
# Switch off the timeout.
@dmp.diff_timeout = 0
# Simple cases.
assert_equal(
[[:delete, "a"], [:insert, "b"]],
@dmp.diff_main("a", "b", false)
)
assert_equal([
[:delete, "Apple"], [:insert, "Banana"], [:equal, "s are a"],
[:insert, "lso"], [:equal, " fruit."]
],
@dmp.diff_main("Apples are a fruit.", "Bananas are also fruit.", false))
assert_equal([
[:delete, "a"], [:insert, "\u0680"], [:equal, "x"],
[:delete, "\t"], [:insert, "\0"]
],
@dmp.diff_main("ax\t", "\u0680x\0", false))
# Overlaps.
assert_equal([
[:delete, "1"], [:equal, "a"], [:delete, "y"],
[:equal, "b"], [:delete, "2"], [:insert, "xab"]
],
@dmp.diff_main("1ayb2", "abxab", false))
assert_equal(
[[:insert, "xaxcx"], [:equal, "abc"], [:delete, "y"]],
@dmp.diff_main("abcy", "xaxcxabc", false)
)
assert_equal([
[:delete, "ABCD"], [:equal, "a"], [:delete, "="], [:insert, "-"],
[:equal, "bcd"], [:delete, "="], [:insert, "-"],
[:equal, "efghijklmnopqrs"], [:delete, "EFGHIJKLMNOefg"]
],
@dmp.diff_main(
"ABCDa=bcd=efghijklmnopqrsEFGHIJKLMNOefg",
"a-bcd-efghijklmnopqrs",
false
))
# Large equality.
assert_equal(
[
[:insert, " "], [:equal, "a"], [:insert, "nd"],
[:equal, " [[Pennsylvania]]"], [:delete, " and [[New"]
],
@dmp.diff_main(
"a [[Pennsylvania]] and [[New", " and [[Pennsylvania]]", false
)
)
# Timeout.
@dmp.diff_timeout = 0.1 # 100ms
a = "`Twas brillig, and the slithy toves\nDid gyre and gimble in the " \
"wabe:\nAll mimsy were the borogoves,\nAnd the mome raths outgrabe.\n"
b = "I am the very model of a modern major general,\nI\'ve information " \
"vegetable, animal, and mineral,\nI know the kings of England, and " \
"I quote the fights historical,\nFrom Marathon to Waterloo, in " \
"order categorical.\n"
# Increase the text lengths by 1024 times to ensure a timeout.
a *= 1024
b *= 1024
start_time = Time.now
@dmp.diff_main(a, b)
end_time = Time.now
# Test that we took at least the timeout period.
assert_equal(true, @dmp.diff_timeout <= end_time - start_time)
# Test that we didn't take forever (be forgiving).
# Theoretically this test could fail very occasionally if the
# OS task swaps or locks up for a second at the wrong moment.
assert_equal(true, @dmp.diff_timeout * 1000 * 2 > end_time - start_time)
@dmp.diff_timeout = 0
# Test the linemode speedup.
# Must be long to pass the 100 char cutoff.
# Simple line-mode.
a = "1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n" \
"1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n" \
"1234567890\n1234567890\n1234567890\n"
b = "abcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\n" \
"abcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\n" \
"abcdefghij\nabcdefghij\nabcdefghij\n"
assert_equal(@dmp.diff_main(a, b, false), @dmp.diff_main(a, b, true))
# Single line-mode.
a = "123456789012345678901234567890123456789012345678901234567890" \
"123456789012345678901234567890123456789012345678901234567890" \
"1234567890"
b = "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij" \
"abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij"
assert_equal(@dmp.diff_main(a, b, false), @dmp.diff_main(a, b, true))
# Overlap line-mode.
a = "1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n" \
"1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n" \
"1234567890\n1234567890\n1234567890\n"
b = "abcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n" \
"1234567890\n1234567890\n1234567890\nabcdefghij\n1234567890\n" \
"1234567890\n1234567890\nabcdefghij\n"
diffs_linemode = @dmp.diff_main(a, b, false)
diffs_textmode = @dmp.diff_main(a, b, true)
assert_equal(
@dmp.diff_text1(diffs_linemode),
@dmp.diff_text1(diffs_textmode)
)
assert_equal(
@dmp.diff_text2(diffs_linemode),
@dmp.diff_text2(diffs_textmode)
)
# Test null inputs.
assert_raises ArgumentError do
@dmp.diff_main(nil, nil)
end
end
def test_match_alphabet
# Initialise the bitmasks for Bitap.
# Unique.
assert_equal({"a" => 4, "b" => 2, "c" => 1}, @dmp.match_alphabet("abc"))
# Duplicates.
assert_equal({"a" => 37, "b" => 18, "c" => 8}, @dmp.match_alphabet("abcaba"))
end
def test_match_bitap
# Bitap algorithm.
@dmp.match_distance = 100
@dmp.match_threshold = 0.5
# Exact matches.
assert_equal(5, @dmp.match_bitap("abcdefghijk", "fgh", 5))
assert_equal(5, @dmp.match_bitap("abcdefghijk", "fgh", 0))
# Fuzzy matches.
assert_equal(4, @dmp.match_bitap("abcdefghijk", "efxhi", 0))
assert_equal(2, @dmp.match_bitap("abcdefghijk", "cdefxyhijk", 5))
assert_equal(-1, @dmp.match_bitap("abcdefghijk", "bxy", 1))
# Overflow.
assert_equal(2, @dmp.match_bitap("123456789xx0", "3456789x0", 2))
# Threshold test.
@dmp.match_threshold = 0.4
assert_equal(4, @dmp.match_bitap("abcdefghijk", "efxyhi", 1))
@dmp.match_threshold = 0.3
assert_equal(-1, @dmp.match_bitap("abcdefghijk", "efxyhi", 1))
@dmp.match_threshold = 0.0
assert_equal(1, @dmp.match_bitap("abcdefghijk", "bcdef", 1))
@dmp.match_threshold = 0.5
# Multiple select.
assert_equal(0, @dmp.match_bitap("abcdexyzabcde", "abccde", 3))
assert_equal(8, @dmp.match_bitap("abcdexyzabcde", "abccde", 5))
# Distance test.
@dmp.match_distance = 10 # Strict location.
assert_equal(
-1,
@dmp.match_bitap("abcdefghijklmnopqrstuvwxyz", "abcdefg", 24)
)
assert_equal(
0,
@dmp.match_bitap("abcdefghijklmnopqrstuvwxyz", "abcdxxefg", 1)
)
@dmp.match_distance = 1000 # Loose location.
assert_equal(
0,
@dmp.match_bitap("abcdefghijklmnopqrstuvwxyz", "abcdefg", 24)
)
end
def test_match_main
# Full match.
# Shortcut matches.
assert_equal(0, @dmp.match_main("abcdef", "abcdef", 1000))
assert_equal(-1, @dmp.match_main("", "abcdef", 1))
assert_equal(3, @dmp.match_main("abcdef", "", 3))
assert_equal(3, @dmp.match_main("abcdef", "de", 3))
# Beyond end match.
assert_equal(3, @dmp.match_main("abcdef", "defy", 4))
# Oversized pattern.
assert_equal(0, @dmp.match_main("abcdef", "abcdefy", 0))
# Complex match.
assert_equal(
4,
@dmp.match_main(
"I am the very model of a modern major general.",
" that berry ",
5
)
)
# Test null inputs.
assert_raises ArgumentError do
@dmp.match_main(nil, nil, 0)
end
end
# Patch tests
def test_patch_obj
# Patch Object.
p = PatchObj.new
p.start1 = 20
p.start2 = 21
p.length1 = 18
p.length2 = 17
p.diffs = [
[:equal, "jump"],
[:delete, "s"],
[:insert, "ed"],
[:equal, " over "],
[:delete, "the"],
[:insert, "a"],
[:equal, "\nlaz"]
]
strp = p.to_s
assert_equal(
"@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n %0Alaz\n",
strp
)
end
def test_patch_from_text
assert_equal([], @dmp.patch_from_text(""))
[
"@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n %0Alaz\n",
"@@ -1 +1 @@\n-a\n+b\n",
"@@ -1 +1 @@\n-a\n+b\n",
"@@ -0,0 +1,3 @@\n+abc\n"
].each do |strp|
assert_equal(strp, @dmp.patch_from_text(strp).first.to_s)
end
# Generates error.
assert_raises ArgumentError do
@dmp.patch_from_text('Bad\nPatch\n')
end
end
def test_patch_to_text
[
"@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n laz\n",
"@@ -1,9 +1,9 @@\n-f\n+F\n oo+fooba\n@@ -7,9 +7,9 @@\n obar\n-,\n+.\n tes\n"
].each do |strp|
p = @dmp.patch_from_text(strp)
assert_equal(strp, @dmp.patch_to_text(p))
end
end
def test_patch_add_context
@dmp.patch_margin = 4
p = @dmp.patch_from_text("@@ -21,4 +21,10 @@\n-jump\n+somersault\n").first
@dmp.patch_add_context(p, "The quick brown fox jumps over the lazy dog.")
assert_equal(
"@@ -17,12 +17,18 @@\n fox \n-jump\n+somersault\n s ov\n",
p.to_s
)
# Same, but not enough trailing context.
p = @dmp.patch_from_text("@@ -21,4 +21,10 @@\n-jump\n+somersault\n").first
@dmp.patch_add_context(p, "The quick brown fox jumps.")
assert_equal(
"@@ -17,10 +17,16 @@\n fox \n-jump\n+somersault\n s.\n",
p.to_s
)
# Same, but not enough leading context.
p = @dmp.patch_from_text("@@ -3 +3,2 @@\n-e\n+at\n").first
@dmp.patch_add_context(p, "The quick brown fox jumps.")
assert_equal(
"@@ -1,7 +1,8 @@\n Th\n-e\n+at\n qui\n",
p.to_s
)
# Same, but with ambiguity.
p = @dmp.patch_from_text("@@ -3 +3,2 @@\n-e\n+at\n").first
@dmp.patch_add_context(
p,
"The quick brown fox jumps. The quick brown fox crashes."
)
assert_equal(
"@@ -1,27 +1,28 @@\n Th\n-e\n+at\n quick brown fox jumps. \n",
p.to_s
)
end
def test_patch_make
# Null case.
patches = @dmp.patch_make("", "")
assert_equal("", @dmp.patch_to_text(patches))
text1 = "The quick brown fox jumps over the lazy dog."
text2 = "That quick brown fox jumped over a lazy dog."
# Text2+Text1 inputs.
expected = "@@ -1,8 +1,7 @@\n Th\n-at\n+e\n qui\n@@ -21,17 +21,18 " \
"@@\n jump\n-ed\n+s\n over \n-a\n+the\n laz\n"
# The second patch must be "-21,17 +21,18",
# not "-22,17 +21,18" due to rolling context
patches = @dmp.patch_make(text2, text1)
assert_equal(expected, @dmp.patch_to_text(patches))
# Text1+Text2 inputs.
expected = "@@ -1,11 +1,12 @@\n Th\n-e\n+at\n quick b\n@@ -22,18" \
" +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n laz\n"
patches = @dmp.patch_make(text1, text2)
assert_equal(expected, @dmp.patch_to_text(patches))
# Diff input.
diffs = @dmp.diff_main(text1, text2, false)
patches = @dmp.patch_make(diffs)
assert_equal(expected, @dmp.patch_to_text(patches))
# Text1+Diff inputs.
patches = @dmp.patch_make(text1, diffs)
assert_equal(expected, @dmp.patch_to_text(patches))
# Text1+Text2+Diff inputs (deprecated)
patches = @dmp.patch_make(text1, text2, diffs)
assert_equal(expected, @dmp.patch_to_text(patches))
# Character encoding.
patches = @dmp.patch_make(
'`1234567890-=[]\\;\',./',
'~!@#$%^&*()_+{}|:"<>?'
)
assert_equal(
"@@ -1,21 +1,21 @@\n-%601234567890-=%5B%5D%5C;\',./\n+~!" \
"@\#$%25%5E&*()_+%7B%7D%7C:%22%3C%3E?\n",
@dmp.patch_to_text(patches)
)
# Character decoding.
diffs = [
[:delete, '`1234567890-=[]\\;\',./'],
[:insert, '~!@#$%^&*()_+{}|:"<>?']
]
assert_equal(
diffs,
@dmp.patch_from_text(
"@@ -1,21 +1,21 @@\n-%601234567890-=%5B%5D%5C;\',./\n+~!" \
"@\#$%25%5E&*()_+%7B%7D%7C:%22%3C%3E?\n"
).first.diffs
)
# Long string with repeats.
text1 = "abcdef" * 100
text2 = text1 + "123"
expected = "@@ -573,28 +573,31 @@\n cdefabcdefabcdefabcdefabcdef\n+123\n"
patches = @dmp.patch_make(text1, text2)
assert_equal(expected, @dmp.patch_to_text(patches))
# Test null inputs.
assert_raises ArgumentError do
@dmp.patch_make(nil)
end
end
def test_patch_split_max
# Assumes that dmp.Match_MaxBits is 32.
patches = @dmp.patch_make(
"abcdefghijklmnopqrstuvwxyz01234567890",
"XabXcdXefXghXijXklXmnXopXqrXstXuvXwxXyzX01X23X45X67X89X0"
)
@dmp.patch_split_max(patches)
assert_equal(
"@@ -1,32 +1,46 @@\n+X\n ab\n+X\n cd\n+X\n ef\n+X\n gh\n+X\n " \
"ij\n+X\n kl\n+X\n mn\n+X\n op\n+X\n qr\n+X\n st\n+X\n uv\n+X\n " \
"wx\n+X\n yz\n+X\n 012345\n@@ -25,13 +39,18 @@\n zX01\n+X\n 23\n+X\n " \
"45\n+X\n 67\n+X\n 89\n+X\n 0\n",
@dmp.patch_to_text(patches)
)
patches = @dmp.patch_make(
"abcdef1234567890123456789012345678901234567890" \
"123456789012345678901234567890uvwxyz",
"abcdefuvwxyz"
)
old_to_text = @dmp.patch_to_text(patches)
@dmp.patch_split_max(patches)
assert_equal(old_to_text, @dmp.patch_to_text(patches))
patches = @dmp.patch_make(
"1234567890123456789012345678901234567890123456789012345678901234567890",
"abc"
)
@dmp.patch_split_max(patches)
assert_equal(
"@@ -1,32 +1,4 @@\n-1234567890123456789012345678\n 9012\n" \
"@@ -29,32 +1,4 @@\n-9012345678901234567890123456\n 7890\n" \
"@@ -57,14 +1,3 @@\n-78901234567890\n+abc\n",
@dmp.patch_to_text(patches)
)
patches = @dmp.patch_make(
"abcdefghij , h : 0 , t : 1 abcdefghij , h : 0 , t : 1 abcdefghij , h : 0 , t : 1",
"abcdefghij , h : 1 , t : 1 abcdefghij , h : 1 , t : 1 abcdefghij , h : 0 , t : 1"
)
@dmp.patch_split_max(patches)
assert_equal(
"@@ -2,32 +2,32 @@\n bcdefghij , h : \n-0\n+1\n , t : 1 abcdef\n" \
"@@ -29,32 +29,32 @@\n bcdefghij , h : \n-0\n+1\n , t : 1 abcdef\n",
@dmp.patch_to_text(patches)
)
end
def test_patch_add_padding
# Both edges full.
patches = @dmp.patch_make("", "test")
assert_equal("@@ -0,0 +1,4 @@\n+test\n", @dmp.patch_to_text(patches))
@dmp.patch_add_padding(patches)
assert_equal(
"@@ -1,8 +1,12 @@\n %01%02%03%04\n+test\n %01%02%03%04\n",
@dmp.patch_to_text(patches)
)
# Both edges partial.
patches = @dmp.patch_make("XY", "XtestY")
assert_equal("@@ -1,2 +1,6 @@\n X\n+test\n Y\n", @dmp.patch_to_text(patches))
@dmp.patch_add_padding(patches)
assert_equal(
"@@ -2,8 +2,12 @@\n %02%03%04X\n+test\n Y%01%02%03\n",
@dmp.patch_to_text(patches)
)
# Both edges none.
patches = @dmp.patch_make("XXXXYYYY", "XXXXtestYYYY")
assert_equal(
"@@ -1,8 +1,12 @@\n XXXX\n+test\n YYYY\n",
@dmp.patch_to_text(patches)
)
@dmp.patch_add_padding(patches)
assert_equal(
"@@ -5,8 +5,12 @@\n XXXX\n+test\n YYYY\n",
@dmp.patch_to_text(patches)
)
end
def test_patch_apply
@dmp.match_distance = 1000
@dmp.match_threshold = 0.5
@dmp.patch_delete_threshold = 0.5
# Null case.
patches = @dmp.patch_make("", "")
results = @dmp.patch_apply(patches, "Hello world.")
assert_equal(["Hello world.", []], results)
# Exact match.
patches = @dmp.patch_make(
"The quick brown fox jumps over the lazy dog.",
"That quick brown fox jumped over a lazy dog."
)
results = @dmp.patch_apply(
patches,
"The quick brown fox jumps over the lazy dog."
)
assert_equal(
["That quick brown fox jumped over a lazy dog.", [true, true]],
results
)
# Partial match.
results = @dmp.patch_apply(
patches,
"The quick red rabbit jumps over the tired tiger."
)
assert_equal(
["That quick red rabbit jumped over a tired tiger.", [true, true]],
results
)
# Failed match.
results = @dmp.patch_apply(
patches,
"I am the very model of a modern major general."
)
assert_equal(
["I am the very model of a modern major general.", [false, false]],
results
)
# Big delete, small change.
patches = @dmp.patch_make(
"x1234567890123456789012345678901234567890123456789012345678901234567890y",
"xabcy"
)
results = @dmp.patch_apply(
patches,
"x123456789012345678901234567890-----++++++++++-----" \
"123456789012345678901234567890y"
)
assert_equal(["xabcy", [true, true]], results)
# Big delete, big change 1.
patches = @dmp.patch_make(
"x1234567890123456789012345678901234567890123456789012345678901234567890y",
"xabcy"
)
results = @dmp.patch_apply(
patches,
"x12345678901234567890---------------++++++++++---------------" \
"12345678901234567890y"
)
assert_equal([
"xabc12345678901234567890---------------++++++++++---------------" \
"12345678901234567890y",
[false, true]
],
results)
# Big delete, big change 2.
@dmp.patch_delete_threshold = 0.6
patches = @dmp.patch_make(
"x1234567890123456789012345678901234567890123456789012345678901234567890y",
"xabcy"
)
results = @dmp.patch_apply(
patches,
"x12345678901234567890---------------++++++++++---------------" \
"12345678901234567890y"
)
assert_equal(["xabcy", [true, true]], results)
@dmp.patch_delete_threshold = 0.5
# Compensate for failed patch.
@dmp.match_threshold = 0.0
@dmp.match_distance = 0
patches = @dmp.patch_make(
"abcdefghijklmnopqrstuvwxyz--------------------1234567890",
"abcXXXXXXXXXXdefghijklmnopqrstuvwxyz--------------------" \
"1234567YYYYYYYYYY890"
)
results = @dmp.patch_apply(
patches,
"ABCDEFGHIJKLMNOPQRSTUVWXYZ--------------------1234567890"
)
assert_equal([
"ABCDEFGHIJKLMNOPQRSTUVWXYZ--------------------1234567YYYYYYYYYY890",
[false, true]
],
results)
@dmp.match_threshold = 0.5
@dmp.match_distance = 1000
# No side effects.
patches = @dmp.patch_make("", "test")
patchstr = @dmp.patch_to_text(patches)
@dmp.patch_apply(patches, "")
assert_equal(patchstr, @dmp.patch_to_text(patches))
# No side effects with major delete.
patches = @dmp.patch_make(
"The quick brown fox jumps over the lazy dog.",
"Woof"
)
patchstr = @dmp.patch_to_text(patches)
@dmp.patch_apply(patches, "The quick brown fox jumps over the lazy dog.")
assert_equal(patchstr, @dmp.patch_to_text(patches))
# Edge exact match.
patches = @dmp.patch_make("", "test")
results = @dmp.patch_apply(patches, "")
assert_equal(["test", [true]], results)
# Near edge exact match.
patches = @dmp.patch_make("XY", "XtestY")
results = @dmp.patch_apply(patches, "XY")
assert_equal(["XtestY", [true]], results)
# Edge partial match.
patches = @dmp.patch_make("y", "y123")
results = @dmp.patch_apply(patches, "x")
assert_equal(["x123", [true]], results)
end
end