/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 breezy/tests/per_merger.py

  • Committer: Jelmer Vernooij
  • Date: 2020-04-05 19:11:34 UTC
  • mto: (7490.7.16 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200405191134-0aebh8ikiwygxma5
Populate the .gitignore file.

Show diffs side-by-side

added added

removed removed

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