~bzr-pqm/bzr/bzr.dev

2052.3.2 by John Arbash Meinel
Change Copyright .. by Canonical to Copyright ... Canonical
1
# Copyright (C) 2004, 2005 Canonical Ltd
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
2
#
821 by Martin Pool
- start code for built-in diff3-style resolve
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.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
7
#
821 by Martin Pool
- start code for built-in diff3-style resolve
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.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
12
#
821 by Martin Pool
- start code for built-in diff3-style resolve
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
1185.31.25 by John Arbash Meinel
Renamed all of the tests from selftest/foo.py to tests/test_foo.py
18
from bzrlib.tests import TestCaseInTempDir, TestCase
821 by Martin Pool
- start code for built-in diff3-style resolve
19
from bzrlib.merge3 import Merge3
1558.15.6 by Aaron Bentley
Added more tests
20
from bzrlib.errors import CantReprocessAndShowBase, BinaryFile
821 by Martin Pool
- start code for built-in diff3-style resolve
21
1102 by Martin Pool
- merge test refactoring from robertc
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.
974.1.50 by aaron.bentley at utoronto
Merge of robertc@robertcollins.net-20050826013321-52eee1f1da679ee9
86
<<<<<<< LAO
87
=======
1102 by Martin Pool
- merge test refactoring from robertc
88
     
89
       -- The Way of Lao-Tzu, tr. Wing-tsit Chan
90
974.1.50 by aaron.bentley at utoronto
Merge of robertc@robertcollins.net-20050826013321-52eee1f1da679ee9
91
>>>>>>> TAO
1102 by Martin Pool
- merge test refactoring from robertc
92
""")
93
94
class TestMerge3(TestCase):
95
96
    def test_no_changes(self):
97
        """No conflicts because nothing changed"""
821 by Martin Pool
- start code for built-in diff3-style resolve
98
        m3 = Merge3(['aaa', 'bbb'],
99
                    ['aaa', 'bbb'],
100
                    ['aaa', 'bbb'])
101
102
        self.assertEquals(m3.find_unconflicted(),
103
                          [(0, 2)])
104
822 by Martin Pool
- Renamed merge3 test suite for easier access.
105
        self.assertEquals(list(m3.find_sync_regions()),
824 by Martin Pool
- Merge3.find_sync_regions yields just a 6-tuple, not a tuple of tuples
106
                          [(0, 2,
107
                            0, 2,
825 by Martin Pool
- Merge3.find_sync_regions always returns a zero-length sentinal at the end to
108
                            0, 2),
109
                           (2,2, 2,2, 2,2)])
110
111
        self.assertEquals(list(m3.merge_regions()),
112
                          [('unchanged', 0, 2)])
113
827 by Martin Pool
- new Merge3.merge_groups feeds back the merged lines
114
        self.assertEquals(list(m3.merge_groups()),
115
                          [('unchanged', ['aaa', 'bbb'])])
116
1102 by Martin Pool
- merge test refactoring from robertc
117
    def test_front_insert(self):
825 by Martin Pool
- Merge3.find_sync_regions always returns a zero-length sentinal at the end to
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)])
827 by Martin Pool
- new Merge3.merge_groups feeds back the merged lines
131
132
        self.assertEquals(list(m3.merge_groups()),
133
                          [('a', ['aaa', 'bbb']),
134
                           ('unchanged', ['zz'])])
825 by Martin Pool
- Merge3.find_sync_regions always returns a zero-length sentinal at the end to
135
        
1102 by Martin Pool
- merge test refactoring from robertc
136
    def test_null_insert(self):
825 by Martin Pool
- Merge3.find_sync_regions always returns a zero-length sentinal at the end to
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)])
828 by Martin Pool
- code to represent merges in regular text conflict form
147
148
        self.assertEquals(list(m3.merge_lines()),
149
                          ['aaa', 'bbb'])
821 by Martin Pool
- start code for built-in diff3-style resolve
150
1102 by Martin Pool
- merge test refactoring from robertc
151
    def test_no_conflicts(self):
152
        """No conflicts because only one side changed"""
822 by Martin Pool
- Renamed merge3 test suite for easier access.
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()),
825 by Martin Pool
- Merge3.find_sync_regions always returns a zero-length sentinal at the end to
161
                          [(0,1, 0,1, 0,1),
162
                           (1,2, 2,3, 1,2),
163
                           (2,2, 3,3, 2,2),])
164
826 by Martin Pool
- Actually merge unsynchronized regions. Woot!
165
        self.assertEquals(list(m3.merge_regions()),
166
                          [('unchanged', 0, 1),
167
                           ('a', 1, 2),
168
                           ('unchanged', 1, 2),])
169
1102 by Martin Pool
- merge test refactoring from robertc
170
    def test_append_a(self):
836 by Martin Pool
- more merge tests from john
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
1102 by Martin Pool
- merge test refactoring from robertc
178
    def test_append_b(self):
836 by Martin Pool
- more merge tests from john
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
1102 by Martin Pool
- merge test refactoring from robertc
186
    def test_append_agreement(self):
836 by Martin Pool
- more merge tests from john
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
1102 by Martin Pool
- merge test refactoring from robertc
194
    def test_append_clash(self):
836 by Martin Pool
- more merge tests from john
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
1102 by Martin Pool
- merge test refactoring from robertc
215
    def test_insert_agreement(self):
830 by Martin Pool
- handle chunks which differ from the base but agree
216
        m3 = Merge3(['aaa\n', 'bbb\n'],
217
                    ['aaa\n', '222\n', 'bbb\n'],
218
                    ['aaa\n', '222\n', 'bbb\n'])
219
836 by Martin Pool
- more merge tests from john
220
        ml = m3.merge_lines(name_a='a',
221
                            name_b='b',
222
                            start_marker='<<',
223
                            mid_marker='--',
224
                            end_marker='>>')
1143 by Martin Pool
- remove dead code and remove some small errors (pychecker)
225
        self.assertEquals(''.join(ml), 'aaa\n222\nbbb\n')
226
        
830 by Martin Pool
- handle chunks which differ from the base but agree
227
1102 by Martin Pool
- merge test refactoring from robertc
228
    def test_insert_clash(self):
229
        """Both try to insert lines in the same place."""
829 by Martin Pool
- More merge3 cvs-form stuff
230
        m3 = Merge3(['aaa\n', 'bbb\n'],
231
                    ['aaa\n', '111\n', 'bbb\n'],
232
                    ['aaa\n', '222\n', 'bbb\n'])
821 by Martin Pool
- start code for built-in diff3-style resolve
233
234
        self.assertEquals(m3.find_unconflicted(),
235
                          [(0, 1), (1, 2)])
236
822 by Martin Pool
- Renamed merge3 test suite for easier access.
237
        self.assertEquals(list(m3.find_sync_regions()),
825 by Martin Pool
- Merge3.find_sync_regions always returns a zero-length sentinal at the end to
238
                          [(0,1, 0,1, 0,1),
239
                           (1,2, 2,3, 2,3),
240
                           (2,2, 3,3, 3,3),])
821 by Martin Pool
- start code for built-in diff3-style resolve
241
826 by Martin Pool
- Actually merge unsynchronized regions. Woot!
242
        self.assertEquals(list(m3.merge_regions()),
243
                          [('unchanged', 0,1),
827 by Martin Pool
- new Merge3.merge_groups feeds back the merged lines
244
                           ('conflict', 1,1, 1,2, 1,2),
826 by Martin Pool
- Actually merge unsynchronized regions. Woot!
245
                           ('unchanged', 1,2)])
246
827 by Martin Pool
- new Merge3.merge_groups feeds back the merged lines
247
        self.assertEquals(list(m3.merge_groups()),
829 by Martin Pool
- More merge3 cvs-form stuff
248
                          [('unchanged', ['aaa\n']),
249
                           ('conflict', [], ['111\n'], ['222\n']),
250
                           ('unchanged', ['bbb\n']),
827 by Martin Pool
- new Merge3.merge_groups feeds back the merged lines
251
                           ])
252
829 by Martin Pool
- More merge3 cvs-form stuff
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
''')
828 by Martin Pool
- code to represent merges in regular text conflict form
267
1102 by Martin Pool
- merge test refactoring from robertc
268
    def test_replace_clash(self):
269
        """Both try to insert lines in the same place."""
821 by Martin Pool
- start code for built-in diff3-style resolve
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
822 by Martin Pool
- Renamed merge3 test suite for easier access.
277
        self.assertEquals(list(m3.find_sync_regions()),
825 by Martin Pool
- Merge3.find_sync_regions always returns a zero-length sentinal at the end to
278
                          [(0,1, 0,1, 0,1),
279
                           (2,3, 2,3, 2,3),
280
                           (3,3, 3,3, 3,3),])
822 by Martin Pool
- Renamed merge3 test suite for easier access.
281
1102 by Martin Pool
- merge test refactoring from robertc
282
    def test_replace_multi(self):
283
        """Replacement with regions of different size."""
822 by Martin Pool
- Renamed merge3 test suite for easier access.
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()),
825 by Martin Pool
- Merge3.find_sync_regions always returns a zero-length sentinal at the end to
293
                          [(0,1, 0,1, 0,1),
294
                           (3,4, 4,5, 5,6),
295
                           (4,4, 5,5, 6,6),])
822 by Martin Pool
- Renamed merge3 test suite for easier access.
296
1102 by Martin Pool
- merge test refactoring from robertc
297
    def test_merge_poem(self):
298
        """Test case from diff3 manual"""
831 by Martin Pool
- add merge3 test case from Lao-Tzu
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)
1185.24.1 by Aaron Bentley
Got reprocessing working
304
1711.2.25 by John Arbash Meinel
Update the merge3 tests, so that they test more realistic data on 'minimal_conflicts'
305
    def test_minimal_conflicts_common(self):
1185.24.1 by Aaron Bentley
Got reprocessing working
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))
1711.2.25 by John Arbash Meinel
Update the merge3 tests, so that they test more realistic data on 'minimal_conflicts'
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)
1185.24.1 by Aaron Bentley
Got reprocessing working
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)
1558.15.6 by Aaron Bentley
Added more tests
363
364
    def test_binary(self):
365
        self.assertRaises(BinaryFile, Merge3, ['\x00'], ['a'], ['b'])
1551.10.38 by Aaron Bentley
merge3 auto-detects line endings for conflict markers
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))
3249.3.1 by John Arbash Meinel
Implement cherrypick support for Merge3
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))