~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_merge3.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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))
 
386
 
 
387
    def test_merge3_cherrypick(self):
 
388
        base_text = "a\nb\n"
 
389
        this_text = "a\n"
 
390
        other_text = "a\nb\nc\n"
 
391
        # When cherrypicking, lines in base are not part of the conflict
 
392
        m3 = Merge3(base_text.splitlines(True), this_text.splitlines(True),
 
393
                    other_text.splitlines(True), is_cherrypick=True)
 
394
        m_lines = m3.merge_lines()
 
395
        self.assertEqualDiff('a\n<<<<<<<\n=======\nc\n>>>>>>>\n',
 
396
                             ''.join(m_lines))
 
397
 
 
398
        # This is not symmetric
 
399
        m3 = Merge3(base_text.splitlines(True), other_text.splitlines(True),
 
400
                    this_text.splitlines(True), is_cherrypick=True)
 
401
        m_lines = m3.merge_lines()
 
402
        self.assertEqualDiff('a\n<<<<<<<\nb\nc\n=======\n>>>>>>>\n',
 
403
                             ''.join(m_lines))
 
404
 
 
405
    def test_merge3_cherrypick_w_mixed(self):
 
406
        base_text = 'a\nb\nc\nd\ne\n'
 
407
        this_text = 'a\nb\nq\n'
 
408
        other_text = 'a\nb\nc\nd\nf\ne\ng\n'
 
409
        # When cherrypicking, lines in base are not part of the conflict
 
410
        m3 = Merge3(base_text.splitlines(True), this_text.splitlines(True),
 
411
                    other_text.splitlines(True), is_cherrypick=True)
 
412
        m_lines = m3.merge_lines()
 
413
        self.assertEqualDiff('a\n'
 
414
                             'b\n'
 
415
                             '<<<<<<<\n'
 
416
                             'q\n'
 
417
                             '=======\n'
 
418
                             'f\n'
 
419
                             '>>>>>>>\n'
 
420
                             '<<<<<<<\n'
 
421
                             '=======\n'
 
422
                             'g\n'
 
423
                             '>>>>>>>\n',
 
424
                             ''.join(m_lines))