~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/testmerge3.py

  • Committer: Martin Pool
  • Date: 2005-07-07 10:22:02 UTC
  • Revision ID: mbp@sourcefrog.net-20050707102201-2d2a13a25098b101
- rearrange and clear up merged weave

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2004, 2005 by 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
 
18
 
from bzrlib.tests import TestCaseInTempDir, TestCase
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
 
 
18
from bzrlib.selftest import InTempDir, TestBase
19
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"""
 
20
 
 
21
 
 
22
 
 
23
 
 
24
 
 
25
class NoChanges(TestBase):
 
26
    """No conflicts because nothing changed"""
 
27
    def runTest(self):
98
28
        m3 = Merge3(['aaa', 'bbb'],
99
29
                    ['aaa', 'bbb'],
100
30
                    ['aaa', 'bbb'])
114
44
        self.assertEquals(list(m3.merge_groups()),
115
45
                          [('unchanged', ['aaa', 'bbb'])])
116
46
 
117
 
    def test_front_insert(self):
 
47
 
 
48
class FrontInsert(TestBase):
 
49
    def runTest(self):
118
50
        m3 = Merge3(['zz'],
119
51
                    ['aaa', 'bbb', 'zz'],
120
52
                    ['zz'])
132
64
        self.assertEquals(list(m3.merge_groups()),
133
65
                          [('a', ['aaa', 'bbb']),
134
66
                           ('unchanged', ['zz'])])
 
67
        
 
68
    
135
69
 
136
 
    def test_null_insert(self):
 
70
class NullInsert(TestBase):
 
71
    def runTest(self):
137
72
        m3 = Merge3([],
138
73
                    ['aaa', 'bbb'],
139
74
                    [])
 
75
 
140
76
        # todo: should use a sentinal at end as from get_matching_blocks
141
77
        # to match without zz
142
78
        self.assertEquals(list(m3.find_sync_regions()),
147
83
 
148
84
        self.assertEquals(list(m3.merge_lines()),
149
85
                          ['aaa', 'bbb'])
 
86
        
 
87
    
150
88
 
151
 
    def test_no_conflicts(self):
152
 
        """No conflicts because only one side changed"""
 
89
class NoConflicts(TestBase):
 
90
    """No conflicts because only one side changed"""
 
91
    def runTest(self):
153
92
        m3 = Merge3(['aaa', 'bbb'],
154
93
                    ['aaa', '111', 'bbb'],
155
94
                    ['aaa', 'bbb'])
167
106
                           ('a', 1, 2),
168
107
                           ('unchanged', 1, 2),])
169
108
 
170
 
    def test_append_a(self):
 
109
 
 
110
 
 
111
class AppendA(TestBase):
 
112
    def runTest(self):
171
113
        m3 = Merge3(['aaa\n', 'bbb\n'],
172
114
                    ['aaa\n', 'bbb\n', '222\n'],
173
115
                    ['aaa\n', 'bbb\n'])
175
117
        self.assertEquals(''.join(m3.merge_lines()),
176
118
                          'aaa\nbbb\n222\n')
177
119
 
178
 
    def test_append_b(self):
 
120
class AppendB(TestBase):
 
121
    def runTest(self):
179
122
        m3 = Merge3(['aaa\n', 'bbb\n'],
180
123
                    ['aaa\n', 'bbb\n'],
181
124
                    ['aaa\n', 'bbb\n', '222\n'])
183
126
        self.assertEquals(''.join(m3.merge_lines()),
184
127
                          'aaa\nbbb\n222\n')
185
128
 
186
 
    def test_append_agreement(self):
 
129
class AppendAgreement(TestBase):
 
130
    def runTest(self):
187
131
        m3 = Merge3(['aaa\n', 'bbb\n'],
188
132
                    ['aaa\n', 'bbb\n', '222\n'],
189
133
                    ['aaa\n', 'bbb\n', '222\n'])
191
135
        self.assertEquals(''.join(m3.merge_lines()),
192
136
                          'aaa\nbbb\n222\n')
193
137
 
194
 
    def test_append_clash(self):
 
138
class AppendClash(TestBase):
 
139
    def runTest(self):
195
140
        m3 = Merge3(['aaa\n', 'bbb\n'],
196
141
                    ['aaa\n', 'bbb\n', '222\n'],
197
142
                    ['aaa\n', 'bbb\n', '333\n'])
212
157
>> b
213
158
''')
214
159
 
215
 
    def test_insert_agreement(self):
 
160
 
 
161
class InsertAgreement(TestBase):
 
162
    def runTest(self):
216
163
        m3 = Merge3(['aaa\n', 'bbb\n'],
217
164
                    ['aaa\n', '222\n', 'bbb\n'],
218
165
                    ['aaa\n', '222\n', 'bbb\n'])
222
169
                            start_marker='<<',
223
170
                            mid_marker='--',
224
171
                            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."""
 
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):
230
181
        m3 = Merge3(['aaa\n', 'bbb\n'],
231
182
                    ['aaa\n', '111\n', 'bbb\n'],
232
183
                    ['aaa\n', '222\n', 'bbb\n'])
265
216
bbb
266
217
''')
267
218
 
268
 
    def test_replace_clash(self):
269
 
        """Both try to insert lines in the same place."""
 
219
 
 
220
 
 
221
class ReplaceClash(TestBase):
 
222
    """Both try to insert lines in the same place."""
 
223
    def runTest(self):
270
224
        m3 = Merge3(['aaa', '000', 'bbb'],
271
225
                    ['aaa', '111', 'bbb'],
272
226
                    ['aaa', '222', 'bbb'])
279
233
                           (2,3, 2,3, 2,3),
280
234
                           (3,3, 3,3, 3,3),])
281
235
 
282
 
    def test_replace_multi(self):
283
 
        """Replacement with regions of different size."""
 
236
 
 
237
 
 
238
class ReplaceMulti(TestBase):
 
239
    """Replacement with regions of different size."""
 
240
    def runTest(self):
284
241
        m3 = Merge3(['aaa', '000', '000', 'bbb'],
285
242
                    ['aaa', '111', '111', '111', 'bbb'],
286
243
                    ['aaa', '222', '222', '222', '222', 'bbb'])
294
251
                           (3,4, 4,5, 5,6),
295
252
                           (4,4, 5,5, 6,6),])
296
253
 
297
 
    def test_merge_poem(self):
298
 
        """Test case from diff3 manual"""
 
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):
299
340
        m3 = Merge3(TZU, LAO, TAO)
300
341
        ml = list(m3.merge_lines('LAO', 'TAO'))
301
342
        self.log('merge result:')
302
343
        self.log(''.join(ml))
303
344
        self.assertEquals(ml, MERGED_RESULT)
304
345
 
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))
 
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
    ]