~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_merge3.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-04-08 13:29:05 UTC
  • mfrom: (4258.1.1 bzr.dev)
  • Revision ID: pqm@pqm.ubuntu.com-20090408132905-lbf9hl67lmbttykx
(james_w) Add --fixes lp: to the launchpad plugin help.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005 by Canonical Ltd
2
 
 
 
1
# Copyright (C) 2004, 2005 Canonical Ltd
 
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
 
 
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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.selftest import InTempDir, TestBase
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
 
 
18
from bzrlib.tests import TestCaseInTempDir, TestCase
19
19
from bzrlib.merge3 import Merge3
20
 
 
21
 
 
22
 
 
23
 
 
24
 
 
25
 
class NoChanges(TestBase):
26
 
    """No conflicts because nothing changed"""
27
 
    def runTest(self):
 
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"""
28
98
        m3 = Merge3(['aaa', 'bbb'],
29
99
                    ['aaa', 'bbb'],
30
100
                    ['aaa', 'bbb'])
44
114
        self.assertEquals(list(m3.merge_groups()),
45
115
                          [('unchanged', ['aaa', 'bbb'])])
46
116
 
47
 
 
48
 
class FrontInsert(TestBase):
49
 
    def runTest(self):
 
117
    def test_front_insert(self):
50
118
        m3 = Merge3(['zz'],
51
119
                    ['aaa', 'bbb', 'zz'],
52
120
                    ['zz'])
64
132
        self.assertEquals(list(m3.merge_groups()),
65
133
                          [('a', ['aaa', 'bbb']),
66
134
                           ('unchanged', ['zz'])])
67
 
        
68
 
    
69
135
 
70
 
class NullInsert(TestBase):
71
 
    def runTest(self):
 
136
    def test_null_insert(self):
72
137
        m3 = Merge3([],
73
138
                    ['aaa', 'bbb'],
74
139
                    [])
75
 
 
76
140
        # todo: should use a sentinal at end as from get_matching_blocks
77
141
        # to match without zz
78
142
        self.assertEquals(list(m3.find_sync_regions()),
83
147
 
84
148
        self.assertEquals(list(m3.merge_lines()),
85
149
                          ['aaa', 'bbb'])
86
 
        
87
 
    
88
150
 
89
 
class NoConflicts(TestBase):
90
 
    """No conflicts because only one side changed"""
91
 
    def runTest(self):
 
151
    def test_no_conflicts(self):
 
152
        """No conflicts because only one side changed"""
92
153
        m3 = Merge3(['aaa', 'bbb'],
93
154
                    ['aaa', '111', 'bbb'],
94
155
                    ['aaa', 'bbb'])
106
167
                           ('a', 1, 2),
107
168
                           ('unchanged', 1, 2),])
108
169
 
109
 
 
110
 
 
111
 
class AppendA(TestBase):
112
 
    def runTest(self):
 
170
    def test_append_a(self):
113
171
        m3 = Merge3(['aaa\n', 'bbb\n'],
114
172
                    ['aaa\n', 'bbb\n', '222\n'],
115
173
                    ['aaa\n', 'bbb\n'])
117
175
        self.assertEquals(''.join(m3.merge_lines()),
118
176
                          'aaa\nbbb\n222\n')
119
177
 
120
 
class AppendB(TestBase):
121
 
    def runTest(self):
 
178
    def test_append_b(self):
122
179
        m3 = Merge3(['aaa\n', 'bbb\n'],
123
180
                    ['aaa\n', 'bbb\n'],
124
181
                    ['aaa\n', 'bbb\n', '222\n'])
126
183
        self.assertEquals(''.join(m3.merge_lines()),
127
184
                          'aaa\nbbb\n222\n')
128
185
 
129
 
class AppendAgreement(TestBase):
130
 
    def runTest(self):
 
186
    def test_append_agreement(self):
131
187
        m3 = Merge3(['aaa\n', 'bbb\n'],
132
188
                    ['aaa\n', 'bbb\n', '222\n'],
133
189
                    ['aaa\n', 'bbb\n', '222\n'])
135
191
        self.assertEquals(''.join(m3.merge_lines()),
136
192
                          'aaa\nbbb\n222\n')
137
193
 
138
 
class AppendClash(TestBase):
139
 
    def runTest(self):
 
194
    def test_append_clash(self):
140
195
        m3 = Merge3(['aaa\n', 'bbb\n'],
141
196
                    ['aaa\n', 'bbb\n', '222\n'],
142
197
                    ['aaa\n', 'bbb\n', '333\n'])
157
212
>> b
158
213
''')
159
214
 
160
 
 
161
 
class InsertAgreement(TestBase):
162
 
    def runTest(self):
 
215
    def test_insert_agreement(self):
163
216
        m3 = Merge3(['aaa\n', 'bbb\n'],
164
217
                    ['aaa\n', '222\n', 'bbb\n'],
165
218
                    ['aaa\n', '222\n', 'bbb\n'])
169
222
                            start_marker='<<',
170
223
                            mid_marker='--',
171
224
                            end_marker='>>')
172
 
        self.assertEquals(''.join(m3.merge_lines()),
173
 
                          'aaa\n222\nbbb\n')
174
 
 
175
 
 
176
 
 
177
 
 
178
 
class InsertClash(TestBase):
179
 
    """Both try to insert lines in the same place."""
180
 
    def runTest(self):
 
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."""
181
230
        m3 = Merge3(['aaa\n', 'bbb\n'],
182
231
                    ['aaa\n', '111\n', 'bbb\n'],
183
232
                    ['aaa\n', '222\n', 'bbb\n'])
216
265
bbb
217
266
''')
218
267
 
219
 
 
220
 
 
221
 
class ReplaceClash(TestBase):
222
 
    """Both try to insert lines in the same place."""
223
 
    def runTest(self):
 
268
    def test_replace_clash(self):
 
269
        """Both try to insert lines in the same place."""
224
270
        m3 = Merge3(['aaa', '000', 'bbb'],
225
271
                    ['aaa', '111', 'bbb'],
226
272
                    ['aaa', '222', 'bbb'])
233
279
                           (2,3, 2,3, 2,3),
234
280
                           (3,3, 3,3, 3,3),])
235
281
 
236
 
 
237
 
 
238
 
class ReplaceMulti(TestBase):
239
 
    """Replacement with regions of different size."""
240
 
    def runTest(self):
 
282
    def test_replace_multi(self):
 
283
        """Replacement with regions of different size."""
241
284
        m3 = Merge3(['aaa', '000', '000', 'bbb'],
242
285
                    ['aaa', '111', '111', '111', 'bbb'],
243
286
                    ['aaa', '222', '222', '222', '222', 'bbb'])
251
294
                           (3,4, 4,5, 5,6),
252
295
                           (4,4, 5,5, 6,6),])
253
296
 
254
 
        
255
 
        
256
 
 
257
 
 
258
 
 
259
 
 
260
 
def split_lines(t):
261
 
    from cStringIO import StringIO
262
 
    return StringIO(t).readlines()
263
 
 
264
 
 
265
 
 
266
 
############################################################
267
 
# test case from the gnu diffutils manual
268
 
 
269
 
# common base
270
 
TZU = split_lines("""     The Nameless is the origin of Heaven and Earth;
271
 
     The named is the mother of all things.
272
 
     
273
 
     Therefore let there always be non-being,
274
 
       so we may see their subtlety,
275
 
     And let there always be being,
276
 
       so we may see their outcome.
277
 
     The two are the same,
278
 
     But after they are produced,
279
 
       they have different names.
280
 
     They both may be called deep and profound.
281
 
     Deeper and more profound,
282
 
     The door of all subtleties!
283
 
""")
284
 
 
285
 
LAO = split_lines("""     The Way that can be told of is not the eternal Way;
286
 
     The name that can be named is not the eternal name.
287
 
     The Nameless is the origin of Heaven and Earth;
288
 
     The Named is the mother of all things.
289
 
     Therefore let there always be non-being,
290
 
       so we may see their subtlety,
291
 
     And let there always be being,
292
 
       so we may see their outcome.
293
 
     The two are the same,
294
 
     But after they are produced,
295
 
       they have different names.
296
 
""")
297
 
 
298
 
 
299
 
TAO = split_lines("""     The Way that can be told of is not the eternal Way;
300
 
     The name that can be named is not the eternal name.
301
 
     The Nameless is the origin of Heaven and Earth;
302
 
     The named is the mother of all things.
303
 
     
304
 
     Therefore let there always be non-being,
305
 
       so we may see their subtlety,
306
 
     And let there always be being,
307
 
       so we may see their result.
308
 
     The two are the same,
309
 
     But after they are produced,
310
 
       they have different names.
311
 
     
312
 
       -- The Way of Lao-Tzu, tr. Wing-tsit Chan
313
 
 
314
 
""")
315
 
 
316
 
MERGED_RESULT = split_lines("""     The Way that can be told of is not the eternal Way;
317
 
     The name that can be named is not the eternal name.
318
 
     The Nameless is the origin of Heaven and Earth;
319
 
     The Named is the mother of all things.
320
 
     Therefore let there always be non-being,
321
 
       so we may see their subtlety,
322
 
     And let there always be being,
323
 
       so we may see their result.
324
 
     The two are the same,
325
 
     But after they are produced,
326
 
       they have different names.
327
 
<<<<<<<< LAO
328
 
========
329
 
     
330
 
       -- The Way of Lao-Tzu, tr. Wing-tsit Chan
331
 
 
332
 
>>>>>>>> TAO
333
 
""")
334
 
 
335
 
 
336
 
 
337
 
class MergePoem(TestBase):
338
 
    """Test case from diff3 manual"""
339
 
    def runTest(self):
 
297
    def test_merge_poem(self):
 
298
        """Test case from diff3 manual"""
340
299
        m3 = Merge3(TZU, LAO, TAO)
341
300
        ml = list(m3.merge_lines('LAO', 'TAO'))
342
301
        self.log('merge result:')
343
302
        self.log(''.join(ml))
344
303
        self.assertEquals(ml, MERGED_RESULT)
345
304
 
346
 
 
347
 
 
348
 
TEST_CLASSES = [
349
 
    NoChanges,
350
 
    FrontInsert,
351
 
    NullInsert,
352
 
    NoConflicts,
353
 
    AppendA,
354
 
    AppendB,
355
 
    AppendAgreement,
356
 
    AppendClash,
357
 
    InsertAgreement,
358
 
    InsertClash,
359
 
    ReplaceClash,
360
 
    ReplaceMulti,
361
 
    MergePoem,
362
 
    ]
 
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))