~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_annotate.py

  • Committer: Martin Pool
  • Date: 2005-08-24 08:59:32 UTC
  • Revision ID: mbp@sourcefrog.net-20050824085932-c61f1f1f1c930e13
- Add a simple UIFactory 

  The idea of this is to let a client of bzrlib set some 
  policy about how output is displayed.

  In this revision all that's done is that progress bars
  are constructed by a policy established by the application
  rather than being randomly constructed in the library 
  or passed down the calls.  This avoids progress bars
  popping up while running the test suite and cleans up
  some code.

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)