Mercurial > hg
comparison tests/test-simplemerge.py @ 4362:465b9ea02868
Import 3-way merge code from bzr
merge3.py is imported as contrib/simplemerge
test_merge3.py is imported as tests/test-simplemerge.py
author | Alexis S. L. Carvalho <alexis@cecm.usp.br> |
---|---|
date | Mon, 16 Apr 2007 20:17:39 -0300 |
parents | |
children | 2e3c54fb79a3 |
comparison
equal
deleted
inserted
replaced
4361:99c853a1408c | 4362:465b9ea02868 |
---|---|
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)) |