/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:03:59 UTC
  • mto: This revision was merged to the branch mainline in revision 5197.
  • Revision ID: jelmer@samba.org-20100430110359-ow3e3grh7sxy93pa
Remove more unused imports.

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
 
 
237
218
# FIXME: Get rid of parametrized (in the class name) once we delete
238
219
# TestResolveConflicts -- vila 20100308
239
220
class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
240
221
    """This class provides a base to test single conflict resolution.
241
222
 
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.
 
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.
285
236
    """
286
237
 
287
238
    # Set by daughter classes
290
241
 
291
242
    # Set by load_tests
292
243
    _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
 
        """
 
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):
322
290
        # Only concrete classes return actual scenarios
323
291
        return []
324
292
 
331
299
        builder.build_snapshot('start', None, [
332
300
                ('add', ('', 'root-id', 'directory', ''))])
333
301
        # Add a minimal base content
334
 
        base_actions = self._get_actions(self._base_actions)()
335
 
        builder.build_snapshot('base', ['start'], base_actions)
 
302
        _, _, actions_base = self._get_actions(self._actions_base)()
 
303
        builder.build_snapshot('base', ['start'], actions_base)
336
304
        # Modify the base content in branch
337
 
        actions_other = self._get_actions(self._other['actions'])()
 
305
        (self._other_path, self._other_id,
 
306
         actions_other) = self._get_actions(self._actions_other)()
338
307
        builder.build_snapshot('other', ['base'], actions_other)
339
308
        # Modify the base content in trunk
340
 
        actions_this = self._get_actions(self._this['actions'])()
 
309
        (self._this_path, self._this_id,
 
310
         actions_this) = self._get_actions(self._actions_this)()
341
311
        builder.build_snapshot('this', ['base'], actions_this)
342
312
        # builder.get_branch() tip is now 'this'
343
313
 
350
320
    def _get_check(self, name):
351
321
        return getattr(self, 'check_%s' % name)
352
322
 
 
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
 
353
394
    def _merge_other_into_this(self):
354
395
        b = self.builder.get_branch()
355
396
        wt = b.bzrdir.sprout('branch').open_workingtree()
364
405
        self._assert_conflict(wt, c)
365
406
 
366
407
    def _get_resolve_path_arg(self, wt, action):
367
 
        raise NotImplementedError(self._get_resolve_path_arg)
 
408
        return self._item_path
368
409
 
369
410
    def check_resolved(self, wt, action):
370
411
        path = self._get_resolve_path_arg(wt, action)
377
418
        wt = self._merge_other_into_this()
378
419
        self.assertConflict(wt)
379
420
        self.check_resolved(wt, 'take_this')
380
 
        check_this = self._get_check(self._this['check'])
 
421
        check_this = self._get_check(self._check_this)
381
422
        check_this()
382
423
 
383
424
    def test_resolve_taking_other(self):
384
425
        wt = self._merge_other_into_this()
385
426
        self.assertConflict(wt)
386
427
        self.check_resolved(wt, 'take_other')
387
 
        check_other = self._get_check(self._other['check'])
 
428
        check_other = self._get_check(self._check_other)
388
429
        check_other()
389
430
 
390
431
 
391
432
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
392
433
 
393
434
    _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():
 
435
    @classmethod
 
436
    def scenarios(klass):
402
437
        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')),),
 
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')),
410
443
            ]
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
 
444
        return klass.mirror_scenarios(base_scenarios)
430
445
 
431
446
    def assertContentsConflict(self, wt, c):
432
 
        self.assertEqual(self._file_id, c.file_id)
433
 
        self.assertEqual(self._path, c.path)
 
447
        self.assertEqual(self._other_id, c.file_id)
 
448
        self.assertEqual(self._other_path, c.path)
434
449
    _assert_conflict = assertContentsConflict
435
450
 
436
451
 
 
452
 
437
453
class TestResolvePathConflict(TestParametrizedResolveConflicts):
438
454
 
439
455
    _conflict_type = conflicts.PathConflict,
440
456
 
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
 
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',)
449
463
        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')),
 
464
            (('file_renamed',
 
465
              dict(actions='rename_file', check='file_renamed')),
455
466
             ('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')),
 
467
              dict(actions='delete_file', check='file_doesnt_exist')),
 
468
             for_file),
 
469
            (('file_renamed',
 
470
              dict(actions='rename_file', check='file_renamed')),
465
471
             ('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')),
 
472
              dict(actions='rename_file2', check='file_renamed2')),
 
473
             for_file),
 
474
            (('dir_renamed',
 
475
              dict(actions='rename_dir', check='dir_renamed')),
473
476
             ('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')),
 
477
              dict(actions='delete_dir', check='dir_doesnt_exist')),
 
478
             for_dir),
 
479
            (('dir_renamed',
 
480
              dict(actions='rename_dir', check='dir_renamed')),
483
481
             ('dir_renamed2',
484
 
              dict(actions='rename_dir2', check='dir_renamed2',
485
 
                   path='new-dir2', file_id='dir-id')),),
 
482
              dict(actions='rename_dir2', check='dir_renamed2')),
 
483
             for_dir),
486
484
        ]
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')
 
485
        return klass.mirror_scenarios(base_scenarios)
522
486
 
523
487
    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
 
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:]
543
492
 
544
493
    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)
 
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)
553
497
    _assert_conflict = assertPathConflict
554
498
 
555
499
 
569
513
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
570
514
 
571
515
    _conflict_type = conflicts.DuplicateEntry,
572
 
 
573
 
    @staticmethod
574
 
    def scenarios():
575
 
        # Each side dict additionally defines:
576
 
        # - path involved
577
 
        # - file-id involved
 
516
    @classmethod
 
517
    def scenarios(klass):
578
518
        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')),),
 
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')),
587
524
            ]
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']
 
525
        return klass.mirror_scenarios(base_scenarios)
607
526
 
608
527
    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)
 
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)
617
531
    _assert_conflict = assertDuplicateEntry
618
532
 
619
533
 
787
701
class TestResolveParentLoop(TestParametrizedResolveConflicts):
788
702
 
789
703
    _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'
 
704
    @classmethod
 
705
    def scenarios(klass):
801
706
        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))),
 
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')),
818
717
            ]
819
 
        return mirror_scenarios(base_scenarios)
 
718
        return klass.mirror_scenarios(base_scenarios)
820
719
 
821
720
    def do_create_dir1_dir2(self):
822
 
        return [('add', ('dir1', 'dir1-id', 'directory', '')),
823
 
                ('add', ('dir2', 'dir2-id', 'directory', '')),]
 
721
        return (None, None,
 
722
                [('add', ('dir1', 'dir1-id', 'directory', '')),
 
723
                 ('add', ('dir2', 'dir2-id', 'directory', '')),
 
724
                 ])
824
725
 
825
726
    def do_move_dir1_into_dir2(self):
826
 
        return [('rename', ('dir1', 'dir2/dir1'))]
 
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'))])
827
729
 
828
730
    def check_dir1_moved(self):
829
731
        self.failIfExists('branch/dir1')
830
732
        self.failUnlessExists('branch/dir2/dir1')
831
733
 
832
734
    def do_move_dir2_into_dir1(self):
833
 
        return [('rename', ('dir2', 'dir1/dir2'))]
 
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'))])
834
737
 
835
738
    def check_dir2_moved(self):
836
739
        self.failIfExists('branch/dir2')
837
740
        self.failUnlessExists('branch/dir1/dir2')
838
741
 
839
742
    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', '')),]
 
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
                 ])
844
749
 
845
750
    def do_move_dir1_into_dir4(self):
846
 
        return [('rename', ('dir1', 'dir3/dir4/dir1'))]
 
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'))])
847
754
 
848
755
    def check_dir1_2_moved(self):
849
756
        self.failIfExists('branch/dir1')
851
758
        self.failUnlessExists('branch/dir3/dir4/dir1/dir2')
852
759
 
853
760
    def do_move_dir3_into_dir2(self):
854
 
        return [('rename', ('dir3', 'dir1/dir2/dir3'))]
 
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'))])
855
764
 
856
765
    def check_dir3_4_moved(self):
857
766
        self.failIfExists('branch/dir3')
859
768
        self.failUnlessExists('branch/dir1/dir2/dir3/dir4')
860
769
 
861
770
    def _get_resolve_path_arg(self, wt, action):
862
 
        # ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
 
771
        # ParentLoop is unsual as it says: 
 
772
        # moving <conflict_path> into <path>.  Cancelled move.
863
773
        # 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'])
 
774
        # <conflict_path> instead
 
775
        path = wt.id2path(self._other_id)
 
776
        return path
866
777
 
867
778
    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)
 
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)
870
789
        # The conflict paths are irrelevant (they are deterministic but not
871
790
        # worth checking since they don't provide the needed information
872
791
        # 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
792
    _assert_conflict = assertParentLoop
879
793
 
880
794
 
 
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
 
881
846
class TestResolveNonDirectoryParent(TestResolveConflicts):
882
847
 
883
848
    preamble = """