/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: Martin
  • Date: 2018-08-21 00:53:34 UTC
  • mto: This revision was merged to the branch mainline in revision 7074.
  • Revision ID: gzlist@googlemail.com-20180821005334-e1ogxakojyybpwib
Fix recursion check in C bencode implementation

Hard to get Cython to do the right thing but by inverting the
return code can use the standard except handling.

Avoid going through a Python call when encoding, which requires
the encode recursion check to work too.

Adjust tests to use a smaller limit to be more managable.

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
from ..transform import TreeTransform
 
32
 
 
33
 
 
34
 
 
35
def load_tests(loader, standard_tests, pattern):
38
36
    """Multiply tests for tranport implementations."""
39
37
    result = loader.suiteClass()
40
38
    scenarios = [
41
39
        (name, {'merge_type': merger})
42
 
        for name, merger in option._merge_type_registry.items()]
 
40
        for name, merger in _mod_merge.merge_type_registry.items()]
43
41
    return multiply_tests(standard_tests, scenarios, result)
44
42
 
45
43
 
46
44
class TestMergeImplementation(TestCaseWithTransport):
47
45
 
48
46
    def do_merge(self, target_tree, source_tree, **kwargs):
49
 
        merger = _mod_merge.Merger.from_revision_ids(None,
 
47
        merger = _mod_merge.Merger.from_revision_ids(
50
48
            target_tree, source_tree.last_revision(),
51
49
            other_branch=source_tree.branch)
52
50
        merger.merge_type=self.merge_type
59
57
        this_tree.lock_write()
60
58
        self.addCleanup(this_tree.unlock)
61
59
        self.build_tree_contents([
62
 
            ('this/file1', 'a\nb\n'),
63
 
            ('this/file2', 'a\nb\n')
 
60
            ('this/file1', b'a\nb\n'),
 
61
            ('this/file2', b'a\nb\n')
64
62
        ])
65
63
        this_tree.add(['file1', 'file2'])
66
64
        this_tree.commit('Added files')
67
 
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
 
65
        other_tree = this_tree.controldir.sprout('other').open_workingtree()
68
66
        self.build_tree_contents([
69
 
            ('other/file1', 'a\nb\nc\n'),
70
 
            ('other/file2', 'a\nb\nc\n')
 
67
            ('other/file1', b'a\nb\nc\n'),
 
68
            ('other/file2', b'a\nb\nc\n')
71
69
        ])
72
70
        other_tree.commit('modified both')
73
71
        self.build_tree_contents([
74
 
            ('this/file1', 'd\na\nb\n'),
75
 
            ('this/file2', 'd\na\nb\n')
 
72
            ('this/file1', b'd\na\nb\n'),
 
73
            ('this/file2', b'd\na\nb\n')
76
74
        ])
77
75
        this_tree.commit('modified both')
78
76
        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')
 
77
        self.assertFileEqual(b'd\na\nb\nc\n', 'this/file1')
 
78
        self.assertFileEqual(b'd\na\nb\n', 'this/file2')
81
79
 
82
80
    def test_merge_move_and_change(self):
83
81
        this_tree = self.make_branch_and_tree('this')
84
82
        this_tree.lock_write()
85
83
        self.addCleanup(this_tree.unlock)
86
84
        self.build_tree_contents([
87
 
            ('this/file1', 'line 1\nline 2\nline 3\nline 4\n'),
 
85
            ('this/file1', b'line 1\nline 2\nline 3\nline 4\n'),
88
86
        ])
89
87
        this_tree.add('file1',)
90
88
        this_tree.commit('Added file')
91
 
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
 
89
        other_tree = this_tree.controldir.sprout('other').open_workingtree()
92
90
        self.build_tree_contents([
93
 
            ('other/file1', 'line 1\nline 2 to 2.1\nline 3\nline 4\n'),
 
91
            ('other/file1', b'line 1\nline 2 to 2.1\nline 3\nline 4\n'),
94
92
        ])
95
93
        other_tree.commit('Changed 2 to 2.1')
96
94
        self.build_tree_contents([
97
 
            ('this/file1', 'line 1\nline 3\nline 2\nline 4\n'),
 
95
            ('this/file1', b'line 1\nline 3\nline 2\nline 4\n'),
98
96
        ])
99
97
        this_tree.commit('Swapped 2 & 3')
100
98
        self.do_merge(this_tree, other_tree)
102
100
            self.expectFailure(
103
101
                "lca merge doesn't conflict for move and change",
104
102
                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')
 
103
                b'line 1\n'
 
104
                b'<<<<<<< TREE\n'
 
105
                b'line 3\n'
 
106
                b'line 2\n'
 
107
                b'=======\n'
 
108
                b'line 2 to 2.1\n'
 
109
                b'line 3\n'
 
110
                b'>>>>>>> MERGE-SOURCE\n'
 
111
                b'line 4\n', 'this/file1')
114
112
        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')
 
113
            self.assertFileEqual(b'line 1\n'
 
114
                b'<<<<<<< TREE\n'
 
115
                b'line 3\n'
 
116
                b'line 2\n'
 
117
                b'=======\n'
 
118
                b'line 2 to 2.1\n'
 
119
                b'line 3\n'
 
120
                b'>>>>>>> MERGE-SOURCE\n'
 
121
                b'line 4\n', 'this/file1')
124
122
 
125
123
    def test_modify_conflicts_with_delete(self):
126
124
        # If one side deletes a line, and the other modifies that line, then
127
125
        # the modification should be considered a conflict
128
126
        builder = self.make_branch_builder('test')
129
127
        builder.start_series()
130
 
        builder.build_snapshot('BASE-id', None,
 
128
        builder.build_snapshot(None,
131
129
            [('add', ('', None, 'directory', None)),
132
 
             ('add', ('foo', 'foo-id', 'file', 'a\nb\nc\nd\ne\n')),
133
 
            ])
 
130
             ('add', ('foo', b'foo-id', 'file', b'a\nb\nc\nd\ne\n')),
 
131
            ], revision_id=b'BASE-id')
134
132
        # Delete 'b\n'
135
 
        builder.build_snapshot('OTHER-id', ['BASE-id'],
136
 
            [('modify', ('foo-id', 'a\nc\nd\ne\n'))])
 
133
        builder.build_snapshot([b'BASE-id'],
 
134
            [('modify', ('foo', b'a\nc\nd\ne\n'))],
 
135
            revision_id=b'OTHER-id')
137
136
        # 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'))])
 
137
        builder.build_snapshot([b'BASE-id'],
 
138
            [('modify', ('foo', b'a\nb2\nc\nd\nX\ne\n'))],
 
139
            revision_id=b'THIS-id')
140
140
        builder.finish_series()
141
141
        branch = builder.get_branch()
142
 
        this_tree = branch.bzrdir.create_workingtree()
 
142
        this_tree = branch.controldir.create_workingtree()
143
143
        this_tree.lock_write()
144
144
        self.addCleanup(this_tree.unlock)
145
 
        other_tree = this_tree.bzrdir.sprout('other',
146
 
                                             'OTHER-id').open_workingtree()
 
145
        other_tree = this_tree.controldir.sprout('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
149
                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')
 
150
                    b'a\n'
 
151
                    b'<<<<<<< TREE\n'
 
152
                    b'b2\n'
 
153
                    b'=======\n'
 
154
                    b'>>>>>>> MERGE-SOURCE\n'
 
155
                    b'c\n'
 
156
                    b'd\n'
 
157
                    b'X\n'
 
158
                    b'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
172
        transform = TreeTransform(wt)
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)
 
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'))
190
205
        self.assertRaises(errors.ExistingPendingDeletion, self.do_merge, wt, wt)
191
206
        self.assertRaises(errors.LockError, wt.unlock)
192
207
 
195
210
    """Tests that the 'merge_file_content' hook is invoked."""
196
211
 
197
212
    def setUp(self):
198
 
        TestCaseWithTransport.setUp(self)
 
213
        super(TestHookMergeFileContent, self).setUp()
199
214
        self.hook_log = []
200
215
 
201
216
    def install_hook_inactive(self):
223
238
        class HookSuccess(_mod_merge.AbstractPerFileMerger):
224
239
            def merge_contents(self, merge_params):
225
240
                test.hook_log.append(('success',))
226
 
                if merge_params.file_id == '1':
227
 
                    return 'success', ['text-merged-by-hook']
 
241
                if merge_params.file_id == b'1':
 
242
                    return 'success', [b'text-merged-by-hook']
228
243
                return 'not_applicable', None
229
244
        def hook_success_factory(merger):
230
245
            return HookSuccess(merger)
236
251
        class HookConflict(_mod_merge.AbstractPerFileMerger):
237
252
            def merge_contents(self, merge_params):
238
253
                test.hook_log.append(('conflict',))
239
 
                if merge_params.file_id == '1':
 
254
                if merge_params.file_id == b'1':
240
255
                    return ('conflicted',
241
 
                        ['text-with-conflict-markers-from-hook'])
 
256
                        [b'text-with-conflict-markers-from-hook'])
242
257
                return 'not_applicable', None
243
258
        def hook_conflict_factory(merger):
244
259
            return HookConflict(merger)
250
265
        class HookDelete(_mod_merge.AbstractPerFileMerger):
251
266
            def merge_contents(self, merge_params):
252
267
                test.hook_log.append(('delete',))
253
 
                if merge_params.file_id == '1':
 
268
                if merge_params.file_id == b'1':
254
269
                    return 'delete', None
255
270
                return 'not_applicable', None
256
271
        def hook_delete_factory(merger):
284
299
        return builder
285
300
 
286
301
    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")
 
302
        builder.add_file(file_id, builder.tree_root, "name1", b"text1", True)
 
303
        builder.change_contents(file_id, other=b"text4", this=b"text3")
289
304
 
290
305
    def test_change_vs_change(self):
291
306
        """Hook is used for (changed, changed)"""
292
307
        self.install_hook_success()
293
308
        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")
 
309
        builder.add_file(b"1", builder.tree_root, "name1", b"text1", True)
 
310
        builder.change_contents(b"1", other=b"text4", this=b"text3")
296
311
        conflicts = builder.merge(self.merge_type)
297
312
        self.assertEqual(conflicts, [])
298
 
        self.assertEqual(
299
 
            builder.this.get_file('1').read(), 'text-merged-by-hook')
 
313
        with builder.this.get_file('name1') as f:
 
314
            self.assertEqual(f.read(), b'text-merged-by-hook')
300
315
 
301
316
    def test_change_vs_deleted(self):
302
317
        """Hook is used for (changed, deleted)"""
303
318
        self.install_hook_success()
304
319
        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)
 
320
        builder.add_file(b"1", builder.tree_root, "name1", b"text1", True)
 
321
        builder.change_contents(b"1", this=b"text2")
 
322
        builder.remove_file(b"1", other=True)
308
323
        conflicts = builder.merge(self.merge_type)
309
324
        self.assertEqual(conflicts, [])
310
 
        self.assertEqual(
311
 
            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')
312
327
 
313
328
    def test_result_can_be_delete(self):
314
329
        """A hook's result can be the deletion of a file."""
315
330
        self.install_hook_delete()
316
331
        builder = self.make_merge_builder()
317
 
        self.create_file_needing_contents_merge(builder, "1")
 
332
        self.create_file_needing_contents_merge(builder, b"1")
318
333
        conflicts = builder.merge(self.merge_type)
319
334
        self.assertEqual(conflicts, [])
320
 
        self.assertRaises(errors.NoSuchId, builder.this.id2path, '1')
 
335
        self.assertRaises(errors.NoSuchId, builder.this.id2path, b'1')
321
336
        self.assertEqual([], list(builder.this.list_files()))
322
337
 
323
338
    def test_result_can_be_conflict(self):
324
339
        """A hook's result can be a conflict."""
325
340
        self.install_hook_conflict()
326
341
        builder = self.make_merge_builder()
327
 
        self.create_file_needing_contents_merge(builder, "1")
 
342
        self.create_file_needing_contents_merge(builder, b"1")
328
343
        conflicts = builder.merge(self.merge_type)
329
 
        self.assertEqual(conflicts, [TextConflict('name1', file_id='1')])
 
344
        self.assertEqual(conflicts, [TextConflict('name1', file_id=b'1')])
330
345
        # The hook still gets to set the file contents in this case, so that it
331
346
        # can insert custom conflict markers.
332
 
        self.assertEqual(
333
 
            builder.this.get_file('1').read(),
334
 
            'text-with-conflict-markers-from-hook')
 
347
        with builder.this.get_file('name1') as f:
 
348
            self.assertEqual(f.read(), b'text-with-conflict-markers-from-hook')
335
349
 
336
350
    def test_can_access_this_other_and_base_versions(self):
337
351
        """The hook function can call params.merger.get_lines to access the
339
353
        """
340
354
        self.install_hook_log_lines()
341
355
        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")
 
356
        builder.add_file(b"1", builder.tree_root, "name1", b"text1", True)
 
357
        builder.change_contents(b"1", this=b"text2", other=b"text3")
344
358
        conflicts = builder.merge(self.merge_type)
345
359
        self.assertEqual(
346
 
            [('log_lines', ['text2'], ['text3'], ['text1'])], self.hook_log)
 
360
            [('log_lines', [b'text2'], [b'text3'], [b'text1'])], self.hook_log)
347
361
 
348
362
    def test_chain_when_not_active(self):
349
363
        """When a hook function returns None, merging still works."""
350
364
        self.install_hook_inactive()
351
365
        self.install_hook_success()
352
366
        builder = self.make_merge_builder()
353
 
        self.create_file_needing_contents_merge(builder, "1")
 
367
        self.create_file_needing_contents_merge(builder, b"1")
354
368
        conflicts = builder.merge(self.merge_type)
355
369
        self.assertEqual(conflicts, [])
356
 
        self.assertEqual(
357
 
            builder.this.get_file('1').read(), 'text-merged-by-hook')
 
370
        with builder.this.get_file('name1') as f:
 
371
            self.assertEqual(f.read(), b'text-merged-by-hook')
358
372
        self.assertEqual([('inactive',), ('success',)], self.hook_log)
359
373
 
360
374
    def test_chain_when_not_applicable(self):
364
378
        self.install_hook_noop()
365
379
        self.install_hook_success()
366
380
        builder = self.make_merge_builder()
367
 
        self.create_file_needing_contents_merge(builder, "1")
 
381
        self.create_file_needing_contents_merge(builder, b"1")
368
382
        conflicts = builder.merge(self.merge_type)
369
383
        self.assertEqual(conflicts, [])
370
 
        self.assertEqual(
371
 
            builder.this.get_file('1').read(), 'text-merged-by-hook')
 
384
        with builder.this.get_file('name1') as f:
 
385
            self.assertEqual(f.read(), b'text-merged-by-hook')
372
386
        self.assertEqual([('no-op',), ('success',)], self.hook_log)
373
387
 
374
388
    def test_chain_stops_after_success(self):
377
391
        self.install_hook_success()
378
392
        self.install_hook_noop()
379
393
        builder = self.make_merge_builder()
380
 
        self.create_file_needing_contents_merge(builder, "1")
 
394
        self.create_file_needing_contents_merge(builder, b"1")
381
395
        conflicts = builder.merge(self.merge_type)
382
396
        self.assertEqual([('success',)], self.hook_log)
383
397
 
387
401
        self.install_hook_conflict()
388
402
        self.install_hook_noop()
389
403
        builder = self.make_merge_builder()
390
 
        self.create_file_needing_contents_merge(builder, "1")
 
404
        self.create_file_needing_contents_merge(builder, b"1")
391
405
        conflicts = builder.merge(self.merge_type)
392
406
        self.assertEqual([('conflict',)], self.hook_log)
393
407
 
397
411
        self.install_hook_delete()
398
412
        self.install_hook_noop()
399
413
        builder = self.make_merge_builder()
400
 
        self.create_file_needing_contents_merge(builder, "1")
 
414
        self.create_file_needing_contents_merge(builder, b"1")
401
415
        conflicts = builder.merge(self.merge_type)
402
416
        self.assertEqual([('delete',)], self.hook_log)
403
417