195
class TestResolveContentConflicts(TestResolveConflicts):
197
# FIXME: We need to add the reverse case (delete in trunk, modify in
198
# branch) but that could wait until the resolution mechanism is implemented.
203
$ echo 'trunk content' >file
205
$ bzr commit -m 'Create trunk'
207
$ bzr branch . ../branch
210
$ bzr commit -m 'Delete file'
213
$ echo 'more content' >>file
214
$ bzr commit -m 'Modify file'
219
2>Contents conflict in file
220
2>1 conflicts encountered.
223
def test_take_this(self):
225
$ bzr rm file.OTHER --force # a simple rm file.OTHER is valid too
227
$ bzr commit --strict -m 'No more conflicts nor unknown files'
230
def test_take_other(self):
232
$ bzr mv file.OTHER file
234
$ bzr commit --strict -m 'No more conflicts nor unknown files'
237
def test_resolve_taking_this(self):
239
$ bzr resolve --take-this file
240
$ bzr commit --strict -m 'No more conflicts nor unknown files'
243
def test_resolve_taking_other(self):
245
$ bzr resolve --take-other file
246
$ bzr commit --strict -m 'No more conflicts nor unknown files'
250
class TestResolveDuplicateEntry(TestResolveConflicts):
255
$ echo 'trunk content' >file
257
$ bzr commit -m 'Create trunk'
258
$ echo 'trunk content too' >file2
260
$ bzr commit -m 'Add file2 in trunk'
262
$ bzr branch . -r 1 ../branch
264
$ echo 'branch content' >file2
266
$ bzr commit -m 'Add file2 in branch'
270
2>R file2 => file2.moved
271
2>Conflict adding file file2. Moved existing file to file2.moved.
272
2>1 conflicts encountered.
275
def test_keep_this(self):
277
$ bzr rm file2 --force
278
$ bzr mv file2.moved file2
280
$ bzr commit --strict -m 'No more conflicts nor unknown files'
283
def test_keep_other(self):
284
self.failIfExists('branch/file2.moved')
286
$ bzr rm file2.moved --force
288
$ bzr commit --strict -m 'No more conflicts nor unknown files'
290
self.failIfExists('branch/file2.moved')
292
def test_resolve_taking_this(self):
294
$ bzr resolve --take-this file2
295
$ bzr commit --strict -m 'No more conflicts nor unknown files'
298
def test_resolve_taking_other(self):
300
$ bzr resolve --take-other file2
301
$ bzr commit --strict -m 'No more conflicts nor unknown files'
218
# FIXME: Get rid of parametrized (in the class name) once we delete
219
# TestResolveConflicts -- vila 20100308
220
class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
221
"""This class provides a base to test single conflict resolution.
223
The aim is to define scenarios in daughter classes (one for each conflict
224
type) that create a single conflict object when one branch is merged in
225
another (and vice versa). Each class can define as many scenarios as
226
needed. Each scenario should define a couple of actions that will be
227
swapped to define the sibling scenarios.
229
From there, both resolutions are tested (--take-this and --take-other).
231
Each conflict type use its attributes in a specific way, so each class
232
should define a specific _assert_conflict method.
234
Since the resolution change the working tree state, each action should
235
define an associated check.
238
# Set by daughter classes
239
_conflict_type = None
240
_assert_conflict = None
245
_other_actions = None
249
# Set by _this_actions and other_actions
250
# FIXME: rename them this_args and other_args so the tests can use them
258
def mirror_scenarios(klass, base_scenarios):
261
"""Modify dict to apply to the given side.
263
'actions' key is turned into '_actions_this' if side is 'this' for
267
# Turn each key into _side_key
268
for k,v in d.iteritems():
269
t['_%s_%s' % (k, side)] = v
271
# Each base scenario is duplicated switching the roles of 'this' and
273
left = [l for l, r, c in base_scenarios]
274
right = [r for l, r, c in base_scenarios]
275
common = [c for l, r, c in base_scenarios]
276
for (lname, ldict), (rname, rdict), common in zip(left, right, common):
277
a = tests.multiply_scenarios([(lname, adapt(ldict, 'this'))],
278
[(rname, adapt(rdict, 'other'))])
279
b = tests.multiply_scenarios(
280
[(rname, adapt(rdict, 'this'))],
281
[(lname, adapt(ldict, 'other'))])
282
# Inject the common parameters in all scenarios
283
for name, d in a + b:
285
scenarios.extend(a + b)
289
def scenarios(klass):
290
# Only concrete classes return actual scenarios
294
super(TestParametrizedResolveConflicts, self).setUp()
295
builder = self.make_branch_builder('trunk')
296
builder.start_series()
298
# Create an empty trunk
299
builder.build_snapshot('start', None, [
300
('add', ('', 'root-id', 'directory', ''))])
301
# Add a minimal base content
302
_, _, actions_base = self._get_actions(self._actions_base)()
303
builder.build_snapshot('base', ['start'], actions_base)
304
# Modify the base content in branch
305
(self._other_path, self._other_id,
306
actions_other) = self._get_actions(self._actions_other)()
307
builder.build_snapshot('other', ['base'], actions_other)
308
# Modify the base content in trunk
309
(self._this_path, self._this_id,
310
actions_this) = self._get_actions(self._actions_this)()
311
builder.build_snapshot('this', ['base'], actions_this)
312
# builder.get_branch() tip is now 'this'
314
builder.finish_series()
315
self.builder = builder
317
def _get_actions(self, name):
318
return getattr(self, 'do_%s' % name)
320
def _get_check(self, name):
321
return getattr(self, 'check_%s' % name)
323
def do_nothing(self):
324
return (None, None, [])
326
def do_create_file(self):
327
return ('file', 'file-id',
328
[('add', ('file', 'file-id', 'file', 'trunk content\n'))])
330
def do_create_file_a(self):
331
return ('file', 'file-a-id',
332
[('add', ('file', 'file-a-id', 'file', 'file a content\n'))])
334
def check_file_content_a(self):
335
self.assertFileEqual('file a content\n', 'branch/file')
337
def do_create_file_b(self):
338
return ('file', 'file-b-id',
339
[('add', ('file', 'file-b-id', 'file', 'file b content\n'))])
341
def check_file_content_b(self):
342
self.assertFileEqual('file b content\n', 'branch/file')
344
def do_create_dir(self):
345
return ('dir', 'dir-id', [('add', ('dir', 'dir-id', 'directory', ''))])
347
def do_modify_file(self):
348
return ('file', 'file-id',
349
[('modify', ('file-id', 'trunk content\nmore content\n'))])
351
def check_file_has_more_content(self):
352
self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
354
def do_delete_file(self):
355
return ('file', 'file-id', [('unversion', 'file-id')])
357
def check_file_doesnt_exist(self):
358
self.failIfExists('branch/file')
360
def do_rename_file(self):
361
return ('new-file', 'file-id', [('rename', ('file', 'new-file'))])
363
def check_file_renamed(self):
364
self.failIfExists('branch/file')
365
self.failUnlessExists('branch/new-file')
367
def do_rename_file2(self):
368
return ('new-file2', 'file-id', [('rename', ('file', 'new-file2'))])
370
def check_file_renamed2(self):
371
self.failIfExists('branch/file')
372
self.failUnlessExists('branch/new-file2')
374
def do_rename_dir(self):
375
return ('new-dir', 'dir-id', [('rename', ('dir', 'new-dir'))])
377
def check_dir_renamed(self):
378
self.failIfExists('branch/dir')
379
self.failUnlessExists('branch/new-dir')
381
def do_rename_dir2(self):
382
return ('new-dir2', 'dir-id', [('rename', ('dir', 'new-dir2'))])
384
def check_dir_renamed2(self):
385
self.failIfExists('branch/dir')
386
self.failUnlessExists('branch/new-dir2')
388
def do_delete_dir(self):
389
return ('<deleted>', 'dir-id', [('unversion', 'dir-id')])
391
def check_dir_doesnt_exist(self):
392
self.failIfExists('branch/dir')
394
def _merge_other_into_this(self):
395
b = self.builder.get_branch()
396
wt = b.bzrdir.sprout('branch').open_workingtree()
397
wt.merge_from_branch(b, 'other')
400
def assertConflict(self, wt):
401
confs = wt.conflicts()
402
self.assertLength(1, confs)
404
self.assertIsInstance(c, self._conflict_type)
405
self._assert_conflict(wt, c)
407
def _get_resolve_path_arg(self, wt, action):
408
return self._item_path
410
def check_resolved(self, wt, action):
411
path = self._get_resolve_path_arg(wt, action)
412
conflicts.resolve(wt, [path], action=action)
413
# Check that we don't have any conflicts nor unknown left
414
self.assertLength(0, wt.conflicts())
415
self.assertLength(0, list(wt.unknowns()))
417
def test_resolve_taking_this(self):
418
wt = self._merge_other_into_this()
419
self.assertConflict(wt)
420
self.check_resolved(wt, 'take_this')
421
check_this = self._get_check(self._check_this)
424
def test_resolve_taking_other(self):
425
wt = self._merge_other_into_this()
426
self.assertConflict(wt)
427
self.check_resolved(wt, 'take_other')
428
check_other = self._get_check(self._check_other)
432
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
434
_conflict_type = conflicts.ContentsConflict,
436
def scenarios(klass):
438
(('file_modified', dict(actions='modify_file',
439
check='file_has_more_content')),
440
('file_deleted', dict(actions='delete_file',
441
check='file_doesnt_exist')),
442
dict(_actions_base='create_file', _item_path='file')),
444
return klass.mirror_scenarios(base_scenarios)
446
def assertContentsConflict(self, wt, c):
447
self.assertEqual(self._other_id, c.file_id)
448
self.assertEqual(self._other_path, c.path)
449
_assert_conflict = assertContentsConflict
453
class TestResolvePathConflict(TestParametrizedResolveConflicts):
455
_conflict_type = conflicts.PathConflict,
458
def scenarios(klass):
459
for_file = dict(_actions_base='create_file',
460
_item_path='new-file', _item_id='file-id',)
461
for_dir = dict(_actions_base='create_dir',
462
_item_path='new-dir', _item_id='dir-id',)
465
dict(actions='rename_file', check='file_renamed')),
467
dict(actions='delete_file', check='file_doesnt_exist')),
470
dict(actions='rename_file', check='file_renamed')),
472
dict(actions='rename_file2', check='file_renamed2')),
475
dict(actions='rename_dir', check='dir_renamed')),
477
dict(actions='delete_dir', check='dir_doesnt_exist')),
480
dict(actions='rename_dir', check='dir_renamed')),
482
dict(actions='rename_dir2', check='dir_renamed2')),
485
return klass.mirror_scenarios(base_scenarios)
487
def do_delete_file(self):
488
sup = super(TestResolvePathConflict, self).do_delete_file()
489
# PathConflicts handle deletion differently and requires a special
491
return ('<deleted>',) + sup[1:]
493
def assertPathConflict(self, wt, c):
494
self.assertEqual(self._item_id, c.file_id)
495
self.assertEqual(self._this_path, c.path)
496
self.assertEqual(self._other_path, c.conflict_path)
497
_assert_conflict = assertPathConflict
500
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
501
"""Same as TestResolvePathConflict but a specific conflict object.
504
def assertPathConflict(self, c):
505
# We create a conflict object as it was created before the fix and
506
# inject it into the working tree, the test will exercise the
507
# compatibility code.
508
old_c = conflicts.PathConflict('<deleted>', self._item_path,
510
wt.set_conflicts(conflicts.ConflictList([old_c]))
513
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
515
_conflict_type = conflicts.DuplicateEntry,
517
def scenarios(klass):
519
(('filea_created', dict(actions='create_file_a',
520
check='file_content_a')),
521
('fileb_created', dict(actions='create_file_b',
522
check='file_content_b')),
523
dict(_actions_base='nothing', _item_path='file')),
525
return klass.mirror_scenarios(base_scenarios)
527
def assertDuplicateEntry(self, wt, c):
528
self.assertEqual(self._this_id, c.file_id)
529
self.assertEqual(self._item_path + '.moved', c.path)
530
self.assertEqual(self._item_path, c.conflict_path)
531
_assert_conflict = assertDuplicateEntry
305
534
class TestResolveUnversionedParent(TestResolveConflicts):
469
class TestResolvePathConflict(TestResolveConflicts):
476
$ bzr commit -m 'Create trunk'
477
$ bzr mv file file-in-trunk
478
$ bzr commit -m 'Renamed to file-in-trunk'
480
$ bzr branch . -r 1 ../branch
482
$ bzr mv file file-in-branch
483
$ bzr commit -m 'Renamed to file-in-branch'
486
2>R file-in-branch => file-in-trunk
487
2>Path conflict: file-in-branch / file-in-trunk
488
2>1 conflicts encountered.
491
def test_keep_source(self):
493
$ bzr resolve file-in-trunk
494
$ bzr commit --strict -m 'No more conflicts nor unknown files'
497
def test_keep_target(self):
499
$ bzr mv file-in-trunk file-in-branch
500
$ bzr resolve file-in-branch
501
$ bzr commit --strict -m 'No more conflicts nor unknown files'
504
def test_resolve_taking_this(self):
506
$ bzr resolve --take-this file-in-branch
507
$ bzr commit --strict -m 'No more conflicts nor unknown files'
510
def test_resolve_taking_other(self):
512
$ bzr resolve --take-other file-in-branch
513
$ bzr commit --strict -m 'No more conflicts nor unknown files'
517
class TestResolveParentLoop(TestResolveConflicts):
701
class TestResolveParentLoop(TestParametrizedResolveConflicts):
703
_conflict_type = conflicts.ParentLoop,
705
def scenarios(klass):
707
(('dir1_into_dir2', dict(actions='move_dir1_into_dir2',
708
check='dir1_moved')),
709
('dir2_into_dir1', dict(actions='move_dir2_into_dir1',
710
check='dir2_moved')),
711
dict(_actions_base='create_dir1_dir2')),
712
(('dir1_into_dir4', dict(actions='move_dir1_into_dir4',
713
check='dir1_2_moved')),
714
('dir3_into_dir2', dict(actions='move_dir3_into_dir2',
715
check='dir3_4_moved')),
716
dict(_actions_base='create_dir1_4')),
718
return klass.mirror_scenarios(base_scenarios)
720
def do_create_dir1_dir2(self):
722
[('add', ('dir1', 'dir1-id', 'directory', '')),
723
('add', ('dir2', 'dir2-id', 'directory', '')),
726
def do_move_dir1_into_dir2(self):
727
# The arguments are the file-id to move and the targeted file-id dir.
728
return ('dir1-id', 'dir2-id', [('rename', ('dir1', 'dir2/dir1'))])
730
def check_dir1_moved(self):
731
self.failIfExists('branch/dir1')
732
self.failUnlessExists('branch/dir2/dir1')
734
def do_move_dir2_into_dir1(self):
735
# The arguments are the file-id to move and the targeted file-id dir.
736
return ('dir2-id', 'dir1-id', [('rename', ('dir2', 'dir1/dir2'))])
738
def check_dir2_moved(self):
739
self.failIfExists('branch/dir2')
740
self.failUnlessExists('branch/dir1/dir2')
742
def do_create_dir1_4(self):
744
[('add', ('dir1', 'dir1-id', 'directory', '')),
745
('add', ('dir1/dir2', 'dir2-id', 'directory', '')),
746
('add', ('dir3', 'dir3-id', 'directory', '')),
747
('add', ('dir3/dir4', 'dir4-id', 'directory', '')),
750
def do_move_dir1_into_dir4(self):
751
# The arguments are the file-id to move and the targeted file-id dir.
752
return ('dir1-id', 'dir4-id',
753
[('rename', ('dir1', 'dir3/dir4/dir1'))])
755
def check_dir1_2_moved(self):
756
self.failIfExists('branch/dir1')
757
self.failUnlessExists('branch/dir3/dir4/dir1')
758
self.failUnlessExists('branch/dir3/dir4/dir1/dir2')
760
def do_move_dir3_into_dir2(self):
761
# The arguments are the file-id to move and the targeted file-id dir.
762
return ('dir3-id', 'dir2-id',
763
[('rename', ('dir3', 'dir1/dir2/dir3'))])
765
def check_dir3_4_moved(self):
766
self.failIfExists('branch/dir3')
767
self.failUnlessExists('branch/dir1/dir2/dir3')
768
self.failUnlessExists('branch/dir1/dir2/dir3/dir4')
770
def _get_resolve_path_arg(self, wt, action):
771
# ParentLoop is unsual as it says:
772
# moving <conflict_path> into <path>. Cancelled move.
773
# But since <path> doesn't exist in the working tree, we need to use
774
# <conflict_path> instead
775
path = wt.id2path(self._other_id)
778
def assertParentLoop(self, wt, c):
779
if 'taking_other(' in self.id() and 'dir4' in self.id():
780
raise tests.KnownFailure(
781
"ParentLoop doesn't carry enough info to resolve")
782
# The relevant file-ids are other_args swapped (which is the main
783
# reason why they should be renamed other_args instead of Other_path
784
# and other_id). In the conflict object, they represent:
785
# c.file_id: the directory being moved
786
# c.conflict_id_id: The target directory
787
self.assertEqual(self._other_path, c.file_id)
788
self.assertEqual(self._other_id, c.conflict_file_id)
789
# The conflict paths are irrelevant (they are deterministic but not
790
# worth checking since they don't provide the needed information
792
_assert_conflict = assertParentLoop
795
class OldTestResolveParentLoop(TestResolveConflicts):