212
def resolve_conflict_scenarios():
214
(dict(_conflict_type=conflicts.ContentsConflict,
215
_item_path='file', _item_id='file-id',),
216
('file_modified', dict(actions='modify_file',
217
check='file_has_more_content')),
218
('file_deleted', dict(actions='delete_file',
219
check='file_doesnt_exist'))),
220
(dict(_conflict_type=conflicts.PathConflict,
221
_item_path='new-dir', _item_id='dir-id',),
222
('dir_renamed', dict(actions='rename_dir', check='dir_renamed')),
223
('dir_deleted', dict(actions='delete_dir', check='dir_doesnt_exist'))),
225
# Each base scenario is duplicated switching the roles of this and other
227
for common, (tname, tdict), (oname, odict) in base_scenarios:
229
d.update(_this_actions=tdict['actions'], _check_this=tdict['check'],
230
_other_actions=odict['actions'], _check_other=odict['check'])
231
scenarios.append(('%s,%s' % (tname, oname), d))
233
d.update(_this_actions=odict['actions'], _check_this=odict['check'],
234
_other_actions=tdict['actions'], _check_other=tdict['check'])
235
scenarios.append(('%s,%s' % (oname, tname), d))
238
# FIXME: Get rid of parametrized once we delete TestResolveConflicts
218
# FIXME: Get rid of parametrized (in the class name) once we delete
219
# TestResolveConflicts -- vila 20100308
239
220
class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
222
# Set by daughter classes
223
_conflict_type = None
224
_assert_conflict = None
241
226
# Set by load_tests
242
228
_this_actions = None
243
229
_other_actions = None
244
_conflict_type = None
245
230
_item_path = None
233
# Set by _this_actions and other_actions
240
def mirror_scenarios(klass, base_scenarios):
243
"""Modify dict to apply to the given side.
245
'actions' key is turned into '_actions_this' if side is 'this' for
249
# Turn each key into _side_key
250
for k,v in d.iteritems():
251
t['_%s_%s' % (k, side)] = v
253
# Each base scenario is duplicated switching the roles of 'this' and
255
left = [l for l, r, c in base_scenarios]
256
right = [r for l, r, c in base_scenarios]
257
common = [c for l, r, c in base_scenarios]
258
for (lname, ldict), (rname, rdict), common in zip(left, right, common):
259
a = tests.multiply_scenarios([(lname, adapt(ldict, 'this'))],
260
[(rname, adapt(rdict, 'other'))])
261
b = tests.multiply_scenarios(
262
[(rname, adapt(rdict, 'this'))],
263
[(lname, adapt(ldict, 'other'))])
264
# Inject the common parameters in all scenarios
265
for name, d in a + b:
267
scenarios.extend(a + b)
271
def scenarios(klass):
272
# Only concrete classes return actual scenarios
249
276
super(TestParametrizedResolveConflicts, self).setUp()
250
277
builder = self.make_branch_builder('trunk')
251
278
builder.start_series()
252
280
# Create an empty trunk
253
281
builder.build_snapshot('start', None, [
254
282
('add', ('', 'root-id', 'directory', ''))])
255
283
# Add a minimal base content
256
builder.build_snapshot(
258
('add', ('file', 'file-id', 'file', 'trunk content\n')),
259
('add', ('dir', 'dir-id', 'directory', '')),
284
_, _, actions_base = self._get_actions(self._actions_base)()
285
builder.build_snapshot('base', ['start'], actions_base)
261
286
# Modify the base content in branch
262
other_actions = self._get_actions(self._other_actions)
263
builder.build_snapshot('other', ['base'], other_actions())
287
(self._other_path, self._other_id,
288
actions_other) = self._get_actions(self._actions_other)()
289
builder.build_snapshot('other', ['base'], actions_other)
264
290
# Modify the base content in trunk
265
this_actions = self._get_actions(self._this_actions)
266
builder.build_snapshot('this', ['base'], this_actions())
291
(self._this_path, self._this_id,
292
actions_this) = self._get_actions(self._actions_this)()
293
builder.build_snapshot('this', ['base'], actions_this)
294
# builder.get_branch() tip is now 'this'
267
296
builder.finish_series()
268
297
self.builder = builder
273
302
def _get_check(self, name):
274
303
return getattr(self, 'check_%s' % name)
276
def assertConflict(self, wt, **kwargs):
305
def assertConflict(self, wt):
277
306
confs = wt.conflicts()
278
307
self.assertLength(1, confs)
280
309
self.assertIsInstance(c, self._conflict_type)
281
sentinel = object() # An impossible value
282
for k, v in kwargs.iteritems():
283
self.assertEqual(v, getattr(c, k, sentinel), "for key '%s'" % k)
310
self._assert_conflict(wt, c)
285
def check_resolved(self, wt, item, action):
286
conflicts.resolve(wt, [item], action=action)
312
def check_resolved(self, wt, path, action):
313
conflicts.resolve(wt, [path], action=action)
287
314
# Check that we don't have any conflicts nor unknown left
288
315
self.assertLength(0, wt.conflicts())
289
316
self.assertLength(0, list(wt.unknowns()))
318
def do_create_file(self):
319
return ('file', 'file-id',
320
[('add', ('file', 'file-id', 'file', 'trunk content\n'))])
322
def do_create_dir(self):
323
return ('dir', 'dir-id', [('add', ('dir', 'dir-id', 'directory', ''))])
291
325
def do_modify_file(self):
292
return [('modify', ('file-id', 'trunk content\nmore content\n'))]
326
return ('file', 'file-id',
327
[('modify', ('file-id', 'trunk content\nmore content\n'))])
294
329
def check_file_has_more_content(self):
295
330
self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
297
332
def do_delete_file(self):
298
return [('unversion', 'file-id')]
333
return ('file', 'file-id', [('unversion', 'file-id')])
300
335
def check_file_doesnt_exist(self):
301
336
self.failIfExists('branch/file')
338
def do_rename_file(self):
339
return ('new-file', 'file-id', [('rename', ('file', 'new-file'))])
341
def check_file_renamed(self):
342
self.failIfExists('branch/file')
343
self.failUnlessExists('branch/new-file')
303
345
def do_rename_dir(self):
304
return [('rename', ('dir', 'new-dir'))]
346
return ('new-dir', 'dir-id', [('rename', ('dir', 'new-dir'))])
306
348
def check_dir_renamed(self):
307
349
self.failIfExists('branch/dir')
308
350
self.failUnlessExists('branch/new-dir')
352
def do_rename_dir2(self):
353
return ('new-dir2', 'dir-id', [('rename', ('dir', 'new-dir2'))])
355
def check_dir_renamed2(self):
356
self.failIfExists('branch/dir')
357
self.failUnlessExists('branch/new-dir2')
310
359
def do_delete_dir(self):
311
return [('unversion', 'dir-id')]
360
return ('<deleted>', 'dir-id', [('unversion', 'dir-id')])
313
362
def check_dir_doesnt_exist(self):
314
363
self.failIfExists('branch/dir')
322
371
def test_resolve_taking_this(self):
323
372
wt = self._merge_other_into_this()
324
self.assertConflict(wt, path=self._item_path, file_id=self._item_id)
373
self.assertConflict(wt)
325
374
self.check_resolved(wt, self._item_path, 'take_this')
326
375
check_this = self._get_check(self._check_this)
329
378
def test_resolve_taking_other(self):
330
379
wt = self._merge_other_into_this()
331
self.assertConflict(wt, path=self._item_path, file_id=self._item_id)
380
self.assertConflict(wt)
332
381
self.check_resolved(wt, self._item_path, 'take_other')
333
382
check_other = self._get_check(self._check_other)
386
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
388
_conflict_type = conflicts.ContentsConflict,
390
def scenarios(klass):
391
common = dict(_actions_base='create_file',
392
_item_path='file', item_id='file-id',
395
(('file_modified', dict(actions='modify_file',
396
check='file_has_more_content')),
397
('file_deleted', dict(actions='delete_file',
398
check='file_doesnt_exist')),
399
dict(_actions_base='create_file',
400
_item_path='file', item_id='file-id',)),
402
return klass.mirror_scenarios(base_scenarios)
404
def assertContentsConflict(self, wt, c):
405
self.assertEqual(self._other_id, c.file_id)
406
self.assertEqual(self._other_path, c.path)
407
_assert_conflict = assertContentsConflict
411
class TestResolvePathConflict(TestParametrizedResolveConflicts):
413
_conflict_type = conflicts.PathConflict,
416
def scenarios(klass):
417
for_dirs = dict(_actions_base='create_dir',
418
_item_path='new-dir', _item_id='dir-id',)
421
dict(actions='rename_file', check='file_renamed')),
423
dict(actions='delete_file', check='file_doesnt_exist')),
424
dict(_actions_base='create_file',
425
_item_path='new-file', _item_id='file-id',)),
427
dict(actions='rename_dir', check='dir_renamed')),
429
dict(actions='delete_dir', check='dir_doesnt_exist')),
432
dict(actions='rename_dir', check='dir_renamed')),
434
dict(actions='rename_dir2', check='dir_renamed2')),
437
return klass.mirror_scenarios(base_scenarios)
439
def do_delete_file(self):
440
sup = super(TestResolvePathConflict, self).do_delete_file()
441
# PathConflicts handle deletion differently and requires a special
443
return ('<deleted>',) + sup[1:]
445
def assertPathConflict(self, wt, c):
446
self.assertEqual(self._item_id, c.file_id)
447
self.assertEqual(self._this_path, c.path)
448
self.assertEqual(self._other_path, c.conflict_path)
449
_assert_conflict = assertPathConflict
452
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
453
"""Same as TestResolvePathConflict but a specific conflict object.
456
def assertPathConflict(self, c):
457
# We create a conflict object as it was created before the fix and
458
# inject it into the working tree, the test will exercise the
459
# compatibility code.
460
old_c = conflicts.PathConflict('<deleted>', self._item_path,
462
wt.set_conflicts(conflicts.ConflictList([c]))
337
465
class TestResolveDuplicateEntry(TestResolveConflicts):