~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_merge3.py

  • Committer: Martin Pool
  • Date: 2005-05-03 08:00:27 UTC
  • Revision ID: mbp@sourcefrog.net-20050503080027-908edb5b39982198
doc

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005 by 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'])