/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
2490.2.5 by Aaron Bentley
Use GraphWalker.unique_ancestor to determine merge base
1
# Copyright (C) 2007 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
2490.2.28 by Aaron Bentley
Fix handling of null revision
17
from bzrlib import (
18
    errors,
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
19
    graph as _mod_graph,
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
20
    symbol_versioning,
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
21
    tests,
2490.2.28 by Aaron Bentley
Fix handling of null revision
22
    )
2490.2.1 by Aaron Bentley
Start work on GraphWalker
23
from bzrlib.revision import NULL_REVISION
24
from bzrlib.tests import TestCaseWithMemoryTransport
25
2490.2.25 by Aaron Bentley
Update from review
26
27
# Ancestry 1:
28
#
29
#  NULL_REVISION
30
#       |
31
#     rev1
32
#      /\
33
#  rev2a rev2b
34
#     |    |
35
#   rev3  /
36
#     |  /
37
#   rev4
2490.2.2 by Aaron Bentley
add minimal-common-ancestor calculation
38
ancestry_1 = {'rev1': [NULL_REVISION], 'rev2a': ['rev1'], 'rev2b': ['rev1'],
39
              'rev3': ['rev2a'], 'rev4': ['rev3', 'rev2b']}
2490.2.25 by Aaron Bentley
Update from review
40
41
42
# Ancestry 2:
43
#
44
#  NULL_REVISION
45
#    /    \
46
# rev1a  rev1b
47
#   |
48
# rev2a
49
#   |
50
# rev3a
51
#   |
52
# rev4a
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
53
ancestry_2 = {'rev1a': [NULL_REVISION], 'rev2a': ['rev1a'],
54
              'rev1b': [NULL_REVISION], 'rev3a': ['rev2a'], 'rev4a': ['rev3a']}
2490.2.2 by Aaron Bentley
add minimal-common-ancestor calculation
55
2490.2.25 by Aaron Bentley
Update from review
56
57
# Criss cross ancestry
58
#
59
#     NULL_REVISION
60
#         |
61
#        rev1
62
#        /  \
63
#    rev2a  rev2b
64
#       |\  /|
65
#       |  X |
66
#       |/  \|
67
#    rev3a  rev3b
2490.2.3 by Aaron Bentley
Implement new merge base picker
68
criss_cross = {'rev1': [NULL_REVISION], 'rev2a': ['rev1'], 'rev2b': ['rev1'],
69
               'rev3a': ['rev2a', 'rev2b'], 'rev3b': ['rev2b', 'rev2a']}
70
2490.2.25 by Aaron Bentley
Update from review
71
72
# Criss-cross 2
73
#
74
#  NULL_REVISION
75
#    /   \
76
# rev1a  rev1b
77
#   |\   /|
78
#   | \ / |
79
#   |  X  |
80
#   | / \ |
81
#   |/   \|
82
# rev2a  rev2b
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
83
criss_cross2 = {'rev1a': [NULL_REVISION], 'rev1b': [NULL_REVISION],
84
                'rev2a': ['rev1a', 'rev1b'], 'rev2b': ['rev1b', 'rev1a']}
85
2490.2.25 by Aaron Bentley
Update from review
86
87
# Mainline:
88
#
89
#  NULL_REVISION
90
#       |
91
#      rev1
92
#      /  \
93
#      | rev2b
94
#      |  /
95
#     rev2a
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
96
mainline = {'rev1': [NULL_REVISION], 'rev2a': ['rev1', 'rev2b'],
97
            'rev2b': ['rev1']}
98
2490.2.25 by Aaron Bentley
Update from review
99
100
# feature branch:
101
#
102
#  NULL_REVISION
103
#       |
104
#      rev1
105
#       |
106
#     rev2b
107
#       |
108
#     rev3b
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
109
feature_branch = {'rev1': [NULL_REVISION],
110
                  'rev2b': ['rev1'], 'rev3b': ['rev2b']}
111
2490.2.25 by Aaron Bentley
Update from review
112
113
# History shortcut
114
#  NULL_REVISION
115
#       |
116
#     rev1------
117
#     /  \      \
118
#  rev2a rev2b rev2c
119
#    |  /   \   /
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
120
#  rev3a    rev3b
2490.2.9 by Aaron Bentley
Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors
121
history_shortcut = {'rev1': [NULL_REVISION], 'rev2a': ['rev1'],
122
                    'rev2b': ['rev1'], 'rev2c': ['rev1'],
123
                    'rev3a': ['rev2a', 'rev2b'], 'rev3b': ['rev2b', 'rev2c']}
124
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
125
# Extended history shortcut
126
#  NULL_REVISION
127
#       |
128
#       a
129
#       |\
130
#       b |
131
#       | |
132
#       c |
133
#       | |
134
#       d |
135
#       |\|
136
#       e f
137
extended_history_shortcut = {'a': [NULL_REVISION],
138
                             'b': ['a'],
139
                             'c': ['b'],
140
                             'd': ['c'],
141
                             'e': ['d'],
142
                             'f': ['a', 'd'],
143
                            }
144
145
# Double shortcut
146
# Both sides will see 'A' first, even though it is actually a decendent of a
147
# different common revision.
148
#
149
#  NULL_REVISION
150
#       |
151
#       a
152
#      /|\
153
#     / b \
154
#    /  |  \
155
#   |   c   |
156
#   |  / \  |
157
#   | d   e |
158
#   |/     \|
159
#   f       g
160
161
double_shortcut = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'],
162
                   'd':['c'], 'e':['c'], 'f':['a', 'd'],
163
                   'g':['a', 'e']}
164
165
# Complex shortcut
166
# This has a failure mode in that a shortcut will find some nodes in common,
3377.3.1 by John Arbash Meinel
Bring in some of the changes from graph_update and graph_optimization
167
# but the common searcher won't have time to find that one branche is actually
168
# in common. The extra nodes at the beginning are because we want to avoid
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
169
# walking off the graph. Specifically, node G should be considered common, but
170
# is likely to be seen by M long before the common searcher finds it.
171
#
172
# NULL_REVISION
173
#     |
174
#     a
175
#     |
176
#     b
177
#     |
178
#     c
179
#     |
180
#     d
181
#     |\
182
#     e f
183
#     | |\
3377.3.1 by John Arbash Meinel
Bring in some of the changes from graph_update and graph_optimization
184
#     | g h
185
#     |/| |
186
#     i j |
187
#     | | |
188
#     | k |
189
#     | | |
190
#     | l |
191
#     |/|/
192
#     m n
193
complex_shortcut = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'],
194
                    'e':['d'], 'f':['d'], 'g':['f'], 'h':['f'],
195
                    'i':['e', 'g'], 'j':['g'], 'k':['j'],
196
                    'l':['k'], 'm':['i', 'l'], 'n':['l', 'h']}
197
198
# NULL_REVISION
199
#     |
200
#     a
201
#     |
202
#     b
203
#     |
204
#     c
205
#     |
206
#     d
207
#     |\
3377.3.13 by John Arbash Meinel
Change _search_for_extra_common slightly.
208
#     e |
209
#     | |
210
#     f |
211
#     | |
212
#     g h
3377.3.1 by John Arbash Meinel
Bring in some of the changes from graph_update and graph_optimization
213
#     | |\
3377.3.13 by John Arbash Meinel
Change _search_for_extra_common slightly.
214
#     i | j
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
215
#     |\| |
216
#     | k |
217
#     | | |
218
#     | l |
3377.3.13 by John Arbash Meinel
Change _search_for_extra_common slightly.
219
#     | | |
220
#     | m |
221
#     | | |
222
#     | n |
223
#     | | |
224
#     | o |
225
#     | | |
226
#     | p |
227
#     | | |
228
#     | q |
229
#     | | |
230
#     | r |
231
#     | | |
232
#     | s |
233
#     | | |
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
234
#     |/|/
3377.3.13 by John Arbash Meinel
Change _search_for_extra_common slightly.
235
#     t u
236
complex_shortcut2 = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'],
237
                    'e':['d'], 'f':['e'],
238
                    'g':['f'], 'h':['d'], 'k':['h', 'i'], 'j':['h'],
239
                    'i':['g'], 'l':['k'], 'm':['l'],
240
                    'n':['m'], 't':['i', 's'], 'u':['s', 'j'],
241
                    'o':['n'], 'p':['o'], 'q':['p'],
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
242
                    'r':['q'], 's':['r'],
243
                    }
244
245
# Shortcut with extra root
246
# We have a long history shortcut, and an extra root, which is why we can't
247
# stop searchers based on seeing NULL_REVISION
248
#  NULL_REVISION
249
#       |   |
250
#       a   |
251
#       |\  |
252
#       b | |
253
#       | | |
254
#       c | |
255
#       | | |
256
#       d | g
257
#       |\|/
258
#       e f
259
shortcut_extra_root = {'a': [NULL_REVISION],
260
                       'b': ['a'],
261
                       'c': ['b'],
262
                       'd': ['c'],
263
                       'e': ['d'],
264
                       'f': ['a', 'd', 'g'],
265
                       'g': [NULL_REVISION],
266
                      }
267
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
268
#  NULL_REVISION
269
#       |
270
#       f
271
#       |
272
#       e
273
#      / \
274
#     b   d
275
#     | \ |
276
#     a   c
277
278
boundary = {'a': ['b'], 'c': ['b', 'd'], 'b':['e'], 'd':['e'], 'e': ['f'],
279
            'f':[NULL_REVISION]}
280
281
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
282
# A graph that contains a ghost
283
#  NULL_REVISION
284
#       |
285
#       f
286
#       |
287
#       e   g
288
#      / \ /
289
#     b   d
290
#     | \ |
291
#     a   c
292
293
with_ghost = {'a': ['b'], 'c': ['b', 'd'], 'b':['e'], 'd':['e', 'g'],
3228.4.10 by John Arbash Meinel
Respond to abentley's review comments.
294
              'e': ['f'], 'f':[NULL_REVISION], NULL_REVISION:()}
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
295
296
297
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
298
class InstrumentedParentsProvider(object):
299
300
    def __init__(self, parents_provider):
301
        self.calls = []
302
        self._real_parents_provider = parents_provider
303
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
304
    def get_parent_map(self, nodes):
305
        self.calls.extend(nodes)
306
        return self._real_parents_provider.get_parent_map(nodes)
307
3377.3.1 by John Arbash Meinel
Bring in some of the changes from graph_update and graph_optimization
308
    def get_parent_map(self, nodes):
309
        self.calls.extend(nodes)
310
        return self._real_parents_provider.get_parent_map(nodes)
311
2490.2.25 by Aaron Bentley
Update from review
312
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
313
class TestGraph(TestCaseWithMemoryTransport):
2490.2.1 by Aaron Bentley
Start work on GraphWalker
314
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
315
    def make_graph(self, ancestors):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
316
        return _mod_graph.Graph(_mod_graph.DictParentsProvider(ancestors))
2490.2.3 by Aaron Bentley
Implement new merge base picker
317
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
318
    def prepare_memory_tree(self, location):
319
        tree = self.make_branch_and_memory_tree(location)
2490.2.1 by Aaron Bentley
Start work on GraphWalker
320
        tree.lock_write()
321
        tree.add('.')
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
322
        return tree
323
324
    def build_ancestry(self, tree, ancestors):
2490.2.25 by Aaron Bentley
Update from review
325
        """Create an ancestry as specified by a graph dict
326
327
        :param tree: A tree to use
328
        :param ancestors: a dict of {node: [node_parent, ...]}
329
        """
2490.2.1 by Aaron Bentley
Start work on GraphWalker
330
        pending = [NULL_REVISION]
331
        descendants = {}
332
        for descendant, parents in ancestors.iteritems():
333
            for parent in parents:
334
                descendants.setdefault(parent, []).append(descendant)
335
        while len(pending) > 0:
336
            cur_node = pending.pop()
337
            for descendant in descendants.get(cur_node, []):
2490.2.3 by Aaron Bentley
Implement new merge base picker
338
                if tree.branch.repository.has_revision(descendant):
339
                    continue
2490.2.1 by Aaron Bentley
Start work on GraphWalker
340
                parents = [p for p in ancestors[descendant] if p is not
341
                           NULL_REVISION]
342
                if len([p for p in parents if not
343
                    tree.branch.repository.has_revision(p)]) > 0:
344
                    continue
345
                tree.set_parent_ids(parents)
2490.2.3 by Aaron Bentley
Implement new merge base picker
346
                if len(parents) > 0:
347
                    left_parent = parents[0]
348
                else:
349
                    left_parent = NULL_REVISION
2490.2.1 by Aaron Bentley
Start work on GraphWalker
350
                tree.branch.set_last_revision_info(
2490.2.3 by Aaron Bentley
Implement new merge base picker
351
                    len(tree.branch._lefthand_history(left_parent)),
352
                    left_parent)
2490.2.1 by Aaron Bentley
Start work on GraphWalker
353
                tree.commit(descendant, rev_id=descendant)
354
                pending.append(descendant)
2490.2.2 by Aaron Bentley
add minimal-common-ancestor calculation
355
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
356
    def test_lca(self):
2490.2.25 by Aaron Bentley
Update from review
357
        """Test finding least common ancestor.
2490.2.3 by Aaron Bentley
Implement new merge base picker
358
359
        ancestry_1 should always have a single common ancestor
360
        """
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
361
        graph = self.make_graph(ancestry_1)
2490.2.28 by Aaron Bentley
Fix handling of null revision
362
        self.assertRaises(errors.InvalidRevisionId, graph.find_lca, None)
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
363
        self.assertEqual(set([NULL_REVISION]),
364
                         graph.find_lca(NULL_REVISION, NULL_REVISION))
365
        self.assertEqual(set([NULL_REVISION]),
366
                         graph.find_lca(NULL_REVISION, 'rev1'))
367
        self.assertEqual(set(['rev1']), graph.find_lca('rev1', 'rev1'))
368
        self.assertEqual(set(['rev1']), graph.find_lca('rev2a', 'rev2b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
369
2520.4.104 by Aaron Bentley
Avoid infinite loop when there is no unique lca
370
    def test_no_unique_lca(self):
371
        """Test error when one revision is not in the graph"""
372
        graph = self.make_graph(ancestry_1)
373
        self.assertRaises(errors.NoCommonAncestor, graph.find_unique_lca,
374
                          'rev1', '1rev')
375
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
376
    def test_lca_criss_cross(self):
2490.2.25 by Aaron Bentley
Update from review
377
        """Test least-common-ancestor after a criss-cross merge."""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
378
        graph = self.make_graph(criss_cross)
2490.2.3 by Aaron Bentley
Implement new merge base picker
379
        self.assertEqual(set(['rev2a', 'rev2b']),
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
380
                         graph.find_lca('rev3a', 'rev3b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
381
        self.assertEqual(set(['rev2b']),
2490.2.25 by Aaron Bentley
Update from review
382
                         graph.find_lca('rev3a', 'rev3b', 'rev2b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
383
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
384
    def test_lca_shortcut(self):
2490.2.25 by Aaron Bentley
Update from review
385
        """Test least-common ancestor on this history shortcut"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
386
        graph = self.make_graph(history_shortcut)
387
        self.assertEqual(set(['rev2b']), graph.find_lca('rev3a', 'rev3b'))
2490.2.9 by Aaron Bentley
Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors
388
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
389
    def test_recursive_unique_lca(self):
2490.2.25 by Aaron Bentley
Update from review
390
        """Test finding a unique least common ancestor.
2490.2.3 by Aaron Bentley
Implement new merge base picker
391
392
        ancestry_1 should always have a single common ancestor
393
        """
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
394
        graph = self.make_graph(ancestry_1)
395
        self.assertEqual(NULL_REVISION,
396
                         graph.find_unique_lca(NULL_REVISION, NULL_REVISION))
397
        self.assertEqual(NULL_REVISION,
398
                         graph.find_unique_lca(NULL_REVISION, 'rev1'))
399
        self.assertEqual('rev1', graph.find_unique_lca('rev1', 'rev1'))
400
        self.assertEqual('rev1', graph.find_unique_lca('rev2a', 'rev2b'))
1551.19.10 by Aaron Bentley
Merge now warns when it encounters a criss-cross
401
        self.assertEqual(('rev1', 1,),
402
                         graph.find_unique_lca('rev2a', 'rev2b',
403
                         count_steps=True))
2490.2.3 by Aaron Bentley
Implement new merge base picker
404
3377.3.1 by John Arbash Meinel
Bring in some of the changes from graph_update and graph_optimization
405
    def assertRemoveDescendants(self, expected, graph, revisions):
406
        parents = graph.get_parent_map(revisions)
407
        self.assertEqual(expected,
408
                         graph._remove_simple_descendants(revisions, parents))
409
410
    def test__remove_simple_descendants(self):
411
        graph = self.make_graph(ancestry_1)
412
        self.assertRemoveDescendants(set(['rev1']), graph,
413
            set(['rev1', 'rev2a', 'rev2b', 'rev3', 'rev4']))
414
415
    def test__remove_simple_descendants_disjoint(self):
416
        graph = self.make_graph(ancestry_1)
417
        self.assertRemoveDescendants(set(['rev1', 'rev3']), graph,
418
            set(['rev1', 'rev3']))
419
420
    def test__remove_simple_descendants_chain(self):
421
        graph = self.make_graph(ancestry_1)
422
        self.assertRemoveDescendants(set(['rev1']), graph,
423
            set(['rev1', 'rev2a', 'rev3']))
424
425
    def test__remove_simple_descendants_siblings(self):
426
        graph = self.make_graph(ancestry_1)
427
        self.assertRemoveDescendants(set(['rev2a', 'rev2b']), graph,
428
            set(['rev2a', 'rev2b', 'rev3']))
429
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
430
    def test_unique_lca_criss_cross(self):
431
        """Ensure we don't pick non-unique lcas in a criss-cross"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
432
        graph = self.make_graph(criss_cross)
433
        self.assertEqual('rev1', graph.find_unique_lca('rev3a', 'rev3b'))
1551.19.10 by Aaron Bentley
Merge now warns when it encounters a criss-cross
434
        lca, steps = graph.find_unique_lca('rev3a', 'rev3b', count_steps=True)
435
        self.assertEqual('rev1', lca)
436
        self.assertEqual(2, steps)
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
437
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
438
    def test_unique_lca_null_revision(self):
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
439
        """Ensure we pick NULL_REVISION when necessary"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
440
        graph = self.make_graph(criss_cross2)
441
        self.assertEqual('rev1b', graph.find_unique_lca('rev2a', 'rev1b'))
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
442
        self.assertEqual(NULL_REVISION,
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
443
                         graph.find_unique_lca('rev2a', 'rev2b'))
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
444
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
445
    def test_unique_lca_null_revision2(self):
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
446
        """Ensure we pick NULL_REVISION when necessary"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
447
        graph = self.make_graph(ancestry_2)
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
448
        self.assertEqual(NULL_REVISION,
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
449
                         graph.find_unique_lca('rev4a', 'rev1b'))
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
450
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
451
    def test_lca_double_shortcut(self):
452
        graph = self.make_graph(double_shortcut)
453
        self.assertEqual('c', graph.find_unique_lca('f', 'g'))
454
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
455
    def test_common_ancestor_two_repos(self):
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
456
        """Ensure we do unique_lca using data from two repos"""
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
457
        mainline_tree = self.prepare_memory_tree('mainline')
458
        self.build_ancestry(mainline_tree, mainline)
3010.1.6 by Robert Collins
Locking in test_graph.
459
        self.addCleanup(mainline_tree.unlock)
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
460
461
        # This is cheating, because the revisions in the graph are actually
462
        # different revisions, despite having the same revision-id.
463
        feature_tree = self.prepare_memory_tree('feature')
464
        self.build_ancestry(feature_tree, feature_branch)
3010.1.6 by Robert Collins
Locking in test_graph.
465
        self.addCleanup(feature_tree.unlock)
466
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
467
        graph = mainline_tree.branch.repository.get_graph(
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
468
            feature_tree.branch.repository)
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
469
        self.assertEqual('rev2b', graph.find_unique_lca('rev2a', 'rev3b'))
2490.2.23 by Aaron Bentley
Adapt find_borders to produce a graph difference
470
471
    def test_graph_difference(self):
472
        graph = self.make_graph(ancestry_1)
473
        self.assertEqual((set(), set()), graph.find_difference('rev1', 'rev1'))
474
        self.assertEqual((set(), set(['rev1'])),
475
                         graph.find_difference(NULL_REVISION, 'rev1'))
476
        self.assertEqual((set(['rev1']), set()),
477
                         graph.find_difference('rev1', NULL_REVISION))
478
        self.assertEqual((set(['rev2a', 'rev3']), set(['rev2b'])),
479
                         graph.find_difference('rev3', 'rev2b'))
480
        self.assertEqual((set(['rev4', 'rev3', 'rev2a']), set()),
481
                         graph.find_difference('rev4', 'rev2b'))
482
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
483
    def test_graph_difference_separate_ancestry(self):
484
        graph = self.make_graph(ancestry_2)
485
        self.assertEqual((set(['rev1a']), set(['rev1b'])),
486
                         graph.find_difference('rev1a', 'rev1b'))
487
        self.assertEqual((set(['rev1a', 'rev2a', 'rev3a', 'rev4a']),
488
                          set(['rev1b'])),
489
                         graph.find_difference('rev4a', 'rev1b'))
490
2490.2.23 by Aaron Bentley
Adapt find_borders to produce a graph difference
491
    def test_graph_difference_criss_cross(self):
492
        graph = self.make_graph(criss_cross)
493
        self.assertEqual((set(['rev3a']), set(['rev3b'])),
494
                         graph.find_difference('rev3a', 'rev3b'))
495
        self.assertEqual((set([]), set(['rev3b', 'rev2b'])),
496
                         graph.find_difference('rev2a', 'rev3b'))
2490.2.25 by Aaron Bentley
Update from review
497
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
498
    def test_graph_difference_extended_history(self):
499
        graph = self.make_graph(extended_history_shortcut)
3377.3.1 by John Arbash Meinel
Bring in some of the changes from graph_update and graph_optimization
500
        # self.expectFailure('find_difference cannot handle shortcuts',
501
        #     self.assertEqual, (set(['e']), set(['f'])),
502
        #         graph.find_difference('e', 'f'))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
503
        self.assertEqual((set(['e']), set(['f'])),
504
                         graph.find_difference('e', 'f'))
505
        self.assertEqual((set(['f']), set(['e'])),
506
                         graph.find_difference('f', 'e'))
507
508
    def test_graph_difference_double_shortcut(self):
509
        graph = self.make_graph(double_shortcut)
510
        self.assertEqual((set(['d', 'f']), set(['e', 'g'])),
511
                         graph.find_difference('f', 'g'))
512
513
    def test_graph_difference_complex_shortcut(self):
514
        graph = self.make_graph(complex_shortcut)
3377.3.1 by John Arbash Meinel
Bring in some of the changes from graph_update and graph_optimization
515
        # self.expectFailure('find_difference cannot handle shortcuts',
516
        #     self.assertEqual, (set(['m']), set(['h', 'n'])),
517
        #         graph.find_difference('m', 'n'))
518
        self.assertEqual((set(['m', 'i', 'e']), set(['n', 'h'])),
519
                         graph.find_difference('m', 'n'))
520
521
    def test_graph_difference_complex_shortcut2(self):
522
        graph = self.make_graph(complex_shortcut2)
3377.3.13 by John Arbash Meinel
Change _search_for_extra_common slightly.
523
        self.assertEqual((set(['t']), set(['j', 'u'])),
524
                         graph.find_difference('t', 'u'))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
525
526
    def test_graph_difference_shortcut_extra_root(self):
527
        graph = self.make_graph(shortcut_extra_root)
3377.3.1 by John Arbash Meinel
Bring in some of the changes from graph_update and graph_optimization
528
        # self.expectFailure('find_difference cannot handle shortcuts',
529
        #     self.assertEqual, (set(['e']), set(['f', 'g'])),
530
        #         graph.find_difference('e', 'f'))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
531
        self.assertEqual((set(['e']), set(['f', 'g'])),
532
                         graph.find_difference('e', 'f'))
533
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
534
    def test_stacked_parents_provider(self):
535
        parents1 = _mod_graph.DictParentsProvider({'rev2': ['rev3']})
536
        parents2 = _mod_graph.DictParentsProvider({'rev1': ['rev4']})
537
        stacked = _mod_graph._StackedParentsProvider([parents1, parents2])
538
        self.assertEqual({'rev1':['rev4'], 'rev2':['rev3']},
539
                         stacked.get_parent_map(['rev1', 'rev2']))
540
        self.assertEqual({'rev2':['rev3'], 'rev1':['rev4']},
541
                         stacked.get_parent_map(['rev2', 'rev1']))
542
        self.assertEqual({'rev2':['rev3']},
543
                         stacked.get_parent_map(['rev2', 'rev2']))
544
        self.assertEqual({'rev1':['rev4']},
545
                         stacked.get_parent_map(['rev1', 'rev1']))
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
546
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
547
    def test_iter_topo_order(self):
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
548
        graph = self.make_graph(ancestry_1)
549
        args = ['rev2a', 'rev3', 'rev1']
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
550
        topo_args = list(graph.iter_topo_order(args))
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
551
        self.assertEqual(set(args), set(topo_args))
552
        self.assertTrue(topo_args.index('rev2a') > topo_args.index('rev1'))
553
        self.assertTrue(topo_args.index('rev2a') < topo_args.index('rev3'))
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
554
555
    def test_is_ancestor(self):
556
        graph = self.make_graph(ancestry_1)
2653.2.3 by Aaron Bentley
correctly handle Graph.is_ancestor(x, x)
557
        self.assertEqual(True, graph.is_ancestor('null:', 'null:'))
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
558
        self.assertEqual(True, graph.is_ancestor('null:', 'rev1'))
559
        self.assertEqual(False, graph.is_ancestor('rev1', 'null:'))
560
        self.assertEqual(True, graph.is_ancestor('null:', 'rev4'))
561
        self.assertEqual(False, graph.is_ancestor('rev4', 'null:'))
562
        self.assertEqual(False, graph.is_ancestor('rev4', 'rev2b'))
563
        self.assertEqual(True, graph.is_ancestor('rev2b', 'rev4'))
564
        self.assertEqual(False, graph.is_ancestor('rev2b', 'rev3'))
565
        self.assertEqual(False, graph.is_ancestor('rev3', 'rev2b'))
566
        instrumented_provider = InstrumentedParentsProvider(graph)
567
        instrumented_graph = _mod_graph.Graph(instrumented_provider)
568
        instrumented_graph.is_ancestor('rev2a', 'rev2b')
569
        self.assertTrue('null:' not in instrumented_provider.calls)
570
571
    def test_is_ancestor_boundary(self):
572
        """Ensure that we avoid searching the whole graph.
573
        
574
        This requires searching through b as a common ancestor, so we
575
        can identify that e is common.
576
        """
577
        graph = self.make_graph(boundary)
578
        instrumented_provider = InstrumentedParentsProvider(graph)
579
        graph = _mod_graph.Graph(instrumented_provider)
580
        self.assertFalse(graph.is_ancestor('a', 'c'))
581
        self.assertTrue('null:' not in instrumented_provider.calls)
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
582
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
583
    def test_iter_ancestry(self):
3228.4.10 by John Arbash Meinel
Respond to abentley's review comments.
584
        nodes = boundary.copy()
585
        nodes[NULL_REVISION] = ()
586
        graph = self.make_graph(nodes)
587
        expected = nodes.copy()
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
588
        expected.pop('a') # 'a' is not in the ancestry of 'c', all the
589
                          # other nodes are
3228.4.4 by John Arbash Meinel
Change iter_ancestry to take a group instead of a single node,
590
        self.assertEqual(expected, dict(graph.iter_ancestry(['c'])))
3228.4.10 by John Arbash Meinel
Respond to abentley's review comments.
591
        self.assertEqual(nodes, dict(graph.iter_ancestry(['a', 'c'])))
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
592
593
    def test_iter_ancestry_with_ghost(self):
594
        graph = self.make_graph(with_ghost)
595
        expected = with_ghost.copy()
596
        # 'a' is not in the ancestry of 'c', and 'g' is a ghost
3228.4.10 by John Arbash Meinel
Respond to abentley's review comments.
597
        expected['g'] = None
3228.4.4 by John Arbash Meinel
Change iter_ancestry to take a group instead of a single node,
598
        self.assertEqual(expected, dict(graph.iter_ancestry(['a', 'c'])))
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
599
        expected.pop('a') 
3228.4.4 by John Arbash Meinel
Change iter_ancestry to take a group instead of a single node,
600
        self.assertEqual(expected, dict(graph.iter_ancestry(['c'])))
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
601
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
602
    def test_filter_candidate_lca(self):
603
        """Test filter_candidate_lca for a corner case
604
1551.15.83 by Aaron Bentley
Update test documentation
605
        This tests the case where we encounter the end of iteration for 'e'
606
        in the same pass as we discover that 'd' is an ancestor of 'e', and
607
        therefore 'e' can't be an lca.
608
609
        To compensate for different dict orderings on other Python
610
        implementations, we mirror 'd' and 'e' with 'b' and 'a'.
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
611
        """
612
        # This test is sensitive to the iteration order of dicts.  It will
1551.15.82 by Aaron Bentley
Add symmetrical alternative to test case
613
        # pass incorrectly if 'e' and 'a' sort before 'c'
1551.15.84 by Aaron Bentley
Add ancestry graph for test case
614
        #
615
        # NULL_REVISION
616
        #     / \
617
        #    a   e
618
        #    |   |
619
        #    b   d
620
        #     \ /
621
        #      c
1551.15.82 by Aaron Bentley
Add symmetrical alternative to test case
622
        graph = self.make_graph({'c': ['b', 'd'], 'd': ['e'], 'b': ['a'],
623
                                 'a': [NULL_REVISION], 'e': [NULL_REVISION]})
2776.1.4 by Robert Collins
Trivial review feedback changes.
624
        self.assertEqual(set(['c']), graph.heads(['a', 'c', 'e']))
625
626
    def test_heads_null(self):
627
        graph = self.make_graph(ancestry_1)
628
        self.assertEqual(set(['null:']), graph.heads(['null:']))
629
        self.assertEqual(set(['rev1']), graph.heads(['null:', 'rev1']))
630
        self.assertEqual(set(['rev1']), graph.heads(['rev1', 'null:']))
631
        self.assertEqual(set(['rev1']), graph.heads(set(['rev1', 'null:'])))
632
        self.assertEqual(set(['rev1']), graph.heads(('rev1', 'null:')))
633
634
    def test_heads_one(self):
3052.5.5 by John Arbash Meinel
Special case Graph.heads() for NULL_REVISION rather than is_ancestor.
635
        # A single node will always be a head
2776.1.4 by Robert Collins
Trivial review feedback changes.
636
        graph = self.make_graph(ancestry_1)
637
        self.assertEqual(set(['null:']), graph.heads(['null:']))
638
        self.assertEqual(set(['rev1']), graph.heads(['rev1']))
639
        self.assertEqual(set(['rev2a']), graph.heads(['rev2a']))
640
        self.assertEqual(set(['rev2b']), graph.heads(['rev2b']))
641
        self.assertEqual(set(['rev3']), graph.heads(['rev3']))
642
        self.assertEqual(set(['rev4']), graph.heads(['rev4']))
643
644
    def test_heads_single(self):
645
        graph = self.make_graph(ancestry_1)
646
        self.assertEqual(set(['rev4']), graph.heads(['null:', 'rev4']))
647
        self.assertEqual(set(['rev2a']), graph.heads(['rev1', 'rev2a']))
648
        self.assertEqual(set(['rev2b']), graph.heads(['rev1', 'rev2b']))
649
        self.assertEqual(set(['rev3']), graph.heads(['rev1', 'rev3']))
650
        self.assertEqual(set(['rev4']), graph.heads(['rev1', 'rev4']))
651
        self.assertEqual(set(['rev4']), graph.heads(['rev2a', 'rev4']))
652
        self.assertEqual(set(['rev4']), graph.heads(['rev2b', 'rev4']))
653
        self.assertEqual(set(['rev4']), graph.heads(['rev3', 'rev4']))
654
655
    def test_heads_two_heads(self):
656
        graph = self.make_graph(ancestry_1)
657
        self.assertEqual(set(['rev2a', 'rev2b']),
658
                         graph.heads(['rev2a', 'rev2b']))
659
        self.assertEqual(set(['rev3', 'rev2b']),
660
                         graph.heads(['rev3', 'rev2b']))
661
662
    def test_heads_criss_cross(self):
663
        graph = self.make_graph(criss_cross)
664
        self.assertEqual(set(['rev2a']),
665
                         graph.heads(['rev2a', 'rev1']))
666
        self.assertEqual(set(['rev2b']),
667
                         graph.heads(['rev2b', 'rev1']))
668
        self.assertEqual(set(['rev3a']),
669
                         graph.heads(['rev3a', 'rev1']))
670
        self.assertEqual(set(['rev3b']),
671
                         graph.heads(['rev3b', 'rev1']))
672
        self.assertEqual(set(['rev2a', 'rev2b']),
673
                         graph.heads(['rev2a', 'rev2b']))
674
        self.assertEqual(set(['rev3a']),
675
                         graph.heads(['rev3a', 'rev2a']))
676
        self.assertEqual(set(['rev3a']),
677
                         graph.heads(['rev3a', 'rev2b']))
678
        self.assertEqual(set(['rev3a']),
679
                         graph.heads(['rev3a', 'rev2a', 'rev2b']))
680
        self.assertEqual(set(['rev3b']),
681
                         graph.heads(['rev3b', 'rev2a']))
682
        self.assertEqual(set(['rev3b']),
683
                         graph.heads(['rev3b', 'rev2b']))
684
        self.assertEqual(set(['rev3b']),
685
                         graph.heads(['rev3b', 'rev2a', 'rev2b']))
686
        self.assertEqual(set(['rev3a', 'rev3b']),
687
                         graph.heads(['rev3a', 'rev3b']))
688
        self.assertEqual(set(['rev3a', 'rev3b']),
689
                         graph.heads(['rev3a', 'rev3b', 'rev2a', 'rev2b']))
690
691
    def test_heads_shortcut(self):
692
        graph = self.make_graph(history_shortcut)
693
694
        self.assertEqual(set(['rev2a', 'rev2b', 'rev2c']),
695
                         graph.heads(['rev2a', 'rev2b', 'rev2c']))
696
        self.assertEqual(set(['rev3a', 'rev3b']),
697
                         graph.heads(['rev3a', 'rev3b']))
698
        self.assertEqual(set(['rev3a', 'rev3b']),
699
                         graph.heads(['rev2a', 'rev3a', 'rev3b']))
700
        self.assertEqual(set(['rev2a', 'rev3b']),
701
                         graph.heads(['rev2a', 'rev3b']))
702
        self.assertEqual(set(['rev2c', 'rev3a']),
703
                         graph.heads(['rev2c', 'rev3a']))
2921.3.1 by Robert Collins
* Graph ``heads()`` queries have been bugfixed to no longer access all
704
705
    def _run_heads_break_deeper(self, graph_dict, search):
706
        """Run heads on a graph-as-a-dict.
707
        
708
        If the search asks for the parents of 'deeper' the test will fail.
709
        """
710
        class stub(object):
711
            pass
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
712
        def get_parent_map(keys):
713
            result = {}
714
            for key in keys:
715
                if key == 'deeper':
716
                    self.fail('key deeper was accessed')
717
                result[key] = graph_dict[key]
718
            return result
3377.3.1 by John Arbash Meinel
Bring in some of the changes from graph_update and graph_optimization
719
        def get_parent_map(keys):
720
            result = {}
721
            for key in keys:
722
                if key == 'deeper':
723
                    self.fail('key deeper was accessed')
724
                result[key] = graph_dict[key]
725
            return result
2921.3.1 by Robert Collins
* Graph ``heads()`` queries have been bugfixed to no longer access all
726
        an_obj = stub()
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
727
        an_obj.get_parent_map = get_parent_map
2921.3.1 by Robert Collins
* Graph ``heads()`` queries have been bugfixed to no longer access all
728
        graph = _mod_graph.Graph(an_obj)
729
        return graph.heads(search)
730
731
    def test_heads_limits_search(self):
732
        # test that a heads query does not search all of history
733
        graph_dict = {
734
            'left':['common'],
735
            'right':['common'],
736
            'common':['deeper'],
737
        }
738
        self.assertEqual(set(['left', 'right']),
739
            self._run_heads_break_deeper(graph_dict, ['left', 'right']))
740
741
    def test_heads_limits_search_assymetric(self):
742
        # test that a heads query does not search all of history
743
        graph_dict = {
744
            'left':['midleft'],
745
            'midleft':['common'],
746
            'right':['common'],
747
            'common':['aftercommon'],
748
            'aftercommon':['deeper'],
749
        }
750
        self.assertEqual(set(['left', 'right']),
751
            self._run_heads_break_deeper(graph_dict, ['left', 'right']))
752
753
    def test_heads_limits_search_common_search_must_continue(self):
754
        # test that common nodes are still queried, preventing
755
        # all-the-way-to-origin behaviour in the following graph:
756
        graph_dict = {
757
            'h1':['shortcut', 'common1'],
758
            'h2':['common1'],
759
            'shortcut':['common2'],
760
            'common1':['common2'],
761
            'common2':['deeper'],
762
        }
763
        self.assertEqual(set(['h1', 'h2']),
764
            self._run_heads_break_deeper(graph_dict, ['h1', 'h2']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
765
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
766
    def test_breadth_first_search_start_ghosts(self):
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
767
        graph = self.make_graph({})
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
768
        # with_ghosts reports the ghosts
769
        search = graph._make_breadth_first_searcher(['a-ghost'])
770
        self.assertEqual((set(), set(['a-ghost'])), search.next_with_ghosts())
771
        self.assertRaises(StopIteration, search.next_with_ghosts)
772
        # next includes them
773
        search = graph._make_breadth_first_searcher(['a-ghost'])
774
        self.assertEqual(set(['a-ghost']), search.next())
775
        self.assertRaises(StopIteration, search.next)
776
777
    def test_breadth_first_search_deep_ghosts(self):
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
778
        graph = self.make_graph({
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
779
            'head':['present'],
780
            'present':['child', 'ghost'],
781
            'child':[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
782
            })
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
783
        # with_ghosts reports the ghosts
784
        search = graph._make_breadth_first_searcher(['head'])
785
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
786
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
787
        self.assertEqual((set(['child']), set(['ghost'])),
788
            search.next_with_ghosts())
789
        self.assertRaises(StopIteration, search.next_with_ghosts)
790
        # next includes them
791
        search = graph._make_breadth_first_searcher(['head'])
792
        self.assertEqual(set(['head']), search.next())
793
        self.assertEqual(set(['present']), search.next())
794
        self.assertEqual(set(['child', 'ghost']),
795
            search.next())
796
        self.assertRaises(StopIteration, search.next)
797
798
    def test_breadth_first_search_change_next_to_next_with_ghosts(self):
3177.3.3 by Robert Collins
Review feedback.
799
        # To make the API robust, we allow calling both next() and
800
        # next_with_ghosts() on the same searcher.
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
801
        graph = self.make_graph({
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
802
            'head':['present'],
803
            'present':['child', 'ghost'],
804
            'child':[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
805
            })
3177.3.3 by Robert Collins
Review feedback.
806
        # start with next_with_ghosts
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
807
        search = graph._make_breadth_first_searcher(['head'])
808
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
809
        self.assertEqual(set(['present']), search.next())
810
        self.assertEqual((set(['child']), set(['ghost'])),
811
            search.next_with_ghosts())
812
        self.assertRaises(StopIteration, search.next)
3177.3.3 by Robert Collins
Review feedback.
813
        # start with next
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
814
        search = graph._make_breadth_first_searcher(['head'])
815
        self.assertEqual(set(['head']), search.next())
816
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
817
        self.assertEqual(set(['child', 'ghost']),
818
            search.next())
819
        self.assertRaises(StopIteration, search.next_with_ghosts)
820
3177.3.2 by Robert Collins
Update graph searchers stop_searching_any and start_searching for next_with_ghosts.
821
    def test_breadth_first_change_search(self):
3177.3.3 by Robert Collins
Review feedback.
822
        # Changing the search should work with both next and next_with_ghosts.
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
823
        graph = self.make_graph({
3177.3.2 by Robert Collins
Update graph searchers stop_searching_any and start_searching for next_with_ghosts.
824
            'head':['present'],
825
            'present':['stopped'],
826
            'other':['other_2'],
827
            'other_2':[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
828
            })
3177.3.2 by Robert Collins
Update graph searchers stop_searching_any and start_searching for next_with_ghosts.
829
        search = graph._make_breadth_first_searcher(['head'])
830
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
831
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
832
        self.assertEqual(set(['present']),
833
            search.stop_searching_any(['present']))
834
        self.assertEqual((set(['other']), set(['other_ghost'])),
835
            search.start_searching(['other', 'other_ghost']))
836
        self.assertEqual((set(['other_2']), set()), search.next_with_ghosts())
837
        self.assertRaises(StopIteration, search.next_with_ghosts)
838
        # next includes them
839
        search = graph._make_breadth_first_searcher(['head'])
840
        self.assertEqual(set(['head']), search.next())
841
        self.assertEqual(set(['present']), search.next())
842
        self.assertEqual(set(['present']),
843
            search.stop_searching_any(['present']))
844
        search.start_searching(['other', 'other_ghost'])
845
        self.assertEqual(set(['other_2']), search.next())
846
        self.assertRaises(StopIteration, search.next)
847
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
848
    def assertSeenAndResult(self, instructions, search, next):
849
        """Check the results of .seen and get_result() for a seach.
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
850
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
851
        :param instructions: A list of tuples:
852
            (seen, recipe, included_keys, starts, stops).
853
            seen, recipe and included_keys are results to check on the search
854
            and the searches get_result(). starts and stops are parameters to
855
            pass to start_searching and stop_searching_any during each
856
            iteration, if they are not None.
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
857
        :param search: The search to use.
858
        :param next: A callable to advance the search.
859
        """
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
860
        for seen, recipe, included_keys, starts, stops in instructions:
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
861
            next()
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
862
            if starts is not None:
863
                search.start_searching(starts)
864
            if stops is not None:
865
                search.stop_searching_any(stops)
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
866
            result = search.get_result()
867
            self.assertEqual(recipe, result.get_recipe())
868
            self.assertEqual(set(included_keys), result.get_keys())
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
869
            self.assertEqual(seen, search.seen)
870
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
871
    def test_breadth_first_get_result_excludes_current_pending(self):
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
872
        graph = self.make_graph({
873
            'head':['child'],
874
            'child':[NULL_REVISION],
3184.1.3 by Robert Collins
Automatically exclude ghosts.
875
            NULL_REVISION:[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
876
            })
877
        search = graph._make_breadth_first_searcher(['head'])
878
        # At the start, nothing has been seen, to its all excluded:
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
879
        result = search.get_result()
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
880
        self.assertEqual((set(['head']), set(['head']), 0),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
881
            result.get_recipe())
882
        self.assertEqual(set(), result.get_keys())
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
883
        self.assertEqual(set(), search.seen)
884
        # using next:
885
        expected = [
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
886
            (set(['head']), (set(['head']), set(['child']), 1),
887
             ['head'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
888
            (set(['head', 'child']), (set(['head']), set([NULL_REVISION]), 2),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
889
             ['head', 'child'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
890
            (set(['head', 'child', NULL_REVISION]), (set(['head']), set(), 3),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
891
             ['head', 'child', NULL_REVISION], None, None),
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
892
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
893
        self.assertSeenAndResult(expected, search, search.next)
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
894
        # using next_with_ghosts:
895
        search = graph._make_breadth_first_searcher(['head'])
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
896
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
897
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
898
    def test_breadth_first_get_result_starts_stops(self):
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
899
        graph = self.make_graph({
900
            'head':['child'],
901
            'child':[NULL_REVISION],
902
            'otherhead':['otherchild'],
903
            'otherchild':['excluded'],
904
            'excluded':[NULL_REVISION],
3184.1.3 by Robert Collins
Automatically exclude ghosts.
905
            NULL_REVISION:[]
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
906
            })
907
        search = graph._make_breadth_first_searcher([])
908
        # Starting with nothing and adding a search works:
909
        search.start_searching(['head'])
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
910
        # head has been seen:
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
911
        result = search.get_result()
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
912
        self.assertEqual((set(['head']), set(['child']), 1),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
913
            result.get_recipe())
914
        self.assertEqual(set(['head']), result.get_keys())
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
915
        self.assertEqual(set(['head']), search.seen)
916
        # using next:
917
        expected = [
918
            # stop at child, and start a new search at otherhead:
919
            # - otherhead counts as seen immediately when start_searching is
920
            # called.
921
            (set(['head', 'child', 'otherhead']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
922
             (set(['head', 'otherhead']), set(['child', 'otherchild']), 2),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
923
             ['head', 'otherhead'], ['otherhead'], ['child']),
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
924
            (set(['head', 'child', 'otherhead', 'otherchild']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
925
             (set(['head', 'otherhead']), set(['child', 'excluded']), 3),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
926
             ['head', 'otherhead', 'otherchild'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
927
            # stop searching excluded now
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
928
            (set(['head', 'child', 'otherhead', 'otherchild', 'excluded']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
929
             (set(['head', 'otherhead']), set(['child', 'excluded']), 3),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
930
             ['head', 'otherhead', 'otherchild'], None, ['excluded']),
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
931
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
932
        self.assertSeenAndResult(expected, search, search.next)
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
933
        # using next_with_ghosts:
934
        search = graph._make_breadth_first_searcher([])
935
        search.start_searching(['head'])
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
936
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
937
3184.2.1 by Robert Collins
Handle stopping ghosts in searches properly.
938
    def test_breadth_first_stop_searching_not_queried(self):
939
        # A client should be able to say 'stop node X' even if X has not been
940
        # returned to the client.
941
        graph = self.make_graph({
942
            'head':['child', 'ghost1'],
943
            'child':[NULL_REVISION],
944
            NULL_REVISION:[],
945
            })
946
        search = graph._make_breadth_first_searcher(['head'])
947
        expected = [
948
            # NULL_REVISION and ghost1 have not been returned
949
            (set(['head']), (set(['head']), set(['child', 'ghost1']), 1),
950
             ['head'], None, [NULL_REVISION, 'ghost1']),
951
            # ghost1 has been returned, NULL_REVISION is to be returned in the
952
            # next iteration.
953
            (set(['head', 'child', 'ghost1']),
954
             (set(['head']), set(['ghost1', NULL_REVISION]), 2),
955
             ['head', 'child'], None, [NULL_REVISION, 'ghost1']),
956
            ]
957
        self.assertSeenAndResult(expected, search, search.next)
958
        # using next_with_ghosts:
959
        search = graph._make_breadth_first_searcher(['head'])
960
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
961
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
962
    def test_breadth_first_get_result_ghosts_are_excluded(self):
3184.1.3 by Robert Collins
Automatically exclude ghosts.
963
        graph = self.make_graph({
964
            'head':['child', 'ghost'],
965
            'child':[NULL_REVISION],
966
            NULL_REVISION:[],
967
            })
968
        search = graph._make_breadth_first_searcher(['head'])
969
        # using next:
970
        expected = [
971
            (set(['head']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
972
             (set(['head']), set(['ghost', 'child']), 1),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
973
             ['head'], None, None),
3184.1.3 by Robert Collins
Automatically exclude ghosts.
974
            (set(['head', 'child', 'ghost']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
975
             (set(['head']), set([NULL_REVISION, 'ghost']), 2),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
976
             ['head', 'child'], None, None),
3184.1.3 by Robert Collins
Automatically exclude ghosts.
977
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
978
        self.assertSeenAndResult(expected, search, search.next)
3184.1.3 by Robert Collins
Automatically exclude ghosts.
979
        # using next_with_ghosts:
980
        search = graph._make_breadth_first_searcher(['head'])
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
981
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.3 by Robert Collins
Automatically exclude ghosts.
982
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
983
    def test_breadth_first_get_result_starting_a_ghost_ghost_is_excluded(self):
3184.1.4 by Robert Collins
Correctly exclude ghosts when ghosts are started on an existing search.
984
        graph = self.make_graph({
985
            'head':['child'],
986
            'child':[NULL_REVISION],
987
            NULL_REVISION:[],
988
            })
989
        search = graph._make_breadth_first_searcher(['head'])
990
        # using next:
991
        expected = [
992
            (set(['head', 'ghost']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
993
             (set(['head', 'ghost']), set(['child', 'ghost']), 1),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
994
             ['head'], ['ghost'], None),
3184.1.4 by Robert Collins
Correctly exclude ghosts when ghosts are started on an existing search.
995
            (set(['head', 'child', 'ghost']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
996
             (set(['head', 'ghost']), set([NULL_REVISION, 'ghost']), 2),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
997
             ['head', 'child'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
998
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
999
        self.assertSeenAndResult(expected, search, search.next)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1000
        # using next_with_ghosts:
1001
        search = graph._make_breadth_first_searcher(['head'])
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1002
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1003
1004
    def test_breadth_first_revision_count_includes_NULL_REVISION(self):
1005
        graph = self.make_graph({
1006
            'head':[NULL_REVISION],
1007
            NULL_REVISION:[],
1008
            })
1009
        search = graph._make_breadth_first_searcher(['head'])
1010
        # using next:
1011
        expected = [
1012
            (set(['head']),
1013
             (set(['head']), set([NULL_REVISION]), 1),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1014
             ['head'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1015
            (set(['head', NULL_REVISION]),
1016
             (set(['head']), set([]), 2),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1017
             ['head', NULL_REVISION], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1018
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1019
        self.assertSeenAndResult(expected, search, search.next)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1020
        # using next_with_ghosts:
1021
        search = graph._make_breadth_first_searcher(['head'])
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1022
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1023
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1024
    def test_breadth_first_search_get_result_after_StopIteration(self):
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1025
        # StopIteration should not invalid anything..
1026
        graph = self.make_graph({
1027
            'head':[NULL_REVISION],
1028
            NULL_REVISION:[],
1029
            })
1030
        search = graph._make_breadth_first_searcher(['head'])
1031
        # using next:
1032
        expected = [
1033
            (set(['head']),
1034
             (set(['head']), set([NULL_REVISION]), 1),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1035
             ['head'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1036
            (set(['head', 'ghost', NULL_REVISION]),
1037
             (set(['head', 'ghost']), set(['ghost']), 2),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1038
             ['head', NULL_REVISION], ['ghost'], None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1039
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1040
        self.assertSeenAndResult(expected, search, search.next)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1041
        self.assertRaises(StopIteration, search.next)
1042
        self.assertEqual(set(['head', 'ghost', NULL_REVISION]), search.seen)
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1043
        result = search.get_result()
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1044
        self.assertEqual((set(['ghost', 'head']), set(['ghost']), 2),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1045
            result.get_recipe())
1046
        self.assertEqual(set(['head', NULL_REVISION]), result.get_keys())
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1047
        # using next_with_ghosts:
1048
        search = graph._make_breadth_first_searcher(['head'])
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1049
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1050
        self.assertRaises(StopIteration, search.next)
1051
        self.assertEqual(set(['head', 'ghost', NULL_REVISION]), search.seen)
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1052
        result = search.get_result()
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1053
        self.assertEqual((set(['ghost', 'head']), set(['ghost']), 2),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1054
            result.get_recipe())
1055
        self.assertEqual(set(['head', NULL_REVISION]), result.get_keys())
3184.1.4 by Robert Collins
Correctly exclude ghosts when ghosts are started on an existing search.
1056
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1057
3377.3.21 by John Arbash Meinel
Simple brute-force implementation of find_unique_ancestors
1058
class TestFindUniqueAncestors(tests.TestCase):
1059
1060
    def make_graph(self, ancestors):
1061
        return _mod_graph.Graph(_mod_graph.DictParentsProvider(ancestors))
1062
3377.3.24 by John Arbash Meinel
For some reason find_unique_ancestors is much slower than it should be.
1063
    def make_breaking_graph(self, ancestors, break_on):
1064
        """Make a Graph that raises an exception if we hit a node."""
1065
        g = self.make_graph(ancestors)
1066
        orig_parent_map = g.get_parent_map
1067
        def get_parent_map(keys):
1068
            bad_keys = set(keys).intersection(break_on)
1069
            if bad_keys:
1070
                self.fail('key(s) %s was accessed' % (sorted(bad_keys),))
1071
            return orig_parent_map(keys)
1072
        g.get_parent_map = get_parent_map
1073
        return g
1074
3377.3.21 by John Arbash Meinel
Simple brute-force implementation of find_unique_ancestors
1075
    def assertFindUniqueAncestors(self, graph, expected, node, common):
1076
        actual = graph.find_unique_ancestors(node, common)
1077
        self.assertEqual(expected, sorted(actual))
1078
1079
    def test_empty_set(self):
1080
        graph = self.make_graph(ancestry_1)
1081
        self.assertFindUniqueAncestors(graph, [], 'rev1', ['rev1'])
1082
        self.assertFindUniqueAncestors(graph, [], 'rev2b', ['rev2b'])
1083
        self.assertFindUniqueAncestors(graph, [], 'rev3', ['rev1', 'rev3'])
1084
1085
    def test_single_node(self):
1086
        graph = self.make_graph(ancestry_1)
1087
        self.assertFindUniqueAncestors(graph, ['rev2a'], 'rev2a', ['rev1'])
1088
        self.assertFindUniqueAncestors(graph, ['rev2b'], 'rev2b', ['rev1'])
1089
        self.assertFindUniqueAncestors(graph, ['rev3'], 'rev3', ['rev2a'])
1090
3377.3.24 by John Arbash Meinel
For some reason find_unique_ancestors is much slower than it should be.
1091
    def test_minimal_ancestry(self):
1092
        graph = self.make_breaking_graph(extended_history_shortcut,
1093
                                         [NULL_REVISION, 'a', 'b'])
1094
        self.assertFindUniqueAncestors(graph, ['e'], 'e', ['d'])
1095
3377.3.25 by John Arbash Meinel
A few more minimal ancestry checks
1096
        graph = self.make_breaking_graph(extended_history_shortcut,
1097
                                         ['b'])
1098
        self.assertFindUniqueAncestors(graph, ['f'], 'f', ['a', 'd'])
1099
1100
        graph = self.make_breaking_graph(complex_shortcut,
3377.3.27 by John Arbash Meinel
some simple updates
1101
                                         ['a', 'b'])
3377.3.25 by John Arbash Meinel
A few more minimal ancestry checks
1102
        self.assertFindUniqueAncestors(graph, ['h'], 'h', ['i'])
1103
        self.assertFindUniqueAncestors(graph, ['e', 'g', 'i'], 'i', ['h'])
3377.3.27 by John Arbash Meinel
some simple updates
1104
        self.assertFindUniqueAncestors(graph, ['h'], 'h', ['g'])
3377.3.26 by John Arbash Meinel
Found a graph leak.
1105
        self.assertFindUniqueAncestors(graph, ['h'], 'h', ['j'])
1106
3377.3.21 by John Arbash Meinel
Simple brute-force implementation of find_unique_ancestors
1107
    def test_in_ancestry(self):
1108
        graph = self.make_graph(ancestry_1)
1109
        self.assertFindUniqueAncestors(graph, [], 'rev1', ['rev3'])
1110
        self.assertFindUniqueAncestors(graph, [], 'rev2b', ['rev4'])
1111
1112
    def test_multiple_revisions(self):
1113
        graph = self.make_graph(ancestry_1)
1114
        self.assertFindUniqueAncestors(graph,
1115
            ['rev4'], 'rev4', ['rev3', 'rev2b'])
1116
        self.assertFindUniqueAncestors(graph,
1117
            ['rev2a', 'rev3', 'rev4'], 'rev4', ['rev2b'])
1118
3377.3.22 by John Arbash Meinel
include some tests using the complex graphs
1119
    def test_complex_shortcut(self):
1120
        graph = self.make_graph(complex_shortcut)
1121
        self.assertFindUniqueAncestors(graph,
1122
            ['h', 'n'], 'n', ['m'])
1123
        self.assertFindUniqueAncestors(graph,
1124
            ['e', 'i', 'm'], 'm', ['n'])
1125
1126
    def test_complex_shortcut2(self):
1127
        graph = self.make_graph(complex_shortcut2)
1128
        self.assertFindUniqueAncestors(graph,
1129
            ['j', 'u'], 'u', ['t'])
1130
        self.assertFindUniqueAncestors(graph,
1131
            ['t'], 't', ['u'])
1132
3377.3.21 by John Arbash Meinel
Simple brute-force implementation of find_unique_ancestors
1133
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1134
class TestCachingParentsProvider(tests.TestCase):
1135
1136
    def setUp(self):
1137
        super(TestCachingParentsProvider, self).setUp()
1138
        dict_pp = _mod_graph.DictParentsProvider({'a':('b',)})
1139
        self.inst_pp = InstrumentedParentsProvider(dict_pp)
1140
        self.caching_pp = _mod_graph.CachingParentsProvider(self.inst_pp)
1141
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1142
    def test_get_parent_map(self):
1143
        """Requesting the same revision should be returned from cache"""
1144
        self.assertEqual({}, self.caching_pp._cache)
1145
        self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
1146
        self.assertEqual(['a'], self.inst_pp.calls)
1147
        self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
1148
        # No new call, as it should have been returned from the cache
1149
        self.assertEqual(['a'], self.inst_pp.calls)
1150
        self.assertEqual({'a':('b',)}, self.caching_pp._cache)
1151
1152
    def test_get_parent_map_not_present(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1153
        """The cache should also track when a revision doesn't exist"""
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1154
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1155
        self.assertEqual(['b'], self.inst_pp.calls)
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1156
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1157
        # No new calls
1158
        self.assertEqual(['b'], self.inst_pp.calls)
1159
        self.assertEqual({'b':None}, self.caching_pp._cache)
1160
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1161
    def test_get_parent_map_mixed(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1162
        """Anything that can be returned from cache, should be"""
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1163
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1164
        self.assertEqual(['b'], self.inst_pp.calls)
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1165
        self.assertEqual({'a':('b',)},
1166
                         self.caching_pp.get_parent_map(['a', 'b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1167
        self.assertEqual(['b', 'a'], self.inst_pp.calls)
1168
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1169
    def test_get_parent_map_repeated(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1170
        """Asking for the same parent 2x will only forward 1 request."""
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1171
        self.assertEqual({'a':('b',)},
1172
                         self.caching_pp.get_parent_map(['b', 'a', 'b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1173
        # Use sorted because we don't care about the order, just that each is
1174
        # only present 1 time.
1175
        self.assertEqual(['a', 'b'], sorted(self.inst_pp.calls))