~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_conflicts.py

  • Committer: Martin Pool
  • Date: 2010-03-15 06:54:44 UTC
  • mto: This revision was merged to the branch mainline in revision 5095.
  • Revision ID: mbp@canonical.com-20100315065444-gfs7vp8te4ez5rc9
Fix typo in ReadVFile.readline (thanks mnordhoff)

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
 
35
35
    sp_tests, remaining_tests = tests.split_suite_by_condition(
36
36
        standard_tests, tests.condition_isinstance((
37
 
                TestParametrizedResolveConflicts,
 
37
                TestResolveContentConflicts,
38
38
                )))
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)
 
39
    tests.multiply_tests(sp_tests, content_conflict_scenarios(), result)
44
40
 
45
41
    # No parametrization for the remaining tests
46
42
    result.addTests(remaining_tests)
198
194
# FIXME: The shell-like tests should be converted to real whitebox tests... or
199
195
# moved to a blackbox module -- vila 20100205
200
196
 
201
 
# FIXME: test missing for multiple conflicts
202
 
 
203
197
# FIXME: Tests missing for DuplicateID conflict type
204
198
class TestResolveConflicts(script.TestCaseWithTransportAndScript):
205
199
 
215
209
    pass
216
210
 
217
211
 
218
 
def mirror_scenarios(base_scenarios):
219
 
    """Return a list of mirrored scenarios.
220
 
 
221
 
    Each scenario in base_scenarios is duplicated switching the roles of 'this'
222
 
    and 'other'
223
 
    """
224
 
    scenarios = []
225
 
    for common, (lname, ldict), (rname, rdict) in base_scenarios:
226
 
        a = tests.multiply_scenarios([(lname, dict(_this=ldict))],
227
 
                                     [(rname, dict(_other=rdict))])
228
 
        b = tests.multiply_scenarios([(rname, dict(_this=rdict))],
229
 
                                     [(lname, dict(_other=ldict))])
230
 
        # Inject the common parameters in all scenarios
231
 
        for name, d in a + b:
232
 
            d.update(common)
233
 
        scenarios.extend(a + b)
234
 
    return scenarios
235
 
 
236
 
 
237
 
# FIXME: Get rid of parametrized (in the class name) once we delete
238
 
# TestResolveConflicts -- vila 20100308
239
 
class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
240
 
    """This class provides a base to test single conflict resolution.
241
 
 
242
 
    Since all conflict objects are created with specific semantics for their
243
 
    attributes, each class should implement the necessary functions and
244
 
    attributes described below.
245
 
 
246
 
    Each class should define the scenarios that create the expected (single)
247
 
    conflict.
248
 
 
249
 
    Each scenario describes:
250
 
    * how to create 'base' tree (and revision)
251
 
    * how to create 'left' tree (and revision, parent rev 'base')
252
 
    * how to create 'right' tree (and revision, parent rev 'base')
253
 
    * how to check that changes in 'base'->'left' have been taken
254
 
    * how to check that changes in 'base'->'right' have been taken
255
 
 
256
 
    From each base scenario, we generate two concrete scenarios where:
257
 
    * this=left, other=right
258
 
    * this=right, other=left
259
 
 
260
 
    Then the test case verifies each concrete scenario by:
261
 
    * creating a branch containing the 'base', 'this' and 'other' revisions
262
 
    * creating a working tree for the 'this' revision
263
 
    * performing the merge of 'other' into 'this'
264
 
    * verifying the expected conflict was generated
265
 
    * resolving with --take-this or --take-other, and running the corresponding
266
 
      checks (for either 'base'->'this', or 'base'->'other')
267
 
 
268
 
    :cvar _conflict_type: The expected class of the generated conflict.
269
 
 
270
 
    :cvar _assert_conflict: A method receiving the working tree and the
271
 
        conflict object and checking its attributes.
272
 
 
273
 
    :cvar _base_actions: The branchbuilder actions to create the 'base'
274
 
        revision.
275
 
 
276
 
    :cvar _this: The dict related to 'base' -> 'this'. It contains at least:
277
 
      * 'actions': The branchbuilder actions to create the 'this'
278
 
          revision.
279
 
      * 'check': how to check the changes after resolution with --take-this.
280
 
 
281
 
    :cvar _other: The dict related to 'base' -> 'other'. It contains at least:
282
 
      * 'actions': The branchbuilder actions to create the 'other'
283
 
          revision.
284
 
      * 'check': how to check the changes after resolution with --take-other.
285
 
    """
286
 
 
287
 
    # Set by daughter classes
288
 
    _conflict_type = None
289
 
    _assert_conflict = None
 
212
def content_conflict_scenarios():
 
213
    return [('file,None', dict(_this_actions='modify_file',
 
214
                               _check_this='file_has_more_content',
 
215
                               _other_actions='delete_file',
 
216
                               _check_other='file_doesnt_exist',
 
217
                               )),
 
218
            ('None,file', dict(_this_actions='delete_file',
 
219
                               _check_this='file_doesnt_exist',
 
220
                               _other_actions='modify_file',
 
221
                               _check_other='file_has_more_content',
 
222
                               )),
 
223
            ]
 
224
 
 
225
 
 
226
class TestResolveContentConflicts(tests.TestCaseWithTransport):
290
227
 
291
228
    # Set by load_tests
292
 
    _base_actions = None
293
 
    _this = None
294
 
    _other = None
295
 
 
296
 
    @staticmethod
297
 
    def scenarios():
298
 
        """Return the scenario list for the conflict type defined by the class.
299
 
 
300
 
        Each scenario is of the form:
301
 
        (common, (left_name, left_dict), (right_name, right_dict))
302
 
 
303
 
        * common is a dict
304
 
 
305
 
        * left_name and right_name are the scenario names that will be combined
306
 
 
307
 
        * left_dict and right_dict are the attributes specific to each half of
308
 
          the scenario. They should include at least 'actions' and 'check' and
309
 
          will be available as '_this' and '_other' test instance attributes.
310
 
 
311
 
        Daughters classes are free to add their specific attributes as they see
312
 
        fit in any of the three dicts.
313
 
 
314
 
        This is a class method so that load_tests can find it.
315
 
 
316
 
        '_base_actions' in the common dict, 'actions' and 'check' in the left
317
 
        and right dicts use names that map to methods in the test classes. Some
318
 
        prefixes are added to these names to get the correspong methods (see
319
 
        _get_actions() and _get_check()). The motivation here is to avoid
320
 
        collisions in the class namespace.
321
 
        """
322
 
        # Only concrete classes return actual scenarios
323
 
        return []
 
229
    this_actions = None
 
230
    other_actions = None
324
231
 
325
232
    def setUp(self):
326
 
        super(TestParametrizedResolveConflicts, self).setUp()
 
233
        super(TestResolveContentConflicts, self).setUp()
327
234
        builder = self.make_branch_builder('trunk')
328
235
        builder.start_series()
329
 
 
330
236
        # Create an empty trunk
331
237
        builder.build_snapshot('start', None, [
332
238
                ('add', ('', 'root-id', 'directory', ''))])
333
239
        # Add a minimal base content
334
 
        base_actions = self._get_actions(self._base_actions)()
335
 
        builder.build_snapshot('base', ['start'], base_actions)
 
240
        builder.build_snapshot('base', ['start'], [
 
241
                ('add', ('file', 'file-id', 'file', 'trunk content\n'))])
336
242
        # Modify the base content in branch
337
 
        actions_other = self._get_actions(self._other['actions'])()
338
 
        builder.build_snapshot('other', ['base'], actions_other)
 
243
        other_actions = self._get_actions(self._other_actions)
 
244
        builder.build_snapshot('other', ['base'], other_actions())
339
245
        # Modify the base content in trunk
340
 
        actions_this = self._get_actions(self._this['actions'])()
341
 
        builder.build_snapshot('this', ['base'], actions_this)
342
 
        # builder.get_branch() tip is now 'this'
343
 
 
 
246
        this_actions = self._get_actions(self._this_actions)
 
247
        builder.build_snapshot('this', ['base'], this_actions())
344
248
        builder.finish_series()
345
249
        self.builder = builder
346
250
 
350
254
    def _get_check(self, name):
351
255
        return getattr(self, 'check_%s' % name)
352
256
 
 
257
    def do_modify_file(self):
 
258
        return [('modify', ('file-id', 'trunk content\nmore content\n'))]
 
259
 
 
260
    def check_file_has_more_content(self):
 
261
        self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
 
262
 
 
263
    def do_delete_file(self):
 
264
        return [('unversion', 'file-id')]
 
265
 
 
266
    def check_file_doesnt_exist(self):
 
267
        self.failIfExists('branch/file')
 
268
 
353
269
    def _merge_other_into_this(self):
354
270
        b = self.builder.get_branch()
355
271
        wt = b.bzrdir.sprout('branch').open_workingtree()
356
272
        wt.merge_from_branch(b, 'other')
357
273
        return wt
358
274
 
359
 
    def assertConflict(self, wt):
 
275
    def assertConflict(self, wt, ctype, **kwargs):
360
276
        confs = wt.conflicts()
361
277
        self.assertLength(1, confs)
362
278
        c = confs[0]
363
 
        self.assertIsInstance(c, self._conflict_type)
364
 
        self._assert_conflict(wt, c)
365
 
 
366
 
    def _get_resolve_path_arg(self, wt, action):
367
 
        raise NotImplementedError(self._get_resolve_path_arg)
368
 
 
369
 
    def check_resolved(self, wt, action):
370
 
        path = self._get_resolve_path_arg(wt, action)
371
 
        conflicts.resolve(wt, [path], action=action)
 
279
        self.assertIsInstance(c, ctype)
 
280
        sentinel = object() # An impossible value
 
281
        for k, v in kwargs.iteritems():
 
282
            self.assertEqual(v, getattr(c, k, sentinel))
 
283
 
 
284
    def check_resolved(self, wt, item, action):
 
285
        conflicts.resolve(wt, [item], action=action)
372
286
        # Check that we don't have any conflicts nor unknown left
373
287
        self.assertLength(0, wt.conflicts())
374
288
        self.assertLength(0, list(wt.unknowns()))
375
289
 
376
290
    def test_resolve_taking_this(self):
377
291
        wt = self._merge_other_into_this()
378
 
        self.assertConflict(wt)
379
 
        self.check_resolved(wt, 'take_this')
380
 
        check_this = self._get_check(self._this['check'])
 
292
        self.assertConflict(wt, conflicts.ContentsConflict,
 
293
                            path='file', file_id='file-id',)
 
294
        self.check_resolved(wt, 'file', 'take_this')
 
295
        check_this = self._get_check(self._check_this)
381
296
        check_this()
382
297
 
383
298
    def test_resolve_taking_other(self):
384
299
        wt = self._merge_other_into_this()
385
 
        self.assertConflict(wt)
386
 
        self.check_resolved(wt, 'take_other')
387
 
        check_other = self._get_check(self._other['check'])
 
300
        self.assertConflict(wt, conflicts.ContentsConflict,
 
301
                            path='file', file_id='file-id',)
 
302
        self.check_resolved(wt, 'file', 'take_other')
 
303
        check_other = self._get_check(self._check_other)
388
304
        check_other()
389
305
 
390
306
 
391
 
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
392
 
 
393
 
    _conflict_type = conflicts.ContentsConflict,
394
 
 
395
 
    # Set by load_tests from scenarios()
396
 
    # path and file-id for the file involved in the conflict
397
 
    _path = None
398
 
    _file_id = None
399
 
 
400
 
    @staticmethod
401
 
    def scenarios():
402
 
        base_scenarios = [
403
 
            # File modified/deleted
404
 
            (dict(_base_actions='create_file',
405
 
                  _path='file', _file_id='file-id'),
406
 
             ('file_modified',
407
 
              dict(actions='modify_file', check='file_has_more_content')),
408
 
             ('file_deleted',
409
 
              dict(actions='delete_file', check='file_doesnt_exist')),),
410
 
            ]
411
 
        return mirror_scenarios(base_scenarios)
412
 
 
413
 
    def do_create_file(self):
414
 
        return [('add', ('file', 'file-id', 'file', 'trunk content\n'))]
415
 
 
416
 
    def do_modify_file(self):
417
 
        return [('modify', ('file-id', 'trunk content\nmore content\n'))]
418
 
 
419
 
    def check_file_has_more_content(self):
420
 
        self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
421
 
 
422
 
    def do_delete_file(self):
423
 
        return [('unversion', 'file-id')]
424
 
 
425
 
    def check_file_doesnt_exist(self):
426
 
        self.failIfExists('branch/file')
427
 
 
428
 
    def _get_resolve_path_arg(self, wt, action):
429
 
        return self._path
430
 
 
431
 
    def assertContentsConflict(self, wt, c):
432
 
        self.assertEqual(self._file_id, c.file_id)
433
 
        self.assertEqual(self._path, c.path)
434
 
    _assert_conflict = assertContentsConflict
435
 
 
436
 
 
437
 
class TestResolvePathConflict(TestParametrizedResolveConflicts):
438
 
 
439
 
    _conflict_type = conflicts.PathConflict,
440
 
 
441
 
    def do_nothing(self):
442
 
        return []
443
 
 
444
 
    @staticmethod
445
 
    def scenarios():
446
 
        # Each side dict additionally defines:
447
 
        # - path path involved (can be '<deleted>')
448
 
        # - file-id involved
449
 
        base_scenarios = [
450
 
            # File renamed/deleted
451
 
            (dict(_base_actions='create_file'),
452
 
             ('file_renamed',
453
 
              dict(actions='rename_file', check='file_renamed',
454
 
                   path='new-file', file_id='file-id')),
455
 
             ('file_deleted',
456
 
              dict(actions='delete_file', check='file_doesnt_exist',
457
 
                   # PathConflicts deletion handling requires a special
458
 
                   # hard-coded value
459
 
                   path='<deleted>', file_id='file-id')),),
460
 
            # File renamed/renamed differently
461
 
            (dict(_base_actions='create_file'),
462
 
             ('file_renamed',
463
 
              dict(actions='rename_file', check='file_renamed',
464
 
                   path='new-file', file_id='file-id')),
465
 
             ('file_renamed2',
466
 
              dict(actions='rename_file2', check='file_renamed2',
467
 
                   path='new-file2', file_id='file-id')),),
468
 
            # Dir renamed/deleted
469
 
            (dict(_base_actions='create_dir'),
470
 
             ('dir_renamed',
471
 
              dict(actions='rename_dir', check='dir_renamed',
472
 
                   path='new-dir', file_id='dir-id')),
473
 
             ('dir_deleted',
474
 
              dict(actions='delete_dir', check='dir_doesnt_exist',
475
 
                   # PathConflicts deletion handling requires a special
476
 
                   # hard-coded value
477
 
                   path='<deleted>', file_id='dir-id')),),
478
 
            # Dir renamed/renamed differently
479
 
            (dict(_base_actions='create_dir'),
480
 
             ('dir_renamed',
481
 
              dict(actions='rename_dir', check='dir_renamed',
482
 
                   path='new-dir', file_id='dir-id')),
483
 
             ('dir_renamed2',
484
 
              dict(actions='rename_dir2', check='dir_renamed2',
485
 
                   path='new-dir2', file_id='dir-id')),),
486
 
        ]
487
 
        return mirror_scenarios(base_scenarios)
488
 
 
489
 
    def do_create_file(self):
490
 
        return [('add', ('file', 'file-id', 'file', 'trunk content\n'))]
491
 
 
492
 
    def do_create_dir(self):
493
 
        return [('add', ('dir', 'dir-id', 'directory', ''))]
494
 
 
495
 
    def do_rename_file(self):
496
 
        return [('rename', ('file', 'new-file'))]
497
 
 
498
 
    def check_file_renamed(self):
499
 
        self.failIfExists('branch/file')
500
 
        self.failUnlessExists('branch/new-file')
501
 
 
502
 
    def do_rename_file2(self):
503
 
        return [('rename', ('file', 'new-file2'))]
504
 
 
505
 
    def check_file_renamed2(self):
506
 
        self.failIfExists('branch/file')
507
 
        self.failUnlessExists('branch/new-file2')
508
 
 
509
 
    def do_rename_dir(self):
510
 
        return [('rename', ('dir', 'new-dir'))]
511
 
 
512
 
    def check_dir_renamed(self):
513
 
        self.failIfExists('branch/dir')
514
 
        self.failUnlessExists('branch/new-dir')
515
 
 
516
 
    def do_rename_dir2(self):
517
 
        return [('rename', ('dir', 'new-dir2'))]
518
 
 
519
 
    def check_dir_renamed2(self):
520
 
        self.failIfExists('branch/dir')
521
 
        self.failUnlessExists('branch/new-dir2')
522
 
 
523
 
    def do_delete_file(self):
524
 
        return [('unversion', 'file-id')]
525
 
 
526
 
    def check_file_doesnt_exist(self):
527
 
        self.failIfExists('branch/file')
528
 
 
529
 
    def do_delete_dir(self):
530
 
        return [('unversion', 'dir-id')]
531
 
 
532
 
    def check_dir_doesnt_exist(self):
533
 
        self.failIfExists('branch/dir')
534
 
 
535
 
    def _get_resolve_path_arg(self, wt, action):
536
 
        tpath = self._this['path']
537
 
        opath = self._other['path']
538
 
        if tpath == '<deleted>':
539
 
            path = opath
540
 
        else:
541
 
            path = tpath
542
 
        return path
543
 
 
544
 
    def assertPathConflict(self, wt, c):
545
 
        tpath = self._this['path']
546
 
        tfile_id = self._this['file_id']
547
 
        opath = self._other['path']
548
 
        ofile_id = self._other['file_id']
549
 
        self.assertEqual(tfile_id, ofile_id) # Sanity check
550
 
        self.assertEqual(tfile_id, c.file_id)
551
 
        self.assertEqual(tpath, c.path)
552
 
        self.assertEqual(opath, c.conflict_path)
553
 
    _assert_conflict = assertPathConflict
554
 
 
555
 
 
556
 
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
557
 
    """Same as TestResolvePathConflict but a specific conflict object.
558
 
    """
559
 
 
560
 
    def assertPathConflict(self, c):
561
 
        # We create a conflict object as it was created before the fix and
562
 
        # inject it into the working tree, the test will exercise the
563
 
        # compatibility code.
564
 
        old_c = conflicts.PathConflict('<deleted>', self._item_path,
565
 
                                       file_id=None)
566
 
        wt.set_conflicts(conflicts.ConflictList([old_c]))
567
 
 
568
 
 
569
 
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
570
 
 
571
 
    _conflict_type = conflicts.DuplicateEntry,
572
 
 
573
 
    @staticmethod
574
 
    def scenarios():
575
 
        # Each side dict additionally defines:
576
 
        # - path involved
577
 
        # - file-id involved
578
 
        base_scenarios = [
579
 
            # File created with different file-ids
580
 
            (dict(_base_actions='nothing'),
581
 
             ('filea_created',
582
 
              dict(actions='create_file_a', check='file_content_a',
583
 
                   path='file', file_id='file-a-id')),
584
 
             ('fileb_created',
585
 
              dict(actions='create_file_b', check='file_content_b',
586
 
                   path='file', file_id='file-b-id')),),
587
 
            ]
588
 
        return mirror_scenarios(base_scenarios)
589
 
 
590
 
    def do_nothing(self):
591
 
        return []
592
 
 
593
 
    def do_create_file_a(self):
594
 
        return [('add', ('file', 'file-a-id', 'file', 'file a content\n'))]
595
 
 
596
 
    def check_file_content_a(self):
597
 
        self.assertFileEqual('file a content\n', 'branch/file')
598
 
 
599
 
    def do_create_file_b(self):
600
 
        return [('add', ('file', 'file-b-id', 'file', 'file b content\n'))]
601
 
 
602
 
    def check_file_content_b(self):
603
 
        self.assertFileEqual('file b content\n', 'branch/file')
604
 
 
605
 
    def _get_resolve_path_arg(self, wt, action):
606
 
        return self._this['path']
607
 
 
608
 
    def assertDuplicateEntry(self, wt, c):
609
 
        tpath = self._this['path']
610
 
        tfile_id = self._this['file_id']
611
 
        opath = self._other['path']
612
 
        ofile_id = self._other['file_id']
613
 
        self.assertEqual(tpath, opath) # Sanity check
614
 
        self.assertEqual(tfile_id, c.file_id)
615
 
        self.assertEqual(tpath + '.moved', c.path)
616
 
        self.assertEqual(tpath, c.conflict_path)
617
 
    _assert_conflict = assertDuplicateEntry
 
307
class TestResolveDuplicateEntry(TestResolveConflicts):
 
308
 
 
309
    preamble = """
 
310
$ bzr init trunk
 
311
$ cd trunk
 
312
$ echo 'trunk content' >file
 
313
$ bzr add file
 
314
$ bzr commit -m 'Create trunk'
 
315
 
 
316
$ echo 'trunk content too' >file2
 
317
$ bzr add file2
 
318
$ bzr commit -m 'Add file2 in trunk'
 
319
 
 
320
$ bzr branch . -r 1 ../branch
 
321
$ cd ../branch
 
322
$ echo 'branch content' >file2
 
323
$ bzr add file2
 
324
$ bzr commit -m 'Add file2 in branch'
 
325
 
 
326
$ bzr merge ../trunk
 
327
2>+N  file2
 
328
2>R   file2 => file2.moved
 
329
2>Conflict adding file file2.  Moved existing file to file2.moved.
 
330
2>1 conflicts encountered.
 
331
"""
 
332
 
 
333
    def test_keep_this(self):
 
334
        self.run_script("""
 
335
$ bzr rm file2  --force
 
336
$ bzr mv file2.moved file2
 
337
$ bzr resolve file2
 
338
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
339
""")
 
340
 
 
341
    def test_keep_other(self):
 
342
        self.failIfExists('branch/file2.moved')
 
343
        self.run_script("""
 
344
$ bzr rm file2.moved --force
 
345
$ bzr resolve file2
 
346
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
347
""")
 
348
        self.failIfExists('branch/file2.moved')
 
349
 
 
350
    def test_resolve_taking_this(self):
 
351
        self.run_script("""
 
352
$ bzr resolve --take-this file2
 
353
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
354
""")
 
355
 
 
356
    def test_resolve_taking_other(self):
 
357
        self.run_script("""
 
358
$ bzr resolve --take-other file2
 
359
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
360
""")
618
361
 
619
362
 
620
363
class TestResolveUnversionedParent(TestResolveConflicts):
784
527
""")
785
528
 
786
529
 
787
 
class TestResolveParentLoop(TestParametrizedResolveConflicts):
788
 
 
789
 
    _conflict_type = conflicts.ParentLoop,
790
 
 
791
 
    _this_args = None
792
 
    _other_args = None
793
 
 
794
 
    @staticmethod
795
 
    def scenarios():
796
 
        # Each side dict additionally defines:
797
 
        # - dir_id: the directory being moved
798
 
        # - target_id: The target directory
799
 
        # - xfail: whether the test is expected to fail if the action is
800
 
        #     involved as 'other'
801
 
        base_scenarios = [
802
 
            # Dirs moved into each other
803
 
            (dict(_base_actions='create_dir1_dir2'),
804
 
             ('dir1_into_dir2',
805
 
              dict(actions='move_dir1_into_dir2', check='dir1_moved',
806
 
                   dir_id='dir1-id', target_id='dir2-id', xfail=False)),
807
 
             ('dir2_into_dir1',
808
 
              dict(actions='move_dir2_into_dir1', check='dir2_moved',
809
 
                   dir_id='dir2-id', target_id='dir1-id', xfail=False))),
810
 
            # Subdirs moved into each other
811
 
            (dict(_base_actions='create_dir1_4'),
812
 
             ('dir1_into_dir4',
813
 
              dict(actions='move_dir1_into_dir4', check='dir1_2_moved',
814
 
                   dir_id='dir1-id', target_id='dir4-id', xfail=True)),
815
 
             ('dir3_into_dir2',
816
 
              dict(actions='move_dir3_into_dir2', check='dir3_4_moved',
817
 
                   dir_id='dir3-id', target_id='dir2-id', xfail=True))),
818
 
            ]
819
 
        return mirror_scenarios(base_scenarios)
820
 
 
821
 
    def do_create_dir1_dir2(self):
822
 
        return [('add', ('dir1', 'dir1-id', 'directory', '')),
823
 
                ('add', ('dir2', 'dir2-id', 'directory', '')),]
824
 
 
825
 
    def do_move_dir1_into_dir2(self):
826
 
        return [('rename', ('dir1', 'dir2/dir1'))]
827
 
 
828
 
    def check_dir1_moved(self):
829
 
        self.failIfExists('branch/dir1')
830
 
        self.failUnlessExists('branch/dir2/dir1')
831
 
 
832
 
    def do_move_dir2_into_dir1(self):
833
 
        return [('rename', ('dir2', 'dir1/dir2'))]
834
 
 
835
 
    def check_dir2_moved(self):
836
 
        self.failIfExists('branch/dir2')
837
 
        self.failUnlessExists('branch/dir1/dir2')
838
 
 
839
 
    def do_create_dir1_4(self):
840
 
        return [('add', ('dir1', 'dir1-id', 'directory', '')),
841
 
                ('add', ('dir1/dir2', 'dir2-id', 'directory', '')),
842
 
                ('add', ('dir3', 'dir3-id', 'directory', '')),
843
 
                ('add', ('dir3/dir4', 'dir4-id', 'directory', '')),]
844
 
 
845
 
    def do_move_dir1_into_dir4(self):
846
 
        return [('rename', ('dir1', 'dir3/dir4/dir1'))]
847
 
 
848
 
    def check_dir1_2_moved(self):
849
 
        self.failIfExists('branch/dir1')
850
 
        self.failUnlessExists('branch/dir3/dir4/dir1')
851
 
        self.failUnlessExists('branch/dir3/dir4/dir1/dir2')
852
 
 
853
 
    def do_move_dir3_into_dir2(self):
854
 
        return [('rename', ('dir3', 'dir1/dir2/dir3'))]
855
 
 
856
 
    def check_dir3_4_moved(self):
857
 
        self.failIfExists('branch/dir3')
858
 
        self.failUnlessExists('branch/dir1/dir2/dir3')
859
 
        self.failUnlessExists('branch/dir1/dir2/dir3/dir4')
860
 
 
861
 
    def _get_resolve_path_arg(self, wt, action):
862
 
        # ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
863
 
        # But since <path> doesn't exist in the working tree, we need to use
864
 
        # <conflict_path> instead, and that, in turn, is given by dir_id. Pfew.
865
 
        return wt.id2path(self._other['dir_id'])
866
 
 
867
 
    def assertParentLoop(self, wt, c):
868
 
        self.assertEqual(self._other['dir_id'], c.file_id)
869
 
        self.assertEqual(self._other['target_id'], c.conflict_file_id)
870
 
        # The conflict paths are irrelevant (they are deterministic but not
871
 
        # worth checking since they don't provide the needed information
872
 
        # anyway)
873
 
        if self._other['xfail']:
874
 
            # It's a bit hackish to raise from here relying on being called for
875
 
            # both tests but this avoid overriding test_resolve_taking_other
876
 
            raise tests.KnownFailure(
877
 
                "ParentLoop doesn't carry enough info to resolve --take-other")
878
 
    _assert_conflict = assertParentLoop
 
530
class TestResolvePathConflict(TestResolveConflicts):
 
531
 
 
532
    preamble = """
 
533
$ bzr init trunk
 
534
$ cd trunk
 
535
$ echo 'Boo!' >file
 
536
$ bzr add
 
537
$ bzr commit -m 'Create trunk'
 
538
 
 
539
$ bzr mv file file-in-trunk
 
540
$ bzr commit -m 'Renamed to file-in-trunk'
 
541
 
 
542
$ bzr branch . -r 1 ../branch
 
543
$ cd ../branch
 
544
$ bzr mv file file-in-branch
 
545
$ bzr commit -m 'Renamed to file-in-branch'
 
546
 
 
547
$ bzr merge ../trunk
 
548
2>R   file-in-branch => file-in-trunk
 
549
2>Path conflict: file-in-branch / file-in-trunk
 
550
2>1 conflicts encountered.
 
551
"""
 
552
 
 
553
    def test_keep_source(self):
 
554
        self.run_script("""
 
555
$ bzr resolve file-in-trunk
 
556
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
557
""")
 
558
 
 
559
    def test_keep_target(self):
 
560
        self.run_script("""
 
561
$ bzr mv file-in-trunk file-in-branch
 
562
$ bzr resolve file-in-branch
 
563
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
564
""")
 
565
 
 
566
    def test_resolve_taking_this(self):
 
567
        self.run_script("""
 
568
$ bzr resolve --take-this file-in-branch
 
569
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
570
""")
 
571
 
 
572
    def test_resolve_taking_other(self):
 
573
        self.run_script("""
 
574
$ bzr resolve --take-other file-in-branch
 
575
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
576
""")
 
577
 
 
578
 
 
579
class TestResolveParentLoop(TestResolveConflicts):
 
580
 
 
581
    preamble = """
 
582
$ bzr init trunk
 
583
$ cd trunk
 
584
$ bzr mkdir dir1
 
585
$ bzr mkdir dir2
 
586
$ bzr commit -m 'Create trunk'
 
587
 
 
588
$ bzr mv dir2 dir1
 
589
$ bzr commit -m 'Moved dir2 into dir1'
 
590
 
 
591
$ bzr branch . -r 1 ../branch
 
592
$ cd ../branch
 
593
$ bzr mv dir1 dir2
 
594
$ bzr commit -m 'Moved dir1 into dir2'
 
595
 
 
596
$ bzr merge ../trunk
 
597
2>Conflict moving dir2/dir1 into dir2.  Cancelled move.
 
598
2>1 conflicts encountered.
 
599
"""
 
600
 
 
601
    def test_take_this(self):
 
602
        self.run_script("""
 
603
$ bzr resolve dir2
 
604
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
605
""")
 
606
 
 
607
    def test_take_other(self):
 
608
        self.run_script("""
 
609
$ bzr mv dir2/dir1 dir1
 
610
$ bzr mv dir2 dir1
 
611
$ bzr resolve dir2
 
612
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
613
""")
 
614
 
 
615
    def test_resolve_taking_this(self):
 
616
        self.run_script("""
 
617
$ bzr resolve --take-this dir2
 
618
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
619
""")
 
620
        self.failUnlessExists('dir2')
 
621
 
 
622
    def test_resolve_taking_other(self):
 
623
        self.run_script("""
 
624
$ bzr resolve --take-other dir2
 
625
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
626
""")
 
627
        self.failUnlessExists('dir1')
879
628
 
880
629
 
881
630
class TestResolveNonDirectoryParent(TestResolveConflicts):