~parthm/bzr/no-chown-if-bzrlog-exists

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_conflicts.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-03-25 09:38:23 UTC
  • mfrom: (5112.1.1 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20100325093823-kwkh6crnkc0xfxh6
(vila) Better PathConflict resolution when a deletion is involved

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
 
                TestResolveContentConflicts,
 
37
                TestParametrizedResolveConflicts,
38
38
                )))
39
 
    tests.multiply_tests(sp_tests, content_conflict_scenarios(), result)
 
39
    # Each test class defines its own scenarios. This is needed for
 
40
    # TestResolvePathConflictBefore531967 that verifies that the same tests as
 
41
    # TestResolvePathConflict still pass.
 
42
    for test in tests.iter_suite_tests(sp_tests):
 
43
        tests.apply_scenarios(test, test.scenarios(), result)
40
44
 
41
45
    # No parametrization for the remaining tests
42
46
    result.addTests(remaining_tests)
194
198
# FIXME: The shell-like tests should be converted to real whitebox tests... or
195
199
# moved to a blackbox module -- vila 20100205
196
200
 
 
201
# FIXME: test missing for multiple conflicts
 
202
 
197
203
# FIXME: Tests missing for DuplicateID conflict type
198
204
class TestResolveConflicts(script.TestCaseWithTransportAndScript):
199
205
 
209
215
    pass
210
216
 
211
217
 
212
 
def 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):
 
218
# FIXME: Get rid of parametrized (in the class name) once we delete
 
219
# TestResolveConflicts -- vila 20100308
 
220
class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
 
221
    """This class provides a base to test single conflict resolution.
 
222
 
 
223
    The aim is to define scenarios in daughter classes (one for each conflict
 
224
    type) that create a single conflict object when one branch is merged in
 
225
    another (and vice versa). Each class can define as many scenarios as
 
226
    needed. Each scenario should define a couple of actions that will be
 
227
    swapped to define the sibling scenarios.
 
228
 
 
229
    From there, both resolutions are tested (--take-this and --take-other).
 
230
 
 
231
    Each conflict type use its attributes in a specific way, so each class 
 
232
    should define a specific _assert_conflict method.
 
233
 
 
234
    Since the resolution change the working tree state, each action should
 
235
    define an associated check.
 
236
    """
 
237
 
 
238
    # Set by daughter classes
 
239
    _conflict_type = None
 
240
    _assert_conflict = None
227
241
 
228
242
    # Set by load_tests
229
 
    this_actions = None
230
 
    other_actions = None
 
243
    _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
    _this_path = None
 
251
    _this_id = None
 
252
    _other_path = None
 
253
    _other_id = None
 
254
 
 
255
    @classmethod
 
256
    def mirror_scenarios(klass, base_scenarios):
 
257
        scenarios = []
 
258
        def adapt(d, side):
 
259
            """Modify dict to apply to the given side.
 
260
 
 
261
            'actions' key is turned into '_actions_this' if side is 'this' for
 
262
            example.
 
263
            """
 
264
            t = {}
 
265
            # Turn each key into _side_key
 
266
            for k,v in d.iteritems():
 
267
                t['_%s_%s' % (k, side)] = v
 
268
            return t
 
269
        # Each base scenario is duplicated switching the roles of 'this' and
 
270
        # 'other'
 
271
        left = [l for l, r, c in base_scenarios]
 
272
        right = [r for l, r, c in base_scenarios]
 
273
        common = [c for l, r, c in base_scenarios]
 
274
        for (lname, ldict), (rname, rdict), common in zip(left, right, common):
 
275
            a = tests.multiply_scenarios([(lname, adapt(ldict, 'this'))],
 
276
                                         [(rname, adapt(rdict, 'other'))])
 
277
            b = tests.multiply_scenarios(
 
278
                    [(rname, adapt(rdict, 'this'))],
 
279
                    [(lname, adapt(ldict, 'other'))])
 
280
            # Inject the common parameters in all scenarios
 
281
            for name, d in a + b:
 
282
                d.update(common)
 
283
            scenarios.extend(a + b)
 
284
        return scenarios
 
285
 
 
286
    @classmethod
 
287
    def scenarios(klass):
 
288
        # Only concrete classes return actual scenarios
 
289
        return []
231
290
 
232
291
    def setUp(self):
233
 
        super(TestResolveContentConflicts, self).setUp()
 
292
        super(TestParametrizedResolveConflicts, self).setUp()
234
293
        builder = self.make_branch_builder('trunk')
235
294
        builder.start_series()
 
295
 
236
296
        # Create an empty trunk
237
297
        builder.build_snapshot('start', None, [
238
298
                ('add', ('', 'root-id', 'directory', ''))])
239
299
        # Add a minimal base content
240
 
        builder.build_snapshot('base', ['start'], [
241
 
                ('add', ('file', 'file-id', 'file', 'trunk content\n'))])
 
300
        _, _, actions_base = self._get_actions(self._actions_base)()
 
301
        builder.build_snapshot('base', ['start'], actions_base)
242
302
        # Modify the base content in branch
243
 
        other_actions = self._get_actions(self._other_actions)
244
 
        builder.build_snapshot('other', ['base'], other_actions())
 
303
        (self._other_path, self._other_id,
 
304
         actions_other) = self._get_actions(self._actions_other)()
 
305
        builder.build_snapshot('other', ['base'], actions_other)
245
306
        # Modify the base content in trunk
246
 
        this_actions = self._get_actions(self._this_actions)
247
 
        builder.build_snapshot('this', ['base'], this_actions())
 
307
        (self._this_path, self._this_id,
 
308
         actions_this) = self._get_actions(self._actions_this)()
 
309
        builder.build_snapshot('this', ['base'], actions_this)
 
310
        # builder.get_branch() tip is now 'this'
 
311
 
248
312
        builder.finish_series()
249
313
        self.builder = builder
250
314
 
254
318
    def _get_check(self, name):
255
319
        return getattr(self, 'check_%s' % name)
256
320
 
 
321
    def assertConflict(self, wt):
 
322
        confs = wt.conflicts()
 
323
        self.assertLength(1, confs)
 
324
        c = confs[0]
 
325
        self.assertIsInstance(c, self._conflict_type)
 
326
        self._assert_conflict(wt, c)
 
327
 
 
328
    def check_resolved(self, wt, path, action):
 
329
        conflicts.resolve(wt, [path], action=action)
 
330
        # Check that we don't have any conflicts nor unknown left
 
331
        self.assertLength(0, wt.conflicts())
 
332
        self.assertLength(0, list(wt.unknowns()))
 
333
 
 
334
    def do_create_file(self):
 
335
        return ('file', 'file-id',
 
336
                [('add', ('file', 'file-id', 'file', 'trunk content\n'))])
 
337
 
 
338
    def do_create_dir(self):
 
339
        return ('dir', 'dir-id', [('add', ('dir', 'dir-id', 'directory', ''))])
 
340
 
257
341
    def do_modify_file(self):
258
 
        return [('modify', ('file-id', 'trunk content\nmore content\n'))]
 
342
        return ('file', 'file-id',
 
343
                [('modify', ('file-id', 'trunk content\nmore content\n'))])
259
344
 
260
345
    def check_file_has_more_content(self):
261
346
        self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
262
347
 
263
348
    def do_delete_file(self):
264
 
        return [('unversion', 'file-id')]
 
349
        return ('file', 'file-id', [('unversion', 'file-id')])
265
350
 
266
351
    def check_file_doesnt_exist(self):
267
352
        self.failIfExists('branch/file')
268
353
 
 
354
    def do_rename_file(self):
 
355
        return ('new-file', 'file-id', [('rename', ('file', 'new-file'))])
 
356
 
 
357
    def check_file_renamed(self):
 
358
        self.failIfExists('branch/file')
 
359
        self.failUnlessExists('branch/new-file')
 
360
 
 
361
    def do_rename_dir(self):
 
362
        return ('new-dir', 'dir-id', [('rename', ('dir', 'new-dir'))])
 
363
 
 
364
    def check_dir_renamed(self):
 
365
        self.failIfExists('branch/dir')
 
366
        self.failUnlessExists('branch/new-dir')
 
367
 
 
368
    def do_rename_dir2(self):
 
369
        return ('new-dir2', 'dir-id', [('rename', ('dir', 'new-dir2'))])
 
370
 
 
371
    def check_dir_renamed2(self):
 
372
        self.failIfExists('branch/dir')
 
373
        self.failUnlessExists('branch/new-dir2')
 
374
 
 
375
    def do_delete_dir(self):
 
376
        return ('<deleted>', 'dir-id', [('unversion', 'dir-id')])
 
377
 
 
378
    def check_dir_doesnt_exist(self):
 
379
        self.failIfExists('branch/dir')
 
380
 
269
381
    def _merge_other_into_this(self):
270
382
        b = self.builder.get_branch()
271
383
        wt = b.bzrdir.sprout('branch').open_workingtree()
272
384
        wt.merge_from_branch(b, 'other')
273
385
        return wt
274
386
 
275
 
    def assertConflict(self, wt, ctype, **kwargs):
276
 
        confs = wt.conflicts()
277
 
        self.assertLength(1, confs)
278
 
        c = confs[0]
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)
286
 
        # Check that we don't have any conflicts nor unknown left
287
 
        self.assertLength(0, wt.conflicts())
288
 
        self.assertLength(0, list(wt.unknowns()))
289
 
 
290
387
    def test_resolve_taking_this(self):
291
388
        wt = self._merge_other_into_this()
292
 
        self.assertConflict(wt, conflicts.ContentsConflict,
293
 
                            path='file', file_id='file-id',)
294
 
        self.check_resolved(wt, 'file', 'take_this')
 
389
        self.assertConflict(wt)
 
390
        self.check_resolved(wt, self._item_path, 'take_this')
295
391
        check_this = self._get_check(self._check_this)
296
392
        check_this()
297
393
 
298
394
    def test_resolve_taking_other(self):
299
395
        wt = self._merge_other_into_this()
300
 
        self.assertConflict(wt, conflicts.ContentsConflict,
301
 
                            path='file', file_id='file-id',)
302
 
        self.check_resolved(wt, 'file', 'take_other')
 
396
        self.assertConflict(wt)
 
397
        self.check_resolved(wt, self._item_path, 'take_other')
303
398
        check_other = self._get_check(self._check_other)
304
399
        check_other()
305
400
 
306
401
 
 
402
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
 
403
 
 
404
    _conflict_type = conflicts.ContentsConflict,
 
405
    @classmethod
 
406
    def scenarios(klass):
 
407
        common = dict(_actions_base='create_file',
 
408
                      _item_path='file', item_id='file-id',
 
409
                      )
 
410
        base_scenarios = [
 
411
            (('file_modified', dict(actions='modify_file',
 
412
                                   check='file_has_more_content')),
 
413
             ('file_deleted', dict(actions='delete_file',
 
414
                                   check='file_doesnt_exist')),
 
415
             dict(_actions_base='create_file',
 
416
                  _item_path='file', item_id='file-id',)),
 
417
            ]
 
418
        return klass.mirror_scenarios(base_scenarios)
 
419
 
 
420
    def assertContentsConflict(self, wt, c):
 
421
        self.assertEqual(self._other_id, c.file_id)
 
422
        self.assertEqual(self._other_path, c.path)
 
423
    _assert_conflict = assertContentsConflict
 
424
 
 
425
 
 
426
 
 
427
class TestResolvePathConflict(TestParametrizedResolveConflicts):
 
428
 
 
429
    _conflict_type = conflicts.PathConflict,
 
430
 
 
431
    @classmethod
 
432
    def scenarios(klass):
 
433
        for_dirs = dict(_actions_base='create_dir',
 
434
                        _item_path='new-dir', _item_id='dir-id',)
 
435
        base_scenarios = [
 
436
            (('file_renamed',
 
437
              dict(actions='rename_file', check='file_renamed')),
 
438
             ('file_deleted',
 
439
              dict(actions='delete_file', check='file_doesnt_exist')),
 
440
             dict(_actions_base='create_file',
 
441
                  _item_path='new-file', _item_id='file-id',)),
 
442
            (('dir_renamed',
 
443
              dict(actions='rename_dir', check='dir_renamed')),
 
444
             ('dir_deleted',
 
445
              dict(actions='delete_dir', check='dir_doesnt_exist')),
 
446
             for_dirs),
 
447
            (('dir_renamed',
 
448
              dict(actions='rename_dir', check='dir_renamed')),
 
449
             ('dir_renamed2',
 
450
              dict(actions='rename_dir2', check='dir_renamed2')),
 
451
             for_dirs),
 
452
        ]
 
453
        return klass.mirror_scenarios(base_scenarios)
 
454
 
 
455
    def do_delete_file(self):
 
456
        sup = super(TestResolvePathConflict, self).do_delete_file()
 
457
        # PathConflicts handle deletion differently and requires a special
 
458
        # hard-coded value
 
459
        return ('<deleted>',) + sup[1:]
 
460
 
 
461
    def assertPathConflict(self, wt, c):
 
462
        self.assertEqual(self._item_id, c.file_id)
 
463
        self.assertEqual(self._this_path, c.path)
 
464
        self.assertEqual(self._other_path, c.conflict_path)
 
465
    _assert_conflict = assertPathConflict
 
466
 
 
467
 
 
468
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
 
469
    """Same as TestResolvePathConflict but a specific conflict object.
 
470
    """
 
471
 
 
472
    def assertPathConflict(self, c):
 
473
        # We create a conflict object as it was created before the fix and
 
474
        # inject it into the working tree, the test will exercise the
 
475
        # compatibility code.
 
476
        old_c = conflicts.PathConflict('<deleted>', self._item_path,
 
477
                                       file_id=None)
 
478
        wt.set_conflicts(conflicts.ConflictList([old_c]))
 
479
 
 
480
 
307
481
class TestResolveDuplicateEntry(TestResolveConflicts):
308
482
 
309
483
    preamble = """
527
701
""")
528
702
 
529
703
 
530
 
class TestResolvePathConflict(TestResolveConflicts):
 
704
class OldTestResolvePathConflict(TestResolveConflicts):
531
705
 
532
706
    preamble = """
533
707
$ bzr init trunk