~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_conflicts.py

Merge bzr.dev into cleanup

Show diffs side-by-side

added added

removed removed

Lines of Context:
36
36
        standard_tests, tests.condition_isinstance((
37
37
                TestParametrizedResolveConflicts,
38
38
                )))
39
 
    tests.multiply_tests(sp_tests, resolve_conflict_scenarios(), result)
 
39
    # Each test class defines its own scenarios. This is needed for
 
40
    # TestResolvePathConflictBefore531967 that verifies that the same tests as
 
41
    # TestResolvePathConflict still pass.
 
42
    for test in tests.iter_suite_tests(sp_tests):
 
43
        tests.apply_scenarios(test, test.scenarios(), result)
40
44
 
41
45
    # No parametrization for the remaining tests
42
46
    result.addTests(remaining_tests)
194
198
# FIXME: The shell-like tests should be converted to real whitebox tests... or
195
199
# moved to a blackbox module -- vila 20100205
196
200
 
 
201
# FIXME: test missing for multiple conflicts
 
202
 
197
203
# FIXME: Tests missing for DuplicateID conflict type
198
204
class TestResolveConflicts(script.TestCaseWithTransportAndScript):
199
205
 
209
215
    pass
210
216
 
211
217
 
212
 
def resolve_conflict_scenarios():
213
 
    base_scenarios = [
214
 
        (dict(_conflict_type=conflicts.ContentsConflict,
215
 
              _item_path='file', _item_id='file-id',),
216
 
         ('file_modified', dict(actions='modify_file',
217
 
                                check='file_has_more_content')),
218
 
         ('file_deleted', dict(actions='delete_file',
219
 
                                check='file_doesnt_exist'))),
220
 
        (dict(_conflict_type=conflicts.PathConflict,
221
 
              _item_path='new-dir', _item_id='dir-id',),
222
 
         ('dir_renamed', dict(actions='rename_dir', check='dir_renamed')),
223
 
         ('dir_deleted', dict(actions='delete_dir', check='dir_doesnt_exist'))),
224
 
        ]
225
 
    # Each base scenario is duplicated switching the roles of this and other
226
 
    scenarios = []
227
 
    for common, (tname, tdict), (oname, odict) in base_scenarios:
228
 
        d = common.copy()
229
 
        d.update(_this_actions=tdict['actions'], _check_this=tdict['check'],
230
 
                 _other_actions=odict['actions'], _check_other=odict['check'])
231
 
        scenarios.append(('%s,%s' % (tname, oname), d))
232
 
        d = common.copy()
233
 
        d.update(_this_actions=odict['actions'], _check_this=odict['check'],
234
 
                 _other_actions=tdict['actions'], _check_other=tdict['check'])
235
 
        scenarios.append(('%s,%s' % (oname, tname), d))
236
 
    return scenarios
237
 
 
238
 
# FIXME: Get rid of parametrized once we delete TestResolveConflicts
 
218
# FIXME: Get rid of parametrized (in the class name) once we delete
 
219
# TestResolveConflicts -- vila 20100308
239
220
class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
 
221
    """This class provides a base to test single conflict resolution.
 
222
 
 
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.
 
228
 
 
229
    From there, both resolutions are tested (--take-this and --take-other).
 
230
 
 
231
    Each conflict type use its attributes in a specific way, so each class 
 
232
    should define a specific _assert_conflict method.
 
233
 
 
234
    Since the resolution change the working tree state, each action should
 
235
    define an associated check.
 
236
    """
 
237
 
 
238
    # Set by daughter classes
 
239
    _conflict_type = None
 
240
    _assert_conflict = None
240
241
 
241
242
    # Set by load_tests
 
243
    _base_actions = None
242
244
    _this_actions = None
243
245
    _other_actions = None
244
 
    _conflict_type = None
245
246
    _item_path = None
246
247
    _item_id = None
247
248
 
 
249
    # Set by _this_actions and other_actions
 
250
    # FIXME: rename them this_args and other_args so the tests can use them
 
251
    # more freely
 
252
    _this_path = None
 
253
    _this_id = None
 
254
    _other_path = None
 
255
    _other_id = None
 
256
 
 
257
    @classmethod
 
258
    def mirror_scenarios(klass, base_scenarios):
 
259
        scenarios = []
 
260
        def adapt(d, side):
 
261
            """Modify dict to apply to the given side.
 
262
 
 
263
            'actions' key is turned into '_actions_this' if side is 'this' for
 
264
            example.
 
265
            """
 
266
            t = {}
 
267
            # Turn each key into _side_key
 
268
            for k,v in d.iteritems():
 
269
                t['_%s_%s' % (k, side)] = v
 
270
            return t
 
271
        # Each base scenario is duplicated switching the roles of 'this' and
 
272
        # 'other'
 
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:
 
284
                d.update(common)
 
285
            scenarios.extend(a + b)
 
286
        return scenarios
 
287
 
 
288
    @classmethod
 
289
    def scenarios(klass):
 
290
        # Only concrete classes return actual scenarios
 
291
        return []
 
292
 
248
293
    def setUp(self):
249
294
        super(TestParametrizedResolveConflicts, self).setUp()
250
295
        builder = self.make_branch_builder('trunk')
251
296
        builder.start_series()
 
297
 
252
298
        # Create an empty trunk
253
299
        builder.build_snapshot('start', None, [
254
300
                ('add', ('', 'root-id', 'directory', ''))])
255
301
        # Add a minimal base content
256
 
        builder.build_snapshot(
257
 
            'base', ['start'], [
258
 
                ('add', ('file', 'file-id', 'file', 'trunk content\n')),
259
 
                ('add', ('dir', 'dir-id', 'directory', '')),
260
 
                ])
 
302
        _, _, actions_base = self._get_actions(self._actions_base)()
 
303
        builder.build_snapshot('base', ['start'], actions_base)
261
304
        # Modify the base content in branch
262
 
        other_actions = self._get_actions(self._other_actions)
263
 
        builder.build_snapshot('other', ['base'], other_actions())
 
305
        (self._other_path, self._other_id,
 
306
         actions_other) = self._get_actions(self._actions_other)()
 
307
        builder.build_snapshot('other', ['base'], actions_other)
264
308
        # Modify the base content in trunk
265
 
        this_actions = self._get_actions(self._this_actions)
266
 
        builder.build_snapshot('this', ['base'], this_actions())
 
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'
 
313
 
267
314
        builder.finish_series()
268
315
        self.builder = builder
269
316
 
273
320
    def _get_check(self, name):
274
321
        return getattr(self, 'check_%s' % name)
275
322
 
276
 
    def assertConflict(self, wt, **kwargs):
277
 
        confs = wt.conflicts()
278
 
        self.assertLength(1, confs)
279
 
        c = confs[0]
280
 
        self.assertIsInstance(c, self._conflict_type)
281
 
        sentinel = object() # An impossible value
282
 
        for k, v in kwargs.iteritems():
283
 
            self.assertEqual(v, getattr(c, k, sentinel), "for key '%s'" % k)
284
 
 
285
 
    def check_resolved(self, wt, item, action):
286
 
        conflicts.resolve(wt, [item], action=action)
287
 
        # Check that we don't have any conflicts nor unknown left
288
 
        self.assertLength(0, wt.conflicts())
289
 
        self.assertLength(0, list(wt.unknowns()))
 
323
    def do_nothing(self):
 
324
        return (None, None, [])
 
325
 
 
326
    def do_create_file(self):
 
327
        return ('file', 'file-id',
 
328
                [('add', ('file', 'file-id', 'file', 'trunk content\n'))])
 
329
 
 
330
    def do_create_file_a(self):
 
331
        return ('file', 'file-a-id',
 
332
                [('add', ('file', 'file-a-id', 'file', 'file a content\n'))])
 
333
 
 
334
    def check_file_content_a(self):
 
335
        self.assertFileEqual('file a content\n', 'branch/file')
 
336
 
 
337
    def do_create_file_b(self):
 
338
        return ('file', 'file-b-id',
 
339
                [('add', ('file', 'file-b-id', 'file', 'file b content\n'))])
 
340
 
 
341
    def check_file_content_b(self):
 
342
        self.assertFileEqual('file b content\n', 'branch/file')
 
343
 
 
344
    def do_create_dir(self):
 
345
        return ('dir', 'dir-id', [('add', ('dir', 'dir-id', 'directory', ''))])
290
346
 
291
347
    def do_modify_file(self):
292
 
        return [('modify', ('file-id', 'trunk content\nmore content\n'))]
 
348
        return ('file', 'file-id',
 
349
                [('modify', ('file-id', 'trunk content\nmore content\n'))])
293
350
 
294
351
    def check_file_has_more_content(self):
295
352
        self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
296
353
 
297
354
    def do_delete_file(self):
298
 
        return [('unversion', 'file-id')]
 
355
        return ('file', 'file-id', [('unversion', 'file-id')])
299
356
 
300
357
    def check_file_doesnt_exist(self):
301
358
        self.failIfExists('branch/file')
302
359
 
 
360
    def do_rename_file(self):
 
361
        return ('new-file', 'file-id', [('rename', ('file', 'new-file'))])
 
362
 
 
363
    def check_file_renamed(self):
 
364
        self.failIfExists('branch/file')
 
365
        self.failUnlessExists('branch/new-file')
 
366
 
 
367
    def do_rename_file2(self):
 
368
        return ('new-file2', 'file-id', [('rename', ('file', 'new-file2'))])
 
369
 
 
370
    def check_file_renamed2(self):
 
371
        self.failIfExists('branch/file')
 
372
        self.failUnlessExists('branch/new-file2')
 
373
 
303
374
    def do_rename_dir(self):
304
 
        return [('rename', ('dir', 'new-dir'))]
 
375
        return ('new-dir', 'dir-id', [('rename', ('dir', 'new-dir'))])
305
376
 
306
377
    def check_dir_renamed(self):
307
378
        self.failIfExists('branch/dir')
308
379
        self.failUnlessExists('branch/new-dir')
309
380
 
 
381
    def do_rename_dir2(self):
 
382
        return ('new-dir2', 'dir-id', [('rename', ('dir', 'new-dir2'))])
 
383
 
 
384
    def check_dir_renamed2(self):
 
385
        self.failIfExists('branch/dir')
 
386
        self.failUnlessExists('branch/new-dir2')
 
387
 
310
388
    def do_delete_dir(self):
311
 
        return [('unversion', 'dir-id')]
 
389
        return ('<deleted>', 'dir-id', [('unversion', 'dir-id')])
312
390
 
313
391
    def check_dir_doesnt_exist(self):
314
392
        self.failIfExists('branch/dir')
319
397
        wt.merge_from_branch(b, 'other')
320
398
        return wt
321
399
 
 
400
    def assertConflict(self, wt):
 
401
        confs = wt.conflicts()
 
402
        self.assertLength(1, confs)
 
403
        c = confs[0]
 
404
        self.assertIsInstance(c, self._conflict_type)
 
405
        self._assert_conflict(wt, c)
 
406
 
 
407
    def _get_resolve_path_arg(self, wt, action):
 
408
        return self._item_path
 
409
 
 
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()))
 
416
 
322
417
    def test_resolve_taking_this(self):
323
418
        wt = self._merge_other_into_this()
324
 
        self.assertConflict(wt, path=self._item_path, file_id=self._item_id)
325
 
        self.check_resolved(wt, self._item_path, 'take_this')
 
419
        self.assertConflict(wt)
 
420
        self.check_resolved(wt, 'take_this')
326
421
        check_this = self._get_check(self._check_this)
327
422
        check_this()
328
423
 
329
424
    def test_resolve_taking_other(self):
330
425
        wt = self._merge_other_into_this()
331
 
        self.assertConflict(wt, path=self._item_path, file_id=self._item_id)
332
 
        self.check_resolved(wt, self._item_path, 'take_other')
 
426
        self.assertConflict(wt)
 
427
        self.check_resolved(wt, 'take_other')
333
428
        check_other = self._get_check(self._check_other)
334
429
        check_other()
335
430
 
336
431
 
337
 
class TestResolveDuplicateEntry(TestResolveConflicts):
338
 
 
339
 
    preamble = """
340
 
$ bzr init trunk
341
 
$ cd trunk
342
 
$ echo 'trunk content' >file
343
 
$ bzr add file
344
 
$ bzr commit -m 'Create trunk'
345
 
 
346
 
$ echo 'trunk content too' >file2
347
 
$ bzr add file2
348
 
$ bzr commit -m 'Add file2 in trunk'
349
 
 
350
 
$ bzr branch . -r 1 ../branch
351
 
$ cd ../branch
352
 
$ echo 'branch content' >file2
353
 
$ bzr add file2
354
 
$ bzr commit -m 'Add file2 in branch'
355
 
 
356
 
$ bzr merge ../trunk
357
 
2>+N  file2
358
 
2>R   file2 => file2.moved
359
 
2>Conflict adding file file2.  Moved existing file to file2.moved.
360
 
2>1 conflicts encountered.
361
 
"""
362
 
 
363
 
    def test_keep_this(self):
364
 
        self.run_script("""
365
 
$ bzr rm file2  --force
366
 
$ bzr mv file2.moved file2
367
 
$ bzr resolve file2
368
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
369
 
""")
370
 
 
371
 
    def test_keep_other(self):
372
 
        self.failIfExists('branch/file2.moved')
373
 
        self.run_script("""
374
 
$ bzr rm file2.moved --force
375
 
$ bzr resolve file2
376
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
377
 
""")
378
 
        self.failIfExists('branch/file2.moved')
379
 
 
380
 
    def test_resolve_taking_this(self):
381
 
        self.run_script("""
382
 
$ bzr resolve --take-this file2
383
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
384
 
""")
385
 
 
386
 
    def test_resolve_taking_other(self):
387
 
        self.run_script("""
388
 
$ bzr resolve --take-other file2
389
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
390
 
""")
 
432
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
 
433
 
 
434
    _conflict_type = conflicts.ContentsConflict,
 
435
    @classmethod
 
436
    def scenarios(klass):
 
437
        base_scenarios = [
 
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')),
 
443
            ]
 
444
        return klass.mirror_scenarios(base_scenarios)
 
445
 
 
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
 
450
 
 
451
 
 
452
 
 
453
class TestResolvePathConflict(TestParametrizedResolveConflicts):
 
454
 
 
455
    _conflict_type = conflicts.PathConflict,
 
456
 
 
457
    @classmethod
 
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',)
 
463
        base_scenarios = [
 
464
            (('file_renamed',
 
465
              dict(actions='rename_file', check='file_renamed')),
 
466
             ('file_deleted',
 
467
              dict(actions='delete_file', check='file_doesnt_exist')),
 
468
             for_file),
 
469
            (('file_renamed',
 
470
              dict(actions='rename_file', check='file_renamed')),
 
471
             ('file_renamed2',
 
472
              dict(actions='rename_file2', check='file_renamed2')),
 
473
             for_file),
 
474
            (('dir_renamed',
 
475
              dict(actions='rename_dir', check='dir_renamed')),
 
476
             ('dir_deleted',
 
477
              dict(actions='delete_dir', check='dir_doesnt_exist')),
 
478
             for_dir),
 
479
            (('dir_renamed',
 
480
              dict(actions='rename_dir', check='dir_renamed')),
 
481
             ('dir_renamed2',
 
482
              dict(actions='rename_dir2', check='dir_renamed2')),
 
483
             for_dir),
 
484
        ]
 
485
        return klass.mirror_scenarios(base_scenarios)
 
486
 
 
487
    def do_delete_file(self):
 
488
        sup = super(TestResolvePathConflict, self).do_delete_file()
 
489
        # PathConflicts handle deletion differently and requires a special
 
490
        # hard-coded value
 
491
        return ('<deleted>',) + sup[1:]
 
492
 
 
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
 
498
 
 
499
 
 
500
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
 
501
    """Same as TestResolvePathConflict but a specific conflict object.
 
502
    """
 
503
 
 
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,
 
509
                                       file_id=None)
 
510
        wt.set_conflicts(conflicts.ConflictList([old_c]))
 
511
 
 
512
 
 
513
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
 
514
 
 
515
    _conflict_type = conflicts.DuplicateEntry,
 
516
    @classmethod
 
517
    def scenarios(klass):
 
518
        base_scenarios = [
 
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')),
 
524
            ]
 
525
        return klass.mirror_scenarios(base_scenarios)
 
526
 
 
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
391
532
 
392
533
 
393
534
class TestResolveUnversionedParent(TestResolveConflicts):
557
698
""")
558
699
 
559
700
 
560
 
class TestResolvePathConflict(TestResolveConflicts):
561
 
 
562
 
    preamble = """
563
 
$ bzr init trunk
564
 
$ cd trunk
565
 
$ echo 'Boo!' >file
566
 
$ bzr add
567
 
$ bzr commit -m 'Create trunk'
568
 
 
569
 
$ bzr mv file file-in-trunk
570
 
$ bzr commit -m 'Renamed to file-in-trunk'
571
 
 
572
 
$ bzr branch . -r 1 ../branch
573
 
$ cd ../branch
574
 
$ bzr mv file file-in-branch
575
 
$ bzr commit -m 'Renamed to file-in-branch'
576
 
 
577
 
$ bzr merge ../trunk
578
 
2>R   file-in-branch => file-in-trunk
579
 
2>Path conflict: file-in-branch / file-in-trunk
580
 
2>1 conflicts encountered.
581
 
"""
582
 
 
583
 
    def test_keep_source(self):
584
 
        self.run_script("""
585
 
$ bzr resolve file-in-trunk
586
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
587
 
""")
588
 
 
589
 
    def test_keep_target(self):
590
 
        self.run_script("""
591
 
$ bzr mv file-in-trunk file-in-branch
592
 
$ bzr resolve file-in-branch
593
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
594
 
""")
595
 
 
596
 
    def test_resolve_taking_this(self):
597
 
        self.run_script("""
598
 
$ bzr resolve --take-this file-in-branch
599
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
600
 
""")
601
 
 
602
 
    def test_resolve_taking_other(self):
603
 
        self.run_script("""
604
 
$ bzr resolve --take-other file-in-branch
605
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
606
 
""")
607
 
 
608
 
 
609
 
class TestResolveParentLoop(TestResolveConflicts):
 
701
class TestResolveParentLoop(TestParametrizedResolveConflicts):
 
702
 
 
703
    _conflict_type = conflicts.ParentLoop,
 
704
    @classmethod
 
705
    def scenarios(klass):
 
706
        base_scenarios = [
 
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')),
 
717
            ]
 
718
        return klass.mirror_scenarios(base_scenarios)
 
719
 
 
720
    def do_create_dir1_dir2(self):
 
721
        return (None, None,
 
722
                [('add', ('dir1', 'dir1-id', 'directory', '')),
 
723
                 ('add', ('dir2', 'dir2-id', 'directory', '')),
 
724
                 ])
 
725
 
 
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'))])
 
729
 
 
730
    def check_dir1_moved(self):
 
731
        self.failIfExists('branch/dir1')
 
732
        self.failUnlessExists('branch/dir2/dir1')
 
733
 
 
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'))])
 
737
 
 
738
    def check_dir2_moved(self):
 
739
        self.failIfExists('branch/dir2')
 
740
        self.failUnlessExists('branch/dir1/dir2')
 
741
 
 
742
    def do_create_dir1_4(self):
 
743
        return (None, None,
 
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', '')),
 
748
                 ])
 
749
 
 
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'))])
 
754
 
 
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')
 
759
 
 
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'))])
 
764
 
 
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')
 
769
 
 
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)
 
776
        return path
 
777
 
 
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
 
791
        # anyway)
 
792
    _assert_conflict = assertParentLoop
 
793
 
 
794
 
 
795
class OldTestResolveParentLoop(TestResolveConflicts):
610
796
 
611
797
    preamble = """
612
798
$ bzr init trunk
624
810
$ bzr commit -m 'Moved dir1 into dir2'
625
811
 
626
812
$ bzr merge ../trunk
627
 
2>Conflict moving dir2/dir1 into dir2.  Cancelled move.
 
813
2>Conflict moving dir2 into dir2/dir1. Cancelled move.
628
814
2>1 conflicts encountered.
629
815
"""
630
816