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( '
<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