--[[ * Test Harness for Diff Match and Patch * * Copyright 2006 Google Inc. * http://code.google.com/p/google-diff-match-patch/ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. --]] local dmp = require 'diff_match_patch' local DIFF_INSERT = dmp.DIFF_INSERT local DIFF_DELETE = dmp.DIFF_DELETE local DIFF_EQUAL = dmp.DIFF_EQUAL -- Utility functions local function pretty(v) if (type(v) == 'string') then return string.format('%q', v):gsub('\\\n','\\n') elseif (type(v) == 'table') then local str = {} local next_i = 1 for i,v in pairs(v) do if (i == next_i) then next_i = next_i + 1 str[#str+1] = pretty(v) else str[#str+1] = '[' .. pretty(i) .. ']=' .. pretty(v) end end return '{'..table.concat(str,',')..'}' else return tostring(v) end end function assertEquals(...) local msg, expected, actual if (select('#', ...) == 2) then expected, actual = ... msg = 'Expected: \'' .. pretty(expected) .. '\' Actual: \'' .. pretty(actual) .. '\'' else msg, expected, actual = ... end assert(expected == actual, msg) end -- If expected and actual are the equivalent, pass the test. function assertEquivalent(...) local msg, expected, actual expected, actual = ... msg = 'Expected: \'' .. pretty(expected) .. '\' Actual: \'' .. pretty(actual) .. '\'' if (_equivalent(expected, actual)) then assertEquals(msg, pretty(expected), pretty(actual)) else assertEquals(msg, expected, actual) end end -- Are a and b the equivalent? -- Recursive. function _equivalent(a, b) if (a == b) then return true end if (type(a) == 'table') and (type(b) == 'table') then for k,v in pairs(a) do if not _equivalent(v, b[k]) then return false end end for k,v in pairs(b) do if not _equivalent(v, a[k]) then return false end end return true end return false end function diff_rebuildtexts(diffs) -- Construct the two texts which made up the diff originally. local text1, text2 = {}, {} for x,diff in ipairs(diffs) do local op, data = diff[1], diff[2] if (op ~= DIFF_INSERT) then text1[#text1+1] = data end if (op ~= DIFF_DELETE) then text2[#text2+1] = data end end return table.concat(text1), table.concat(text2) end -- DIFF TEST FUNCTIONS function testDiffCommonPrefix() -- Detect and remove any common prefix. -- Null case. assertEquals(0, dmp.diff_commonPrefix('abc', 'xyz')) -- Non-null case. assertEquals(4, dmp.diff_commonPrefix('1234abcdef', '1234xyz')) -- Whole case. assertEquals(4, dmp.diff_commonPrefix('1234', '1234xyz')) end function testDiffCommonSuffix() -- Detect and remove any common suffix. -- Null case. assertEquals(0, dmp.diff_commonSuffix('abc', 'xyz')) -- Non-null case. assertEquals(4, dmp.diff_commonSuffix('abcdef1234', 'xyz1234')) -- Whole case. assertEquals(4, dmp.diff_commonSuffix('1234', 'xyz1234')) end function testDiffHalfMatch() -- Detect a halfmatch. -- No match. assertEquivalent({nil}, {dmp.diff_halfMatch('1234567890', 'abcdef')}) -- Single Match. assertEquivalent({'12', '90', 'a', 'z', '345678'}, {dmp.diff_halfMatch('1234567890', 'a345678z')}) assertEquivalent({'a', 'z', '12', '90', '345678'}, {dmp.diff_halfMatch('a345678z', '1234567890')}) -- Multiple Matches. assertEquivalent({'12123', '123121', 'a', 'z', '1234123451234'}, {dmp.diff_halfMatch('121231234123451234123121', 'a1234123451234z')}) assertEquivalent({'', '-=-=-=-=-=', 'x', '', 'x-=-=-=-=-=-=-='}, {dmp.diff_halfMatch('x-=-=-=-=-=-=-=-=-=-=-=-=', 'xx-=-=-=-=-=-=-=')}) assertEquivalent({'-=-=-=-=-=', '', '', 'y', '-=-=-=-=-=-=-=y'}, {dmp.diff_halfMatch('-=-=-=-=-=-=-=-=-=-=-=-=y', '-=-=-=-=-=-=-=yy')}) end function testDiffToLines() -- Convert lines down to index arrays. assertEquivalent({{1,2,1}, {2,1,2}, {'alpha\n', 'beta\n'}}, {dmp.diff_toLines('alpha\nbeta\nalpha\n', 'beta\nalpha\nbeta\n')}) assertEquivalent({{}, {1,2,3,3}, {'alpha\r\n', 'beta\r\n', '\r\n'}}, {dmp.diff_toLines('', 'alpha\r\nbeta\r\n\r\n\r\n')}) assertEquivalent({{1}, {2}, {'a', 'b'}}, {dmp.diff_toLines('a', 'b')}) local n = 300 local lineList = {} local lineIndexList = {} for x = 1,n do lineList[x] = x..'\n' lineIndexList[x] = x end assertEquals(n, #lineList) local lines = table.concat(lineList) assertEquivalent({lineIndexList, {}, lineList}, {dmp.diff_toLines(lines, '')}) end function testDiffFromLines() -- Convert chars up to lines. local diffs diffs = {{DIFF_EQUAL, {1,2,1}}, {DIFF_INSERT, {2,1,2}}} dmp.diff_fromLines(diffs, {'alpha\n', 'beta\n'}) assertEquivalent( {{DIFF_EQUAL, 'alpha\nbeta\nalpha\n'},{DIFF_INSERT, 'beta\nalpha\nbeta\n'}}, diffs) local n = 300 local lineList = {} local lineIndexList = {} for x = 1,n do lineList[x] = x .. '\n' lineIndexList[x] = x end local lines = table.concat(lineList) diffs = {{DIFF_DELETE, lineIndexList}} dmp.diff_fromLines(diffs, lineList) assertEquivalent({{DIFF_DELETE, lines}}, diffs) end function testDiffCleanupMerge() -- Cleanup a messy diff. -- Null case. local diffs = {} dmp.diff_cleanupMerge(diffs) assertEquivalent({}, diffs) -- No change case. diffs = {{DIFF_EQUAL, 'a'}, {DIFF_DELETE, 'b'}, {DIFF_INSERT, 'c'}} dmp.diff_cleanupMerge(diffs) assertEquivalent({{DIFF_EQUAL, 'a'}, {DIFF_DELETE, 'b'}, {DIFF_INSERT, 'c'}}, diffs) -- Merge equalities. diffs = {{DIFF_EQUAL, 'a'}, {DIFF_EQUAL, 'b'}, {DIFF_EQUAL, 'c'}} dmp.diff_cleanupMerge(diffs) assertEquivalent({{DIFF_EQUAL, 'abc'}}, diffs) -- Merge deletions. diffs = {{DIFF_DELETE, 'a'}, {DIFF_DELETE, 'b'}, {DIFF_DELETE, 'c'}} dmp.diff_cleanupMerge(diffs) assertEquivalent({{DIFF_DELETE, 'abc'}}, diffs) -- Merge insertions. diffs = {{DIFF_INSERT, 'a'},{DIFF_INSERT, 'b'},{DIFF_INSERT, 'c'}} dmp.diff_cleanupMerge(diffs) assertEquivalent({{DIFF_INSERT, 'abc'}}, diffs) -- Merge interweave. diffs = {{DIFF_DELETE,'a'}, {DIFF_INSERT,'b'}, {DIFF_DELETE,'c'}, {DIFF_INSERT,'d'}, {DIFF_EQUAL,'e'}, {DIFF_EQUAL,'f'}} dmp.diff_cleanupMerge(diffs) assertEquivalent({{DIFF_DELETE, 'ac'},{DIFF_INSERT, 'bd'},{DIFF_EQUAL, 'ef'}}, diffs) -- Prefix and suffix detection. diffs = {{DIFF_DELETE,'a'}, {DIFF_INSERT,'abc'}, {DIFF_DELETE,'dc'}} dmp.diff_cleanupMerge(diffs) assertEquivalent({{DIFF_EQUAL,'a'}, {DIFF_DELETE,'d'}, {DIFF_INSERT,'b'}, {DIFF_EQUAL,'c'}}, diffs) -- Slide edit left. diffs = {{DIFF_EQUAL,'a'}, {DIFF_INSERT,'ba'}, {DIFF_EQUAL, 'c'}} dmp.diff_cleanupMerge(diffs) assertEquivalent({{DIFF_INSERT,'ab'}, {DIFF_EQUAL,'ac'}}, diffs) -- Slide edit right. diffs = {{DIFF_EQUAL, 'c'}, {DIFF_INSERT, 'ab'}, {DIFF_EQUAL, 'a'}} dmp.diff_cleanupMerge(diffs) assertEquivalent({{DIFF_EQUAL, 'ca'},{DIFF_INSERT, 'ba'}}, diffs) -- Slide edit left recursive. diffs = {{DIFF_EQUAL, 'a'},{DIFF_DELETE, 'b'}, {DIFF_EQUAL, 'c'}, {DIFF_DELETE, 'ac'}, {DIFF_EQUAL, 'x'}} dmp.diff_cleanupMerge(diffs) assertEquivalent({{DIFF_DELETE, 'abc'},{DIFF_EQUAL, 'acx'}}, diffs) -- Slide edit right recursive. diffs = {{DIFF_EQUAL, 'x'},{DIFF_DELETE, 'ca'},{DIFF_EQUAL, 'c'}, {DIFF_DELETE, 'b'}, {DIFF_EQUAL, 'a'}} dmp.diff_cleanupMerge(diffs) assertEquivalent({{DIFF_EQUAL, 'xca'},{DIFF_DELETE, 'cba'}}, diffs) end function testDiffCleanupSemanticLossless() -- Slide diffs to match logical boundaries. -- Null case. local diffs = {} dmp.diff_cleanupSemanticLossless(diffs) assertEquivalent({}, diffs) -- Blank lines. diffs = {{DIFF_EQUAL, 'AAA\r\n\r\nBBB'}, {DIFF_INSERT, '\r\nDDD\r\n\r\nBBB'}, {DIFF_EQUAL, '\r\nEEE'}} dmp.diff_cleanupSemanticLossless(diffs) assertEquivalent({{DIFF_EQUAL, 'AAA\r\n\r\n'}, {DIFF_INSERT, 'BBB\r\nDDD\r\n\r\n'}, {DIFF_EQUAL, 'BBB\r\nEEE'}}, diffs) -- Line boundaries. diffs = {{DIFF_EQUAL, 'AAA\r\nBBB'},{DIFF_INSERT, ' DDD\r\nBBB'}, {DIFF_EQUAL, ' EEE'}} dmp.diff_cleanupSemanticLossless(diffs) assertEquivalent({{DIFF_EQUAL, 'AAA\r\n'}, {DIFF_INSERT, 'BBB DDD\r\n'}, {DIFF_EQUAL, 'BBB EEE'}}, diffs) -- Word boundaries. diffs = {{DIFF_EQUAL,'The c'},{DIFF_INSERT,'ow and the c'}, {DIFF_EQUAL, 'at.'}} dmp.diff_cleanupSemanticLossless(diffs) assertEquivalent({{DIFF_EQUAL, 'The '},{DIFF_INSERT, 'cow and the '}, {DIFF_EQUAL, 'cat.'}}, diffs) -- Alphanumeric boundaries. diffs = {{DIFF_EQUAL, 'The-c'},{DIFF_INSERT, 'ow-and-the-c'}, {DIFF_EQUAL, 'at.'}} dmp.diff_cleanupSemanticLossless(diffs) assertEquivalent({{DIFF_EQUAL, 'The-'}, {DIFF_INSERT, 'cow-and-the-'}, {DIFF_EQUAL, 'cat.'}}, diffs) -- Hitting the start. diffs = {{DIFF_EQUAL, 'a'},{DIFF_DELETE, 'a'},{DIFF_EQUAL, 'ax'}} dmp.diff_cleanupSemanticLossless(diffs) assertEquivalent({{DIFF_DELETE, 'a'},{DIFF_EQUAL, 'aax'}}, diffs) -- Hitting the end. diffs = {{DIFF_EQUAL, 'xa'},{DIFF_DELETE, 'a'},{DIFF_EQUAL, 'a'}} dmp.diff_cleanupSemanticLossless(diffs) assertEquivalent({{DIFF_EQUAL, 'xaa'},{DIFF_DELETE, 'a'}}, diffs) end function testDiffCleanupSemantic() -- Cleanup semantically trivial equalities. -- Null case. local diffs = {} dmp.diff_cleanupSemantic(diffs) assertEquivalent({}, diffs) -- No elimination. diffs = {{DIFF_DELETE,'a'}, {DIFF_INSERT,'b'},{DIFF_EQUAL, 'cd'}, {DIFF_DELETE, 'e'}} dmp.diff_cleanupSemantic(diffs) assertEquivalent({{DIFF_DELETE, 'a'}, {DIFF_INSERT, 'b'}, {DIFF_EQUAL, 'cd'}, {DIFF_DELETE, 'e'}}, diffs) -- Simple elimination. diffs = {{DIFF_DELETE, 'a'}, {DIFF_EQUAL, 'b'}, {DIFF_DELETE, 'c'}} dmp.diff_cleanupSemantic(diffs) assertEquivalent({{DIFF_DELETE, 'abc'},{DIFF_INSERT, 'b'}}, diffs) -- Backpass elimination. diffs = {{DIFF_DELETE, 'ab'}, {DIFF_EQUAL, 'cd'}, {DIFF_DELETE, 'e'}, {DIFF_EQUAL, 'f'}, {DIFF_INSERT, 'g'}} dmp.diff_cleanupSemantic(diffs) assertEquivalent({{DIFF_DELETE, 'abcdef'}, {DIFF_INSERT, 'cdfg'}}, diffs) -- Multiple eliminations. diffs = {{DIFF_INSERT, '1'},{DIFF_EQUAL, 'A'},{DIFF_DELETE, 'B'}, {DIFF_INSERT, '2'}, {DIFF_EQUAL, '_'}, {DIFF_INSERT, '1'}, {DIFF_EQUAL, 'A'}, {DIFF_DELETE, 'B'}, {DIFF_INSERT, '2'}} dmp.diff_cleanupSemantic(diffs) assertEquivalent({{DIFF_DELETE, 'AB_AB'}, {DIFF_INSERT, '1A2_1A2'}}, diffs) -- Word boundaries. diffs = {{DIFF_EQUAL, 'The c'},{DIFF_DELETE, 'ow and the c'}, {DIFF_EQUAL, 'at.'}} dmp.diff_cleanupSemantic(diffs) assertEquivalent({{DIFF_EQUAL, 'The '}, {DIFF_DELETE, 'cow and the '}, {DIFF_EQUAL, 'cat.'}}, diffs) end function testDiffCleanupEfficiency() -- Cleanup operationally trivial equalities. local diffs dmp.settings{Diff_EditCost = 4} -- Null case. diffs = {} dmp.diff_cleanupEfficiency(diffs) assertEquivalent({}, diffs) -- No elimination. diffs = {{DIFF_DELETE, 'ab'},{DIFF_INSERT, '12'},{DIFF_EQUAL, 'wxyz'}, {DIFF_DELETE, 'cd'},{DIFF_INSERT, '34'}} dmp.diff_cleanupEfficiency(diffs) assertEquivalent({{DIFF_DELETE, 'ab'},{DIFF_INSERT, '12'}, {DIFF_EQUAL, 'wxyz'},{DIFF_DELETE, 'cd'},{DIFF_INSERT, '34'}}, diffs) -- Four-edit elimination. diffs = {{DIFF_DELETE, 'ab'},{DIFF_INSERT, '12'},{DIFF_EQUAL, 'xyz'}, {DIFF_DELETE, 'cd'}, {DIFF_INSERT, '34'}} dmp.diff_cleanupEfficiency(diffs) assertEquivalent({ {DIFF_DELETE, 'abxyzcd'}, {DIFF_INSERT, '12xyz34'} }, diffs) -- Three-edit elimination. diffs = { {DIFF_INSERT, '12'}, {DIFF_EQUAL, 'x'}, {DIFF_DELETE, 'cd'}, {DIFF_INSERT, '34'} } dmp.diff_cleanupEfficiency(diffs) assertEquivalent({ {DIFF_DELETE, 'xcd'}, {DIFF_INSERT, '12x34'} }, diffs) -- Backpass elimination. diffs = { {DIFF_DELETE, 'ab'}, {DIFF_INSERT, '12'}, {DIFF_EQUAL, 'xy'}, {DIFF_INSERT, '34'}, {DIFF_EQUAL, 'z'}, {DIFF_DELETE, 'cd'}, {DIFF_INSERT, '56'} } dmp.diff_cleanupEfficiency(diffs) assertEquivalent({ {DIFF_DELETE, 'abxyzcd'}, {DIFF_INSERT, '12xy34z56'} }, diffs) -- High cost elimination. dmp.settings{Diff_EditCost = 5} diffs = { {DIFF_DELETE, 'ab'}, {DIFF_INSERT, '12'}, {DIFF_EQUAL, 'wxyz'}, {DIFF_DELETE, 'cd'}, {DIFF_INSERT, '34'} } dmp.diff_cleanupEfficiency(diffs) assertEquivalent({ {DIFF_DELETE, 'abwxyzcd'}, {DIFF_INSERT, '12wxyz34'} }, diffs) dmp.settings{Diff_EditCost = 4} end function testDiffPrettyHtml() -- Pretty print. local diffs = { {DIFF_EQUAL, 'a\n'}, {DIFF_DELETE, 'b'}, {DIFF_INSERT, 'c&d'} } assertEquals( '
' .. '<B>b</B>' .. 'c&d', dmp.diff_prettyHtml(diffs) ) end function testDiffText() -- Compute the source and destination texts. local diffs = { {DIFF_EQUAL, 'jump'}, {DIFF_DELETE, 's'}, {DIFF_INSERT, 'ed'}, {DIFF_EQUAL, ' over '}, {DIFF_DELETE, 'the'}, {DIFF_INSERT, 'a'}, {DIFF_EQUAL, ' lazy'} } assertEquals('jumps over the lazy', dmp.diff_text1(diffs)) assertEquals('jumped over a lazy', dmp.diff_text2(diffs)) end function testDiffDelta() -- Convert a diff into delta string. local diffs = { {DIFF_EQUAL, 'jump'}, {DIFF_DELETE, 's'}, {DIFF_INSERT, 'ed'}, {DIFF_EQUAL, ' over '}, {DIFF_DELETE, 'the'}, {DIFF_INSERT, 'a'}, {DIFF_EQUAL, ' lazy'}, {DIFF_INSERT, 'old dog'} } local text1 = dmp.diff_text1(diffs) assertEquals('jumps over the lazy', text1) local delta = dmp.diff_toDelta(diffs) assertEquals('=4\t-1\t+ed\t=6\t-3\t+a\t=5\t+old dog', delta) -- Convert delta string into a diff. assertEquivalent(diffs, dmp.diff_fromDelta(text1, delta)) --[[ -- Generates error (19 ~= 20). try { assertEquivalent(Error, dmp.diff_fromDelta(text1 + 'x', delta)) end catch (e) { assertEquivalent(nil, nil) end -- Generates error (19 ~= 18). try { assertEquivalent(Error, dmp.diff_fromDelta(text1.substring(1), delta)) end catch (e) { assertEquivalent(nil, nil) end -- Generates error (%c3%xy invalid Unicode). try { assertEquivalent(Error, dmp.diff_fromDelta('', '+%c3%xy')) end catch (e) { assertEquivalent(nil, nil) end -- Test deltas with special characters. diffs = {{DIFF_EQUAL, '\u0680 \000 \t %'}, {DIFF_DELETE, '\u0681 \x01 \n ^'}, {DIFF_INSERT, '\u0682 \x02 \\ |'}} text1 = dmp.diff_text1(diffs) assertEquals('\u0680 \x00 \t %\u0681 \x01 \n ^', text1) delta = dmp.diff_toDelta(diffs) assertEquals('=7\t-7\t+%DA%82 %02 %5C %7C', delta) --]] -- Convert delta string into a diff. assertEquivalent(diffs, dmp.diff_fromDelta(text1, delta)) -- Verify pool of unchanged characters. diffs = { {DIFF_INSERT, 'A-Z a-z 0-9 - _ . ! ~ * \' ( ) ; / ? = @ & = + $ , # '} } local text2 = dmp.diff_text2(diffs) assertEquals( 'A-Z a-z 0-9 - _ . ! ~ * \' ( ) ; / ? = @ & = + $ , # ', text2 ) delta = dmp.diff_toDelta(diffs) assertEquals( '+A-Z a-z 0-9 - _ . ! ~ * \' ( ) ; / ? = @ & = + $ , # ', delta ) -- Convert delta string into a diff. assertEquivalent(diffs, dmp.diff_fromDelta('', delta)) end function testDiffXIndex() -- Translate a location in text1 to text2. -- Translation on equality. assertEquals(6, dmp.diff_xIndex({ {DIFF_DELETE, 'a'}, {DIFF_INSERT, '1234'}, {DIFF_EQUAL, 'xyz'} }, 3)) -- Translation on deletion. assertEquals(2, dmp.diff_xIndex({ {DIFF_EQUAL, 'a'}, {DIFF_DELETE, '1234'}, {DIFF_EQUAL, 'xyz'} }, 4)) end function testDiffLevenshtein() -- Levenshtein with trailing equality. assertEquals(4, dmp.diff_levenshtein({ {DIFF_DELETE, 'abc'}, {DIFF_INSERT, '1234'}, {DIFF_EQUAL, 'xyz'} })) -- Levenshtein with leading equality. assertEquals(4, dmp.diff_levenshtein({ {DIFF_EQUAL, 'xyz'}, {DIFF_DELETE, 'abc'}, {DIFF_INSERT, '1234'} })) -- Levenshtein with middle equality. assertEquals(7, dmp.diff_levenshtein({ {DIFF_DELETE, 'abc'}, {DIFF_EQUAL, 'xyz'}, {DIFF_INSERT, '1234'} })) end function testDiffPath() -- Single letters. -- Trace a path from back to front. local v_map = { {['1,1']=true}; {['1,2']=true, ['2,1']=true}; {['1,3']=true, ['3,1']=true, ['3,3']=true}; {['1,4']=true, ['3,4']=true, ['4,1']=true, ['5,4']=true}; {['1,5']=true, ['3,5']=true, ['5,1']=true, ['5,5']=true, ['6,4']=true}, {['1,6']=true, ['3,6']=true, ['5,6']=true, ['6,1']=true, ['7,4']=true, ['7,6']=true}; {['1,7']=true, ['3,7']=true, ['5,7']=true, ['7,7']=true, ['8,6']=true}; } assertEquivalent({ {DIFF_INSERT, 'W'}, {DIFF_DELETE, 'A'}, {DIFF_EQUAL, '1'}, {DIFF_DELETE, 'B'}, {DIFF_EQUAL, '2'}, {DIFF_INSERT, 'X'}, {DIFF_DELETE, 'C'}, {DIFF_EQUAL, '3'}, {DIFF_DELETE, 'D'} }, dmp.diff_path1(v_map, 'A1B2C3D', 'W12X3')) -- Trace a path from front to back. v_map[#v_map] = nil assertEquivalent({ {DIFF_EQUAL, '4'}, {DIFF_DELETE, 'E'}, {DIFF_INSERT, 'Y'}, {DIFF_EQUAL, '5'}, {DIFF_DELETE, 'F'}, {DIFF_EQUAL, '6'}, {DIFF_DELETE, 'G'}, {DIFF_INSERT, 'Z'} }, dmp.diff_path2(v_map, '4E5F6G', '4Y56Z')) -- Double letters -- Trace a path from back to front. v_map = { {['1,1']=true}; {['1,2']=true, ['2,1']=true}; {['1,3']=true, ['2,2']=true, ['3,1']=true}; {['1,4']=true, ['2,3']=true, ['3,2']=true, ['4,1']=true}; {['1,5']=true, ['2,4']=true, ['4,2']=true, ['5,1']=true, ['5,5']=true}; } assertEquivalent({ {DIFF_INSERT, 'WX'}, {DIFF_DELETE, 'AB'}, {DIFF_EQUAL, '12'} }, dmp.diff_path1(v_map, 'AB12', 'WX12')) -- Trace a path from front to back. v_map = { {['1,1']=true}; {['1,2']=true, ['2,1']=true}; {['2,2']=true, ['3,1']=true, ['3,5']=true}; {['3,2']=true, ['3,6']=true, ['4,1']=true, ['4,5']=true}; {['3,7']=true, ['4,6']=true, ['5,5']=true}; } assertEquivalent({ {DIFF_DELETE, 'CD'}, {DIFF_EQUAL, '34'}, {DIFF_INSERT, 'YZ'} }, dmp.diff_path2(v_map, 'CD34', '34YZ')) end function testDiffMain() -- Perform a trivial diff. local a,b -- Null case. assertEquivalent({ {DIFF_EQUAL, 'abc'} }, dmp.diff_main('abc', 'abc', false)) -- Simple insertion. assertEquivalent({ {DIFF_EQUAL, 'ab'}, {DIFF_INSERT, '123'}, {DIFF_EQUAL, 'c'} }, dmp.diff_main('abc', 'ab123c', false)) -- Simple deletion. assertEquivalent({ {DIFF_EQUAL, 'a'}, {DIFF_DELETE, '123'}, {DIFF_EQUAL, 'bc'} }, dmp.diff_main('a123bc', 'abc', false)) -- Two insertions. assertEquivalent({ {DIFF_EQUAL, 'a'}, {DIFF_INSERT, '123'}, {DIFF_EQUAL, 'b'}, {DIFF_INSERT, '456'}, {DIFF_EQUAL, 'c'} }, dmp.diff_main('abc', 'a123b456c', false)) -- Two deletions. assertEquivalent({ {DIFF_EQUAL, 'a'}, {DIFF_DELETE, '123'}, {DIFF_EQUAL, 'b'}, {DIFF_DELETE, '456'}, {DIFF_EQUAL, 'c'} }, dmp.diff_main('a123b456c', 'abc', false)) -- Perform a real diff. -- Switch off the timeout. --]] dmp.settings{ Diff_Timeout=0, Diff_DualThreshold=32 } -- Simple cases. assertEquivalent({ {DIFF_DELETE, 'a'}, {DIFF_INSERT, 'b'} }, dmp.diff_main('a', 'b', false)) assertEquivalent({ {DIFF_DELETE, 'Apple'}, {DIFF_INSERT, 'Banana'}, {DIFF_EQUAL, 's are a'}, {DIFF_INSERT, 'lso'}, {DIFF_EQUAL, ' fruit.'} }, dmp.diff_main('Apples are a fruit.', 'Bananas are also fruit.', false)) assertEquivalent({ {DIFF_DELETE, 'a'}, {DIFF_INSERT, '\u0680'}, {DIFF_EQUAL, 'x'}, {DIFF_DELETE, '\t'}, {DIFF_INSERT, '\0'} }, dmp.diff_main('ax\t', '\u0680x\0', false)) -- Overlaps. assertEquivalent({ {DIFF_DELETE, '1'}, {DIFF_EQUAL, 'a'}, {DIFF_DELETE, 'y'}, {DIFF_EQUAL, 'b'}, {DIFF_DELETE, '2'}, {DIFF_INSERT, 'xab'} }, dmp.diff_main('1ayb2', 'abxab', false)) assertEquivalent({ {DIFF_INSERT, 'xaxcx'}, {DIFF_EQUAL, 'abc'}, {DIFF_DELETE, 'y'} }, dmp.diff_main('abcy', 'xaxcxabc', false)) --]] -- Sub-optimal double-ended diff. dmp.settings{Diff_DualThreshold = 2} assertEquivalent({ {DIFF_INSERT, 'x'}, {DIFF_EQUAL, 'a'}, {DIFF_DELETE, 'b'}, {DIFF_INSERT, 'x'}, {DIFF_EQUAL, 'c'}, {DIFF_DELETE, 'y'}, {DIFF_INSERT, 'xabc'} }, dmp.diff_main('abcy', 'xaxcxabc', false)) dmp.settings{Diff_DualThreshold = 32} -- Timeout. dmp.settings{Diff_Timeout = 0.001} -- 1ms a = string.rep([[ `Twas brillig, and the slithy toves Did gyre and gimble in the wabe= All mimsy were the borogoves, And the mome raths outgrabe. ]], 11) b = string.rep([[ I am the very model of a modern major general, I've information vegetable, animal, and mineral, I know the kings of England, and I quote the fights historical, From Marathon to Waterloo, in order categorical. ]], 11) assertEquals(nil, dmp.diff_map(a,b)) dmp.settings{Diff_Timeout = 0} -- Test the linemode speedup. -- Must be long to pass the 200 char cutoff. a = string.rep('1234567890\n', 13) b = string.rep('abcdefghij\n', 13) assertEquivalent(dmp.diff_main(a,b,false), dmp.diff_main(a,b,true)) a = string.rep('1234567890\n', 13) b = [[ abcdefghij 1234567890 1234567890 1234567890 abcdefghij 1234567890 1234567890 1234567890 abcdefghij 1234567890 1234567890 1234567890 abcdefghij ]] local texts_linemode = diff_rebuildtexts(dmp.diff_main(a, b, true)) local texts_textmode = diff_rebuildtexts(dmp.diff_main(a, b, false)) assertEquivalent(texts_textmode, texts_linemode) end -- MATCH TEST FUNCTIONS function testMatchAlphabet() -- Initialise the bitmasks for Bitap. -- Unique. assertEquivalent({a=4, b=2, c=1}, dmp.match_alphabet('abc')) -- Duplicates. assertEquivalent({a=37, b=18, c=8}, dmp.match_alphabet('abcaba')) end function testMatchBitap() -- Bitap algorithm. dmp.settings{Match_Distance=100, Match_Threshold=0.5} -- Exact matches. assertEquals(6, dmp.match_bitap('abcdefghijk', 'fgh', 6)) assertEquals(6, dmp.match_bitap('abcdefghijk', 'fgh', 1)) -- Fuzzy matches. assertEquals(5, dmp.match_bitap('abcdefghijk', 'efxhi', 1)) assertEquals(3, dmp.match_bitap('abcdefghijk', 'cdefxyhijk', 6)) assertEquals(nil, dmp.match_bitap('abcdefghijk', 'bxy', 2)) -- Overflow. assertEquals(3, dmp.match_bitap('123456789xx0', '3456789x0', 3)) -- Threshold test. dmp.settings{Match_Threshold = 0.4} assertEquals(5, dmp.match_bitap('abcdefghijk', 'efxyhi', 2)) dmp.settings{Match_Threshold = 0.3} assertEquals(nil, dmp.match_bitap('abcdefghijk', 'efxyhi', 2)) dmp.settings{Match_Threshold = 0.0} assertEquals(2, dmp.match_bitap('abcdefghijk', 'bcdef', 2)) dmp.settings{Match_Threshold = 0.5} -- Multiple select. assertEquals(1, dmp.match_bitap('abcdexyzabcde', 'abccde', 4)) assertEquals(9, dmp.match_bitap('abcdexyzabcde', 'abccde', 6)) -- Distance test. dmp.settings{Match_Distance = 10} -- Strict location. assertEquals(nil, dmp.match_bitap('abcdefghijklmnopqrstuvwxyz', 'abcdefg', 25)) assertEquals(1, dmp.match_bitap('abcdefghijklmnopqrstuvwxyz', 'abcdxxefg', 2)) dmp.settings{Match_Distance = 1000} -- Loose location. assertEquals(1, dmp.match_bitap('abcdefghijklmnopqrstuvwxyz', 'abcdefg', 25)) end function testMatchMain() -- Full match. -- Shortcut matches. assertEquals(1, dmp.match_main('abcdef', 'abcdef', 1000)) assertEquals(nil, dmp.match_main('', 'abcdef', 2)) assertEquals(4, dmp.match_main('abcdef', '', 4)) assertEquals(4, dmp.match_main('abcdef', 'de', 4)) -- Beyond end match. assertEquals(4, dmp.match_main("abcdef", "defy", 5)) -- Oversized pattern. assertEquals(1, dmp.match_main("abcdef", "abcdefy", 1)) -- Complex match. assertEquals(5, dmp.match_main( 'I am the very model of a modern major general.', ' that berry ', 6 )) end -- PATCH TEST FUNCTIONS function testPatchObj() -- Patch Object. local p = dmp.new_patch_obj() p.start1 = 21 p.start2 = 22 p.length1 = 18 p.length2 = 17 p.diffs = { {DIFF_EQUAL, 'jump'}, {DIFF_DELETE, 's'}, {DIFF_INSERT, 'ed'}, {DIFF_EQUAL, ' over '}, {DIFF_DELETE, 'the'}, {DIFF_INSERT, 'a'}, {DIFF_EQUAL, '\nlaz'} } local strp = tostring(p) assertEquals( '@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n %0Alaz\n', strp ) end function testPatchFromText() local strp strp = '' assertEquivalent({}, dmp.patch_fromText(strp)) strp = '@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n %0Alaz\n' assertEquals(strp, tostring(dmp.patch_fromText(strp)[1])) assertEquals( '@@ -1 +1 @@\n-a\n+b\n', tostring(dmp.patch_fromText('@@ -1 +1 @@\n-a\n+b\n')[1]) ) assertEquals( '@@ -1,3 +0,0 @@\n-abc\n', tostring(dmp.patch_fromText('@@ -1,3 +0,0 @@\n-abc\n')[1]) ) assertEquals( '@@ -0,0 +1,3 @@\n+abc\n', tostring(dmp.patch_fromText('@@ -0,0 +1,3 @@\n+abc\n')[1]) ) --[[ -- Generates error. try { assertEquivalent(Error, dmp.patch_fromText('Bad\nPatch\n')) end catch (e) { assertEquivalent(nil, nil) end --]] end function testPatchToText() local strp, p strp = '@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n laz\n' p = dmp.patch_fromText(strp) assertEquals(strp, dmp.patch_toText(p)) strp = '@@ -1,9 +1,9 @@\n-f\n+F\n oo+fooba\n' .. '@@ -7,9 +7,9 @@\n obar\n-,\n+.\n tes\n' p = dmp.patch_fromText(strp) assertEquals(strp, dmp.patch_toText(p)) end function testPatchAddContext() local p dmp.settings{Patch_Margin = 4} p = dmp.patch_fromText('@@ -21,4 +21,10 @@\n-jump\n+somersault\n')[1] dmp.patch_addContext(p, 'The quick brown fox jumps over the lazy dog.') assertEquals( '@@ -17,12 +17,18 @@\n fox \n-jump\n+somersault\n s ov\n', tostring(p) ) -- Same, but not enough trailing context. p = dmp.patch_fromText('@@ -21,4 +21,10 @@\n-jump\n+somersault\n')[1] dmp.patch_addContext(p, 'The quick brown fox jumps.') assertEquals( '@@ -17,10 +17,16 @@\n fox \n-jump\n+somersault\n s.\n', tostring(p) ) -- Same, but not enough leading context. p = dmp.patch_fromText('@@ -3 +3,2 @@\n-e\n+at\n')[1] dmp.patch_addContext(p, 'The quick brown fox jumps.') assertEquals('@@ -1,7 +1,8 @@\n Th\n-e\n+at\n qui\n', tostring(p)) -- Same, but with ambiguity. p = dmp.patch_fromText('@@ -3 +3,2 @@\n-e\n+at\n')[1] dmp.patch_addContext(p, 'The quick brown fox jumps. The quick brown fox crashes.') assertEquals('@@ -1,27 +1,28 @@\n Th\n-e\n+at\n quick brown fox jumps. \n', tostring(p)) end function testPatchMake() local text1 = 'The quick brown fox jumps over the lazy dog.' local text2 = 'That quick brown fox jumped over a lazy dog.' -- Text2+Text1 inputs. local 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. local patches = dmp.patch_make(text2, text1) assertEquals(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) assertEquals(expectedPatch, dmp.patch_toText(patches)) -- Diff input. local diffs = dmp.diff_main(text1, text2, false) patches = dmp.patch_make(diffs) assertEquals(expectedPatch, dmp.patch_toText(patches)) -- Text1+Diff inputs. patches = dmp.patch_make(text1, diffs) assertEquals(expectedPatch, dmp.patch_toText(patches)) -- Text1+Text2+Diff inputs (deprecated). patches = dmp.patch_make(text1, text2, diffs) assertEquals(expectedPatch, dmp.patch_toText(patches)) -- Character encoding. patches = dmp.patch_make('`1234567890-=[]\\;\',./', '~!@#$%^&*()_+{}|="<>?') assertEquals('@@ -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 = { {DIFF_DELETE, '`1234567890-=[]\\;\',./'}, {DIFF_INSERT, '~!@#$%^&*()_+{}|="<>?'} } assertEquivalent(diffs, dmp.patch_fromText( '@@ -1,21 +1,21 @@' .. '\n-%601234567890-=%5B%5D%5C;\',./' .. '\n+~!@#$%25%5E&*()_+%7B%7D%7C=%22%3C%3E?\n' )[1].diffs) -- Long string with repeats. text1 = string.rep('abcdef', 100) text2 = text1 .. '123' expectedPatch = '@@ -573,28 +573,31 @@\n' .. ' cdefabcdefabcdefabcdefabcdef\n+123\n' patches = dmp.patch_make(text1, text2) assertEquals(expectedPatch, dmp.patch_toText(patches)) end function testPatchSplitMax() -- Assumes that dmp.Match_MaxBits is 32. local patches = dmp.patch_make('abcdefghijklmnopqrstuvwxyz01234567890', 'XabXcdXefXghXijXklXmnXopXqrXstXuvXwxXyzX01X23X45X67X89X0') dmp.patch_splitMax(patches) assertEquals('@@ -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('abcdef1234567890123456789012345678901234567890123456789012345678901234567890uvwxyz', 'abcdefuvwxyz') local oldToText = dmp.patch_toText(patches) dmp.patch_splitMax(patches) assertEquals(oldToText, dmp.patch_toText(patches)) patches = dmp.patch_make('1234567890123456789012345678901234567890123456789012345678901234567890', 'abc') dmp.patch_splitMax(patches) assertEquals('@@ -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) assertEquals('@@ -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 function testPatchAddPadding() -- Both edges full. local patches = dmp.patch_make('', 'test') assertEquals('@@ -0,0 +1,4 @@\n+test\n', dmp.patch_toText(patches)) dmp.patch_addPadding(patches) assertEquals('@@ -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') assertEquals('@@ -1,2 +1,6 @@\n X\n+test\n Y\n', dmp.patch_toText(patches)) dmp.patch_addPadding(patches) assertEquals('@@ -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') assertEquals('@@ -1,8 +1,12 @@\n XXXX\n+test\n YYYY\n', dmp.patch_toText(patches)) dmp.patch_addPadding(patches) assertEquals('@@ -5,8 +5,12 @@\n XXXX\n+test\n YYYY\n', dmp.patch_toText(patches)) end function testPatchApply() local patches dmp.settings{Match_Distance = 1000} dmp.settings{Match_Threshold = 0.5} dmp.settings{Patch_DeleteThreshold = 0.5} -- Exact match. patches = dmp.patch_make('The quick brown fox jumps over the lazy dog.', 'That quick brown fox jumped over a lazy dog.') assertEquivalent( {'That quick brown fox jumped over a lazy dog.',{true,true}}, {dmp.patch_apply(patches,'The quick brown fox jumps over the lazy dog.')}) -- Partial match. assertEquivalent( {'That quick red rabbit jumped over a tired tiger.',{true,true}}, {dmp.patch_apply(patches,'The quick red rabbit jumps over the tired tiger.')}) -- Failed match. assertEquivalent( {'I am the very model of a modern major general.', {false, false}}, {dmp.patch_apply(patches, 'I am the very model of a modern major general.')}) -- Big delete, small change. patches = dmp.patch_make( 'x1234567890123456789012345678901234567890123456789012345678901234567890y', 'xabcy') assertEquivalent({'xabcy', {true, true}}, {dmp.patch_apply(patches, 'x123456789012345678901234567890-----++++++++++-----' .. '123456789012345678901234567890y')}) -- Big delete, big change 1. patches = dmp.patch_make('x1234567890123456789012345678901234567890123456789' .. '012345678901234567890y', 'xabcy') assertEquivalent({'xabc12345678901234567890' .. '---------------++++++++++---------------' .. '12345678901234567890y', {false, true}}, {dmp.patch_apply(patches,'x12345678901234567890' .. '---------------++++++++++---------------' .. '12345678901234567890y' )}) -- Big delete, big change 2. dmp.settings{Patch_DeleteThreshold = 0.6} patches = dmp.patch_make( 'x1234567890123456789012345678901234567890123456789' .. '012345678901234567890y', 'xabcy' ) assertEquivalent({'xabcy', {true, true}}, {dmp.patch_apply( patches, 'x12345678901234567890---------------++++++++++---------------' .. '12345678901234567890y' )} ) dmp.settings{Patch_DeleteThreshold = 0.5} -- Compensate for failed patch. dmp.settings{Match_Threshold = 0, Match_Distance = 0} patches = dmp.patch_make( 'abcdefghijklmnopqrstuvwxyz--------------------1234567890', 'abcXXXXXXXXXXdefghijklmnopqrstuvwxyz--------------------' .. '1234567YYYYYYYYYY890' ) assertEquivalent({ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ--------------------1234567YYYYYYYYYY890', {false, true} }, {dmp.patch_apply( patches, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ--------------------1234567890' )}) dmp.settings{Match_Threshold = 0.5} dmp.settings{Match_Distance = 1000} -- No side effects. patches = dmp.patch_make('', 'test') local patchstr = dmp.patch_toText(patches) dmp.patch_apply(patches, '') assertEquals(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.') assertEquals(patchstr, dmp.patch_toText(patches)) -- Edge exact match. patches = dmp.patch_make('', 'test') assertEquivalent({'test', {true}}, {dmp.patch_apply(patches, '')}) -- Near edge exact match. patches = dmp.patch_make('XY', 'XtestY') assertEquivalent({'XtestY', {true}}, {dmp.patch_apply(patches, 'XY')}) -- Edge partial match. patches = dmp.patch_make('y', 'y123') assertEquivalent({'x123', {true}}, {dmp.patch_apply(patches, 'x')}) end