/brz/remove-bazaar

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