~bzr-pqm/bzr/bzr.dev

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
6015.51.1 by Martin Pool
Tolerate empty limbo and pending-deletion directories
177
    def test_merge_with_existing_limbo_empty(self):
178
        """Empty limbo dir is just cleaned up - see bug 427773"""
179
        wt = self.make_branch_and_tree('this')
180
        (limbodir, deletiondir) =  self.get_limbodir_deletiondir(wt)
181
        os.mkdir(limbodir)
182
        self.do_merge(wt, wt)
183
184
    def test_merge_with_existing_limbo_non_empty(self):
185
        wt = self.make_branch_and_tree('this')
186
        (limbodir, deletiondir) =  self.get_limbodir_deletiondir(wt)
187
        os.mkdir(limbodir)
188
        os.mkdir(os.path.join(limbodir, 'something'))
4869.2.5 by Andrew Bennetts
Move per-merger tests into a per_merger test module.
189
        self.assertRaises(errors.ExistingLimbo, self.do_merge, wt, wt)
190
        self.assertRaises(errors.LockError, wt.unlock)
191
6015.51.1 by Martin Pool
Tolerate empty limbo and pending-deletion directories
192
    def test_merge_with_pending_deletion_empty(self):
6015.51.2 by Martin Pool
Get the test names right
193
        wt = self.make_branch_and_tree('this')
194
        (limbodir, deletiondir) =  self.get_limbodir_deletiondir(wt)
195
        os.mkdir(deletiondir)
6015.51.3 by Martin Pool
test_merge_with_pending_deletion_empty: correct the assertions
196
        self.do_merge(wt, wt)
6015.51.2 by Martin Pool
Get the test names right
197
198
    def test_merge_with_pending_deletion_non_empty(self):
6015.51.1 by Martin Pool
Tolerate empty limbo and pending-deletion directories
199
        """Also see bug 427773"""
200
        wt = self.make_branch_and_tree('this')
201
        (limbodir, deletiondir) =  self.get_limbodir_deletiondir(wt)
202
        os.mkdir(deletiondir)
203
        os.mkdir(os.path.join(deletiondir, 'something'))
204
        self.assertRaises(errors.ExistingPendingDeletion, self.do_merge, wt, wt)
205
        self.assertRaises(errors.LockError, wt.unlock)
206
4869.2.5 by Andrew Bennetts
Move per-merger tests into a per_merger test module.
207
4869.3.3 by Andrew Bennetts
Move test_hook_merge_file_content to per_merger.
208
class TestHookMergeFileContent(TestCaseWithTransport):
209
    """Tests that the 'merge_file_content' hook is invoked."""
210
4869.3.10 by Andrew Bennetts
Add more tests.
211
    def setUp(self):
212
        TestCaseWithTransport.setUp(self)
213
        self.hook_log = []
4869.3.34 by Vincent Ladeuil
Finish the patch based on reviews.
214
4797.5.1 by Robert Collins
Support state on per-file merging to permit more efficient use of configuration data.
215
    def install_hook_inactive(self):
216
        def inactive_factory(merger):
217
            # This hook is never active
218
            self.hook_log.append(('inactive',))
219
            return None
220
        _mod_merge.Merger.hooks.install_named_hook(
221
            'merge_file_content', inactive_factory, 'test hook (inactive)')
222
4869.3.5 by Andrew Bennetts
Hook is invoked for delete vs. change conflicts, and may choose to result in deleting the file.
223
    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.
224
        test = self
225
        class HookNA(_mod_merge.AbstractPerFileMerger):
226
            def merge_contents(self, merge_params):
227
                # This hook unconditionally does nothing.
228
                test.hook_log.append(('no-op',))
229
                return 'not_applicable', None
230
        def hook_na_factory(merger):
231
            return HookNA(merger)
4869.3.3 by Andrew Bennetts
Move test_hook_merge_file_content to per_merger.
232
        _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.
233
            '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.
234
235
    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.
236
        test = self
237
        class HookSuccess(_mod_merge.AbstractPerFileMerger):
238
            def merge_contents(self, merge_params):
239
                test.hook_log.append(('success',))
240
                if merge_params.file_id == '1':
241
                    return 'success', ['text-merged-by-hook']
242
                return 'not_applicable', None
243
        def hook_success_factory(merger):
244
            return HookSuccess(merger)
4869.3.3 by Andrew Bennetts
Move test_hook_merge_file_content to per_merger.
245
        _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.
246
            'merge_file_content', hook_success_factory, 'test hook (success)')
4869.3.34 by Vincent Ladeuil
Finish the patch based on reviews.
247
4869.3.6 by Andrew Bennetts
Add more tests.
248
    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.
249
        test = self
250
        class HookConflict(_mod_merge.AbstractPerFileMerger):
251
            def merge_contents(self, merge_params):
252
                test.hook_log.append(('conflict',))
253
                if merge_params.file_id == '1':
254
                    return ('conflicted',
255
                        ['text-with-conflict-markers-from-hook'])
256
                return 'not_applicable', None
257
        def hook_conflict_factory(merger):
258
            return HookConflict(merger)
4869.3.6 by Andrew Bennetts
Add more tests.
259
        _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.
260
            'merge_file_content', hook_conflict_factory, 'test hook (delete)')
4869.3.34 by Vincent Ladeuil
Finish the patch based on reviews.
261
4869.3.5 by Andrew Bennetts
Hook is invoked for delete vs. change conflicts, and may choose to result in deleting the file.
262
    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.
263
        test = self
264
        class HookDelete(_mod_merge.AbstractPerFileMerger):
265
            def merge_contents(self, merge_params):
266
                test.hook_log.append(('delete',))
267
                if merge_params.file_id == '1':
268
                    return 'delete', None
269
                return 'not_applicable', None
270
        def hook_delete_factory(merger):
271
            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.
272
        _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.
273
            'merge_file_content', hook_delete_factory, 'test hook (delete)')
4869.3.34 by Vincent Ladeuil
Finish the patch based on reviews.
274
4869.3.6 by Andrew Bennetts
Add more tests.
275
    def install_hook_log_lines(self):
276
        """Install a hook that saves the get_lines for the this, base and other
277
        versions of the file.
278
        """
4797.5.1 by Robert Collins
Support state on per-file merging to permit more efficient use of configuration data.
279
        test = self
280
        class HookLogLines(_mod_merge.AbstractPerFileMerger):
281
            def merge_contents(self, merge_params):
282
                test.hook_log.append((
283
                    'log_lines',
284
                    merge_params.this_lines,
285
                    merge_params.other_lines,
286
                    merge_params.base_lines,
287
                    ))
288
                return 'not_applicable', None
289
        def hook_log_lines_factory(merger):
290
            return HookLogLines(merger)
4869.3.6 by Andrew Bennetts
Add more tests.
291
        _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.
292
            'merge_file_content', hook_log_lines_factory,
293
            'test hook (log_lines)')
4869.3.6 by Andrew Bennetts
Add more tests.
294
4869.3.3 by Andrew Bennetts
Move test_hook_merge_file_content to per_merger.
295
    def make_merge_builder(self):
296
        builder = MergeBuilder(self.test_base_dir)
297
        self.addCleanup(builder.cleanup)
298
        return builder
299
4869.3.10 by Andrew Bennetts
Add more tests.
300
    def create_file_needing_contents_merge(self, builder, file_id):
301
        builder.add_file(file_id, builder.tree_root, "name1", "text1", True)
302
        builder.change_contents(file_id, other="text4", this="text3")
4869.3.34 by Vincent Ladeuil
Finish the patch based on reviews.
303
4869.3.5 by Andrew Bennetts
Hook is invoked for delete vs. change conflicts, and may choose to result in deleting the file.
304
    def test_change_vs_change(self):
305
        """Hook is used for (changed, changed)"""
306
        self.install_hook_success()
4869.3.3 by Andrew Bennetts
Move test_hook_merge_file_content to per_merger.
307
        builder = self.make_merge_builder()
308
        builder.add_file("1", builder.tree_root, "name1", "text1", True)
309
        builder.change_contents("1", other="text4", this="text3")
310
        conflicts = builder.merge(self.merge_type)
311
        self.assertEqual(conflicts, [])
312
        self.assertEqual(
313
            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.
314
4869.3.5 by Andrew Bennetts
Hook is invoked for delete vs. change conflicts, and may choose to result in deleting the file.
315
    def test_change_vs_deleted(self):
316
        """Hook is used for (changed, deleted)"""
317
        self.install_hook_success()
318
        builder = self.make_merge_builder()
319
        builder.add_file("1", builder.tree_root, "name1", "text1", True)
320
        builder.change_contents("1", this="text2")
321
        builder.remove_file("1", other=True)
322
        conflicts = builder.merge(self.merge_type)
323
        self.assertEqual(conflicts, [])
324
        self.assertEqual(
325
            builder.this.get_file('1').read(), 'text-merged-by-hook')
326
327
    def test_result_can_be_delete(self):
328
        """A hook's result can be the deletion of a file."""
329
        self.install_hook_delete()
330
        builder = self.make_merge_builder()
4869.3.10 by Andrew Bennetts
Add more tests.
331
        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.
332
        conflicts = builder.merge(self.merge_type)
333
        self.assertEqual(conflicts, [])
334
        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.
335
        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.
336
4869.3.6 by Andrew Bennetts
Add more tests.
337
    def test_result_can_be_conflict(self):
338
        """A hook's result can be a conflict."""
339
        self.install_hook_conflict()
340
        builder = self.make_merge_builder()
4869.3.10 by Andrew Bennetts
Add more tests.
341
        self.create_file_needing_contents_merge(builder, "1")
4869.3.6 by Andrew Bennetts
Add more tests.
342
        conflicts = builder.merge(self.merge_type)
343
        self.assertEqual(conflicts, [TextConflict('name1', file_id='1')])
344
        # The hook still gets to set the file contents in this case, so that it
345
        # can insert custom conflict markers.
346
        self.assertEqual(
347
            builder.this.get_file('1').read(),
348
            'text-with-conflict-markers-from-hook')
349
350
    def test_can_access_this_other_and_base_versions(self):
351
        """The hook function can call params.merger.get_lines to access the
352
        THIS/OTHER/BASE versions of the file.
353
        """
354
        self.install_hook_log_lines()
355
        builder = self.make_merge_builder()
356
        builder.add_file("1", builder.tree_root, "name1", "text1", True)
357
        builder.change_contents("1", this="text2", other="text3")
358
        conflicts = builder.merge(self.merge_type)
359
        self.assertEqual(
4869.3.10 by Andrew Bennetts
Add more tests.
360
            [('log_lines', ['text2'], ['text3'], ['text1'])], self.hook_log)
361
4797.5.1 by Robert Collins
Support state on per-file merging to permit more efficient use of configuration data.
362
    def test_chain_when_not_active(self):
363
        """When a hook function returns None, merging still works."""
364
        self.install_hook_inactive()
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([('inactive',), ('success',)], self.hook_log)
373
4869.3.10 by Andrew Bennetts
Add more tests.
374
    def test_chain_when_not_applicable(self):
375
        """When a hook function returns not_applicable, the next function is
376
        tried (when one exists).
377
        """
378
        self.install_hook_noop()
379
        self.install_hook_success()
380
        builder = self.make_merge_builder()
381
        self.create_file_needing_contents_merge(builder, "1")
382
        conflicts = builder.merge(self.merge_type)
383
        self.assertEqual(conflicts, [])
384
        self.assertEqual(
385
            builder.this.get_file('1').read(), 'text-merged-by-hook')
386
        self.assertEqual([('no-op',), ('success',)], self.hook_log)
387
388
    def test_chain_stops_after_success(self):
389
        """When a hook function returns success, no later functions are tried.
390
        """
391
        self.install_hook_success()
392
        self.install_hook_noop()
393
        builder = self.make_merge_builder()
394
        self.create_file_needing_contents_merge(builder, "1")
395
        conflicts = builder.merge(self.merge_type)
396
        self.assertEqual([('success',)], self.hook_log)
397
398
    def test_chain_stops_after_conflict(self):
399
        """When a hook function returns conflict, no later functions are tried.
400
        """
401
        self.install_hook_conflict()
402
        self.install_hook_noop()
403
        builder = self.make_merge_builder()
404
        self.create_file_needing_contents_merge(builder, "1")
405
        conflicts = builder.merge(self.merge_type)
406
        self.assertEqual([('conflict',)], self.hook_log)
407
408
    def test_chain_stops_after_delete(self):
409
        """When a hook function returns delete, no later functions are tried.
410
        """
411
        self.install_hook_delete()
412
        self.install_hook_noop()
413
        builder = self.make_merge_builder()
414
        self.create_file_needing_contents_merge(builder, "1")
415
        conflicts = builder.merge(self.merge_type)
416
        self.assertEqual([('delete',)], self.hook_log)
4869.3.6 by Andrew Bennetts
Add more tests.
417