1
# Copyright (C) 2009 Canonical Ltd
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.
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.
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
17
"""Tests for Annotators."""
29
def load_tests(standard_tests, module, loader):
30
"""Parameterize tests for all versions of groupcompress."""
32
('python', {'module': _annotator_py}),
34
suite = loader.suiteClass()
35
if compiled_annotator.available():
36
scenarios.append(('C', {'module': compiled_annotator.module}))
38
# the compiled module isn't available, so we add a failing test
39
class FailWithoutFeature(tests.TestCase):
41
self.requireFeature(compiled_annotator)
42
suite.addTest(loader.loadTestsFromTestCase(FailWithoutFeature))
43
result = tests.multiply_tests(standard_tests, scenarios, suite)
47
compiled_annotator = tests.ModuleAvailableFeature('bzrlib._annotator_pyx')
50
class TestAnnotator(tests.TestCaseWithMemoryTransport):
52
module = None # Set by load_tests
54
fa_key = ('f-id', 'a-id')
55
fb_key = ('f-id', 'b-id')
56
fc_key = ('f-id', 'c-id')
57
fd_key = ('f-id', 'd-id')
58
fe_key = ('f-id', 'e-id')
59
ff_key = ('f-id', 'f-id')
61
def make_no_graph_texts(self):
62
factory = knit.make_pack_factory(False, False, 2)
63
self.vf = factory(self.get_transport())
64
self.ann = self.module.Annotator(self.vf)
65
self.vf.add_lines(self.fa_key, (), ['simple\n', 'content\n'])
66
self.vf.add_lines(self.fb_key, (), ['simple\n', 'new content\n'])
68
def make_simple_text(self):
69
# TODO: all we really need is a VersionedFile instance, we'd like to
70
# avoid creating all the intermediate stuff
71
factory = knit.make_pack_factory(True, True, 2)
72
self.vf = factory(self.get_transport())
73
# This assumes nothing special happens during __init__, which may be
75
self.ann = self.module.Annotator(self.vf)
78
# B 'simple|new content|'
79
self.vf.add_lines(self.fa_key, [], ['simple\n', 'content\n'])
80
self.vf.add_lines(self.fb_key, [self.fa_key],
81
['simple\n', 'new content\n'])
83
def make_merge_text(self):
84
self.make_simple_text()
87
# B | 'simple|new content|'
89
# | C 'simple|from c|content|'
91
# D 'simple|from c|new content|introduced in merge|'
92
self.vf.add_lines(self.fc_key, [self.fa_key],
93
['simple\n', 'from c\n', 'content\n'])
94
self.vf.add_lines(self.fd_key, [self.fb_key, self.fc_key],
95
['simple\n', 'from c\n', 'new content\n',
96
'introduced in merge\n'])
98
def make_common_merge_text(self):
99
"""Both sides of the merge will have introduced a line."""
100
self.make_simple_text()
101
# A 'simple|content|'
103
# B | 'simple|new content|'
105
# | C 'simple|new content|'
107
# D 'simple|new content|'
108
self.vf.add_lines(self.fc_key, [self.fa_key],
109
['simple\n', 'new content\n'])
110
self.vf.add_lines(self.fd_key, [self.fb_key, self.fc_key],
111
['simple\n', 'new content\n'])
113
def make_many_way_common_merge_text(self):
114
self.make_simple_text()
115
# A-. 'simple|content|'
117
# B | | 'simple|new content|'
119
# | C | 'simple|new content|'
121
# D | 'simple|new content|'
123
# | E 'simple|new content|'
125
# F-' 'simple|new content|'
126
self.vf.add_lines(self.fc_key, [self.fa_key],
127
['simple\n', 'new content\n'])
128
self.vf.add_lines(self.fd_key, [self.fb_key, self.fc_key],
129
['simple\n', 'new content\n'])
130
self.vf.add_lines(self.fe_key, [self.fa_key],
131
['simple\n', 'new content\n'])
132
self.vf.add_lines(self.ff_key, [self.fd_key, self.fe_key],
133
['simple\n', 'new content\n'])
135
def make_merge_and_restored_text(self):
136
self.make_simple_text()
137
# A 'simple|content|'
139
# B | 'simple|new content|'
141
# C | 'simple|content|' # reverted to A
143
# D 'simple|content|'
144
# c reverts back to 'a' for the new content line
145
self.vf.add_lines(self.fc_key, [self.fb_key],
146
['simple\n', 'content\n'])
147
# d merges 'a' and 'c', to find both claim last modified
148
self.vf.add_lines(self.fd_key, [self.fa_key, self.fc_key],
149
['simple\n', 'content\n'])
151
def assertAnnotateEqual(self, expected_annotation, key, exp_text=None):
152
annotation, lines = self.ann.annotate(key)
153
self.assertEqual(expected_annotation, annotation)
155
record = self.vf.get_record_stream([key], 'unordered', True).next()
156
exp_text = record.get_bytes_as('fulltext')
157
self.assertEqualDiff(exp_text, ''.join(lines))
159
def test_annotate_missing(self):
160
self.make_simple_text()
161
self.assertRaises(errors.RevisionNotPresent,
162
self.ann.annotate, ('not', 'present'))
164
def test_annotate_simple(self):
165
self.make_simple_text()
166
self.assertAnnotateEqual([(self.fa_key,)]*2, self.fa_key)
167
self.assertAnnotateEqual([(self.fa_key,), (self.fb_key,)], self.fb_key)
169
def test_annotate_merge_text(self):
170
self.make_merge_text()
171
self.assertAnnotateEqual([(self.fa_key,), (self.fc_key,),
172
(self.fb_key,), (self.fd_key,)],
175
def test_annotate_common_merge_text(self):
176
self.make_common_merge_text()
177
self.assertAnnotateEqual([(self.fa_key,), (self.fb_key, self.fc_key)],
180
def test_annotate_many_way_common_merge_text(self):
181
self.make_many_way_common_merge_text()
182
self.assertAnnotateEqual([(self.fa_key,),
183
(self.fb_key, self.fc_key, self.fe_key)],
186
def test_annotate_merge_and_restored(self):
187
self.make_merge_and_restored_text()
188
self.assertAnnotateEqual([(self.fa_key,), (self.fa_key, self.fc_key)],
191
def test_annotate_flat_simple(self):
192
self.make_simple_text()
193
self.assertEqual([(self.fa_key, 'simple\n'),
194
(self.fa_key, 'content\n'),
195
], self.ann.annotate_flat(self.fa_key))
196
self.assertEqual([(self.fa_key, 'simple\n'),
197
(self.fb_key, 'new content\n'),
198
], self.ann.annotate_flat(self.fb_key))
200
def test_annotate_flat_merge_and_restored_text(self):
201
self.make_merge_and_restored_text()
202
# fc is a simple dominator of fa
203
self.assertEqual([(self.fa_key, 'simple\n'),
204
(self.fc_key, 'content\n'),
205
], self.ann.annotate_flat(self.fd_key))
207
def test_annotate_common_merge_text(self):
208
self.make_common_merge_text()
209
# there is no common point, so we just pick the lexicographical lowest
210
# and 'b-id' comes before 'c-id'
211
self.assertEqual([(self.fa_key, 'simple\n'),
212
(self.fb_key, 'new content\n'),
213
], self.ann.annotate_flat(self.fd_key))
215
def test_annotate_many_way_common_merge_text(self):
216
self.make_many_way_common_merge_text()
217
self.assertEqual([(self.fa_key, 'simple\n'),
218
(self.fb_key, 'new content\n')],
219
self.ann.annotate_flat(self.ff_key))
221
def test_annotate_flat_respects_break_ann_tie(self):
222
tiebreaker = annotate._break_annotation_tie
225
def custom_tiebreaker(annotated_lines):
226
self.assertEqual(2, len(annotated_lines))
227
left = annotated_lines[0]
228
self.assertEqual(2, len(left))
229
self.assertEqual('new content\n', left[1])
230
right = annotated_lines[1]
231
self.assertEqual(2, len(right))
232
self.assertEqual('new content\n', right[1])
233
calls.append((left[0], right[0]))
234
# Our custom tiebreaker takes the *largest* value, rather than
235
# the *smallest* value
236
if left[0] < right[0]:
240
annotate._break_annotation_tie = custom_tiebreaker
241
self.make_many_way_common_merge_text()
242
self.assertEqual([(self.fa_key, 'simple\n'),
243
(self.fe_key, 'new content\n')],
244
self.ann.annotate_flat(self.ff_key))
245
self.assertEqual([(self.fe_key, self.fc_key),
246
(self.fe_key, self.fb_key)], calls)
248
annotate._break_annotation_tie = tiebreaker
251
def test_needed_keys_simple(self):
252
self.make_simple_text()
253
keys, ann_keys = self.ann._get_needed_keys(self.fb_key)
254
self.assertEqual([self.fa_key, self.fb_key], sorted(keys))
255
self.assertEqual({self.fa_key: 1, self.fb_key: 1},
256
self.ann._num_needed_children)
257
self.assertEqual(set(), ann_keys)
259
def test_needed_keys_many(self):
260
self.make_many_way_common_merge_text()
261
keys, ann_keys = self.ann._get_needed_keys(self.ff_key)
262
self.assertEqual([self.fa_key, self.fb_key, self.fc_key,
263
self.fd_key, self.fe_key, self.ff_key,
265
self.assertEqual({self.fa_key: 3,
271
}, self.ann._num_needed_children)
272
self.assertEqual(set(), ann_keys)
274
def test_needed_keys_with_special_text(self):
275
self.make_many_way_common_merge_text()
276
spec_key = ('f-id', revision.CURRENT_REVISION)
277
spec_text = 'simple\nnew content\nlocally modified\n'
278
self.ann.add_special_text(spec_key, [self.fd_key, self.fe_key],
280
keys, ann_keys = self.ann._get_needed_keys(spec_key)
281
self.assertEqual([self.fa_key, self.fb_key, self.fc_key,
282
self.fd_key, self.fe_key,
284
self.assertEqual([spec_key], sorted(ann_keys))
286
def test_needed_keys_with_parent_texts(self):
287
self.make_many_way_common_merge_text()
288
# If 'D' and 'E' are already annotated, we don't need to extract all
290
# D | 'simple|new content|'
292
# | E 'simple|new content|'
294
# F-' 'simple|new content|'
295
self.ann._parent_map[self.fd_key] = (self.fb_key, self.fc_key)
296
self.ann._text_cache[self.fd_key] = ['simple\n', 'new content\n']
297
self.ann._annotations_cache[self.fd_key] = [
299
(self.fb_key, self.fc_key),
301
self.ann._parent_map[self.fe_key] = (self.fa_key,)
302
self.ann._text_cache[self.fe_key] = ['simple\n', 'new content\n']
303
self.ann._annotations_cache[self.fe_key] = [
307
keys, ann_keys = self.ann._get_needed_keys(self.ff_key)
308
self.assertEqual([self.ff_key], sorted(keys))
309
self.assertEqual({self.fd_key: 1,
312
}, self.ann._num_needed_children)
313
self.assertEqual([], sorted(ann_keys))
315
def test_record_annotation_removes_texts(self):
316
self.make_many_way_common_merge_text()
317
# Populate the caches
318
for x in self.ann._get_needed_texts(self.ff_key):
320
self.assertEqual({self.fa_key: 3,
326
}, self.ann._num_needed_children)
327
self.assertEqual([self.fa_key, self.fb_key, self.fc_key,
328
self.fd_key, self.fe_key, self.ff_key,
329
], sorted(self.ann._text_cache.keys()))
330
self.ann._record_annotation(self.fa_key, [], [])
331
self.ann._record_annotation(self.fb_key, [self.fa_key], [])
332
self.assertEqual({self.fa_key: 2,
338
}, self.ann._num_needed_children)
339
self.assertTrue(self.fa_key in self.ann._text_cache)
340
self.assertTrue(self.fa_key in self.ann._annotations_cache)
341
self.ann._record_annotation(self.fc_key, [self.fa_key], [])
342
self.ann._record_annotation(self.fd_key, [self.fb_key, self.fc_key], [])
343
self.assertEqual({self.fa_key: 1,
349
}, self.ann._num_needed_children)
350
self.assertTrue(self.fa_key in self.ann._text_cache)
351
self.assertTrue(self.fa_key in self.ann._annotations_cache)
352
self.assertFalse(self.fb_key in self.ann._text_cache)
353
self.assertFalse(self.fb_key in self.ann._annotations_cache)
354
self.assertFalse(self.fc_key in self.ann._text_cache)
355
self.assertFalse(self.fc_key in self.ann._annotations_cache)
357
def test_annotate_special_text(self):
358
# Things like WT and PreviewTree want to annotate an arbitrary text
359
# ('current:') so we need a way to add that to the group of files to be
361
self.make_many_way_common_merge_text()
362
# A-. 'simple|content|'
364
# B | | 'simple|new content|'
366
# | C | 'simple|new content|'
368
# D | 'simple|new content|'
370
# | E 'simple|new content|'
372
# SPEC 'simple|new content|locally modified|'
373
spec_key = ('f-id', revision.CURRENT_REVISION)
374
spec_text = 'simple\nnew content\nlocally modified\n'
375
self.ann.add_special_text(spec_key, [self.fd_key, self.fe_key],
377
self.assertAnnotateEqual([(self.fa_key,),
378
(self.fb_key, self.fc_key, self.fe_key),
383
def test_no_graph(self):
384
self.make_no_graph_texts()
385
self.assertAnnotateEqual([(self.fa_key,),
388
self.assertAnnotateEqual([(self.fb_key,),