test-simplemerge.py
415 lines
| 14.7 KiB
| text/x-python
|
PythonLexer
/ tests / test-simplemerge.py
|
r4362 | # Copyright (C) 2004, 2005 Canonical Ltd | ||
# | ||||
# This program is free software; you can redistribute it and/or modify | ||||
# it under the terms of the GNU General Public License as published by | ||||
# the Free Software Foundation; either version 2 of the License, or | ||||
# (at your option) any later version. | ||||
# | ||||
# This program is distributed in the hope that it will be useful, | ||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
# GNU General Public License for more details. | ||||
# | ||||
# You should have received a copy of the GNU General Public License | ||||
# along with this program; if not, write to the Free Software | ||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||||
|
r4363 | import unittest | ||
from unittest import TestCase | ||||
|
r6002 | from mercurial import util, simplemerge | ||
# bzr compatible interface, for the tests | ||||
class Merge3(simplemerge.Merge3Text): | ||||
"""3-way merge of texts. | ||||
|
r4362 | |||
|
r6002 | Given BASE, OTHER, THIS, tries to produce a combined text | ||
incorporating the changes from both BASE->OTHER and BASE->THIS. | ||||
All three will typically be sequences of lines.""" | ||||
def __init__(self, base, a, b): | ||||
basetext = '\n'.join([i.strip('\n') for i in base] + ['']) | ||||
atext = '\n'.join([i.strip('\n') for i in a] + ['']) | ||||
btext = '\n'.join([i.strip('\n') for i in b] + ['']) | ||||
if util.binary(basetext) or util.binary(atext) or util.binary(btext): | ||||
raise util.Abort("don't know how to merge binary files") | ||||
simplemerge.Merge3Text.__init__(self, basetext, atext, btext, | ||||
base, a, b) | ||||
|
r4363 | CantReprocessAndShowBase = simplemerge.CantReprocessAndShowBase | ||
|
r4362 | |||
def split_lines(t): | ||||
from cStringIO import StringIO | ||||
return StringIO(t).readlines() | ||||
############################################################ | ||||
# test case data from the gnu diffutils manual | ||||
# common base | ||||
TZU = split_lines(""" The Nameless is the origin of Heaven and Earth; | ||||
The named is the mother of all things. | ||||
|
r4516 | |||
|
r4362 | Therefore let there always be non-being, | ||
so we may see their subtlety, | ||||
And let there always be being, | ||||
so we may see their outcome. | ||||
The two are the same, | ||||
But after they are produced, | ||||
they have different names. | ||||
They both may be called deep and profound. | ||||
Deeper and more profound, | ||||
The door of all subtleties! | ||||
""") | ||||
LAO = split_lines(""" The Way that can be told of is not the eternal Way; | ||||
The name that can be named is not the eternal name. | ||||
The Nameless is the origin of Heaven and Earth; | ||||
The Named is the mother of all things. | ||||
Therefore let there always be non-being, | ||||
so we may see their subtlety, | ||||
And let there always be being, | ||||
so we may see their outcome. | ||||
The two are the same, | ||||
But after they are produced, | ||||
they have different names. | ||||
""") | ||||
TAO = split_lines(""" The Way that can be told of is not the eternal Way; | ||||
The name that can be named is not the eternal name. | ||||
The Nameless is the origin of Heaven and Earth; | ||||
The named is the mother of all things. | ||||
|
r4516 | |||
|
r4362 | Therefore let there always be non-being, | ||
so we may see their subtlety, | ||||
And let there always be being, | ||||
so we may see their result. | ||||
The two are the same, | ||||
But after they are produced, | ||||
they have different names. | ||||
|
r4516 | |||
|
r4362 | -- The Way of Lao-Tzu, tr. Wing-tsit Chan | ||
""") | ||||
MERGED_RESULT = split_lines(""" The Way that can be told of is not the eternal Way; | ||||
The name that can be named is not the eternal name. | ||||
The Nameless is the origin of Heaven and Earth; | ||||
The Named is the mother of all things. | ||||
Therefore let there always be non-being, | ||||
so we may see their subtlety, | ||||
And let there always be being, | ||||
so we may see their result. | ||||
The two are the same, | ||||
But after they are produced, | ||||
they have different names. | ||||
<<<<<<< LAO | ||||
======= | ||||
|
r4516 | |||
|
r4362 | -- The Way of Lao-Tzu, tr. Wing-tsit Chan | ||
>>>>>>> TAO | ||||
""") | ||||
class TestMerge3(TestCase): | ||||
|
r4363 | def log(self, msg): | ||
pass | ||||
|
r4362 | |||
def test_no_changes(self): | ||||
"""No conflicts because nothing changed""" | ||||
m3 = Merge3(['aaa', 'bbb'], | ||||
['aaa', 'bbb'], | ||||
['aaa', 'bbb']) | ||||
self.assertEquals(m3.find_unconflicted(), | ||||
[(0, 2)]) | ||||
self.assertEquals(list(m3.find_sync_regions()), | ||||
[(0, 2, | ||||
0, 2, | ||||
0, 2), | ||||
(2,2, 2,2, 2,2)]) | ||||
self.assertEquals(list(m3.merge_regions()), | ||||
[('unchanged', 0, 2)]) | ||||
self.assertEquals(list(m3.merge_groups()), | ||||
[('unchanged', ['aaa', 'bbb'])]) | ||||
def test_front_insert(self): | ||||
m3 = Merge3(['zz'], | ||||
['aaa', 'bbb', 'zz'], | ||||
['zz']) | ||||
# todo: should use a sentinal at end as from get_matching_blocks | ||||
# to match without zz | ||||
self.assertEquals(list(m3.find_sync_regions()), | ||||
[(0,1, 2,3, 0,1), | ||||
(1,1, 3,3, 1,1),]) | ||||
self.assertEquals(list(m3.merge_regions()), | ||||
[('a', 0, 2), | ||||
('unchanged', 0, 1)]) | ||||
self.assertEquals(list(m3.merge_groups()), | ||||
[('a', ['aaa', 'bbb']), | ||||
('unchanged', ['zz'])]) | ||||
|
r4516 | |||
|
r4362 | def test_null_insert(self): | ||
m3 = Merge3([], | ||||
['aaa', 'bbb'], | ||||
[]) | ||||
# todo: should use a sentinal at end as from get_matching_blocks | ||||
# to match without zz | ||||
self.assertEquals(list(m3.find_sync_regions()), | ||||
[(0,0, 2,2, 0,0)]) | ||||
self.assertEquals(list(m3.merge_regions()), | ||||
[('a', 0, 2)]) | ||||
self.assertEquals(list(m3.merge_lines()), | ||||
['aaa', 'bbb']) | ||||
def test_no_conflicts(self): | ||||
"""No conflicts because only one side changed""" | ||||
m3 = Merge3(['aaa', 'bbb'], | ||||
['aaa', '111', 'bbb'], | ||||
['aaa', 'bbb']) | ||||
self.assertEquals(m3.find_unconflicted(), | ||||
[(0, 1), (1, 2)]) | ||||
self.assertEquals(list(m3.find_sync_regions()), | ||||
[(0,1, 0,1, 0,1), | ||||
(1,2, 2,3, 1,2), | ||||
(2,2, 3,3, 2,2),]) | ||||
self.assertEquals(list(m3.merge_regions()), | ||||
[('unchanged', 0, 1), | ||||
('a', 1, 2), | ||||
('unchanged', 1, 2),]) | ||||
def test_append_a(self): | ||||
m3 = Merge3(['aaa\n', 'bbb\n'], | ||||
['aaa\n', 'bbb\n', '222\n'], | ||||
['aaa\n', 'bbb\n']) | ||||
self.assertEquals(''.join(m3.merge_lines()), | ||||
'aaa\nbbb\n222\n') | ||||
def test_append_b(self): | ||||
m3 = Merge3(['aaa\n', 'bbb\n'], | ||||
['aaa\n', 'bbb\n'], | ||||
['aaa\n', 'bbb\n', '222\n']) | ||||
self.assertEquals(''.join(m3.merge_lines()), | ||||
'aaa\nbbb\n222\n') | ||||
def test_append_agreement(self): | ||||
m3 = Merge3(['aaa\n', 'bbb\n'], | ||||
['aaa\n', 'bbb\n', '222\n'], | ||||
['aaa\n', 'bbb\n', '222\n']) | ||||
self.assertEquals(''.join(m3.merge_lines()), | ||||
'aaa\nbbb\n222\n') | ||||
def test_append_clash(self): | ||||
m3 = Merge3(['aaa\n', 'bbb\n'], | ||||
['aaa\n', 'bbb\n', '222\n'], | ||||
['aaa\n', 'bbb\n', '333\n']) | ||||
ml = m3.merge_lines(name_a='a', | ||||
name_b='b', | ||||
start_marker='<<', | ||||
mid_marker='--', | ||||
end_marker='>>') | ||||
self.assertEquals(''.join(ml), | ||||
|
r4633 | 'aaa\n' | ||
'bbb\n' | ||||
'<< a\n' | ||||
'222\n' | ||||
'--\n' | ||||
'333\n' | ||||
'>> b\n' | ||||
) | ||||
|
r4362 | |||
def test_insert_agreement(self): | ||||
m3 = Merge3(['aaa\n', 'bbb\n'], | ||||
['aaa\n', '222\n', 'bbb\n'], | ||||
['aaa\n', '222\n', 'bbb\n']) | ||||
ml = m3.merge_lines(name_a='a', | ||||
name_b='b', | ||||
start_marker='<<', | ||||
mid_marker='--', | ||||
end_marker='>>') | ||||
self.assertEquals(''.join(ml), 'aaa\n222\nbbb\n') | ||||
|
r4516 | |||
|
r4362 | |||
def test_insert_clash(self): | ||||
"""Both try to insert lines in the same place.""" | ||||
m3 = Merge3(['aaa\n', 'bbb\n'], | ||||
['aaa\n', '111\n', 'bbb\n'], | ||||
['aaa\n', '222\n', 'bbb\n']) | ||||
self.assertEquals(m3.find_unconflicted(), | ||||
[(0, 1), (1, 2)]) | ||||
self.assertEquals(list(m3.find_sync_regions()), | ||||
[(0,1, 0,1, 0,1), | ||||
(1,2, 2,3, 2,3), | ||||
(2,2, 3,3, 3,3),]) | ||||
self.assertEquals(list(m3.merge_regions()), | ||||
[('unchanged', 0,1), | ||||
('conflict', 1,1, 1,2, 1,2), | ||||
('unchanged', 1,2)]) | ||||
self.assertEquals(list(m3.merge_groups()), | ||||
[('unchanged', ['aaa\n']), | ||||
('conflict', [], ['111\n'], ['222\n']), | ||||
('unchanged', ['bbb\n']), | ||||
]) | ||||
ml = m3.merge_lines(name_a='a', | ||||
name_b='b', | ||||
start_marker='<<', | ||||
mid_marker='--', | ||||
end_marker='>>') | ||||
self.assertEquals(''.join(ml), | ||||
'''aaa | ||||
<< a | ||||
111 | ||||
-- | ||||
222 | ||||
>> b | ||||
bbb | ||||
''') | ||||
def test_replace_clash(self): | ||||
"""Both try to insert lines in the same place.""" | ||||
m3 = Merge3(['aaa', '000', 'bbb'], | ||||
['aaa', '111', 'bbb'], | ||||
['aaa', '222', 'bbb']) | ||||
self.assertEquals(m3.find_unconflicted(), | ||||
[(0, 1), (2, 3)]) | ||||
self.assertEquals(list(m3.find_sync_regions()), | ||||
[(0,1, 0,1, 0,1), | ||||
(2,3, 2,3, 2,3), | ||||
(3,3, 3,3, 3,3),]) | ||||
def test_replace_multi(self): | ||||
"""Replacement with regions of different size.""" | ||||
m3 = Merge3(['aaa', '000', '000', 'bbb'], | ||||
['aaa', '111', '111', '111', 'bbb'], | ||||
['aaa', '222', '222', '222', '222', 'bbb']) | ||||
self.assertEquals(m3.find_unconflicted(), | ||||
[(0, 1), (3, 4)]) | ||||
self.assertEquals(list(m3.find_sync_regions()), | ||||
[(0,1, 0,1, 0,1), | ||||
(3,4, 4,5, 5,6), | ||||
(4,4, 5,5, 6,6),]) | ||||
def test_merge_poem(self): | ||||
"""Test case from diff3 manual""" | ||||
m3 = Merge3(TZU, LAO, TAO) | ||||
ml = list(m3.merge_lines('LAO', 'TAO')) | ||||
self.log('merge result:') | ||||
self.log(''.join(ml)) | ||||
self.assertEquals(ml, MERGED_RESULT) | ||||
def test_minimal_conflicts_common(self): | ||||
"""Reprocessing""" | ||||
base_text = ("a\n" * 20).splitlines(True) | ||||
this_text = ("a\n"*10+"b\n" * 10).splitlines(True) | ||||
other_text = ("a\n"*10+"c\n"+"b\n" * 8 + "c\n").splitlines(True) | ||||
m3 = Merge3(base_text, other_text, this_text) | ||||
m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True) | ||||
merged_text = "".join(list(m_lines)) | ||||
|
r4363 | optimal_text = ("a\n" * 10 + "<<<<<<< OTHER\nc\n=======\n" | ||
+ ">>>>>>> THIS\n" | ||||
+ 8* "b\n" + "<<<<<<< OTHER\nc\n=======\n" | ||||
+ 2* "b\n" + ">>>>>>> THIS\n") | ||||
self.assertEquals(optimal_text, merged_text) | ||||
|
r4362 | |||
def test_minimal_conflicts_unique(self): | ||||
def add_newline(s): | ||||
"""Add a newline to each entry in the string""" | ||||
return [(x+'\n') for x in s] | ||||
base_text = add_newline("abcdefghijklm") | ||||
this_text = add_newline("abcdefghijklmNOPQRSTUVWXYZ") | ||||
other_text = add_newline("abcdefghijklm1OPQRSTUVWXY2") | ||||
m3 = Merge3(base_text, other_text, this_text) | ||||
m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True) | ||||
merged_text = "".join(list(m_lines)) | ||||
optimal_text = ''.join(add_newline("abcdefghijklm") | ||||
+ ["<<<<<<< OTHER\n1\n=======\nN\n>>>>>>> THIS\n"] | ||||
+ add_newline('OPQRSTUVWXY') | ||||
+ ["<<<<<<< OTHER\n2\n=======\nZ\n>>>>>>> THIS\n"] | ||||
) | ||||
|
r4363 | self.assertEquals(optimal_text, merged_text) | ||
|
r4362 | |||
def test_minimal_conflicts_nonunique(self): | ||||
def add_newline(s): | ||||
"""Add a newline to each entry in the string""" | ||||
return [(x+'\n') for x in s] | ||||
base_text = add_newline("abacddefgghij") | ||||
this_text = add_newline("abacddefgghijkalmontfprz") | ||||
other_text = add_newline("abacddefgghijknlmontfprd") | ||||
m3 = Merge3(base_text, other_text, this_text) | ||||
m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True) | ||||
merged_text = "".join(list(m_lines)) | ||||
optimal_text = ''.join(add_newline("abacddefgghijk") | ||||
+ ["<<<<<<< OTHER\nn\n=======\na\n>>>>>>> THIS\n"] | ||||
+ add_newline('lmontfpr') | ||||
+ ["<<<<<<< OTHER\nd\n=======\nz\n>>>>>>> THIS\n"] | ||||
) | ||||
|
r4363 | self.assertEquals(optimal_text, merged_text) | ||
|
r4362 | |||
def test_reprocess_and_base(self): | ||||
"""Reprocessing and showing base breaks correctly""" | ||||
base_text = ("a\n" * 20).splitlines(True) | ||||
this_text = ("a\n"*10+"b\n" * 10).splitlines(True) | ||||
other_text = ("a\n"*10+"c\n"+"b\n" * 8 + "c\n").splitlines(True) | ||||
m3 = Merge3(base_text, other_text, this_text) | ||||
|
r4516 | m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True, | ||
|
r4362 | base_marker='|||||||') | ||
self.assertRaises(CantReprocessAndShowBase, list, m_lines) | ||||
def test_binary(self): | ||||
|
r4363 | self.assertRaises(util.Abort, Merge3, ['\x00'], ['a'], ['b']) | ||
|
r4362 | |||
def test_dos_text(self): | ||||
base_text = 'a\r\n' | ||||
this_text = 'b\r\n' | ||||
other_text = 'c\r\n' | ||||
m3 = Merge3(base_text.splitlines(True), other_text.splitlines(True), | ||||
this_text.splitlines(True)) | ||||
m_lines = m3.merge_lines('OTHER', 'THIS') | ||||
self.assertEqual('<<<<<<< OTHER\r\nc\r\n=======\r\nb\r\n' | ||||
'>>>>>>> THIS\r\n'.splitlines(True), list(m_lines)) | ||||
def test_mac_text(self): | ||||
base_text = 'a\r' | ||||
this_text = 'b\r' | ||||
other_text = 'c\r' | ||||
m3 = Merge3(base_text.splitlines(True), other_text.splitlines(True), | ||||
this_text.splitlines(True)) | ||||
m_lines = m3.merge_lines('OTHER', 'THIS') | ||||
self.assertEqual('<<<<<<< OTHER\rc\r=======\rb\r' | ||||
'>>>>>>> THIS\r'.splitlines(True), list(m_lines)) | ||||
|
r4363 | |||
if __name__ == '__main__': | ||||
# hide the timer | ||||
import time | ||||
orig = time.time | ||||
try: | ||||
time.time = lambda: 0 | ||||
unittest.main() | ||||
finally: | ||||
time.time = orig | ||||