/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: John Arbash Meinel
  • Date: 2009-12-22 16:28:47 UTC
  • mto: This revision was merged to the branch mainline in revision 4922.
  • Revision ID: john@arbash-meinel.com-20091222162847-tvnsc69to4l4uf5r
Implement a permute_for_extension helper.

Use it for all of the 'simple' extension permutations.
It basically permutes all tests in the current module, by setting TestCase.module.
Which works well for most of our extension tests. Some had more advanced
handling of permutations (extra permutations, custom vars, etc.)

Show diffs side-by-side

added added

removed removed

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