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

  • Committer: Jelmer Vernooij
  • Date: 2017-05-21 12:41:27 UTC
  • mto: This revision was merged to the branch mainline in revision 6623.
  • Revision ID: jelmer@jelmer.uk-20170521124127-iv8etg0vwymyai6y
s/bzr/brz/ in apport config.

Show diffs side-by-side

added added

removed removed

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