require 'test/unit'
require 'diff_match_patch'
class DiffTest < Test::Unit::TestCase
def setup
@dmp = DiffMatchPatch.new
end
def test_diff_commonPrefix
# Detect any common prefix.
# Null case.
assert_equal(0, @dmp.diff_commonPrefix('abc', 'xyz'))
# Non-null case.
assert_equal(4, @dmp.diff_commonPrefix('1234abcdef', '1234xyz'))
# Whole case.
assert_equal(4, @dmp.diff_commonPrefix('1234', '1234xyz'))
end
def test_diff_commonSuffix
# Detect any common suffix.
# Null case.
assert_equal(0, @dmp.diff_commonSuffix('abc', 'xyz'))
# Non-null case.
assert_equal(4, @dmp.diff_commonSuffix('abcdef1234', 'xyz1234'))
# Whole case.
assert_equal(4, @dmp.diff_commonSuffix('1234', 'xyz1234'))
end
def test_diff_commonOverlap
# Detect any suffix/prefix overlap.
# Null case.
assert_equal(0, @dmp.diff_commonOverlap('', 'abcd'))
# Whole case.
assert_equal(3, @dmp.diff_commonOverlap('abc', 'abcd'))
# No overlap.
assert_equal(0, @dmp.diff_commonOverlap('123456', 'abcd'))
# Overlap.
assert_equal(3, @dmp.diff_commonOverlap('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_commonOverlap('fi', '\ufb01i'));
end
def test_diff_halfMatch
# Detect a halfmatch.
@dmp.diff_timeout = 1
# No match.
assert_equal(nil, @dmp.diff_halfMatch('1234567890', 'abcdef'))
assert_equal(nil, @dmp.diff_halfMatch('12345', '23'))
# Single Match.
assert_equal(
['12', '90', 'a', 'z', '345678'],
@dmp.diff_halfMatch('1234567890', 'a345678z')
)
assert_equal(
['a', 'z', '12', '90', '345678'],
@dmp.diff_halfMatch('a345678z', '1234567890')
)
assert_equal(
['abc', 'z', '1234', '0', '56789'],
@dmp.diff_halfMatch('abc56789z', '1234567890')
)
assert_equal(
['a', 'xyz', '1', '7890', '23456'],
@dmp.diff_halfMatch('a23456xyz', '1234567890')
)
# Multiple Matches.
assert_equal(
['12123', '123121', 'a', 'z', '1234123451234'],
@dmp.diff_halfMatch('121231234123451234123121', 'a1234123451234z')
)
assert_equal(
['', '-=-=-=-=-=', 'x', '', 'x-=-=-=-=-=-=-='],
@dmp.diff_halfMatch('x-=-=-=-=-=-=-=-=-=-=-=-=', 'xx-=-=-=-=-=-=-=')
)
assert_equal(
['-=-=-=-=-=', '', '', 'y', '-=-=-=-=-=-=-=y'],
@dmp.diff_halfMatch('-=-=-=-=-=-=-=-=-=-=-=-=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_halfMatch('qHilloHelloHew', 'xHelloHeHulloy')
)
# Optimal no halfmatch.
@dmp.diff_timeout = 0
assert_equal(nil, @dmp.diff_halfMatch('qHilloHelloHew', 'xHelloHeHulloy'))
end
def test_diff_linesToChars
# Convert lines down to characters.
assert_equal(
["\x01\x02\x01", "\x02\x01\x02", ['', "alpha\n", "beta\n"]],
@dmp.diff_linesToChars("alpha\nbeta\nalpha\n", "beta\nalpha\nbeta\n")
)
assert_equal(
['', "\x01\x02\x03\x03", ['', "alpha\r\n", "beta\r\n", "\r\n"]],
@dmp.diff_linesToChars('', "alpha\r\nbeta\r\n\r\n\r\n")
)
assert_equal(
["\x01", "\x02", ['', 'a', 'b']],
@dmp.diff_linesToChars('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_linesToChars(lines, ''))
end
def test_diff_charsToLines
# Convert chars up to lines.
diffs = [[:equal, "\x01\x02\x01"], [:insert, "\x02\x01\x02"]]
@dmp.diff_charsToLines(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_charsToLines(diffs, line_list)
assert_equal([[:delete, lines]], diffs)
end
def test_diff_cleanupMerge
# Cleanup a messy diff.
# Null case.
diffs = []
@dmp.diff_cleanupMerge(diffs)
assert_equal([], diffs)
# No change case.
diffs = [[:equal, 'a'], [:delete, 'b'], [:insert, 'c']]
@dmp.diff_cleanupMerge(diffs)
assert_equal([[:equal, 'a'], [:delete, 'b'], [:insert, 'c']], diffs)
# Merge equalities.
diffs = [[:equal, 'a'], [:equal, 'b'], [:equal, 'c']]
@dmp.diff_cleanupMerge(diffs)
assert_equal([[:equal, 'abc']], diffs)
# Merge deletions.
diffs = [[:delete, 'a'], [:delete, 'b'], [:delete, 'c']]
@dmp.diff_cleanupMerge(diffs)
assert_equal([[:delete, 'abc']], diffs)
# Merge insertions.
diffs = [[:insert, 'a'], [:insert, 'b'], [:insert, 'c']]
@dmp.diff_cleanupMerge(diffs)
assert_equal([[:insert, 'abc']], diffs)
# Merge interweave.
diffs = [
[:delete, 'a'], [:insert, 'b'], [:delete, 'c'],
[:insert, 'd'], [:equal, 'e'], [:equal, 'f']
]
@dmp.diff_cleanupMerge(diffs)
assert_equal([[:delete, 'ac'], [:insert, 'bd'], [:equal, 'ef']], diffs)
# Prefix and suffix detection.
diffs = [[:delete, 'a'], [:insert, 'abc'], [:delete, 'dc']]
@dmp.diff_cleanupMerge(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_cleanupMerge(diffs)
assert_equal(
[[:equal, 'xa'], [:delete, 'd'], [:insert, 'b'], [:equal, 'cy']],
diffs
)
# Slide edit left.
diffs = [[:equal, 'a'], [:insert, 'ba'], [:equal, 'c']]
@dmp.diff_cleanupMerge(diffs)
assert_equal([[:insert, 'ab'], [:equal, 'ac']], diffs)
# Slide edit right.
diffs = [[:equal, 'c'], [:insert, 'ab'], [:equal, 'a']]
@dmp.diff_cleanupMerge(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_cleanupMerge(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_cleanupMerge(diffs)
assert_equal([[:equal, 'xca'], [:delete, 'cba']], diffs)
end
def test_diff_cleanupSemanticLossless
# Slide diffs to match logical boundaries.
# Null case.
diffs = []
@dmp.diff_cleanupSemanticLossless(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_cleanupSemanticLossless(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_cleanupSemanticLossless(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_cleanupSemanticLossless(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_cleanupSemanticLossless(diffs)
assert_equal(
[[:equal, 'The-'], [:insert, 'cow-and-the-'], [:equal, 'cat.']],
diffs
)
# Hitting the start.
diffs = [[:equal, 'a'], [:delete, 'a'], [:equal, 'ax']]
@dmp.diff_cleanupSemanticLossless(diffs)
assert_equal([[:delete, 'a'], [:equal, 'aax']], diffs)
# Hitting the end.
diffs = [[:equal, 'xa'], [:delete, 'a'], [:equal, 'a']]
@dmp.diff_cleanupSemanticLossless(diffs)
assert_equal([[:equal, 'xaa'], [:delete, 'a']], diffs)
end
def test_diff_cleanupSemantic
# Cleanup semantically trivial equalities.
# Null case.
diffs = []
@dmp.diff_cleanupSemantic(diffs)
assert_equal([], diffs)
# No elimination #1.
diffs = [[:delete, 'ab'], [:insert, 'cd'], [:equal, '12'], [:delete, 'e']]
@dmp.diff_cleanupSemantic(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_cleanupSemantic(diffs)
assert_equal(
[[:delete, 'abc'], [:insert, 'ABC'], [:equal, '1234'], [:delete, 'wxyz']],
diffs
)
# Simple elimination.
diffs = [[:delete, 'a'], [:equal, 'b'], [:delete, 'c']]
@dmp.diff_cleanupSemantic(diffs)
assert_equal([[:delete, 'abc'], [:insert, 'b']], diffs)
# Backpass elimination.
diffs = [
[:delete, 'ab'], [:equal, 'cd'], [:delete, 'e'],
[:equal, 'f'], [:insert, 'g']
]
@dmp.diff_cleanupSemantic(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_cleanupSemantic(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_cleanupSemantic(diffs)
assert_equal(
[[:equal, 'The '], [:delete, 'cow and the '], [:equal, 'cat.']],
diffs
)
# No overlap elimination.
diffs =[[:delete, 'abcxx'],[:insert, 'xxdef']]
@dmp.diff_cleanupSemantic(diffs)
assert_equal([[:delete, 'abcxx'], [:insert, 'xxdef']], diffs)
# Overlap elimination.
diffs = [[:delete, 'abcxxx'], [:insert, 'xxxdef']]
@dmp.diff_cleanupSemantic(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_cleanupSemantic(diffs)
assert_equal([
[:delete, 'abcd'], [:equal, '1212'], [:insert, 'efghi'],
[:equal, '----'], [:delete, 'A'], [:equal, '3'], [:insert, 'BC']
],
diffs
)
end
def test_diff_cleanupEfficiency
# Cleanup operationally trivial equalities.
@dmp.diff_editCost = 4
# Null case.
diffs = []
@dmp.diff_cleanupEfficiency(diffs)
assert_equal([], diffs)
# No elimination.
diffs = [
[:delete, 'ab'], [:insert, '12'], [:equal, 'wxyz'],
[:delete, 'cd'], [:insert, '34']
]
@dmp.diff_cleanupEfficiency(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_cleanupEfficiency(diffs)
assert_equal([[:delete, 'abxyzcd'], [:insert, '12xyz34']], diffs)
# Three-edit elimination.
diffs = [[:insert, '12'], [:equal, 'x'], [:delete, 'cd'], [:insert, '34']]
@dmp.diff_cleanupEfficiency(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_cleanupEfficiency(diffs)
assert_equal([[:delete, 'abxyzcd'], [:insert, '12xy34z56']], diffs)
# High cost elimination.
@dmp.diff_editCost = 5
diffs = [
[:delete, 'ab'], [:insert, '12'], [:equal, 'wxyz'],
[:delete, 'cd'], [:insert, '34']
]
@dmp.diff_cleanupEfficiency(diffs)
assert_equal([[:delete, 'abwxyzcd'], [:insert, '12wxyz34']], diffs)
@dmp.diff_editCost = 4
end
def test_diff_prettyHtml
# Pretty print.
diffs = [[:equal, 'a\n'], [:delete, 'b'], [:insert, 'c&d']]
assert_equal(
'a¶
<B>' +
'b</B>c&d',
@dmp.diff_prettyHtml(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_toDelta(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_fromDelta(text1, delta))
# Generates error (19 != 20).
assert_raise ArgumentError do
@dmp.diff_fromDelta(text1 + 'x', delta)
end
# Generates error (19 != 18).
assert_raise ArgumentError do
@dmp.diff_fromDelta(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_toDelta(diffs)
assert_equal("=7\t-7\t+%DA%82 %02 %5C %7C", delta)
# Convert delta string into a diff.
assert_equal(diffs, @dmp.diff_fromDelta(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_toDelta(diffs)
assert_equal("+A-Z a-z 0-9 - _ . ! ~ * \' ( ) / ? : @ & = + $ , # ", delta)
# Convert delta string into a diff.
assert_equal(diffs, @dmp.diff_fromDelta('', delta))
end
def test_diff_xIndex
# Translate a location in text1 to text2.
# Translation on equality.
diffs = [[:delete, 'a'], [:insert, '1234'], [:equal, 'xyz']]
assert_equal(5, @dmp.diff_xIndex(diffs, 2))
# Translation on deletion.
diffs = [[:equal, 'a'], [:delete, '1234'], [:equal, 'xyz']]
assert_equal(1, @dmp.diff_xIndex(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 = a * 1024
b = 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_raise 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_raise 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_fromText
assert_equal([], @dmp.patch_fromText(""))
[
"@@ -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_fromText(strp).first.to_s)
end
# Generates error.
assert_raise ArgumentError do
@dmp.patch_fromText('Bad\nPatch\n')
end
end
def test_patch_toText
[
"@@ -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_fromText(strp)
assert_equal(strp, @dmp.patch_toText(p))
end
end
def test_patch_addContext
@dmp.patch_margin = 4
p = @dmp.patch_fromText("@@ -21,4 +21,10 @@\n-jump\n+somersault\n").first
@dmp.patch_addContext(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_fromText("@@ -21,4 +21,10 @@\n-jump\n+somersault\n").first
@dmp.patch_addContext(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_fromText("@@ -3 +3,2 @@\n-e\n+at\n").first
@dmp.patch_addContext(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_fromText("@@ -3 +3,2 @@\n-e\n+at\n").first
@dmp.patch_addContext(
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_toText(patches))
text1 = 'The quick brown fox jumps over the lazy dog.'
text2 = 'That quick brown fox jumped over a lazy dog.'
# Text2+Text1 inputs.
expectedPatch = "@@ -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(expectedPatch, @dmp.patch_toText(patches))
# Text1+Text2 inputs.
expectedPatch = "@@ -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(expectedPatch, @dmp.patch_toText(patches))
# Diff input.
diffs = @dmp.diff_main(text1, text2, false)
patches = @dmp.patch_make(diffs)
assert_equal(expectedPatch, @dmp.patch_toText(patches))
# Text1+Diff inputs.
patches = @dmp.patch_make(text1, diffs)
assert_equal(expectedPatch, @dmp.patch_toText(patches))
# Text1+Text2+Diff inputs (deprecated)
patches = @dmp.patch_make(text1, text2, diffs)
assert_equal(expectedPatch, @dmp.patch_toText(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_toText(patches)
)
# Character decoding.
diffs = [
[:delete, '`1234567890-=[]\\;\',./'],
[:insert, '~!@#$%^&*()_+{}|:"<>?']
]
assert_equal(
diffs,
@dmp.patch_fromText(
"@@ -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'
expectedPatch = "@@ -573,28 +573,31 @@\n cdefabcdefabcdefabcdefabcdef\n+123\n"
patches = @dmp.patch_make(text1, text2)
assert_equal(expectedPatch, @dmp.patch_toText(patches))
# Test null inputs.
assert_raise ArgumentError do
@dmp.patch_make(nil)
end
end
def test_patch_splitMax
# Assumes that dmp.Match_MaxBits is 32.
patches = @dmp.patch_make(
'abcdefghijklmnopqrstuvwxyz01234567890',
'XabXcdXefXghXijXklXmnXopXqrXstXuvXwxXyzX01X23X45X67X89X0'
)
@dmp.patch_splitMax(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_toText(patches)
)
patches = @dmp.patch_make(
'abcdef1234567890123456789012345678901234567890' +
'123456789012345678901234567890uvwxyz',
'abcdefuvwxyz'
)
oldToText = @dmp.patch_toText(patches)
@dmp.patch_splitMax(patches)
assert_equal(oldToText, @dmp.patch_toText(patches))
patches = @dmp.patch_make(
'1234567890123456789012345678901234567890123456789012345678901234567890',
'abc'
)
@dmp.patch_splitMax(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_toText(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_splitMax(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_toText(patches)
)
end
def test_patch_addPadding
# Both edges full.
patches = @dmp.patch_make('', 'test')
assert_equal("@@ -0,0 +1,4 @@\n+test\n", @dmp.patch_toText(patches))
@dmp.patch_addPadding(patches)
assert_equal(
"@@ -1,8 +1,12 @@\n %01%02%03%04\n+test\n %01%02%03%04\n",
@dmp.patch_toText(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_toText(patches))
@dmp.patch_addPadding(patches)
assert_equal(
"@@ -2,8 +2,12 @@\n %02%03%04X\n+test\n Y%01%02%03\n",
@dmp.patch_toText(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_toText(patches)
)
@dmp.patch_addPadding(patches)
assert_equal(
"@@ -5,8 +5,12 @@\n XXXX\n+test\n YYYY\n",
@dmp.patch_toText(patches)
)
end
def test_patch_apply
@dmp.match_distance = 1000
@dmp.match_threshold = 0.5
@dmp.patch_deleteThreshold = 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_deleteThreshold = 0.6
patches = @dmp.patch_make(
'x1234567890123456789012345678901234567890123456789012345678901234567890y',
'xabcy'
)
results = @dmp.patch_apply(
patches,
'x12345678901234567890---------------++++++++++---------------' +
'12345678901234567890y'
)
assert_equal(['xabcy', [true, true]], results)
@dmp.patch_deleteThreshold = 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_toText(patches)
@dmp.patch_apply(patches, '')
assert_equal(patchstr, @dmp.patch_toText(patches))
# No side effects with major delete.
patches = @dmp.patch_make(
'The quick brown fox jumps over the lazy dog.',
'Woof'
)
patchstr = @dmp.patch_toText(patches)
@dmp.patch_apply(patches, 'The quick brown fox jumps over the lazy dog.')
assert_equal(patchstr, @dmp.patch_toText(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