~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_conflicts.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-07-12 12:36:57 UTC
  • mfrom: (1732.3.4 bzr.revnoX)
  • Revision ID: pqm@pqm.ubuntu.com-20060712123657-365eeb32b69308bf
(matthieu) revno:x:url revision spec

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 by Canonical Ltd
 
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
#
 
12
 
13
13
# You should have received a copy of the GNU General Public License
14
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
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
18
18
import os
19
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
 
20
from bzrlib import bzrdir
 
21
from bzrlib.tests import TestCaseWithTransport, TestCase
 
22
from bzrlib.branch import Branch
 
23
from bzrlib.conflicts import (MissingParent, ContentsConflict, TextConflict,
 
24
        PathConflict, DuplicateID, DuplicateEntry, ParentLoop, UnversionedParent,
 
25
        ConflictList, 
 
26
        restore)
 
27
from bzrlib.errors import NotConflicted
49
28
 
50
29
 
51
30
# TODO: Test commit with some added, and added-but-missing files
53
32
 
54
33
# The order of 'path' here is important - do not let it
55
34
# 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'),
 
35
example_conflicts = ConflictList([ 
 
36
    MissingParent('Not deleting', 'pathg', 'idg'),
 
37
    ContentsConflict('patha', 'ida'), 
 
38
    TextConflict('patha'),
 
39
    PathConflict('pathb', 'pathc', 'idb'),
 
40
    DuplicateID('Unversioned existing file', 'pathc', 'pathc2', 'idc', 'idc'),
 
41
    DuplicateEntry('Moved existing file to',  'pathdd.moved', 'pathd', 'idd', 
 
42
                   None),
 
43
    ParentLoop('Cancelled move', 'pathe', 'path2e', None, 'id2e'),
 
44
    UnversionedParent('Versioned directory', 'pathf', 'idf'),
76
45
])
77
46
 
78
47
 
79
 
class TestConflicts(tests.TestCaseWithTransport):
 
48
class TestConflicts(TestCaseWithTransport):
80
49
 
81
50
    def test_conflicts(self):
82
51
        """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())
 
52
        tree = self.make_branch_and_tree('.',
 
53
            format=bzrdir.BzrDirFormat6())
 
54
        b = tree.branch
 
55
        file('hello', 'w').write('hello world4')
 
56
        file('hello.THIS', 'w').write('hello world2')
 
57
        file('hello.BASE', 'w').write('hello world1')
 
58
        file('hello.OTHER', 'w').write('hello world3')
 
59
        file('hello.sploo.BASE', 'w').write('yellow world')
 
60
        file('hello.sploo.OTHER', 'w').write('yellow world2')
 
61
        self.assertEqual(len(list(tree.list_files())), 6)
 
62
        conflicts = tree.conflicts()
 
63
        self.assertEqual(len(conflicts), 2)
 
64
        self.assert_('hello' in conflicts[0].path)
 
65
        self.assert_('hello.sploo' in conflicts[1].path)
 
66
        restore('hello')
 
67
        restore('hello.sploo')
 
68
        self.assertEqual(len(tree.conflicts()), 0)
102
69
        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')
 
70
        assert not os.path.lexists('hello.sploo')
 
71
        self.assertRaises(NotConflicted, restore, 'hello')
 
72
        self.assertRaises(NotConflicted, restore, 'hello.sploo')
107
73
 
108
74
    def test_resolve_conflict_dir(self):
109
75
        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
 
                                  ])
 
76
        b = tree.branch
 
77
        file('hello', 'w').write('hello world4')
 
78
        tree.add('hello', 'q')
 
79
        file('hello.THIS', 'w').write('hello world2')
 
80
        file('hello.BASE', 'w').write('hello world1')
114
81
        os.mkdir('hello.OTHER')
115
 
        tree.add('hello', 'q')
116
 
        l = conflicts.ConflictList([conflicts.TextConflict('hello')])
 
82
        l = ConflictList([TextConflict('hello')])
117
83
        l.remove_files(tree)
118
84
 
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):
 
85
 
 
86
class TestConflictStanzas(TestCase):
165
87
 
166
88
    def test_stanza_roundtrip(self):
167
89
        # write and read our example stanza.
168
90
        stanza_iter = example_conflicts.to_stanzas()
169
 
        processed = conflicts.ConflictList.from_stanzas(stanza_iter)
170
 
        for o, p in zip(processed, example_conflicts):
 
91
        processed = ConflictList.from_stanzas(stanza_iter)
 
92
        for o,p in zip(processed, example_conflicts):
171
93
            self.assertEqual(o, p)
172
94
 
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
95
    def test_stanzification(self):
187
96
        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
 
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
 
# FIXME: Get rid of parametrized (in the class name) once we delete
238
 
# TestResolveConflicts -- vila 20100308
239
 
class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
240
 
    """This class provides a base to test single conflict resolution.
241
 
 
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.
285
 
    """
286
 
 
287
 
    # Set by daughter classes
288
 
    _conflict_type = None
289
 
    _assert_conflict = None
290
 
 
291
 
    # Set by load_tests
292
 
    _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
 
        """
322
 
        # Only concrete classes return actual scenarios
323
 
        return []
324
 
 
325
 
    def setUp(self):
326
 
        super(TestParametrizedResolveConflicts, self).setUp()
327
 
        builder = self.make_branch_builder('trunk')
328
 
        builder.start_series()
329
 
 
330
 
        # Create an empty trunk
331
 
        builder.build_snapshot('start', None, [
332
 
                ('add', ('', 'root-id', 'directory', ''))])
333
 
        # Add a minimal base content
334
 
        base_actions = self._get_actions(self._base_actions)()
335
 
        builder.build_snapshot('base', ['start'], base_actions)
336
 
        # Modify the base content in branch
337
 
        actions_other = self._get_actions(self._other['actions'])()
338
 
        builder.build_snapshot('other', ['base'], actions_other)
339
 
        # Modify the base content in trunk
340
 
        actions_this = self._get_actions(self._this['actions'])()
341
 
        builder.build_snapshot('this', ['base'], actions_this)
342
 
        # builder.get_branch() tip is now 'this'
343
 
 
344
 
        builder.finish_series()
345
 
        self.builder = builder
346
 
 
347
 
    def _get_actions(self, name):
348
 
        return getattr(self, 'do_%s' % name)
349
 
 
350
 
    def _get_check(self, name):
351
 
        return getattr(self, 'check_%s' % name)
352
 
 
353
 
    def _merge_other_into_this(self):
354
 
        b = self.builder.get_branch()
355
 
        wt = b.bzrdir.sprout('branch').open_workingtree()
356
 
        wt.merge_from_branch(b, 'other')
357
 
        return wt
358
 
 
359
 
    def assertConflict(self, wt):
360
 
        confs = wt.conflicts()
361
 
        self.assertLength(1, confs)
362
 
        c = confs[0]
363
 
        self.assertIsInstance(c, self._conflict_type)
364
 
        self._assert_conflict(wt, c)
365
 
 
366
 
    def _get_resolve_path_arg(self, wt, action):
367
 
        raise NotImplementedError(self._get_resolve_path_arg)
368
 
 
369
 
    def check_resolved(self, wt, action):
370
 
        path = self._get_resolve_path_arg(wt, action)
371
 
        conflicts.resolve(wt, [path], action=action)
372
 
        # Check that we don't have any conflicts nor unknown left
373
 
        self.assertLength(0, wt.conflicts())
374
 
        self.assertLength(0, list(wt.unknowns()))
375
 
 
376
 
    def test_resolve_taking_this(self):
377
 
        wt = self._merge_other_into_this()
378
 
        self.assertConflict(wt)
379
 
        self.check_resolved(wt, 'take_this')
380
 
        check_this = self._get_check(self._this['check'])
381
 
        check_this()
382
 
 
383
 
    def test_resolve_taking_other(self):
384
 
        wt = self._merge_other_into_this()
385
 
        self.assertConflict(wt)
386
 
        self.check_resolved(wt, 'take_other')
387
 
        check_other = self._get_check(self._other['check'])
388
 
        check_other()
389
 
 
390
 
 
391
 
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
392
 
 
393
 
    _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():
402
 
        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')),),
410
 
            ]
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
430
 
 
431
 
    def assertContentsConflict(self, wt, c):
432
 
        self.assertEqual(self._file_id, c.file_id)
433
 
        self.assertEqual(self._path, c.path)
434
 
    _assert_conflict = assertContentsConflict
435
 
 
436
 
 
437
 
class TestResolvePathConflict(TestParametrizedResolveConflicts):
438
 
 
439
 
    _conflict_type = conflicts.PathConflict,
440
 
 
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
449
 
        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')),
455
 
             ('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')),
465
 
             ('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')),
473
 
             ('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')),
483
 
             ('dir_renamed2',
484
 
              dict(actions='rename_dir2', check='dir_renamed2',
485
 
                   path='new-dir2', file_id='dir-id')),),
486
 
        ]
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')
522
 
 
523
 
    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
543
 
 
544
 
    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)
553
 
    _assert_conflict = assertPathConflict
554
 
 
555
 
 
556
 
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
557
 
    """Same as TestResolvePathConflict but a specific conflict object.
558
 
    """
559
 
 
560
 
    def assertPathConflict(self, c):
561
 
        # We create a conflict object as it was created before the fix and
562
 
        # inject it into the working tree, the test will exercise the
563
 
        # compatibility code.
564
 
        old_c = conflicts.PathConflict('<deleted>', self._item_path,
565
 
                                       file_id=None)
566
 
        wt.set_conflicts(conflicts.ConflictList([old_c]))
567
 
 
568
 
 
569
 
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
570
 
 
571
 
    _conflict_type = conflicts.DuplicateEntry,
572
 
 
573
 
    @staticmethod
574
 
    def scenarios():
575
 
        # Each side dict additionally defines:
576
 
        # - path involved
577
 
        # - file-id involved
578
 
        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')),),
587
 
            ]
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']
607
 
 
608
 
    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)
617
 
    _assert_conflict = assertDuplicateEntry
618
 
 
619
 
 
620
 
class TestResolveUnversionedParent(TestResolveConflicts):
621
 
 
622
 
    # FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
623
 
 
624
 
    # FIXME: While this *creates* UnversionedParent conflicts, this really only
625
 
    # tests MissingParent resolution :-/
626
 
    preamble = """
627
 
$ bzr init trunk
628
 
...
629
 
$ cd trunk
630
 
$ mkdir dir
631
 
$ bzr add -q dir
632
 
$ bzr commit -m 'Create trunk' -q
633
 
$ echo 'trunk content' >dir/file
634
 
$ bzr add -q dir/file
635
 
$ bzr commit -q -m 'Add dir/file in trunk'
636
 
$ bzr branch -q . -r 1 ../branch
637
 
$ cd ../branch
638
 
$ bzr rm dir -q
639
 
$ bzr commit -q -m 'Remove dir in branch'
640
 
$ bzr merge ../trunk
641
 
2>+N  dir/
642
 
2>+N  dir/file
643
 
2>Conflict adding files to dir.  Created directory.
644
 
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
645
 
2>2 conflicts encountered.
646
 
"""
647
 
 
648
 
    def test_take_this(self):
649
 
        self.run_script("""
650
 
$ bzr rm -q dir  --force
651
 
$ bzr resolve dir
652
 
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
653
 
""")
654
 
 
655
 
    def test_take_other(self):
656
 
        self.run_script("""
657
 
$ bzr resolve dir
658
 
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
659
 
""")
660
 
 
661
 
 
662
 
class TestResolveMissingParent(TestResolveConflicts):
663
 
 
664
 
    preamble = """
665
 
$ bzr init trunk
666
 
...
667
 
$ cd trunk
668
 
$ mkdir dir
669
 
$ echo 'trunk content' >dir/file
670
 
$ bzr add -q
671
 
$ bzr commit -m 'Create trunk' -q
672
 
$ echo 'trunk content' >dir/file2
673
 
$ bzr add -q dir/file2
674
 
$ bzr commit -q -m 'Add dir/file2 in branch'
675
 
$ bzr branch -q . -r 1 ../branch
676
 
$ cd ../branch
677
 
$ bzr rm -q dir/file --force
678
 
$ bzr rm -q dir
679
 
$ bzr commit -q -m 'Remove dir/file'
680
 
$ bzr merge ../trunk
681
 
2>+N  dir/
682
 
2>+N  dir/file2
683
 
2>Conflict adding files to dir.  Created directory.
684
 
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
685
 
2>2 conflicts encountered.
686
 
"""
687
 
 
688
 
    def test_keep_them_all(self):
689
 
        self.run_script("""
690
 
$ bzr resolve dir
691
 
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
692
 
""")
693
 
 
694
 
    def test_adopt_child(self):
695
 
        self.run_script("""
696
 
$ bzr mv -q dir/file2 file2
697
 
$ bzr rm -q dir --force
698
 
$ bzr resolve dir
699
 
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
700
 
""")
701
 
 
702
 
    def test_kill_them_all(self):
703
 
        self.run_script("""
704
 
$ bzr rm -q dir --force
705
 
$ bzr resolve dir
706
 
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
707
 
""")
708
 
 
709
 
    def test_resolve_taking_this(self):
710
 
        self.run_script("""
711
 
$ bzr resolve --take-this dir
712
 
2>...
713
 
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
714
 
""")
715
 
 
716
 
    def test_resolve_taking_other(self):
717
 
        self.run_script("""
718
 
$ bzr resolve --take-other dir
719
 
2>...
720
 
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
721
 
""")
722
 
 
723
 
 
724
 
class TestResolveDeletingParent(TestResolveConflicts):
725
 
 
726
 
    preamble = """
727
 
$ bzr init trunk
728
 
...
729
 
$ cd trunk
730
 
$ mkdir dir
731
 
$ echo 'trunk content' >dir/file
732
 
$ bzr add -q
733
 
$ bzr commit -m 'Create trunk' -q
734
 
$ bzr rm -q dir/file --force
735
 
$ bzr rm -q dir --force
736
 
$ bzr commit -q -m 'Remove dir/file'
737
 
$ bzr branch -q . -r 1 ../branch
738
 
$ cd ../branch
739
 
$ echo 'branch content' >dir/file2
740
 
$ bzr add -q dir/file2
741
 
$ bzr commit -q -m 'Add dir/file2 in branch'
742
 
$ bzr merge ../trunk
743
 
2>-D  dir/file
744
 
2>Conflict: can't delete dir because it is not empty.  Not deleting.
745
 
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
746
 
2>2 conflicts encountered.
747
 
"""
748
 
 
749
 
    def test_keep_them_all(self):
750
 
        self.run_script("""
751
 
$ bzr resolve dir
752
 
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
753
 
""")
754
 
 
755
 
    def test_adopt_child(self):
756
 
        self.run_script("""
757
 
$ bzr mv -q dir/file2 file2
758
 
$ bzr rm -q dir --force
759
 
$ bzr resolve dir
760
 
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
761
 
""")
762
 
 
763
 
    def test_kill_them_all(self):
764
 
        self.run_script("""
765
 
$ bzr rm -q dir --force
766
 
$ bzr resolve dir
767
 
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
768
 
""")
769
 
 
770
 
    def test_resolve_taking_this(self):
771
 
        self.run_script("""
772
 
$ bzr resolve --take-this dir
773
 
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
774
 
""")
775
 
 
776
 
    def test_resolve_taking_other(self):
777
 
        self.run_script("""
778
 
$ bzr resolve --take-other dir
779
 
2>deleted dir/file2
780
 
2>deleted dir
781
 
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
782
 
""")
783
 
 
784
 
 
785
 
class TestResolveParentLoop(TestParametrizedResolveConflicts):
786
 
 
787
 
    _conflict_type = conflicts.ParentLoop,
788
 
 
789
 
    _this_args = None
790
 
    _other_args = None
791
 
 
792
 
    @staticmethod
793
 
    def scenarios():
794
 
        # Each side dict additionally defines:
795
 
        # - dir_id: the directory being moved
796
 
        # - target_id: The target directory
797
 
        # - xfail: whether the test is expected to fail if the action is
798
 
        #     involved as 'other'
799
 
        base_scenarios = [
800
 
            # Dirs moved into each other
801
 
            (dict(_base_actions='create_dir1_dir2'),
802
 
             ('dir1_into_dir2',
803
 
              dict(actions='move_dir1_into_dir2', check='dir1_moved',
804
 
                   dir_id='dir1-id', target_id='dir2-id', xfail=False)),
805
 
             ('dir2_into_dir1',
806
 
              dict(actions='move_dir2_into_dir1', check='dir2_moved',
807
 
                   dir_id='dir2-id', target_id='dir1-id', xfail=False))),
808
 
            # Subdirs moved into each other
809
 
            (dict(_base_actions='create_dir1_4'),
810
 
             ('dir1_into_dir4',
811
 
              dict(actions='move_dir1_into_dir4', check='dir1_2_moved',
812
 
                   dir_id='dir1-id', target_id='dir4-id', xfail=True)),
813
 
             ('dir3_into_dir2',
814
 
              dict(actions='move_dir3_into_dir2', check='dir3_4_moved',
815
 
                   dir_id='dir3-id', target_id='dir2-id', xfail=True))),
816
 
            ]
817
 
        return mirror_scenarios(base_scenarios)
818
 
 
819
 
    def do_create_dir1_dir2(self):
820
 
        return [('add', ('dir1', 'dir1-id', 'directory', '')),
821
 
                ('add', ('dir2', 'dir2-id', 'directory', '')),]
822
 
 
823
 
    def do_move_dir1_into_dir2(self):
824
 
        return [('rename', ('dir1', 'dir2/dir1'))]
825
 
 
826
 
    def check_dir1_moved(self):
827
 
        self.failIfExists('branch/dir1')
828
 
        self.failUnlessExists('branch/dir2/dir1')
829
 
 
830
 
    def do_move_dir2_into_dir1(self):
831
 
        return [('rename', ('dir2', 'dir1/dir2'))]
832
 
 
833
 
    def check_dir2_moved(self):
834
 
        self.failIfExists('branch/dir2')
835
 
        self.failUnlessExists('branch/dir1/dir2')
836
 
 
837
 
    def do_create_dir1_4(self):
838
 
        return [('add', ('dir1', 'dir1-id', 'directory', '')),
839
 
                ('add', ('dir1/dir2', 'dir2-id', 'directory', '')),
840
 
                ('add', ('dir3', 'dir3-id', 'directory', '')),
841
 
                ('add', ('dir3/dir4', 'dir4-id', 'directory', '')),]
842
 
 
843
 
    def do_move_dir1_into_dir4(self):
844
 
        return [('rename', ('dir1', 'dir3/dir4/dir1'))]
845
 
 
846
 
    def check_dir1_2_moved(self):
847
 
        self.failIfExists('branch/dir1')
848
 
        self.failUnlessExists('branch/dir3/dir4/dir1')
849
 
        self.failUnlessExists('branch/dir3/dir4/dir1/dir2')
850
 
 
851
 
    def do_move_dir3_into_dir2(self):
852
 
        return [('rename', ('dir3', 'dir1/dir2/dir3'))]
853
 
 
854
 
    def check_dir3_4_moved(self):
855
 
        self.failIfExists('branch/dir3')
856
 
        self.failUnlessExists('branch/dir1/dir2/dir3')
857
 
        self.failUnlessExists('branch/dir1/dir2/dir3/dir4')
858
 
 
859
 
    def _get_resolve_path_arg(self, wt, action):
860
 
        # ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
861
 
        # But since <path> doesn't exist in the working tree, we need to use
862
 
        # <conflict_path> instead, and that, in turn, is given by dir_id. Pfew.
863
 
        return wt.id2path(self._other['dir_id'])
864
 
 
865
 
    def assertParentLoop(self, wt, c):
866
 
        self.assertEqual(self._other['dir_id'], c.file_id)
867
 
        self.assertEqual(self._other['target_id'], c.conflict_file_id)
868
 
        # The conflict paths are irrelevant (they are deterministic but not
869
 
        # worth checking since they don't provide the needed information
870
 
        # anyway)
871
 
        if self._other['xfail']:
872
 
            # It's a bit hackish to raise from here relying on being called for
873
 
            # both tests but this avoid overriding test_resolve_taking_other
874
 
            raise tests.KnownFailure(
875
 
                "ParentLoop doesn't carry enough info to resolve --take-other")
876
 
    _assert_conflict = assertParentLoop
877
 
 
878
 
 
879
 
class TestResolveNonDirectoryParent(TestResolveConflicts):
880
 
 
881
 
    preamble = """
882
 
$ bzr init trunk
883
 
...
884
 
$ cd trunk
885
 
$ bzr mkdir foo
886
 
...
887
 
$ bzr commit -m 'Create trunk' -q
888
 
$ echo "Boing" >foo/bar
889
 
$ bzr add -q foo/bar
890
 
$ bzr commit -q -m 'Add foo/bar'
891
 
$ bzr branch -q . -r 1 ../branch
892
 
$ cd ../branch
893
 
$ rm -r foo
894
 
$ echo "Boo!" >foo
895
 
$ bzr commit -q -m 'foo is now a file'
896
 
$ bzr merge ../trunk
897
 
2>+N  foo.new/bar
898
 
2>RK  foo => foo.new/
899
 
# FIXME: The message is misleading, foo.new *is* a directory when the message
900
 
# is displayed -- vila 090916
901
 
2>Conflict: foo.new is not a directory, but has files in it.  Created directory.
902
 
2>1 conflicts encountered.
903
 
"""
904
 
 
905
 
    def test_take_this(self):
906
 
        self.run_script("""
907
 
$ bzr rm -q foo.new --force
908
 
# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
909
 
# aside ? -- vila 090916
910
 
$ bzr add -q foo
911
 
$ bzr resolve foo.new
912
 
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
913
 
""")
914
 
 
915
 
    def test_take_other(self):
916
 
        self.run_script("""
917
 
$ bzr rm -q foo --force
918
 
$ bzr mv -q foo.new foo
919
 
$ bzr resolve foo
920
 
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
921
 
""")
922
 
 
923
 
    def test_resolve_taking_this(self):
924
 
        self.run_script("""
925
 
$ bzr resolve --take-this foo.new
926
 
2>...
927
 
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
928
 
""")
929
 
 
930
 
    def test_resolve_taking_other(self):
931
 
        self.run_script("""
932
 
$ bzr resolve --take-other foo.new
933
 
2>...
934
 
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
935
 
""")
936
 
 
937
 
 
938
 
class TestMalformedTransform(script.TestCaseWithTransportAndScript):
939
 
 
940
 
    def test_bug_430129(self):
941
 
        # This is nearly like TestResolveNonDirectoryParent but with branch and
942
 
        # trunk switched. As such it should certainly produce the same
943
 
        # conflict.
944
 
        self.run_script("""
945
 
$ bzr init trunk
946
 
...
947
 
$ cd trunk
948
 
$ bzr mkdir foo
949
 
...
950
 
$ bzr commit -m 'Create trunk' -q
951
 
$ rm -r foo
952
 
$ echo "Boo!" >foo
953
 
$ bzr commit -m 'foo is now a file' -q
954
 
$ bzr branch -q . -r 1 ../branch -q
955
 
$ cd ../branch
956
 
$ echo "Boing" >foo/bar
957
 
$ bzr add -q foo/bar -q
958
 
$ bzr commit -m 'Add foo/bar' -q
959
 
$ bzr merge ../trunk
960
 
2>bzr: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
961
 
""")
962
 
 
963
 
 
964
 
class TestResolveActionOption(tests.TestCase):
965
 
 
966
 
    def setUp(self):
967
 
        super(TestResolveActionOption, self).setUp()
968
 
        self.options = [conflicts.ResolveActionOption()]
969
 
        self.parser = option.get_optparser(dict((o.name, o)
970
 
                                                for o in self.options))
971
 
 
972
 
    def parse(self, args):
973
 
        return self.parser.parse_args(args)
974
 
 
975
 
    def test_unknown_action(self):
976
 
        self.assertRaises(errors.BadOptionValue,
977
 
                          self.parse, ['--action', 'take-me-to-the-moon'])
978
 
 
979
 
    def test_done(self):
980
 
        opts, args = self.parse(['--action', 'done'])
981
 
        self.assertEqual({'action':'done'}, opts)
982
 
 
983
 
    def test_take_this(self):
984
 
        opts, args = self.parse(['--action', 'take-this'])
985
 
        self.assertEqual({'action': 'take_this'}, opts)
986
 
        opts, args = self.parse(['--take-this'])
987
 
        self.assertEqual({'action': 'take_this'}, opts)
988
 
 
989
 
    def test_take_other(self):
990
 
        opts, args = self.parse(['--action', 'take-other'])
991
 
        self.assertEqual({'action': 'take_other'}, opts)
992
 
        opts, args = self.parse(['--take-other'])
993
 
        self.assertEqual({'action': 'take_other'}, opts)
 
97
            try:
 
98
                self.assertStartsWith(stanza['file_id'], 'id')
 
99
            except KeyError:
 
100
                pass
 
101
            self.assertStartsWith(stanza['path'], 'path')
 
102
            try:
 
103
                self.assertStartsWith(stanza['conflict_file_id'], 'id')
 
104
                self.assertStartsWith(stanza['conflict_file_path'], 'path')
 
105
            except KeyError:
 
106
                pass