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',
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',
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.
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.
229
From there, both resolutions are tested (--take-this and --take-other).
231
Each conflict type use its attributes in a specific way, so each class
232
should define a specific _assert_conflict method.
234
Since the resolution change the working tree state, each action should
235
define an associated check.
238
# Set by daughter classes
239
_conflict_type = None
240
_assert_conflict = None
228
242
# Set by load_tests
245
_other_actions = None
249
# Set by _this_actions and other_actions
256
def mirror_scenarios(klass, base_scenarios):
259
"""Modify dict to apply to the given side.
261
'actions' key is turned into '_actions_this' if side is 'this' for
265
# Turn each key into _side_key
266
for k,v in d.iteritems():
267
t['_%s_%s' % (k, side)] = v
269
# Each base scenario is duplicated switching the roles of 'this' and
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:
283
scenarios.extend(a + b)
287
def scenarios(klass):
288
# Only concrete classes return actual scenarios
233
super(TestResolveContentConflicts, self).setUp()
292
super(TestParametrizedResolveConflicts, self).setUp()
234
293
builder = self.make_branch_builder('trunk')
235
294
builder.start_series()
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'
248
312
builder.finish_series()
249
313
self.builder = builder
254
318
def _get_check(self, name):
255
319
return getattr(self, 'check_%s' % name)
321
def assertConflict(self, wt):
322
confs = wt.conflicts()
323
self.assertLength(1, confs)
325
self.assertIsInstance(c, self._conflict_type)
326
self._assert_conflict(wt, c)
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()))
334
def do_create_file(self):
335
return ('file', 'file-id',
336
[('add', ('file', 'file-id', 'file', 'trunk content\n'))])
338
def do_create_dir(self):
339
return ('dir', 'dir-id', [('add', ('dir', 'dir-id', 'directory', ''))])
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'))])
260
345
def check_file_has_more_content(self):
261
346
self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
263
348
def do_delete_file(self):
264
return [('unversion', 'file-id')]
349
return ('file', 'file-id', [('unversion', 'file-id')])
266
351
def check_file_doesnt_exist(self):
267
352
self.failIfExists('branch/file')
354
def do_rename_file(self):
355
return ('new-file', 'file-id', [('rename', ('file', 'new-file'))])
357
def check_file_renamed(self):
358
self.failIfExists('branch/file')
359
self.failUnlessExists('branch/new-file')
361
def do_rename_dir(self):
362
return ('new-dir', 'dir-id', [('rename', ('dir', 'new-dir'))])
364
def check_dir_renamed(self):
365
self.failIfExists('branch/dir')
366
self.failUnlessExists('branch/new-dir')
368
def do_rename_dir2(self):
369
return ('new-dir2', 'dir-id', [('rename', ('dir', 'new-dir2'))])
371
def check_dir_renamed2(self):
372
self.failIfExists('branch/dir')
373
self.failUnlessExists('branch/new-dir2')
375
def do_delete_dir(self):
376
return ('<deleted>', 'dir-id', [('unversion', 'dir-id')])
378
def check_dir_doesnt_exist(self):
379
self.failIfExists('branch/dir')
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')
275
def assertConflict(self, wt, ctype, **kwargs):
276
confs = wt.conflicts()
277
self.assertLength(1, confs)
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))
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()))
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)
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)
402
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
404
_conflict_type = conflicts.ContentsConflict,
406
def scenarios(klass):
407
common = dict(_actions_base='create_file',
408
_item_path='file', item_id='file-id',
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',)),
418
return klass.mirror_scenarios(base_scenarios)
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
427
class TestResolvePathConflict(TestParametrizedResolveConflicts):
429
_conflict_type = conflicts.PathConflict,
432
def scenarios(klass):
433
for_dirs = dict(_actions_base='create_dir',
434
_item_path='new-dir', _item_id='dir-id',)
437
dict(actions='rename_file', check='file_renamed')),
439
dict(actions='delete_file', check='file_doesnt_exist')),
440
dict(_actions_base='create_file',
441
_item_path='new-file', _item_id='file-id',)),
443
dict(actions='rename_dir', check='dir_renamed')),
445
dict(actions='delete_dir', check='dir_doesnt_exist')),
448
dict(actions='rename_dir', check='dir_renamed')),
450
dict(actions='rename_dir2', check='dir_renamed2')),
453
return klass.mirror_scenarios(base_scenarios)
455
def do_delete_file(self):
456
sup = super(TestResolvePathConflict, self).do_delete_file()
457
# PathConflicts handle deletion differently and requires a special
459
return ('<deleted>',) + sup[1:]
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
468
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
469
"""Same as TestResolvePathConflict but a specific conflict object.
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,
478
wt.set_conflicts(conflicts.ConflictList([old_c]))
307
481
class TestResolveDuplicateEntry(TestResolveConflicts):