/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/test_annotate.py

  • Committer: Vincent Ladeuil
  • Date: 2012-01-18 14:09:19 UTC
  • mto: This revision was merged to the branch mainline in revision 6468.
  • Revision ID: v.ladeuil+lp@free.fr-20120118140919-rlvdrhpc0nq1lbwi
Change set/remove to require a lock for the branch config files.

This means that tests (or any plugin for that matter) do not requires an
explicit lock on the branch anymore to change a single option. This also
means the optimisation becomes "opt-in" and as such won't be as
spectacular as it may be and/or harder to get right (nothing fails
anymore).

This reduces the diff by ~300 lines.

Code/tests that were updating more than one config option is still taking
a lock to at least avoid some IOs and demonstrate the benefits through
the decreased number of hpss calls.

The duplication between BranchStack and BranchOnlyStack will be removed
once the same sharing is in place for local config files, at which point
the Stack class itself may be able to host the changes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006-2009, 2011 Canonical Ltd
 
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
 
 
17
"""Whitebox tests for annotate functionality."""
 
18
 
 
19
import codecs
 
20
from cStringIO import StringIO
 
21
 
 
22
from bzrlib import (
 
23
    annotate,
 
24
    symbol_versioning,
 
25
    tests,
 
26
    )
 
27
 
 
28
 
 
29
def annotation(text):
 
30
    return [tuple(l.split(' ', 1)) for l in text.splitlines(True)]
 
31
 
 
32
 
 
33
parent_1 = annotation("""\
 
34
rev1 a
 
35
rev2 b
 
36
rev3 c
 
37
rev4 d
 
38
rev5 e
 
39
""")
 
40
 
 
41
 
 
42
parent_2 = annotation("""\
 
43
rev1 a
 
44
rev3 c
 
45
rev4 d
 
46
rev6 f
 
47
rev7 e
 
48
rev8 h
 
49
""")
 
50
 
 
51
 
 
52
expected_2_1 = annotation("""\
 
53
rev1 a
 
54
blahblah b
 
55
rev3 c
 
56
rev4 d
 
57
rev7 e
 
58
""")
 
59
 
 
60
 
 
61
# a: in both, same value, kept
 
62
# b: in 1, kept
 
63
# c: in both, same value, kept
 
64
# d: in both, same value, kept
 
65
# e: 1 and 2 disagree, so it goes to blahblah
 
66
# f: in 2, but not in new, so ignored
 
67
# g: not in 1 or 2, so it goes to blahblah
 
68
# h: only in parent 2, so 2 gets it
 
69
expected_1_2_2 = annotation("""\
 
70
rev1 a
 
71
rev2 b
 
72
rev3 c
 
73
rev4 d
 
74
blahblah e
 
75
blahblah g
 
76
rev8 h
 
77
""")
 
78
 
 
79
 
 
80
new_1 = """\
 
81
a
 
82
b
 
83
c
 
84
d
 
85
e
 
86
""".splitlines(True)
 
87
 
 
88
expected_1 = annotation("""\
 
89
blahblah a
 
90
blahblah b
 
91
blahblah c
 
92
blahblah d
 
93
blahblah e
 
94
""")
 
95
 
 
96
 
 
97
new_2 = """\
 
98
a
 
99
b
 
100
c
 
101
d
 
102
e
 
103
g
 
104
h
 
105
""".splitlines(True)
 
106
 
 
107
 
 
108
# For the 'duplicate' series, both sides introduce the same change, which then
 
109
# gets merged around. The last-modified should properly reflect this.
 
110
# We always change the fourth line so that the file is properly tracked as
 
111
# being modified in each revision. In reality, this probably would happen over
 
112
# many revisions, and it would be a different line that changes.
 
113
# BASE
 
114
#  |\
 
115
#  A B  # line should be annotated as new for A and B
 
116
#  |\|
 
117
#  C D  # line should 'converge' and say A
 
118
#  |/
 
119
#  E    # D should supersede A and stay as D (not become E because C references
 
120
#         A)
 
121
duplicate_base = annotation("""\
 
122
rev-base first
 
123
rev-base second
 
124
rev-base third
 
125
rev-base fourth-base
 
126
""")
 
127
 
 
128
duplicate_A = annotation("""\
 
129
rev-base first
 
130
rev-A alt-second
 
131
rev-base third
 
132
rev-A fourth-A
 
133
""")
 
134
 
 
135
duplicate_B = annotation("""\
 
136
rev-base first
 
137
rev-B alt-second
 
138
rev-base third
 
139
rev-B fourth-B
 
140
""")
 
141
 
 
142
duplicate_C = annotation("""\
 
143
rev-base first
 
144
rev-A alt-second
 
145
rev-base third
 
146
rev-C fourth-C
 
147
""")
 
148
 
 
149
duplicate_D = annotation("""\
 
150
rev-base first
 
151
rev-A alt-second
 
152
rev-base third
 
153
rev-D fourth-D
 
154
""")
 
155
 
 
156
duplicate_E = annotation("""\
 
157
rev-base first
 
158
rev-A alt-second
 
159
rev-base third
 
160
rev-E fourth-E
 
161
""")
 
162
 
 
163
 
 
164
class TestAnnotate(tests.TestCaseWithTransport):
 
165
 
 
166
    def create_merged_trees(self):
 
167
        """create 2 trees with merges between them.
 
168
 
 
169
        rev-1 --+
 
170
         |      |
 
171
        rev-2  rev-1_1_1
 
172
         |      |
 
173
         +------+
 
174
         |
 
175
        rev-3
 
176
        """
 
177
        builder = self.make_branch_builder('branch')
 
178
        builder.start_series()
 
179
        self.addCleanup(builder.finish_series)
 
180
        builder.build_snapshot('rev-1', None, [
 
181
            ('add', ('', 'root-id', 'directory', None)),
 
182
            ('add', ('a', 'a-id', 'file', 'first\n')),
 
183
            ], timestamp=1166046000.00, timezone=0, committer="joe@foo.com")
 
184
        builder.build_snapshot('rev-2', ['rev-1'], [
 
185
            ('modify', ('a-id', 'first\nsecond\n')),
 
186
            ], timestamp=1166046001.00, timezone=0, committer="joe@foo.com")
 
187
        builder.build_snapshot('rev-1_1_1', ['rev-1'], [
 
188
            ('modify', ('a-id', 'first\nthird\n')),
 
189
            ], timestamp=1166046002.00, timezone=0, committer="barry@foo.com")
 
190
        builder.build_snapshot('rev-3', ['rev-2', 'rev-1_1_1'], [
 
191
            ('modify', ('a-id', 'first\nsecond\nthird\n')),
 
192
            ], timestamp=1166046003.00, timezone=0, committer="sal@foo.com")
 
193
        return builder
 
194
 
 
195
    def create_deeply_merged_trees(self):
 
196
        """Create some trees with a more complex merge history.
 
197
 
 
198
        rev-1 --+
 
199
         |      |
 
200
        rev-2  rev-1_1_1 --+
 
201
         |      |          |
 
202
         +------+          |
 
203
         |      |          |
 
204
        rev-3  rev-1_1_2  rev-1_2_1 ------+
 
205
         |      |          |              |
 
206
         +------+          |              |
 
207
         |                 |              |
 
208
        rev-4             rev-1_2_2  rev-1_3_1
 
209
         |                 |              |
 
210
         +-----------------+              |
 
211
         |                                |
 
212
        rev-5                             |
 
213
         |                                |
 
214
         +--------------------------------+
 
215
         |
 
216
        rev-6
 
217
        """
 
218
        builder = self.create_merged_trees()
 
219
        builder.build_snapshot('rev-1_1_2', ['rev-1_1_1'], [])
 
220
        builder.build_snapshot('rev-4', ['rev-3', 'rev-1_1_2'], [])
 
221
        builder.build_snapshot('rev-1_2_1', ['rev-1_1_1'], [
 
222
            ('modify', ('a-id', 'first\nthird\nfourth\n')),
 
223
            ], timestamp=1166046003.00, timezone=0, committer="jerry@foo.com")
 
224
        builder.build_snapshot('rev-1_2_2', ['rev-1_2_1'], [],
 
225
            timestamp=1166046004.00, timezone=0, committer="jerry@foo.com")
 
226
        builder.build_snapshot('rev-5', ['rev-4', 'rev-1_2_2'], [
 
227
            ('modify', ('a-id', 'first\nsecond\nthird\nfourth\n')),
 
228
            ], timestamp=1166046004.00, timezone=0, committer="jerry@foo.com")
 
229
        builder.build_snapshot('rev-1_3_1', ['rev-1_2_1'], [
 
230
            ('modify', ('a-id', 'first\nthird\nfourth\nfifth\nsixth\n')),
 
231
            ], timestamp=1166046005.00, timezone=0, committer="george@foo.com")
 
232
        builder.build_snapshot('rev-6', ['rev-5', 'rev-1_3_1'], [
 
233
            ('modify', ('a-id',
 
234
                        'first\nsecond\nthird\nfourth\nfifth\nsixth\n')),
 
235
            ])
 
236
        return builder
 
237
 
 
238
    def create_duplicate_lines_tree(self):
 
239
        builder = self.make_branch_builder('branch')
 
240
        builder.start_series()
 
241
        self.addCleanup(builder.finish_series)
 
242
        base_text = ''.join(l for r, l in duplicate_base)
 
243
        a_text = ''.join(l for r, l in duplicate_A)
 
244
        b_text = ''.join(l for r, l in duplicate_B)
 
245
        c_text = ''.join(l for r, l in duplicate_C)
 
246
        d_text = ''.join(l for r, l in duplicate_D)
 
247
        e_text = ''.join(l for r, l in duplicate_E)
 
248
        builder.build_snapshot('rev-base', None, [
 
249
            ('add', ('', 'root-id', 'directory', None)),
 
250
            ('add', ('file', 'file-id', 'file', base_text)),
 
251
            ])
 
252
        builder.build_snapshot('rev-A', ['rev-base'], [
 
253
            ('modify', ('file-id', a_text))])
 
254
        builder.build_snapshot('rev-B', ['rev-base'], [
 
255
            ('modify', ('file-id', b_text))])
 
256
        builder.build_snapshot('rev-C', ['rev-A'], [
 
257
            ('modify', ('file-id', c_text))])
 
258
        builder.build_snapshot('rev-D', ['rev-B', 'rev-A'], [
 
259
            ('modify', ('file-id', d_text))])
 
260
        builder.build_snapshot('rev-E', ['rev-C', 'rev-D'], [
 
261
            ('modify', ('file-id', e_text))])
 
262
        return builder
 
263
 
 
264
    def assertAnnotateEqualDiff(self, actual, expected):
 
265
        if actual != expected:
 
266
            # Create an easier to understand diff when the lines don't actually
 
267
            # match
 
268
            self.assertEqualDiff(''.join('\t'.join(l) for l in expected),
 
269
                                 ''.join('\t'.join(l) for l in actual))
 
270
 
 
271
    def assertBranchAnnotate(self, expected, branch, file_id, revision_id,
 
272
            verbose=False, full=False, show_ids=False):
 
273
        tree = branch.repository.revision_tree(revision_id)
 
274
        to_file = StringIO()
 
275
        annotate.annotate_file_tree(tree, file_id, to_file,
 
276
            verbose=verbose, full=full, show_ids=show_ids, branch=branch)
 
277
        self.assertAnnotateEqualDiff(to_file.getvalue(), expected)
 
278
 
 
279
    def assertRepoAnnotate(self, expected, repo, file_id, revision_id):
 
280
        """Assert that the revision is properly annotated."""
 
281
        actual = list(repo.revision_tree(revision_id).annotate_iter(file_id))
 
282
        self.assertAnnotateEqualDiff(actual, expected)
 
283
 
 
284
    def test_annotate_duplicate_lines(self):
 
285
        # XXX: Should this be a per_repository test?
 
286
        builder = self.create_duplicate_lines_tree()
 
287
        repo = builder.get_branch().repository
 
288
        repo.lock_read()
 
289
        self.addCleanup(repo.unlock)
 
290
        self.assertRepoAnnotate(duplicate_base, repo, 'file-id', 'rev-base')
 
291
        self.assertRepoAnnotate(duplicate_A, repo, 'file-id', 'rev-A')
 
292
        self.assertRepoAnnotate(duplicate_B, repo, 'file-id', 'rev-B')
 
293
        self.assertRepoAnnotate(duplicate_C, repo, 'file-id', 'rev-C')
 
294
        self.assertRepoAnnotate(duplicate_D, repo, 'file-id', 'rev-D')
 
295
        self.assertRepoAnnotate(duplicate_E, repo, 'file-id', 'rev-E')
 
296
 
 
297
    def test_annotate_shows_dotted_revnos(self):
 
298
        builder = self.create_merged_trees()
 
299
 
 
300
        self.assertBranchAnnotate('1     joe@foo | first\n'
 
301
                                  '2     joe@foo | second\n'
 
302
                                  '1.1.1 barry@f | third\n',
 
303
                                  builder.get_branch(), 'a-id', 'rev-3')
 
304
 
 
305
    def test_annotate_file(self):
 
306
        builder = self.create_merged_trees()
 
307
 
 
308
        to_file = StringIO()
 
309
        self.applyDeprecated(symbol_versioning.deprecated_in((2, 4, 0)),
 
310
            annotate.annotate_file, builder.get_branch(),
 
311
                'rev-3', 'a-id', to_file=to_file)
 
312
 
 
313
        self.assertAnnotateEqualDiff('1     joe@foo | first\n'
 
314
                                     '2     joe@foo | second\n'
 
315
                                     '1.1.1 barry@f | third\n',
 
316
                                     to_file.getvalue())
 
317
 
 
318
    def test_annotate_limits_dotted_revnos(self):
 
319
        """Annotate should limit dotted revnos to a depth of 12"""
 
320
        builder = self.create_deeply_merged_trees()
 
321
 
 
322
        self.assertBranchAnnotate('1     joe@foo | first\n'
 
323
                                  '2     joe@foo | second\n'
 
324
                                  '1.1.1 barry@f | third\n'
 
325
                                  '1.2.1 jerry@f | fourth\n'
 
326
                                  '1.3.1 george@ | fifth\n'
 
327
                                  '              | sixth\n',
 
328
                                  builder.get_branch(), 'a-id', 'rev-6',
 
329
                                  verbose=False, full=False)
 
330
 
 
331
        self.assertBranchAnnotate('1     joe@foo | first\n'
 
332
                                  '2     joe@foo | second\n'
 
333
                                  '1.1.1 barry@f | third\n'
 
334
                                  '1.2.1 jerry@f | fourth\n'
 
335
                                  '1.3.1 george@ | fifth\n'
 
336
                                  '1.3.1 george@ | sixth\n',
 
337
                                  builder.get_branch(), 'a-id', 'rev-6',
 
338
                                  verbose=False, full=True)
 
339
 
 
340
        # verbose=True shows everything, the full revno, user id, and date
 
341
        self.assertBranchAnnotate('1     joe@foo.com    20061213 | first\n'
 
342
                                  '2     joe@foo.com    20061213 | second\n'
 
343
                                  '1.1.1 barry@foo.com  20061213 | third\n'
 
344
                                  '1.2.1 jerry@foo.com  20061213 | fourth\n'
 
345
                                  '1.3.1 george@foo.com 20061213 | fifth\n'
 
346
                                  '                              | sixth\n',
 
347
                                  builder.get_branch(), 'a-id', 'rev-6',
 
348
                                  verbose=True, full=False)
 
349
 
 
350
        self.assertBranchAnnotate('1     joe@foo.com    20061213 | first\n'
 
351
                                  '2     joe@foo.com    20061213 | second\n'
 
352
                                  '1.1.1 barry@foo.com  20061213 | third\n'
 
353
                                  '1.2.1 jerry@foo.com  20061213 | fourth\n'
 
354
                                  '1.3.1 george@foo.com 20061213 | fifth\n'
 
355
                                  '1.3.1 george@foo.com 20061213 | sixth\n',
 
356
                                  builder.get_branch(), 'a-id', 'rev-6',
 
357
                                  verbose=True, full=True)
 
358
 
 
359
    def test_annotate_uses_branch_context(self):
 
360
        """Dotted revnos should use the Branch context.
 
361
 
 
362
        When annotating a non-mainline revision, the annotation should still
 
363
        use dotted revnos from the mainline.
 
364
        """
 
365
        builder = self.create_deeply_merged_trees()
 
366
 
 
367
        self.assertBranchAnnotate('1     joe@foo | first\n'
 
368
                                  '1.1.1 barry@f | third\n'
 
369
                                  '1.2.1 jerry@f | fourth\n'
 
370
                                  '1.3.1 george@ | fifth\n'
 
371
                                  '              | sixth\n',
 
372
                                  builder.get_branch(), 'a-id', 'rev-1_3_1',
 
373
                                  verbose=False, full=False)
 
374
 
 
375
    def test_annotate_show_ids(self):
 
376
        builder = self.create_deeply_merged_trees()
 
377
 
 
378
        # It looks better with real revision ids :)
 
379
        self.assertBranchAnnotate('    rev-1 | first\n'
 
380
                                  '    rev-2 | second\n'
 
381
                                  'rev-1_1_1 | third\n'
 
382
                                  'rev-1_2_1 | fourth\n'
 
383
                                  'rev-1_3_1 | fifth\n'
 
384
                                  '          | sixth\n',
 
385
                                  builder.get_branch(), 'a-id', 'rev-6',
 
386
                                  show_ids=True, full=False)
 
387
 
 
388
        self.assertBranchAnnotate('    rev-1 | first\n'
 
389
                                  '    rev-2 | second\n'
 
390
                                  'rev-1_1_1 | third\n'
 
391
                                  'rev-1_2_1 | fourth\n'
 
392
                                  'rev-1_3_1 | fifth\n'
 
393
                                  'rev-1_3_1 | sixth\n',
 
394
                                  builder.get_branch(), 'a-id', 'rev-6',
 
395
                                  show_ids=True, full=True)
 
396
 
 
397
    def test_annotate_unicode_author(self):
 
398
        tree1 = self.make_branch_and_tree('tree1')
 
399
 
 
400
        self.build_tree_contents([('tree1/a', 'adi\xc3\xb3s')])
 
401
        tree1.add(['a'], ['a-id'])
 
402
        tree1.commit('a', rev_id='rev-1',
 
403
                     committer=u'Pepe P\xe9rez <pperez@ejemplo.com>',
 
404
                     timestamp=1166046000.00, timezone=0)
 
405
 
 
406
        self.build_tree_contents([('tree1/b', 'bye')])
 
407
        tree1.add(['b'], ['b-id'])
 
408
        tree1.commit('b', rev_id='rev-2',
 
409
                     committer=u'p\xe9rez',
 
410
                     timestamp=1166046000.00, timezone=0)
 
411
 
 
412
        tree1.lock_read()
 
413
        self.addCleanup(tree1.unlock)
 
414
 
 
415
        revtree_1 = tree1.branch.repository.revision_tree('rev-1')
 
416
        revtree_2 = tree1.branch.repository.revision_tree('rev-2')
 
417
 
 
418
        # this passes if no exception is raised
 
419
        to_file = StringIO()
 
420
        annotate.annotate_file_tree(revtree_1, 'a-id',
 
421
            to_file=to_file, branch=tree1.branch)
 
422
 
 
423
        sio = StringIO()
 
424
        to_file = codecs.getwriter('ascii')(sio)
 
425
        to_file.encoding = 'ascii' # codecs does not set it
 
426
        annotate.annotate_file_tree(revtree_2, 'b-id',
 
427
            to_file=to_file, branch=tree1.branch)
 
428
        self.assertEqualDiff('2   p?rez   | bye\n', sio.getvalue())
 
429
 
 
430
        # test now with to_file.encoding = None
 
431
        to_file = tests.StringIOWrapper()
 
432
        to_file.encoding = None
 
433
        annotate.annotate_file_tree(revtree_2, 'b-id',
 
434
            to_file=to_file, branch=tree1.branch)
 
435
        self.assertContainsRe('2   p.rez   | bye\n', to_file.getvalue())
 
436
 
 
437
        # and when it does not exist
 
438
        to_file = StringIO()
 
439
        annotate.annotate_file_tree(revtree_2, 'b-id',
 
440
            to_file=to_file, branch=tree1.branch)
 
441
        self.assertContainsRe('2   p.rez   | bye\n', to_file.getvalue())
 
442
 
 
443
    def test_annotate_author_or_committer(self):
 
444
        tree1 = self.make_branch_and_tree('tree1')
 
445
 
 
446
        self.build_tree_contents([('tree1/a', 'hello')])
 
447
        tree1.add(['a'], ['a-id'])
 
448
        tree1.commit('a', rev_id='rev-1',
 
449
                     committer='Committer <committer@example.com>',
 
450
                     timestamp=1166046000.00, timezone=0)
 
451
 
 
452
        self.build_tree_contents([('tree1/b', 'bye')])
 
453
        tree1.add(['b'], ['b-id'])
 
454
        tree1.commit('b', rev_id='rev-2',
 
455
                     committer='Committer <committer@example.com>',
 
456
                     authors=['Author <author@example.com>'],
 
457
                     timestamp=1166046000.00, timezone=0)
 
458
 
 
459
        tree1.lock_read()
 
460
        self.addCleanup(tree1.unlock)
 
461
 
 
462
        self.assertBranchAnnotate('1   committ | hello\n', tree1.branch,
 
463
            'a-id', 'rev-1')
 
464
 
 
465
        to_file = StringIO()
 
466
        self.assertBranchAnnotate('2   author@ | bye\n', tree1.branch,
 
467
            'b-id', 'rev-2')
 
468
 
 
469
 
 
470
class TestReannotate(tests.TestCase):
 
471
 
 
472
    def annotateEqual(self, expected, parents, newlines, revision_id,
 
473
                      blocks=None):
 
474
        annotate_list = list(annotate.reannotate(parents, newlines,
 
475
                             revision_id, blocks))
 
476
        self.assertEqual(len(expected), len(annotate_list))
 
477
        for e, a in zip(expected, annotate_list):
 
478
            self.assertEqual(e, a)
 
479
 
 
480
    def test_reannotate(self):
 
481
        self.annotateEqual(parent_1, [parent_1], new_1, 'blahblah')
 
482
        self.annotateEqual(expected_2_1, [parent_2], new_1, 'blahblah')
 
483
        self.annotateEqual(expected_1_2_2, [parent_1, parent_2], new_2,
 
484
                           'blahblah')
 
485
 
 
486
    def test_reannotate_no_parents(self):
 
487
        self.annotateEqual(expected_1, [], new_1, 'blahblah')
 
488
 
 
489
    def test_reannotate_left_matching_blocks(self):
 
490
        """Ensure that left_matching_blocks has an impact.
 
491
 
 
492
        In this case, the annotation is ambiguous, so the hint isn't actually
 
493
        lying.
 
494
        """
 
495
        parent = [('rev1', 'a\n')]
 
496
        new_text = ['a\n', 'a\n']
 
497
        blocks = [(0, 0, 1), (1, 2, 0)]
 
498
        self.annotateEqual([('rev1', 'a\n'), ('rev2', 'a\n')], [parent],
 
499
                           new_text, 'rev2', blocks)
 
500
        blocks = [(0, 1, 1), (1, 2, 0)]
 
501
        self.annotateEqual([('rev2', 'a\n'), ('rev1', 'a\n')], [parent],
 
502
                           new_text, 'rev2', blocks)