~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_conflicts.py

  • Committer: mbp at sourcefrog
  • Date: 2005-03-09 06:49:00 UTC
  • Revision ID: mbp@sourcefrog.net-20050309064900-74935ffb7350b24b
import more files from baz

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
2
 
#
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2 of the License, or
6
 
# (at your option) any later version.
7
 
#
8
 
# This program is distributed in the hope that it will be useful,
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
# GNU General Public License for more details.
12
 
#
13
 
# You should have received a copy of the GNU General Public License
14
 
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
 
18
 
import os
19
 
 
20
 
from bzrlib import (
21
 
    branchbuilder,
22
 
    bzrdir,
23
 
    conflicts,
24
 
    errors,
25
 
    option,
26
 
    tests,
27
 
    workingtree,
28
 
    )
29
 
from bzrlib.tests import script
30
 
 
31
 
 
32
 
def load_tests(standard_tests, module, loader):
33
 
    result = loader.suiteClass()
34
 
 
35
 
    sp_tests, remaining_tests = tests.split_suite_by_condition(
36
 
        standard_tests, tests.condition_isinstance((
37
 
                TestParametrizedResolveConflicts,
38
 
                )))
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)
44
 
 
45
 
    # No parametrization for the remaining tests
46
 
    result.addTests(remaining_tests)
47
 
 
48
 
    return result
49
 
 
50
 
 
51
 
# TODO: Test commit with some added, and added-but-missing files
52
 
# RBC 20060124 is that not tested in test_commit.py ?
53
 
 
54
 
# The order of 'path' here is important - do not let it
55
 
# be a sorted list.
56
 
# u'\xe5' == a with circle
57
 
# '\xc3\xae' == u'\xee' == i with hat
58
 
# So these are u'path' and 'id' only with a circle and a hat. (shappo?)
59
 
example_conflicts = conflicts.ConflictList(
60
 
    [conflicts.MissingParent('Not deleting', u'p\xe5thg', '\xc3\xaedg'),
61
 
     conflicts.ContentsConflict(u'p\xe5tha', None, '\xc3\xaeda'),
62
 
     conflicts.TextConflict(u'p\xe5tha'),
63
 
     conflicts.PathConflict(u'p\xe5thb', u'p\xe5thc', '\xc3\xaedb'),
64
 
     conflicts.DuplicateID('Unversioned existing file',
65
 
                           u'p\xe5thc', u'p\xe5thc2',
66
 
                           '\xc3\xaedc', '\xc3\xaedc'),
67
 
    conflicts.DuplicateEntry('Moved existing file to',
68
 
                             u'p\xe5thdd.moved', u'p\xe5thd',
69
 
                             '\xc3\xaedd', None),
70
 
    conflicts.ParentLoop('Cancelled move', u'p\xe5the', u'p\xe5th2e',
71
 
                         None, '\xc3\xaed2e'),
72
 
    conflicts.UnversionedParent('Versioned directory',
73
 
                                u'p\xe5thf', '\xc3\xaedf'),
74
 
    conflicts.NonDirectoryParent('Created directory',
75
 
                                 u'p\xe5thg', '\xc3\xaedg'),
76
 
])
77
 
 
78
 
 
79
 
class TestConflicts(tests.TestCaseWithTransport):
80
 
 
81
 
    def test_conflicts(self):
82
 
        """Conflicts are detected properly"""
83
 
        # Use BzrDirFormat6 so we can fake conflicts
84
 
        tree = self.make_branch_and_tree('.', format=bzrdir.BzrDirFormat6())
85
 
        self.build_tree_contents([('hello', 'hello world4'),
86
 
                                  ('hello.THIS', 'hello world2'),
87
 
                                  ('hello.BASE', 'hello world1'),
88
 
                                  ('hello.OTHER', 'hello world3'),
89
 
                                  ('hello.sploo.BASE', 'yellowworld'),
90
 
                                  ('hello.sploo.OTHER', 'yellowworld2'),
91
 
                                  ])
92
 
        tree.lock_read()
93
 
        self.assertLength(6, list(tree.list_files()))
94
 
        tree.unlock()
95
 
        tree_conflicts = tree.conflicts()
96
 
        self.assertLength(2, tree_conflicts)
97
 
        self.assertTrue('hello' in tree_conflicts[0].path)
98
 
        self.assertTrue('hello.sploo' in tree_conflicts[1].path)
99
 
        conflicts.restore('hello')
100
 
        conflicts.restore('hello.sploo')
101
 
        self.assertLength(0, tree.conflicts())
102
 
        self.assertFileEqual('hello world2', 'hello')
103
 
        self.assertFalse(os.path.lexists('hello.sploo'))
104
 
        self.assertRaises(errors.NotConflicted, conflicts.restore, 'hello')
105
 
        self.assertRaises(errors.NotConflicted,
106
 
                          conflicts.restore, 'hello.sploo')
107
 
 
108
 
    def test_resolve_conflict_dir(self):
109
 
        tree = self.make_branch_and_tree('.')
110
 
        self.build_tree_contents([('hello', 'hello world4'),
111
 
                                  ('hello.THIS', 'hello world2'),
112
 
                                  ('hello.BASE', 'hello world1'),
113
 
                                  ])
114
 
        os.mkdir('hello.OTHER')
115
 
        tree.add('hello', 'q')
116
 
        l = conflicts.ConflictList([conflicts.TextConflict('hello')])
117
 
        l.remove_files(tree)
118
 
 
119
 
    def test_select_conflicts(self):
120
 
        tree = self.make_branch_and_tree('.')
121
 
        clist = conflicts.ConflictList
122
 
 
123
 
        def check_select(not_selected, selected, paths, **kwargs):
124
 
            self.assertEqual(
125
 
                (not_selected, selected),
126
 
                tree_conflicts.select_conflicts(tree, paths, **kwargs))
127
 
 
128
 
        foo = conflicts.ContentsConflict('foo')
129
 
        bar = conflicts.ContentsConflict('bar')
130
 
        tree_conflicts = clist([foo, bar])
131
 
 
132
 
        check_select(clist([bar]), clist([foo]), ['foo'])
133
 
        check_select(clist(), tree_conflicts,
134
 
                     [''], ignore_misses=True, recurse=True)
135
 
 
136
 
        foobaz  = conflicts.ContentsConflict('foo/baz')
137
 
        tree_conflicts = clist([foobaz, bar])
138
 
 
139
 
        check_select(clist([bar]), clist([foobaz]),
140
 
                     ['foo'], ignore_misses=True, recurse=True)
141
 
 
142
 
        qux = conflicts.PathConflict('qux', 'foo/baz')
143
 
        tree_conflicts = clist([qux])
144
 
 
145
 
        check_select(clist(), tree_conflicts,
146
 
                     ['foo'], ignore_misses=True, recurse=True)
147
 
        check_select (tree_conflicts, clist(), ['foo'], ignore_misses=True)
148
 
 
149
 
    def test_resolve_conflicts_recursive(self):
150
 
        tree = self.make_branch_and_tree('.')
151
 
        self.build_tree(['dir/', 'dir/hello'])
152
 
        tree.add(['dir', 'dir/hello'])
153
 
 
154
 
        dirhello = conflicts.ConflictList([conflicts.TextConflict('dir/hello')])
155
 
        tree.set_conflicts(dirhello)
156
 
 
157
 
        conflicts.resolve(tree, ['dir'], recursive=False, ignore_misses=True)
158
 
        self.assertEqual(dirhello, tree.conflicts())
159
 
 
160
 
        conflicts.resolve(tree, ['dir'], recursive=True, ignore_misses=True)
161
 
        self.assertEqual(conflicts.ConflictList([]), tree.conflicts())
162
 
 
163
 
 
164
 
class TestConflictStanzas(tests.TestCase):
165
 
 
166
 
    def test_stanza_roundtrip(self):
167
 
        # write and read our example stanza.
168
 
        stanza_iter = example_conflicts.to_stanzas()
169
 
        processed = conflicts.ConflictList.from_stanzas(stanza_iter)
170
 
        for o, p in zip(processed, example_conflicts):
171
 
            self.assertEqual(o, p)
172
 
 
173
 
            self.assertIsInstance(o.path, unicode)
174
 
 
175
 
            if o.file_id is not None:
176
 
                self.assertIsInstance(o.file_id, str)
177
 
 
178
 
            conflict_path = getattr(o, 'conflict_path', None)
179
 
            if conflict_path is not None:
180
 
                self.assertIsInstance(conflict_path, unicode)
181
 
 
182
 
            conflict_file_id = getattr(o, 'conflict_file_id', None)
183
 
            if conflict_file_id is not None:
184
 
                self.assertIsInstance(conflict_file_id, str)
185
 
 
186
 
    def test_stanzification(self):
187
 
        for stanza in example_conflicts.to_stanzas():
188
 
            if 'file_id' in stanza:
189
 
                # In Stanza form, the file_id has to be unicode.
190
 
                self.assertStartsWith(stanza['file_id'], u'\xeed')
191
 
            self.assertStartsWith(stanza['path'], u'p\xe5th')
192
 
            if 'conflict_path' in stanza:
193
 
                self.assertStartsWith(stanza['conflict_path'], u'p\xe5th')
194
 
            if 'conflict_file_id' in stanza:
195
 
                self.assertStartsWith(stanza['conflict_file_id'], u'\xeed')
196
 
 
197
 
 
198
 
# FIXME: The shell-like tests should be converted to real whitebox tests... or
199
 
# moved to a blackbox module -- vila 20100205
200
 
 
201
 
# FIXME: test missing for multiple conflicts
202
 
 
203
 
# FIXME: Tests missing for DuplicateID conflict type
204
 
class TestResolveConflicts(script.TestCaseWithTransportAndScript):
205
 
 
206
 
    preamble = None # The setup script set by daughter classes
207
 
 
208
 
    def setUp(self):
209
 
        super(TestResolveConflicts, self).setUp()
210
 
        self.run_script(self.preamble)
211
 
 
212
 
 
213
 
class TestResolveTextConflicts(TestResolveConflicts):
214
 
    # TBC
215
 
    pass
216
 
 
217
 
 
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
241
 
 
242
 
    # Set by load_tests
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
 
    # 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):
290
 
        # Only concrete classes return actual scenarios
291
 
        return []
292
 
 
293
 
    def setUp(self):
294
 
        super(TestParametrizedResolveConflicts, self).setUp()
295
 
        builder = self.make_branch_builder('trunk')
296
 
        builder.start_series()
297
 
 
298
 
        # Create an empty trunk
299
 
        builder.build_snapshot('start', None, [
300
 
                ('add', ('', 'root-id', 'directory', ''))])
301
 
        # Add a minimal base content
302
 
        _, _, actions_base = self._get_actions(self._actions_base)()
303
 
        builder.build_snapshot('base', ['start'], actions_base)
304
 
        # Modify the base content in branch
305
 
        (self._other_path, self._other_id,
306
 
         actions_other) = self._get_actions(self._actions_other)()
307
 
        builder.build_snapshot('other', ['base'], actions_other)
308
 
        # Modify the base content in trunk
309
 
        (self._this_path, self._this_id,
310
 
         actions_this) = self._get_actions(self._actions_this)()
311
 
        builder.build_snapshot('this', ['base'], actions_this)
312
 
        # builder.get_branch() tip is now 'this'
313
 
 
314
 
        builder.finish_series()
315
 
        self.builder = builder
316
 
 
317
 
    def _get_actions(self, name):
318
 
        return getattr(self, 'do_%s' % name)
319
 
 
320
 
    def _get_check(self, name):
321
 
        return getattr(self, 'check_%s' % name)
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
 
 
394
 
    def _merge_other_into_this(self):
395
 
        b = self.builder.get_branch()
396
 
        wt = b.bzrdir.sprout('branch').open_workingtree()
397
 
        wt.merge_from_branch(b, 'other')
398
 
        return wt
399
 
 
400
 
    def assertConflict(self, wt):
401
 
        confs = wt.conflicts()
402
 
        self.assertLength(1, confs)
403
 
        c = confs[0]
404
 
        self.assertIsInstance(c, self._conflict_type)
405
 
        self._assert_conflict(wt, c)
406
 
 
407
 
    def _get_resolve_path_arg(self, wt, action):
408
 
        return self._item_path
409
 
 
410
 
    def check_resolved(self, wt, action):
411
 
        path = self._get_resolve_path_arg(wt, action)
412
 
        conflicts.resolve(wt, [path], action=action)
413
 
        # Check that we don't have any conflicts nor unknown left
414
 
        self.assertLength(0, wt.conflicts())
415
 
        self.assertLength(0, list(wt.unknowns()))
416
 
 
417
 
    def test_resolve_taking_this(self):
418
 
        wt = self._merge_other_into_this()
419
 
        self.assertConflict(wt)
420
 
        self.check_resolved(wt, 'take_this')
421
 
        check_this = self._get_check(self._check_this)
422
 
        check_this()
423
 
 
424
 
    def test_resolve_taking_other(self):
425
 
        wt = self._merge_other_into_this()
426
 
        self.assertConflict(wt)
427
 
        self.check_resolved(wt, 'take_other')
428
 
        check_other = self._get_check(self._check_other)
429
 
        check_other()
430
 
 
431
 
 
432
 
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
433
 
 
434
 
    _conflict_type = conflicts.ContentsConflict,
435
 
    @classmethod
436
 
    def scenarios(klass):
437
 
        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')),
443
 
            ]
444
 
        return klass.mirror_scenarios(base_scenarios)
445
 
 
446
 
    def assertContentsConflict(self, wt, c):
447
 
        self.assertEqual(self._other_id, c.file_id)
448
 
        self.assertEqual(self._other_path, c.path)
449
 
    _assert_conflict = assertContentsConflict
450
 
 
451
 
 
452
 
 
453
 
class TestResolvePathConflict(TestParametrizedResolveConflicts):
454
 
 
455
 
    _conflict_type = conflicts.PathConflict,
456
 
 
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',)
463
 
        base_scenarios = [
464
 
            (('file_renamed',
465
 
              dict(actions='rename_file', check='file_renamed')),
466
 
             ('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')),
471
 
             ('file_renamed2',
472
 
              dict(actions='rename_file2', check='file_renamed2')),
473
 
             for_file),
474
 
            (('dir_renamed',
475
 
              dict(actions='rename_dir', check='dir_renamed')),
476
 
             ('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')),
481
 
             ('dir_renamed2',
482
 
              dict(actions='rename_dir2', check='dir_renamed2')),
483
 
             for_dir),
484
 
        ]
485
 
        return klass.mirror_scenarios(base_scenarios)
486
 
 
487
 
    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:]
492
 
 
493
 
    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)
497
 
    _assert_conflict = assertPathConflict
498
 
 
499
 
 
500
 
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
501
 
    """Same as TestResolvePathConflict but a specific conflict object.
502
 
    """
503
 
 
504
 
    def assertPathConflict(self, c):
505
 
        # We create a conflict object as it was created before the fix and
506
 
        # inject it into the working tree, the test will exercise the
507
 
        # compatibility code.
508
 
        old_c = conflicts.PathConflict('<deleted>', self._item_path,
509
 
                                       file_id=None)
510
 
        wt.set_conflicts(conflicts.ConflictList([old_c]))
511
 
 
512
 
 
513
 
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
514
 
 
515
 
    _conflict_type = conflicts.DuplicateEntry,
516
 
    @classmethod
517
 
    def scenarios(klass):
518
 
        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')),
524
 
            ]
525
 
        return klass.mirror_scenarios(base_scenarios)
526
 
 
527
 
    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)
531
 
    _assert_conflict = assertDuplicateEntry
532
 
 
533
 
 
534
 
class TestResolveUnversionedParent(TestResolveConflicts):
535
 
 
536
 
    # FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
537
 
 
538
 
    # FIXME: While this *creates* UnversionedParent conflicts, this really only
539
 
    # tests MissingParent resolution :-/
540
 
    preamble = """
541
 
$ bzr init trunk
542
 
$ cd trunk
543
 
$ mkdir dir
544
 
$ bzr add dir
545
 
$ bzr commit -m 'Create trunk'
546
 
 
547
 
$ echo 'trunk content' >dir/file
548
 
$ bzr add dir/file
549
 
$ bzr commit -m 'Add dir/file in trunk'
550
 
 
551
 
$ bzr branch . -r 1 ../branch
552
 
$ cd ../branch
553
 
$ bzr rm dir
554
 
$ bzr commit -m 'Remove dir in branch'
555
 
 
556
 
$ bzr merge ../trunk
557
 
2>+N  dir/
558
 
2>+N  dir/file
559
 
2>Conflict adding files to dir.  Created directory.
560
 
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
561
 
2>2 conflicts encountered.
562
 
"""
563
 
 
564
 
    def test_take_this(self):
565
 
        self.run_script("""
566
 
$ bzr rm dir  --force
567
 
$ bzr resolve dir
568
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
569
 
""")
570
 
 
571
 
    def test_take_other(self):
572
 
        self.run_script("""
573
 
$ bzr resolve dir
574
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
575
 
""")
576
 
 
577
 
 
578
 
class TestResolveMissingParent(TestResolveConflicts):
579
 
 
580
 
    preamble = """
581
 
$ bzr init trunk
582
 
$ cd trunk
583
 
$ mkdir dir
584
 
$ echo 'trunk content' >dir/file
585
 
$ bzr add
586
 
$ bzr commit -m 'Create trunk'
587
 
 
588
 
$ echo 'trunk content' >dir/file2
589
 
$ bzr add dir/file2
590
 
$ bzr commit -m 'Add dir/file2 in branch'
591
 
 
592
 
$ bzr branch . -r 1 ../branch
593
 
$ cd ../branch
594
 
$ bzr rm dir/file --force
595
 
$ bzr rm dir
596
 
$ bzr commit -m 'Remove dir/file'
597
 
 
598
 
$ bzr merge ../trunk
599
 
2>+N  dir/
600
 
2>+N  dir/file2
601
 
2>Conflict adding files to dir.  Created directory.
602
 
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
603
 
2>2 conflicts encountered.
604
 
"""
605
 
 
606
 
    def test_keep_them_all(self):
607
 
        self.run_script("""
608
 
$ bzr resolve dir
609
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
610
 
""")
611
 
 
612
 
    def test_adopt_child(self):
613
 
        self.run_script("""
614
 
$ bzr mv dir/file2 file2
615
 
$ bzr rm dir --force
616
 
$ bzr resolve dir
617
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
618
 
""")
619
 
 
620
 
    def test_kill_them_all(self):
621
 
        self.run_script("""
622
 
$ bzr rm dir --force
623
 
$ bzr resolve dir
624
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
625
 
""")
626
 
 
627
 
    def test_resolve_taking_this(self):
628
 
        self.run_script("""
629
 
$ bzr resolve --take-this dir
630
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
631
 
""")
632
 
 
633
 
    def test_resolve_taking_other(self):
634
 
        self.run_script("""
635
 
$ bzr resolve --take-other dir
636
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
637
 
""")
638
 
 
639
 
 
640
 
class TestResolveDeletingParent(TestResolveConflicts):
641
 
 
642
 
    preamble = """
643
 
$ bzr init trunk
644
 
$ cd trunk
645
 
$ mkdir dir
646
 
$ echo 'trunk content' >dir/file
647
 
$ bzr add
648
 
$ bzr commit -m 'Create trunk'
649
 
 
650
 
$ bzr rm dir/file --force
651
 
$ bzr rm dir --force
652
 
$ bzr commit -m 'Remove dir/file'
653
 
 
654
 
$ bzr branch . -r 1 ../branch
655
 
$ cd ../branch
656
 
$ echo 'branch content' >dir/file2
657
 
$ bzr add dir/file2
658
 
$ bzr commit -m 'Add dir/file2 in branch'
659
 
 
660
 
$ bzr merge ../trunk
661
 
2>-D  dir/file
662
 
2>Conflict: can't delete dir because it is not empty.  Not deleting.
663
 
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
664
 
2>2 conflicts encountered.
665
 
"""
666
 
 
667
 
    def test_keep_them_all(self):
668
 
        self.run_script("""
669
 
$ bzr resolve dir
670
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
671
 
""")
672
 
 
673
 
    def test_adopt_child(self):
674
 
        self.run_script("""
675
 
$ bzr mv dir/file2 file2
676
 
$ bzr rm dir --force
677
 
$ bzr resolve dir
678
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
679
 
""")
680
 
 
681
 
    def test_kill_them_all(self):
682
 
        self.run_script("""
683
 
$ bzr rm dir --force
684
 
$ bzr resolve dir
685
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
686
 
""")
687
 
 
688
 
    def test_resolve_taking_this(self):
689
 
        self.run_script("""
690
 
$ bzr resolve --take-this dir
691
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
692
 
""")
693
 
 
694
 
    def test_resolve_taking_other(self):
695
 
        self.run_script("""
696
 
$ bzr resolve --take-other dir
697
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
698
 
""")
699
 
 
700
 
 
701
 
class TestResolveParentLoop(TestParametrizedResolveConflicts):
702
 
 
703
 
    _conflict_type = conflicts.ParentLoop,
704
 
    @classmethod
705
 
    def scenarios(klass):
706
 
        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')),
717
 
            ]
718
 
        return klass.mirror_scenarios(base_scenarios)
719
 
 
720
 
    def do_create_dir1_dir2(self):
721
 
        return (None, None,
722
 
                [('add', ('dir1', 'dir1-id', 'directory', '')),
723
 
                 ('add', ('dir2', 'dir2-id', 'directory', '')),
724
 
                 ])
725
 
 
726
 
    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'))])
729
 
 
730
 
    def check_dir1_moved(self):
731
 
        self.failIfExists('branch/dir1')
732
 
        self.failUnlessExists('branch/dir2/dir1')
733
 
 
734
 
    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'))])
737
 
 
738
 
    def check_dir2_moved(self):
739
 
        self.failIfExists('branch/dir2')
740
 
        self.failUnlessExists('branch/dir1/dir2')
741
 
 
742
 
    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
 
                 ])
749
 
 
750
 
    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'))])
754
 
 
755
 
    def check_dir1_2_moved(self):
756
 
        self.failIfExists('branch/dir1')
757
 
        self.failUnlessExists('branch/dir3/dir4/dir1')
758
 
        self.failUnlessExists('branch/dir3/dir4/dir1/dir2')
759
 
 
760
 
    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'))])
764
 
 
765
 
    def check_dir3_4_moved(self):
766
 
        self.failIfExists('branch/dir3')
767
 
        self.failUnlessExists('branch/dir1/dir2/dir3')
768
 
        self.failUnlessExists('branch/dir1/dir2/dir3/dir4')
769
 
 
770
 
    def _get_resolve_path_arg(self, wt, action):
771
 
        # ParentLoop is unsual as it says: 
772
 
        # moving <conflict_path> into <path>.  Cancelled move.
773
 
        # 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
777
 
 
778
 
    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)
789
 
        # The conflict paths are irrelevant (they are deterministic but not
790
 
        # worth checking since they don't provide the needed information
791
 
        # anyway)
792
 
    _assert_conflict = assertParentLoop
793
 
 
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
 
 
846
 
class TestResolveNonDirectoryParent(TestResolveConflicts):
847
 
 
848
 
    preamble = """
849
 
$ bzr init trunk
850
 
$ cd trunk
851
 
$ bzr mkdir foo
852
 
$ bzr commit -m 'Create trunk'
853
 
$ echo "Boing" >foo/bar
854
 
$ bzr add foo/bar
855
 
$ bzr commit -m 'Add foo/bar'
856
 
 
857
 
$ bzr branch . -r 1 ../branch
858
 
$ cd ../branch
859
 
$ rm -r foo
860
 
$ echo "Boo!" >foo
861
 
$ bzr commit -m 'foo is now a file'
862
 
 
863
 
$ bzr merge ../trunk
864
 
2>+N  foo.new/bar
865
 
2>RK  foo => foo.new/
866
 
# FIXME: The message is misleading, foo.new *is* a directory when the message
867
 
# is displayed -- vila 090916
868
 
2>Conflict: foo.new is not a directory, but has files in it.  Created directory.
869
 
2>1 conflicts encountered.
870
 
"""
871
 
 
872
 
    def test_take_this(self):
873
 
        self.run_script("""
874
 
$ bzr rm foo.new --force
875
 
# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
876
 
# aside ? -- vila 090916
877
 
$ bzr add foo
878
 
$ bzr resolve foo.new
879
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
880
 
""")
881
 
 
882
 
    def test_take_other(self):
883
 
        self.run_script("""
884
 
$ bzr rm foo --force
885
 
$ bzr mv foo.new foo
886
 
$ bzr resolve foo
887
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
888
 
""")
889
 
 
890
 
    def test_resolve_taking_this(self):
891
 
        self.run_script("""
892
 
$ bzr resolve --take-this foo.new
893
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
894
 
""")
895
 
 
896
 
    def test_resolve_taking_other(self):
897
 
        self.run_script("""
898
 
$ bzr resolve --take-other foo.new
899
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
900
 
""")
901
 
 
902
 
 
903
 
class TestMalformedTransform(script.TestCaseWithTransportAndScript):
904
 
 
905
 
    def test_bug_430129(self):
906
 
        # This is nearly like TestResolveNonDirectoryParent but with branch and
907
 
        # trunk switched. As such it should certainly produce the same
908
 
        # conflict.
909
 
        self.run_script("""
910
 
$ bzr init trunk
911
 
$ cd trunk
912
 
$ bzr mkdir foo
913
 
$ bzr commit -m 'Create trunk'
914
 
$ rm -r foo
915
 
$ echo "Boo!" >foo
916
 
$ bzr commit -m 'foo is now a file'
917
 
 
918
 
$ bzr branch . -r 1 ../branch
919
 
$ cd ../branch
920
 
$ echo "Boing" >foo/bar
921
 
$ bzr add foo/bar
922
 
$ bzr commit -m 'Add foo/bar'
923
 
 
924
 
$ bzr merge ../trunk
925
 
2>bzr: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
926
 
""")
927
 
 
928
 
 
929
 
class TestResolveActionOption(tests.TestCase):
930
 
 
931
 
    def setUp(self):
932
 
        super(TestResolveActionOption, self).setUp()
933
 
        self.options = [conflicts.ResolveActionOption()]
934
 
        self.parser = option.get_optparser(dict((o.name, o)
935
 
                                                for o in self.options))
936
 
 
937
 
    def parse(self, args):
938
 
        return self.parser.parse_args(args)
939
 
 
940
 
    def test_unknown_action(self):
941
 
        self.assertRaises(errors.BadOptionValue,
942
 
                          self.parse, ['--action', 'take-me-to-the-moon'])
943
 
 
944
 
    def test_done(self):
945
 
        opts, args = self.parse(['--action', 'done'])
946
 
        self.assertEqual({'action':'done'}, opts)
947
 
 
948
 
    def test_take_this(self):
949
 
        opts, args = self.parse(['--action', 'take-this'])
950
 
        self.assertEqual({'action': 'take_this'}, opts)
951
 
        opts, args = self.parse(['--take-this'])
952
 
        self.assertEqual({'action': 'take_this'}, opts)
953
 
 
954
 
    def test_take_other(self):
955
 
        opts, args = self.parse(['--action', 'take-other'])
956
 
        self.assertEqual({'action': 'take_other'}, opts)
957
 
        opts, args = self.parse(['--take-other'])
958
 
        self.assertEqual({'action': 'take_other'}, opts)