/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/per_merger.py

  • Committer: Martin Pool
  • Date: 2007-10-03 08:06:44 UTC
  • mto: This revision was merged to the branch mainline in revision 2901.
  • Revision ID: mbp@sourcefrog.net-20071003080644-oivy0gkg98sex0ed
Avoid internal error tracebacks on failure to lock on readonly transport (#129701).

Add new LockFailed, which doesn't imply that we failed to get it because of
contention.  Raise this if we fail to create the pending or lock directories
because of Transport errors.

UnlockableTransport is not an internal error.

ReadOnlyLockError has a message which didn't match its name or usage; it's now
deprecated and callers are updated to use LockFailed which is more appropriate.

Add zero_ninetytwo deprecation symbol.

Unify assertMatchesRe with TestCase.assertContainsRe.

When the constructor is deprecated, just say that the class is deprecated, not
the __init__ method - this works better with applyDeprecated in tests.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2009, 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
 
"""Implementation tests for bzrlib.merge.Merger."""
18
 
 
19
 
import os
20
 
 
21
 
from bzrlib.conflicts import TextConflict
22
 
from bzrlib import (
23
 
    errors,
24
 
    merge as _mod_merge,
25
 
    option,
26
 
    progress,
27
 
    )
28
 
from bzrlib.tests import (
29
 
    multiply_tests,
30
 
    TestCaseWithTransport,
31
 
    )
32
 
from bzrlib.tests.test_merge_core import MergeBuilder
33
 
from bzrlib.transform import TreeTransform
34
 
 
35
 
 
36
 
 
37
 
def load_tests(standard_tests, module, loader):
38
 
    """Multiply tests for tranport implementations."""
39
 
    result = loader.suiteClass()
40
 
    scenarios = [
41
 
        (name, {'merge_type': merger})
42
 
        for name, merger in option._merge_type_registry.items()]
43
 
    return multiply_tests(standard_tests, scenarios, result)
44
 
 
45
 
 
46
 
class TestMergeImplementation(TestCaseWithTransport):
47
 
 
48
 
    def do_merge(self, target_tree, source_tree, **kwargs):
49
 
        merger = _mod_merge.Merger.from_revision_ids(None,
50
 
            target_tree, source_tree.last_revision(),
51
 
            other_branch=source_tree.branch)
52
 
        merger.merge_type=self.merge_type
53
 
        for name, value in kwargs.items():
54
 
            setattr(merger, name, value)
55
 
        merger.do_merge()
56
 
 
57
 
    def test_merge_specific_file(self):
58
 
        this_tree = self.make_branch_and_tree('this')
59
 
        this_tree.lock_write()
60
 
        self.addCleanup(this_tree.unlock)
61
 
        self.build_tree_contents([
62
 
            ('this/file1', 'a\nb\n'),
63
 
            ('this/file2', 'a\nb\n')
64
 
        ])
65
 
        this_tree.add(['file1', 'file2'])
66
 
        this_tree.commit('Added files')
67
 
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
68
 
        self.build_tree_contents([
69
 
            ('other/file1', 'a\nb\nc\n'),
70
 
            ('other/file2', 'a\nb\nc\n')
71
 
        ])
72
 
        other_tree.commit('modified both')
73
 
        self.build_tree_contents([
74
 
            ('this/file1', 'd\na\nb\n'),
75
 
            ('this/file2', 'd\na\nb\n')
76
 
        ])
77
 
        this_tree.commit('modified both')
78
 
        self.do_merge(this_tree, other_tree, interesting_files=['file1'])
79
 
        self.assertFileEqual('d\na\nb\nc\n', 'this/file1')
80
 
        self.assertFileEqual('d\na\nb\n', 'this/file2')
81
 
 
82
 
    def test_merge_move_and_change(self):
83
 
        this_tree = self.make_branch_and_tree('this')
84
 
        this_tree.lock_write()
85
 
        self.addCleanup(this_tree.unlock)
86
 
        self.build_tree_contents([
87
 
            ('this/file1', 'line 1\nline 2\nline 3\nline 4\n'),
88
 
        ])
89
 
        this_tree.add('file1',)
90
 
        this_tree.commit('Added file')
91
 
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
92
 
        self.build_tree_contents([
93
 
            ('other/file1', 'line 1\nline 2 to 2.1\nline 3\nline 4\n'),
94
 
        ])
95
 
        other_tree.commit('Changed 2 to 2.1')
96
 
        self.build_tree_contents([
97
 
            ('this/file1', 'line 1\nline 3\nline 2\nline 4\n'),
98
 
        ])
99
 
        this_tree.commit('Swapped 2 & 3')
100
 
        self.do_merge(this_tree, other_tree)
101
 
        if self.merge_type is _mod_merge.LCAMerger:
102
 
            self.expectFailure(
103
 
                "lca merge doesn't conflict for move and change",
104
 
                self.assertFileEqual,
105
 
                'line 1\n'
106
 
                '<<<<<<< TREE\n'
107
 
                'line 3\n'
108
 
                'line 2\n'
109
 
                '=======\n'
110
 
                'line 2 to 2.1\n'
111
 
                'line 3\n'
112
 
                '>>>>>>> MERGE-SOURCE\n'
113
 
                'line 4\n', 'this/file1')
114
 
        else:
115
 
            self.assertFileEqual('line 1\n'
116
 
                '<<<<<<< TREE\n'
117
 
                'line 3\n'
118
 
                'line 2\n'
119
 
                '=======\n'
120
 
                'line 2 to 2.1\n'
121
 
                'line 3\n'
122
 
                '>>>>>>> MERGE-SOURCE\n'
123
 
                'line 4\n', 'this/file1')
124
 
 
125
 
    def test_modify_conflicts_with_delete(self):
126
 
        # If one side deletes a line, and the other modifies that line, then
127
 
        # the modification should be considered a conflict
128
 
        builder = self.make_branch_builder('test')
129
 
        builder.start_series()
130
 
        builder.build_snapshot('BASE-id', None,
131
 
            [('add', ('', None, 'directory', None)),
132
 
             ('add', ('foo', 'foo-id', 'file', 'a\nb\nc\nd\ne\n')),
133
 
            ])
134
 
        # Delete 'b\n'
135
 
        builder.build_snapshot('OTHER-id', ['BASE-id'],
136
 
            [('modify', ('foo-id', 'a\nc\nd\ne\n'))])
137
 
        # Modify 'b\n', add 'X\n'
138
 
        builder.build_snapshot('THIS-id', ['BASE-id'],
139
 
            [('modify', ('foo-id', 'a\nb2\nc\nd\nX\ne\n'))])
140
 
        builder.finish_series()
141
 
        branch = builder.get_branch()
142
 
        this_tree = branch.bzrdir.create_workingtree()
143
 
        this_tree.lock_write()
144
 
        self.addCleanup(this_tree.unlock)
145
 
        other_tree = this_tree.bzrdir.sprout('other',
146
 
                                             'OTHER-id').open_workingtree()
147
 
        self.do_merge(this_tree, other_tree)
148
 
        if self.merge_type is _mod_merge.LCAMerger:
149
 
            self.expectFailure("lca merge doesn't track deleted lines",
150
 
                self.assertFileEqual,
151
 
                    'a\n'
152
 
                    '<<<<<<< TREE\n'
153
 
                    'b2\n'
154
 
                    '=======\n'
155
 
                    '>>>>>>> MERGE-SOURCE\n'
156
 
                    'c\n'
157
 
                    'd\n'
158
 
                    'X\n'
159
 
                    'e\n', 'test/foo')
160
 
        else:
161
 
            self.assertFileEqual(
162
 
                'a\n'
163
 
                '<<<<<<< TREE\n'
164
 
                'b2\n'
165
 
                '=======\n'
166
 
                '>>>>>>> MERGE-SOURCE\n'
167
 
                'c\n'
168
 
                'd\n'
169
 
                'X\n'
170
 
                'e\n', 'test/foo')
171
 
 
172
 
    def get_limbodir_deletiondir(self, wt):
173
 
        transform = TreeTransform(wt)
174
 
        limbodir = transform._limbodir
175
 
        deletiondir = transform._deletiondir
176
 
        transform.finalize()
177
 
        return (limbodir, deletiondir)
178
 
 
179
 
    def test_merge_with_existing_limbo(self):
180
 
        wt = self.make_branch_and_tree('this')
181
 
        (limbodir, deletiondir) =  self.get_limbodir_deletiondir(wt)
182
 
        os.mkdir(limbodir)
183
 
        self.assertRaises(errors.ExistingLimbo, self.do_merge, wt, wt)
184
 
        self.assertRaises(errors.LockError, wt.unlock)
185
 
 
186
 
    def test_merge_with_pending_deletion(self):
187
 
        wt = self.make_branch_and_tree('this')
188
 
        (limbodir, deletiondir) =  self.get_limbodir_deletiondir(wt)
189
 
        os.mkdir(deletiondir)
190
 
        self.assertRaises(errors.ExistingPendingDeletion, self.do_merge, wt, wt)
191
 
        self.assertRaises(errors.LockError, wt.unlock)
192
 
 
193
 
 
194
 
class TestHookMergeFileContent(TestCaseWithTransport):
195
 
    """Tests that the 'merge_file_content' hook is invoked."""
196
 
 
197
 
    def setUp(self):
198
 
        TestCaseWithTransport.setUp(self)
199
 
        self.hook_log = []
200
 
 
201
 
    def install_hook_inactive(self):
202
 
        def inactive_factory(merger):
203
 
            # This hook is never active
204
 
            self.hook_log.append(('inactive',))
205
 
            return None
206
 
        _mod_merge.Merger.hooks.install_named_hook(
207
 
            'merge_file_content', inactive_factory, 'test hook (inactive)')
208
 
 
209
 
    def install_hook_noop(self):
210
 
        test = self
211
 
        class HookNA(_mod_merge.AbstractPerFileMerger):
212
 
            def merge_contents(self, merge_params):
213
 
                # This hook unconditionally does nothing.
214
 
                test.hook_log.append(('no-op',))
215
 
                return 'not_applicable', None
216
 
        def hook_na_factory(merger):
217
 
            return HookNA(merger)
218
 
        _mod_merge.Merger.hooks.install_named_hook(
219
 
            'merge_file_content', hook_na_factory, 'test hook (no-op)')
220
 
 
221
 
    def install_hook_success(self):
222
 
        test = self
223
 
        class HookSuccess(_mod_merge.AbstractPerFileMerger):
224
 
            def merge_contents(self, merge_params):
225
 
                test.hook_log.append(('success',))
226
 
                if merge_params.file_id == '1':
227
 
                    return 'success', ['text-merged-by-hook']
228
 
                return 'not_applicable', None
229
 
        def hook_success_factory(merger):
230
 
            return HookSuccess(merger)
231
 
        _mod_merge.Merger.hooks.install_named_hook(
232
 
            'merge_file_content', hook_success_factory, 'test hook (success)')
233
 
 
234
 
    def install_hook_conflict(self):
235
 
        test = self
236
 
        class HookConflict(_mod_merge.AbstractPerFileMerger):
237
 
            def merge_contents(self, merge_params):
238
 
                test.hook_log.append(('conflict',))
239
 
                if merge_params.file_id == '1':
240
 
                    return ('conflicted',
241
 
                        ['text-with-conflict-markers-from-hook'])
242
 
                return 'not_applicable', None
243
 
        def hook_conflict_factory(merger):
244
 
            return HookConflict(merger)
245
 
        _mod_merge.Merger.hooks.install_named_hook(
246
 
            'merge_file_content', hook_conflict_factory, 'test hook (delete)')
247
 
 
248
 
    def install_hook_delete(self):
249
 
        test = self
250
 
        class HookDelete(_mod_merge.AbstractPerFileMerger):
251
 
            def merge_contents(self, merge_params):
252
 
                test.hook_log.append(('delete',))
253
 
                if merge_params.file_id == '1':
254
 
                    return 'delete', None
255
 
                return 'not_applicable', None
256
 
        def hook_delete_factory(merger):
257
 
            return HookDelete(merger)
258
 
        _mod_merge.Merger.hooks.install_named_hook(
259
 
            'merge_file_content', hook_delete_factory, 'test hook (delete)')
260
 
 
261
 
    def install_hook_log_lines(self):
262
 
        """Install a hook that saves the get_lines for the this, base and other
263
 
        versions of the file.
264
 
        """
265
 
        test = self
266
 
        class HookLogLines(_mod_merge.AbstractPerFileMerger):
267
 
            def merge_contents(self, merge_params):
268
 
                test.hook_log.append((
269
 
                    'log_lines',
270
 
                    merge_params.this_lines,
271
 
                    merge_params.other_lines,
272
 
                    merge_params.base_lines,
273
 
                    ))
274
 
                return 'not_applicable', None
275
 
        def hook_log_lines_factory(merger):
276
 
            return HookLogLines(merger)
277
 
        _mod_merge.Merger.hooks.install_named_hook(
278
 
            'merge_file_content', hook_log_lines_factory,
279
 
            'test hook (log_lines)')
280
 
 
281
 
    def make_merge_builder(self):
282
 
        builder = MergeBuilder(self.test_base_dir)
283
 
        self.addCleanup(builder.cleanup)
284
 
        return builder
285
 
 
286
 
    def create_file_needing_contents_merge(self, builder, file_id):
287
 
        builder.add_file(file_id, builder.tree_root, "name1", "text1", True)
288
 
        builder.change_contents(file_id, other="text4", this="text3")
289
 
 
290
 
    def test_change_vs_change(self):
291
 
        """Hook is used for (changed, changed)"""
292
 
        self.install_hook_success()
293
 
        builder = self.make_merge_builder()
294
 
        builder.add_file("1", builder.tree_root, "name1", "text1", True)
295
 
        builder.change_contents("1", other="text4", this="text3")
296
 
        conflicts = builder.merge(self.merge_type)
297
 
        self.assertEqual(conflicts, [])
298
 
        self.assertEqual(
299
 
            builder.this.get_file('1').read(), 'text-merged-by-hook')
300
 
 
301
 
    def test_change_vs_deleted(self):
302
 
        """Hook is used for (changed, deleted)"""
303
 
        self.install_hook_success()
304
 
        builder = self.make_merge_builder()
305
 
        builder.add_file("1", builder.tree_root, "name1", "text1", True)
306
 
        builder.change_contents("1", this="text2")
307
 
        builder.remove_file("1", other=True)
308
 
        conflicts = builder.merge(self.merge_type)
309
 
        self.assertEqual(conflicts, [])
310
 
        self.assertEqual(
311
 
            builder.this.get_file('1').read(), 'text-merged-by-hook')
312
 
 
313
 
    def test_result_can_be_delete(self):
314
 
        """A hook's result can be the deletion of a file."""
315
 
        self.install_hook_delete()
316
 
        builder = self.make_merge_builder()
317
 
        self.create_file_needing_contents_merge(builder, "1")
318
 
        conflicts = builder.merge(self.merge_type)
319
 
        self.assertEqual(conflicts, [])
320
 
        self.assertRaises(errors.NoSuchId, builder.this.id2path, '1')
321
 
        self.assertEqual([], list(builder.this.list_files()))
322
 
 
323
 
    def test_result_can_be_conflict(self):
324
 
        """A hook's result can be a conflict."""
325
 
        self.install_hook_conflict()
326
 
        builder = self.make_merge_builder()
327
 
        self.create_file_needing_contents_merge(builder, "1")
328
 
        conflicts = builder.merge(self.merge_type)
329
 
        self.assertEqual(conflicts, [TextConflict('name1', file_id='1')])
330
 
        # The hook still gets to set the file contents in this case, so that it
331
 
        # can insert custom conflict markers.
332
 
        self.assertEqual(
333
 
            builder.this.get_file('1').read(),
334
 
            'text-with-conflict-markers-from-hook')
335
 
 
336
 
    def test_can_access_this_other_and_base_versions(self):
337
 
        """The hook function can call params.merger.get_lines to access the
338
 
        THIS/OTHER/BASE versions of the file.
339
 
        """
340
 
        self.install_hook_log_lines()
341
 
        builder = self.make_merge_builder()
342
 
        builder.add_file("1", builder.tree_root, "name1", "text1", True)
343
 
        builder.change_contents("1", this="text2", other="text3")
344
 
        conflicts = builder.merge(self.merge_type)
345
 
        self.assertEqual(
346
 
            [('log_lines', ['text2'], ['text3'], ['text1'])], self.hook_log)
347
 
 
348
 
    def test_chain_when_not_active(self):
349
 
        """When a hook function returns None, merging still works."""
350
 
        self.install_hook_inactive()
351
 
        self.install_hook_success()
352
 
        builder = self.make_merge_builder()
353
 
        self.create_file_needing_contents_merge(builder, "1")
354
 
        conflicts = builder.merge(self.merge_type)
355
 
        self.assertEqual(conflicts, [])
356
 
        self.assertEqual(
357
 
            builder.this.get_file('1').read(), 'text-merged-by-hook')
358
 
        self.assertEqual([('inactive',), ('success',)], self.hook_log)
359
 
 
360
 
    def test_chain_when_not_applicable(self):
361
 
        """When a hook function returns not_applicable, the next function is
362
 
        tried (when one exists).
363
 
        """
364
 
        self.install_hook_noop()
365
 
        self.install_hook_success()
366
 
        builder = self.make_merge_builder()
367
 
        self.create_file_needing_contents_merge(builder, "1")
368
 
        conflicts = builder.merge(self.merge_type)
369
 
        self.assertEqual(conflicts, [])
370
 
        self.assertEqual(
371
 
            builder.this.get_file('1').read(), 'text-merged-by-hook')
372
 
        self.assertEqual([('no-op',), ('success',)], self.hook_log)
373
 
 
374
 
    def test_chain_stops_after_success(self):
375
 
        """When a hook function returns success, no later functions are tried.
376
 
        """
377
 
        self.install_hook_success()
378
 
        self.install_hook_noop()
379
 
        builder = self.make_merge_builder()
380
 
        self.create_file_needing_contents_merge(builder, "1")
381
 
        conflicts = builder.merge(self.merge_type)
382
 
        self.assertEqual([('success',)], self.hook_log)
383
 
 
384
 
    def test_chain_stops_after_conflict(self):
385
 
        """When a hook function returns conflict, no later functions are tried.
386
 
        """
387
 
        self.install_hook_conflict()
388
 
        self.install_hook_noop()
389
 
        builder = self.make_merge_builder()
390
 
        self.create_file_needing_contents_merge(builder, "1")
391
 
        conflicts = builder.merge(self.merge_type)
392
 
        self.assertEqual([('conflict',)], self.hook_log)
393
 
 
394
 
    def test_chain_stops_after_delete(self):
395
 
        """When a hook function returns delete, no later functions are tried.
396
 
        """
397
 
        self.install_hook_delete()
398
 
        self.install_hook_noop()
399
 
        builder = self.make_merge_builder()
400
 
        self.create_file_needing_contents_merge(builder, "1")
401
 
        conflicts = builder.merge(self.merge_type)
402
 
        self.assertEqual([('delete',)], self.hook_log)
403