tests/test-simplemerge.py
changeset 4358 465b9ea02868
child 4359 2e3c54fb79a3
equal deleted inserted replaced
4357:99c853a1408c 4358:465b9ea02868
       
     1 # Copyright (C) 2004, 2005 Canonical Ltd
       
     2 #
       
     3 # This program is free software; you can redistribute it and/or modify
       
     4 # it under the terms of the GNU General Public License as published by
       
     5 # the Free Software Foundation; either version 2 of the License, or
       
     6 # (at your option) any later version.
       
     7 #
       
     8 # This program is distributed in the hope that it will be useful,
       
     9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       
    11 # GNU General Public License for more details.
       
    12 #
       
    13 # You should have received a copy of the GNU General Public License
       
    14 # along with this program; if not, write to the Free Software
       
    15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
       
    16 
       
    17 
       
    18 from bzrlib.tests import TestCaseInTempDir, TestCase
       
    19 from bzrlib.merge3 import Merge3
       
    20 from bzrlib.errors import CantReprocessAndShowBase, BinaryFile
       
    21 
       
    22 def split_lines(t):
       
    23     from cStringIO import StringIO
       
    24     return StringIO(t).readlines()
       
    25 
       
    26 ############################################################
       
    27 # test case data from the gnu diffutils manual
       
    28 # common base
       
    29 TZU = split_lines("""     The Nameless is the origin of Heaven and Earth;
       
    30      The named is the mother of all things.
       
    31      
       
    32      Therefore let there always be non-being,
       
    33        so we may see their subtlety,
       
    34      And let there always be being,
       
    35        so we may see their outcome.
       
    36      The two are the same,
       
    37      But after they are produced,
       
    38        they have different names.
       
    39      They both may be called deep and profound.
       
    40      Deeper and more profound,
       
    41      The door of all subtleties!
       
    42 """)
       
    43 
       
    44 LAO = split_lines("""     The Way that can be told of is not the eternal Way;
       
    45      The name that can be named is not the eternal name.
       
    46      The Nameless is the origin of Heaven and Earth;
       
    47      The Named is the mother of all things.
       
    48      Therefore let there always be non-being,
       
    49        so we may see their subtlety,
       
    50      And let there always be being,
       
    51        so we may see their outcome.
       
    52      The two are the same,
       
    53      But after they are produced,
       
    54        they have different names.
       
    55 """)
       
    56 
       
    57 
       
    58 TAO = split_lines("""     The Way that can be told of is not the eternal Way;
       
    59      The name that can be named is not the eternal name.
       
    60      The Nameless is the origin of Heaven and Earth;
       
    61      The named is the mother of all things.
       
    62      
       
    63      Therefore let there always be non-being,
       
    64        so we may see their subtlety,
       
    65      And let there always be being,
       
    66        so we may see their result.
       
    67      The two are the same,
       
    68      But after they are produced,
       
    69        they have different names.
       
    70      
       
    71        -- The Way of Lao-Tzu, tr. Wing-tsit Chan
       
    72 
       
    73 """)
       
    74 
       
    75 MERGED_RESULT = split_lines("""     The Way that can be told of is not the eternal Way;
       
    76      The name that can be named is not the eternal name.
       
    77      The Nameless is the origin of Heaven and Earth;
       
    78      The Named is the mother of all things.
       
    79      Therefore let there always be non-being,
       
    80        so we may see their subtlety,
       
    81      And let there always be being,
       
    82        so we may see their result.
       
    83      The two are the same,
       
    84      But after they are produced,
       
    85        they have different names.
       
    86 <<<<<<< LAO
       
    87 =======
       
    88      
       
    89        -- The Way of Lao-Tzu, tr. Wing-tsit Chan
       
    90 
       
    91 >>>>>>> TAO
       
    92 """)
       
    93 
       
    94 class TestMerge3(TestCase):
       
    95 
       
    96     def test_no_changes(self):
       
    97         """No conflicts because nothing changed"""
       
    98         m3 = Merge3(['aaa', 'bbb'],
       
    99                     ['aaa', 'bbb'],
       
   100                     ['aaa', 'bbb'])
       
   101 
       
   102         self.assertEquals(m3.find_unconflicted(),
       
   103                           [(0, 2)])
       
   104 
       
   105         self.assertEquals(list(m3.find_sync_regions()),
       
   106                           [(0, 2,
       
   107                             0, 2,
       
   108                             0, 2),
       
   109                            (2,2, 2,2, 2,2)])
       
   110 
       
   111         self.assertEquals(list(m3.merge_regions()),
       
   112                           [('unchanged', 0, 2)])
       
   113 
       
   114         self.assertEquals(list(m3.merge_groups()),
       
   115                           [('unchanged', ['aaa', 'bbb'])])
       
   116 
       
   117     def test_front_insert(self):
       
   118         m3 = Merge3(['zz'],
       
   119                     ['aaa', 'bbb', 'zz'],
       
   120                     ['zz'])
       
   121 
       
   122         # todo: should use a sentinal at end as from get_matching_blocks
       
   123         # to match without zz
       
   124         self.assertEquals(list(m3.find_sync_regions()),
       
   125                           [(0,1, 2,3, 0,1),
       
   126                            (1,1, 3,3, 1,1),])
       
   127 
       
   128         self.assertEquals(list(m3.merge_regions()),
       
   129                           [('a', 0, 2),
       
   130                            ('unchanged', 0, 1)])
       
   131 
       
   132         self.assertEquals(list(m3.merge_groups()),
       
   133                           [('a', ['aaa', 'bbb']),
       
   134                            ('unchanged', ['zz'])])
       
   135         
       
   136     def test_null_insert(self):
       
   137         m3 = Merge3([],
       
   138                     ['aaa', 'bbb'],
       
   139                     [])
       
   140         # todo: should use a sentinal at end as from get_matching_blocks
       
   141         # to match without zz
       
   142         self.assertEquals(list(m3.find_sync_regions()),
       
   143                           [(0,0, 2,2, 0,0)])
       
   144 
       
   145         self.assertEquals(list(m3.merge_regions()),
       
   146                           [('a', 0, 2)])
       
   147 
       
   148         self.assertEquals(list(m3.merge_lines()),
       
   149                           ['aaa', 'bbb'])
       
   150 
       
   151     def test_no_conflicts(self):
       
   152         """No conflicts because only one side changed"""
       
   153         m3 = Merge3(['aaa', 'bbb'],
       
   154                     ['aaa', '111', 'bbb'],
       
   155                     ['aaa', 'bbb'])
       
   156 
       
   157         self.assertEquals(m3.find_unconflicted(),
       
   158                           [(0, 1), (1, 2)])
       
   159 
       
   160         self.assertEquals(list(m3.find_sync_regions()),
       
   161                           [(0,1, 0,1, 0,1),
       
   162                            (1,2, 2,3, 1,2),
       
   163                            (2,2, 3,3, 2,2),])
       
   164 
       
   165         self.assertEquals(list(m3.merge_regions()),
       
   166                           [('unchanged', 0, 1),
       
   167                            ('a', 1, 2),
       
   168                            ('unchanged', 1, 2),])
       
   169 
       
   170     def test_append_a(self):
       
   171         m3 = Merge3(['aaa\n', 'bbb\n'],
       
   172                     ['aaa\n', 'bbb\n', '222\n'],
       
   173                     ['aaa\n', 'bbb\n'])
       
   174 
       
   175         self.assertEquals(''.join(m3.merge_lines()),
       
   176                           'aaa\nbbb\n222\n')
       
   177 
       
   178     def test_append_b(self):
       
   179         m3 = Merge3(['aaa\n', 'bbb\n'],
       
   180                     ['aaa\n', 'bbb\n'],
       
   181                     ['aaa\n', 'bbb\n', '222\n'])
       
   182 
       
   183         self.assertEquals(''.join(m3.merge_lines()),
       
   184                           'aaa\nbbb\n222\n')
       
   185 
       
   186     def test_append_agreement(self):
       
   187         m3 = Merge3(['aaa\n', 'bbb\n'],
       
   188                     ['aaa\n', 'bbb\n', '222\n'],
       
   189                     ['aaa\n', 'bbb\n', '222\n'])
       
   190 
       
   191         self.assertEquals(''.join(m3.merge_lines()),
       
   192                           'aaa\nbbb\n222\n')
       
   193 
       
   194     def test_append_clash(self):
       
   195         m3 = Merge3(['aaa\n', 'bbb\n'],
       
   196                     ['aaa\n', 'bbb\n', '222\n'],
       
   197                     ['aaa\n', 'bbb\n', '333\n'])
       
   198 
       
   199         ml = m3.merge_lines(name_a='a',
       
   200                             name_b='b',
       
   201                             start_marker='<<',
       
   202                             mid_marker='--',
       
   203                             end_marker='>>')
       
   204         self.assertEquals(''.join(ml),
       
   205 '''\
       
   206 aaa
       
   207 bbb
       
   208 << a
       
   209 222
       
   210 --
       
   211 333
       
   212 >> b
       
   213 ''')
       
   214 
       
   215     def test_insert_agreement(self):
       
   216         m3 = Merge3(['aaa\n', 'bbb\n'],
       
   217                     ['aaa\n', '222\n', 'bbb\n'],
       
   218                     ['aaa\n', '222\n', 'bbb\n'])
       
   219 
       
   220         ml = m3.merge_lines(name_a='a',
       
   221                             name_b='b',
       
   222                             start_marker='<<',
       
   223                             mid_marker='--',
       
   224                             end_marker='>>')
       
   225         self.assertEquals(''.join(ml), 'aaa\n222\nbbb\n')
       
   226         
       
   227 
       
   228     def test_insert_clash(self):
       
   229         """Both try to insert lines in the same place."""
       
   230         m3 = Merge3(['aaa\n', 'bbb\n'],
       
   231                     ['aaa\n', '111\n', 'bbb\n'],
       
   232                     ['aaa\n', '222\n', 'bbb\n'])
       
   233 
       
   234         self.assertEquals(m3.find_unconflicted(),
       
   235                           [(0, 1), (1, 2)])
       
   236 
       
   237         self.assertEquals(list(m3.find_sync_regions()),
       
   238                           [(0,1, 0,1, 0,1),
       
   239                            (1,2, 2,3, 2,3),
       
   240                            (2,2, 3,3, 3,3),])
       
   241 
       
   242         self.assertEquals(list(m3.merge_regions()),
       
   243                           [('unchanged', 0,1),
       
   244                            ('conflict', 1,1, 1,2, 1,2),
       
   245                            ('unchanged', 1,2)])
       
   246 
       
   247         self.assertEquals(list(m3.merge_groups()),
       
   248                           [('unchanged', ['aaa\n']),
       
   249                            ('conflict', [], ['111\n'], ['222\n']),
       
   250                            ('unchanged', ['bbb\n']),
       
   251                            ])
       
   252 
       
   253         ml = m3.merge_lines(name_a='a',
       
   254                             name_b='b',
       
   255                             start_marker='<<',
       
   256                             mid_marker='--',
       
   257                             end_marker='>>')
       
   258         self.assertEquals(''.join(ml),
       
   259 '''aaa
       
   260 << a
       
   261 111
       
   262 --
       
   263 222
       
   264 >> b
       
   265 bbb
       
   266 ''')
       
   267 
       
   268     def test_replace_clash(self):
       
   269         """Both try to insert lines in the same place."""
       
   270         m3 = Merge3(['aaa', '000', 'bbb'],
       
   271                     ['aaa', '111', 'bbb'],
       
   272                     ['aaa', '222', 'bbb'])
       
   273 
       
   274         self.assertEquals(m3.find_unconflicted(),
       
   275                           [(0, 1), (2, 3)])
       
   276 
       
   277         self.assertEquals(list(m3.find_sync_regions()),
       
   278                           [(0,1, 0,1, 0,1),
       
   279                            (2,3, 2,3, 2,3),
       
   280                            (3,3, 3,3, 3,3),])
       
   281 
       
   282     def test_replace_multi(self):
       
   283         """Replacement with regions of different size."""
       
   284         m3 = Merge3(['aaa', '000', '000', 'bbb'],
       
   285                     ['aaa', '111', '111', '111', 'bbb'],
       
   286                     ['aaa', '222', '222', '222', '222', 'bbb'])
       
   287 
       
   288         self.assertEquals(m3.find_unconflicted(),
       
   289                           [(0, 1), (3, 4)])
       
   290 
       
   291 
       
   292         self.assertEquals(list(m3.find_sync_regions()),
       
   293                           [(0,1, 0,1, 0,1),
       
   294                            (3,4, 4,5, 5,6),
       
   295                            (4,4, 5,5, 6,6),])
       
   296 
       
   297     def test_merge_poem(self):
       
   298         """Test case from diff3 manual"""
       
   299         m3 = Merge3(TZU, LAO, TAO)
       
   300         ml = list(m3.merge_lines('LAO', 'TAO'))
       
   301         self.log('merge result:')
       
   302         self.log(''.join(ml))
       
   303         self.assertEquals(ml, MERGED_RESULT)
       
   304 
       
   305     def test_minimal_conflicts_common(self):
       
   306         """Reprocessing"""
       
   307         base_text = ("a\n" * 20).splitlines(True)
       
   308         this_text = ("a\n"*10+"b\n" * 10).splitlines(True)
       
   309         other_text = ("a\n"*10+"c\n"+"b\n" * 8 + "c\n").splitlines(True)
       
   310         m3 = Merge3(base_text, other_text, this_text)
       
   311         m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
       
   312         merged_text = "".join(list(m_lines))
       
   313         optimal_text = ("a\n" * 10 + "<<<<<<< OTHER\nc\n"
       
   314             + 8* "b\n" + "c\n=======\n"
       
   315             + 10*"b\n" + ">>>>>>> THIS\n")
       
   316         self.assertEqualDiff(optimal_text, merged_text)
       
   317 
       
   318     def test_minimal_conflicts_unique(self):
       
   319         def add_newline(s):
       
   320             """Add a newline to each entry in the string"""
       
   321             return [(x+'\n') for x in s]
       
   322 
       
   323         base_text = add_newline("abcdefghijklm")
       
   324         this_text = add_newline("abcdefghijklmNOPQRSTUVWXYZ")
       
   325         other_text = add_newline("abcdefghijklm1OPQRSTUVWXY2")
       
   326         m3 = Merge3(base_text, other_text, this_text)
       
   327         m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
       
   328         merged_text = "".join(list(m_lines))
       
   329         optimal_text = ''.join(add_newline("abcdefghijklm")
       
   330             + ["<<<<<<< OTHER\n1\n=======\nN\n>>>>>>> THIS\n"]
       
   331             + add_newline('OPQRSTUVWXY')
       
   332             + ["<<<<<<< OTHER\n2\n=======\nZ\n>>>>>>> THIS\n"]
       
   333             )
       
   334         self.assertEqualDiff(optimal_text, merged_text)
       
   335 
       
   336     def test_minimal_conflicts_nonunique(self):
       
   337         def add_newline(s):
       
   338             """Add a newline to each entry in the string"""
       
   339             return [(x+'\n') for x in s]
       
   340 
       
   341         base_text = add_newline("abacddefgghij")
       
   342         this_text = add_newline("abacddefgghijkalmontfprz")
       
   343         other_text = add_newline("abacddefgghijknlmontfprd")
       
   344         m3 = Merge3(base_text, other_text, this_text)
       
   345         m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
       
   346         merged_text = "".join(list(m_lines))
       
   347         optimal_text = ''.join(add_newline("abacddefgghijk")
       
   348             + ["<<<<<<< OTHER\nn\n=======\na\n>>>>>>> THIS\n"]
       
   349             + add_newline('lmontfpr')
       
   350             + ["<<<<<<< OTHER\nd\n=======\nz\n>>>>>>> THIS\n"]
       
   351             )
       
   352         self.assertEqualDiff(optimal_text, merged_text)
       
   353 
       
   354     def test_reprocess_and_base(self):
       
   355         """Reprocessing and showing base breaks correctly"""
       
   356         base_text = ("a\n" * 20).splitlines(True)
       
   357         this_text = ("a\n"*10+"b\n" * 10).splitlines(True)
       
   358         other_text = ("a\n"*10+"c\n"+"b\n" * 8 + "c\n").splitlines(True)
       
   359         m3 = Merge3(base_text, other_text, this_text)
       
   360         m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True, 
       
   361                                  base_marker='|||||||')
       
   362         self.assertRaises(CantReprocessAndShowBase, list, m_lines)
       
   363 
       
   364     def test_binary(self):
       
   365         self.assertRaises(BinaryFile, Merge3, ['\x00'], ['a'], ['b'])
       
   366 
       
   367     def test_dos_text(self):
       
   368         base_text = 'a\r\n'
       
   369         this_text = 'b\r\n'
       
   370         other_text = 'c\r\n'
       
   371         m3 = Merge3(base_text.splitlines(True), other_text.splitlines(True),
       
   372                     this_text.splitlines(True))
       
   373         m_lines = m3.merge_lines('OTHER', 'THIS')
       
   374         self.assertEqual('<<<<<<< OTHER\r\nc\r\n=======\r\nb\r\n'
       
   375             '>>>>>>> THIS\r\n'.splitlines(True), list(m_lines))
       
   376 
       
   377     def test_mac_text(self):
       
   378         base_text = 'a\r'
       
   379         this_text = 'b\r'
       
   380         other_text = 'c\r'
       
   381         m3 = Merge3(base_text.splitlines(True), other_text.splitlines(True),
       
   382                     this_text.splitlines(True))
       
   383         m_lines = m3.merge_lines('OTHER', 'THIS')
       
   384         self.assertEqual('<<<<<<< OTHER\rc\r=======\rb\r'
       
   385             '>>>>>>> THIS\r'.splitlines(True), list(m_lines))