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

  • Committer: Marius Kruger
  • Date: 2010-07-10 21:28:56 UTC
  • mto: (5384.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5385.
  • Revision ID: marius.kruger@enerweb.co.za-20100710212856-uq4ji3go0u5se7hx
* Update documentation
* add NEWS

Show diffs side-by-side

added added

removed removed

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