/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_conflicts.py

  • Committer: Jelmer Vernooij
  • Date: 2010-04-30 11:35:43 UTC
  • mfrom: (5195 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5197.
  • Revision ID: jelmer@samba.org-20100430113543-tiqqhmqa3d8no4iu
merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
215
215
    pass
216
216
 
217
217
 
 
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
 
218
237
# FIXME: Get rid of parametrized (in the class name) once we delete
219
238
# TestResolveConflicts -- vila 20100308
220
239
class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
221
240
    """This class provides a base to test single conflict resolution.
222
241
 
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.
 
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.
236
285
    """
237
286
 
238
287
    # Set by daughter classes
241
290
 
242
291
    # Set by load_tests
243
292
    _base_actions = None
244
 
    _this_actions = None
245
 
    _other_actions = None
246
 
    _item_path = None
247
 
    _item_id = None
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):
 
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
        """
290
322
        # Only concrete classes return actual scenarios
291
323
        return []
292
324
 
299
331
        builder.build_snapshot('start', None, [
300
332
                ('add', ('', 'root-id', 'directory', ''))])
301
333
        # Add a minimal base content
302
 
        _, _, actions_base = self._get_actions(self._actions_base)()
303
 
        builder.build_snapshot('base', ['start'], actions_base)
 
334
        base_actions = self._get_actions(self._base_actions)()
 
335
        builder.build_snapshot('base', ['start'], base_actions)
304
336
        # Modify the base content in branch
305
 
        (self._other_path, self._other_id,
306
 
         actions_other) = self._get_actions(self._actions_other)()
 
337
        actions_other = self._get_actions(self._other['actions'])()
307
338
        builder.build_snapshot('other', ['base'], actions_other)
308
339
        # Modify the base content in trunk
309
 
        (self._this_path, self._this_id,
310
 
         actions_this) = self._get_actions(self._actions_this)()
 
340
        actions_this = self._get_actions(self._this['actions'])()
311
341
        builder.build_snapshot('this', ['base'], actions_this)
312
342
        # builder.get_branch() tip is now 'this'
313
343
 
320
350
    def _get_check(self, name):
321
351
        return getattr(self, 'check_%s' % name)
322
352
 
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', ''))])
346
 
 
347
 
    def do_modify_file(self):
348
 
        return ('file', 'file-id',
349
 
                [('modify', ('file-id', 'trunk content\nmore content\n'))])
350
 
 
351
 
    def check_file_has_more_content(self):
352
 
        self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
353
 
 
354
 
    def do_delete_file(self):
355
 
        return ('file', 'file-id', [('unversion', 'file-id')])
356
 
 
357
 
    def check_file_doesnt_exist(self):
358
 
        self.failIfExists('branch/file')
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
 
 
374
 
    def do_rename_dir(self):
375
 
        return ('new-dir', 'dir-id', [('rename', ('dir', 'new-dir'))])
376
 
 
377
 
    def check_dir_renamed(self):
378
 
        self.failIfExists('branch/dir')
379
 
        self.failUnlessExists('branch/new-dir')
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
 
 
388
 
    def do_delete_dir(self):
389
 
        return ('<deleted>', 'dir-id', [('unversion', 'dir-id')])
390
 
 
391
 
    def check_dir_doesnt_exist(self):
392
 
        self.failIfExists('branch/dir')
393
 
 
394
353
    def _merge_other_into_this(self):
395
354
        b = self.builder.get_branch()
396
355
        wt = b.bzrdir.sprout('branch').open_workingtree()
405
364
        self._assert_conflict(wt, c)
406
365
 
407
366
    def _get_resolve_path_arg(self, wt, action):
408
 
        return self._item_path
 
367
        raise NotImplementedError(self._get_resolve_path_arg)
409
368
 
410
369
    def check_resolved(self, wt, action):
411
370
        path = self._get_resolve_path_arg(wt, action)
418
377
        wt = self._merge_other_into_this()
419
378
        self.assertConflict(wt)
420
379
        self.check_resolved(wt, 'take_this')
421
 
        check_this = self._get_check(self._check_this)
 
380
        check_this = self._get_check(self._this['check'])
422
381
        check_this()
423
382
 
424
383
    def test_resolve_taking_other(self):
425
384
        wt = self._merge_other_into_this()
426
385
        self.assertConflict(wt)
427
386
        self.check_resolved(wt, 'take_other')
428
 
        check_other = self._get_check(self._check_other)
 
387
        check_other = self._get_check(self._other['check'])
429
388
        check_other()
430
389
 
431
390
 
432
391
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
433
392
 
434
393
    _conflict_type = conflicts.ContentsConflict,
435
 
    @classmethod
436
 
    def scenarios(klass):
 
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():
437
402
        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')),
 
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')),),
443
410
            ]
444
 
        return klass.mirror_scenarios(base_scenarios)
 
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
445
430
 
446
431
    def assertContentsConflict(self, wt, c):
447
 
        self.assertEqual(self._other_id, c.file_id)
448
 
        self.assertEqual(self._other_path, c.path)
 
432
        self.assertEqual(self._file_id, c.file_id)
 
433
        self.assertEqual(self._path, c.path)
449
434
    _assert_conflict = assertContentsConflict
450
435
 
451
436
 
452
 
 
453
437
class TestResolvePathConflict(TestParametrizedResolveConflicts):
454
438
 
455
439
    _conflict_type = conflicts.PathConflict,
456
440
 
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',)
 
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
463
449
        base_scenarios = [
464
 
            (('file_renamed',
465
 
              dict(actions='rename_file', check='file_renamed')),
 
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')),
466
455
             ('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')),
 
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')),
471
465
             ('file_renamed2',
472
 
              dict(actions='rename_file2', check='file_renamed2')),
473
 
             for_file),
474
 
            (('dir_renamed',
475
 
              dict(actions='rename_dir', check='dir_renamed')),
 
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')),
476
473
             ('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')),
 
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')),
481
483
             ('dir_renamed2',
482
 
              dict(actions='rename_dir2', check='dir_renamed2')),
483
 
             for_dir),
 
484
              dict(actions='rename_dir2', check='dir_renamed2',
 
485
                   path='new-dir2', file_id='dir-id')),),
484
486
        ]
485
 
        return klass.mirror_scenarios(base_scenarios)
 
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')
486
522
 
487
523
    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:]
 
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
492
543
 
493
544
    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)
 
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)
497
553
    _assert_conflict = assertPathConflict
498
554
 
499
555
 
513
569
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
514
570
 
515
571
    _conflict_type = conflicts.DuplicateEntry,
516
 
    @classmethod
517
 
    def scenarios(klass):
 
572
 
 
573
    @staticmethod
 
574
    def scenarios():
 
575
        # Each side dict additionally defines:
 
576
        # - path involved
 
577
        # - file-id involved
518
578
        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')),
 
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')),),
524
587
            ]
525
 
        return klass.mirror_scenarios(base_scenarios)
 
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']
526
607
 
527
608
    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)
 
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)
531
617
    _assert_conflict = assertDuplicateEntry
532
618
 
533
619
 
701
787
class TestResolveParentLoop(TestParametrizedResolveConflicts):
702
788
 
703
789
    _conflict_type = conflicts.ParentLoop,
704
 
    @classmethod
705
 
    def scenarios(klass):
 
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'
706
801
        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')),
 
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))),
717
818
            ]
718
 
        return klass.mirror_scenarios(base_scenarios)
 
819
        return mirror_scenarios(base_scenarios)
719
820
 
720
821
    def do_create_dir1_dir2(self):
721
 
        return (None, None,
722
 
                [('add', ('dir1', 'dir1-id', 'directory', '')),
723
 
                 ('add', ('dir2', 'dir2-id', 'directory', '')),
724
 
                 ])
 
822
        return [('add', ('dir1', 'dir1-id', 'directory', '')),
 
823
                ('add', ('dir2', 'dir2-id', 'directory', '')),]
725
824
 
726
825
    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'))])
 
826
        return [('rename', ('dir1', 'dir2/dir1'))]
729
827
 
730
828
    def check_dir1_moved(self):
731
829
        self.failIfExists('branch/dir1')
732
830
        self.failUnlessExists('branch/dir2/dir1')
733
831
 
734
832
    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'))])
 
833
        return [('rename', ('dir2', 'dir1/dir2'))]
737
834
 
738
835
    def check_dir2_moved(self):
739
836
        self.failIfExists('branch/dir2')
740
837
        self.failUnlessExists('branch/dir1/dir2')
741
838
 
742
839
    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
 
                 ])
 
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', '')),]
749
844
 
750
845
    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'))])
 
846
        return [('rename', ('dir1', 'dir3/dir4/dir1'))]
754
847
 
755
848
    def check_dir1_2_moved(self):
756
849
        self.failIfExists('branch/dir1')
758
851
        self.failUnlessExists('branch/dir3/dir4/dir1/dir2')
759
852
 
760
853
    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'))])
 
854
        return [('rename', ('dir3', 'dir1/dir2/dir3'))]
764
855
 
765
856
    def check_dir3_4_moved(self):
766
857
        self.failIfExists('branch/dir3')
768
859
        self.failUnlessExists('branch/dir1/dir2/dir3/dir4')
769
860
 
770
861
    def _get_resolve_path_arg(self, wt, action):
771
 
        # ParentLoop is unsual as it says: 
772
 
        # moving <conflict_path> into <path>.  Cancelled move.
 
862
        # ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
773
863
        # 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
 
864
        # <conflict_path> instead, and that, in turn, is given by dir_id. Pfew.
 
865
        return wt.id2path(self._other['dir_id'])
777
866
 
778
867
    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)
 
868
        self.assertEqual(self._other['dir_id'], c.file_id)
 
869
        self.assertEqual(self._other['target_id'], c.conflict_file_id)
789
870
        # The conflict paths are irrelevant (they are deterministic but not
790
871
        # worth checking since they don't provide the needed information
791
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")
792
878
    _assert_conflict = assertParentLoop
793
879
 
794
880
 
795
 
class OldTestResolveParentLoop(TestResolveConflicts):
796
 
 
797
 
    preamble = """
798
 
$ bzr init trunk
799
 
$ cd trunk
800
 
$ bzr mkdir dir1
801
 
$ bzr mkdir dir2
802
 
$ bzr commit -m 'Create trunk'
803
 
 
804
 
$ bzr mv dir2 dir1
805
 
$ bzr commit -m 'Moved dir2 into dir1'
806
 
 
807
 
$ bzr branch . -r 1 ../branch
808
 
$ cd ../branch
809
 
$ bzr mv dir1 dir2
810
 
$ bzr commit -m 'Moved dir1 into dir2'
811
 
 
812
 
$ bzr merge ../trunk
813
 
2>Conflict moving dir2 into dir2/dir1. Cancelled move.
814
 
2>1 conflicts encountered.
815
 
"""
816
 
 
817
 
    def test_take_this(self):
818
 
        self.run_script("""
819
 
$ bzr resolve dir2
820
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
821
 
""")
822
 
 
823
 
    def test_take_other(self):
824
 
        self.run_script("""
825
 
$ bzr mv dir2/dir1 dir1
826
 
$ bzr mv dir2 dir1
827
 
$ bzr resolve dir2
828
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
829
 
""")
830
 
 
831
 
    def test_resolve_taking_this(self):
832
 
        self.run_script("""
833
 
$ bzr resolve --take-this dir2
834
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
835
 
""")
836
 
        self.failUnlessExists('dir2')
837
 
 
838
 
    def test_resolve_taking_other(self):
839
 
        self.run_script("""
840
 
$ bzr resolve --take-other dir2
841
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
842
 
""")
843
 
        self.failUnlessExists('dir1')
844
 
 
845
 
 
846
881
class TestResolveNonDirectoryParent(TestResolveConflicts):
847
882
 
848
883
    preamble = """