~bzr-pqm/bzr/bzr.dev

5557.1.7 by John Arbash Meinel
Merge in the bzr.dev 5582
1
# Copyright (C) 2007-2011 Canonical Ltd
2490.2.5 by Aaron Bentley
Use GraphWalker.unique_ancestor to determine merge base
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
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2490.2.5 by Aaron Bentley
Use GraphWalker.unique_ancestor to determine merge base
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.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
20
    tests,
2490.2.28 by Aaron Bentley
Fix handling of null revision
21
    )
2490.2.1 by Aaron Bentley
Start work on GraphWalker
22
from bzrlib.revision import NULL_REVISION
23
from bzrlib.tests import TestCaseWithMemoryTransport
24
2490.2.25 by Aaron Bentley
Update from review
25
26
# Ancestry 1:
27
#
28
#  NULL_REVISION
29
#       |
30
#     rev1
31
#      /\
32
#  rev2a rev2b
33
#     |    |
34
#   rev3  /
35
#     |  /
36
#   rev4
2490.2.2 by Aaron Bentley
add minimal-common-ancestor calculation
37
ancestry_1 = {'rev1': [NULL_REVISION], 'rev2a': ['rev1'], 'rev2b': ['rev1'],
38
              'rev3': ['rev2a'], 'rev4': ['rev3', 'rev2b']}
2490.2.25 by Aaron Bentley
Update from review
39
40
41
# Ancestry 2:
42
#
43
#  NULL_REVISION
44
#    /    \
45
# rev1a  rev1b
46
#   |
47
# rev2a
48
#   |
49
# rev3a
50
#   |
51
# rev4a
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
52
ancestry_2 = {'rev1a': [NULL_REVISION], 'rev2a': ['rev1a'],
53
              'rev1b': [NULL_REVISION], 'rev3a': ['rev2a'], 'rev4a': ['rev3a']}
2490.2.2 by Aaron Bentley
add minimal-common-ancestor calculation
54
2490.2.25 by Aaron Bentley
Update from review
55
56
# Criss cross ancestry
57
#
58
#     NULL_REVISION
59
#         |
60
#        rev1
61
#        /  \
62
#    rev2a  rev2b
63
#       |\  /|
64
#       |  X |
65
#       |/  \|
66
#    rev3a  rev3b
2490.2.3 by Aaron Bentley
Implement new merge base picker
67
criss_cross = {'rev1': [NULL_REVISION], 'rev2a': ['rev1'], 'rev2b': ['rev1'],
68
               'rev3a': ['rev2a', 'rev2b'], 'rev3b': ['rev2b', 'rev2a']}
69
2490.2.25 by Aaron Bentley
Update from review
70
71
# Criss-cross 2
72
#
73
#  NULL_REVISION
74
#    /   \
75
# rev1a  rev1b
76
#   |\   /|
77
#   | \ / |
78
#   |  X  |
79
#   | / \ |
80
#   |/   \|
81
# rev2a  rev2b
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
82
criss_cross2 = {'rev1a': [NULL_REVISION], 'rev1b': [NULL_REVISION],
83
                'rev2a': ['rev1a', 'rev1b'], 'rev2b': ['rev1b', 'rev1a']}
84
2490.2.25 by Aaron Bentley
Update from review
85
86
# Mainline:
87
#
88
#  NULL_REVISION
89
#       |
90
#      rev1
91
#      /  \
92
#      | rev2b
93
#      |  /
94
#     rev2a
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
95
mainline = {'rev1': [NULL_REVISION], 'rev2a': ['rev1', 'rev2b'],
96
            'rev2b': ['rev1']}
97
2490.2.25 by Aaron Bentley
Update from review
98
99
# feature branch:
100
#
101
#  NULL_REVISION
102
#       |
103
#      rev1
104
#       |
105
#     rev2b
106
#       |
107
#     rev3b
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
108
feature_branch = {'rev1': [NULL_REVISION],
109
                  'rev2b': ['rev1'], 'rev3b': ['rev2b']}
110
2490.2.25 by Aaron Bentley
Update from review
111
112
# History shortcut
113
#  NULL_REVISION
114
#       |
115
#     rev1------
116
#     /  \      \
117
#  rev2a rev2b rev2c
118
#    |  /   \   /
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
119
#  rev3a    rev3b
2490.2.9 by Aaron Bentley
Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors
120
history_shortcut = {'rev1': [NULL_REVISION], 'rev2a': ['rev1'],
121
                    'rev2b': ['rev1'], 'rev2c': ['rev1'],
122
                    'rev3a': ['rev2a', 'rev2b'], 'rev3b': ['rev2b', 'rev2c']}
123
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
124
# Extended history shortcut
125
#  NULL_REVISION
126
#       |
127
#       a
128
#       |\
129
#       b |
130
#       | |
131
#       c |
132
#       | |
133
#       d |
134
#       |\|
135
#       e f
136
extended_history_shortcut = {'a': [NULL_REVISION],
137
                             'b': ['a'],
138
                             'c': ['b'],
139
                             'd': ['c'],
140
                             'e': ['d'],
141
                             'f': ['a', 'd'],
142
                            }
143
144
# Double shortcut
145
# Both sides will see 'A' first, even though it is actually a decendent of a
146
# different common revision.
147
#
148
#  NULL_REVISION
149
#       |
150
#       a
151
#      /|\
152
#     / b \
153
#    /  |  \
154
#   |   c   |
155
#   |  / \  |
156
#   | d   e |
157
#   |/     \|
158
#   f       g
159
160
double_shortcut = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'],
161
                   'd':['c'], 'e':['c'], 'f':['a', 'd'],
162
                   'g':['a', 'e']}
163
164
# Complex shortcut
165
# This has a failure mode in that a shortcut will find some nodes in common,
3377.3.37 by John Arbash Meinel
Ian's first review comments.
166
# but the common searcher won't have time to find that one branch is actually
3377.3.1 by John Arbash Meinel
Bring in some of the changes from graph_update and graph_optimization
167
# 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
168
# walking off the graph. Specifically, node G should be considered common, but
169
# is likely to be seen by M long before the common searcher finds it.
170
#
171
# NULL_REVISION
172
#     |
173
#     a
174
#     |
175
#     b
176
#     |
177
#     c
178
#     |
179
#     d
180
#     |\
181
#     e f
182
#     | |\
3377.3.1 by John Arbash Meinel
Bring in some of the changes from graph_update and graph_optimization
183
#     | g h
184
#     |/| |
185
#     i j |
186
#     | | |
187
#     | k |
188
#     | | |
189
#     | l |
190
#     |/|/
191
#     m n
192
complex_shortcut = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'],
193
                    'e':['d'], 'f':['d'], 'g':['f'], 'h':['f'],
194
                    'i':['e', 'g'], 'j':['g'], 'k':['j'],
195
                    'l':['k'], 'm':['i', 'l'], 'n':['l', 'h']}
196
197
# NULL_REVISION
198
#     |
199
#     a
200
#     |
201
#     b
202
#     |
203
#     c
204
#     |
205
#     d
206
#     |\
3377.3.13 by John Arbash Meinel
Change _search_for_extra_common slightly.
207
#     e |
208
#     | |
209
#     f |
210
#     | |
211
#     g h
3377.3.1 by John Arbash Meinel
Bring in some of the changes from graph_update and graph_optimization
212
#     | |\
3377.3.13 by John Arbash Meinel
Change _search_for_extra_common slightly.
213
#     i | j
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
214
#     |\| |
215
#     | k |
216
#     | | |
217
#     | l |
3377.3.13 by John Arbash Meinel
Change _search_for_extra_common slightly.
218
#     | | |
219
#     | m |
220
#     | | |
221
#     | n |
222
#     | | |
223
#     | o |
224
#     | | |
225
#     | p |
226
#     | | |
227
#     | q |
228
#     | | |
229
#     | r |
230
#     | | |
231
#     | s |
232
#     | | |
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
233
#     |/|/
3377.3.13 by John Arbash Meinel
Change _search_for_extra_common slightly.
234
#     t u
235
complex_shortcut2 = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'],
3377.3.43 by John Arbash Meinel
Ian's review feedback
236
                    'e':['d'], 'f':['e'], 'g':['f'], 'h':['d'], 'i':['g'],
237
                    'j':['h'], 'k':['h', 'i'], 'l':['k'], 'm':['l'], 'n':['m'],
238
                    'o':['n'], 'p':['o'], 'q':['p'], 'r':['q'], 's':['r'],
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
239
                    't':['i', 's'], 'u':['s', 'j'],
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
240
                    }
241
3377.3.36 by John Arbash Meinel
Small updates, try to write a test for the race condition.
242
# Graph where different walkers will race to find the common and uncommon
243
# nodes.
244
#
245
# NULL_REVISION
246
#     |
247
#     a
248
#     |
249
#     b
250
#     |
251
#     c
252
#     |
253
#     d
254
#     |\
255
#     e k
256
#     | |
257
#     f-+-p
258
#     | | |
259
#     | l |
260
#     | | |
261
#     | m |
262
#     | |\|
263
#     g n q
264
#     |\| |
265
#     h o |
266
#     |/| |
267
#     i r |
268
#     | | |
269
#     | s |
270
#     | | |
271
#     | t |
272
#     | | |
273
#     | u |
274
#     | | |
275
#     | v |
276
#     | | |
277
#     | w |
278
#     | | |
279
#     | x |
280
#     | |\|
281
#     | y z
282
#     |/
283
#     j
284
#
3377.4.2 by John Arbash Meinel
Merge in the bzr.dev changes
285
# x is found to be common right away, but is the start of a long series of
3377.3.36 by John Arbash Meinel
Small updates, try to write a test for the race condition.
286
# common commits.
287
# o is actually common, but the i-j shortcut makes it look like it is actually
3377.4.2 by John Arbash Meinel
Merge in the bzr.dev changes
288
# unique to j at first, you have to traverse all of x->o to find it.
289
# q,m gives the walker from j a common point to stop searching, as does p,f.
3377.3.36 by John Arbash Meinel
Small updates, try to write a test for the race condition.
290
# k-n exists so that the second pass still has nodes that are worth searching,
291
# rather than instantly cancelling the extra walker.
292
293
racing_shortcuts = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'],
294
    'e':['d'], 'f':['e'], 'g':['f'], 'h':['g'], 'i':['h', 'o'], 'j':['i', 'y'],
295
    'k':['d'], 'l':['k'], 'm':['l'], 'n':['m'], 'o':['n', 'g'], 'p':['f'],
296
    'q':['p', 'm'], 'r':['o'], 's':['r'], 't':['s'], 'u':['t'], 'v':['u'],
297
    'w':['v'], 'x':['w'], 'y':['x'], 'z':['x', 'q']}
298
3377.3.35 by John Arbash Meinel
Add a test that exercises the multiple interesting unique code
299
300
# A graph with multiple nodes unique to one side.
301
#
302
# NULL_REVISION
303
#     |
304
#     a
305
#     |
306
#     b
307
#     |
308
#     c
309
#     |
310
#     d
311
#     |\
312
#     e f
313
#     |\ \
314
#     g h i
315
#     |\ \ \
316
#     j k l m
317
#     | |/ x|
318
#     | n o p
319
#     | |/  |
320
#     | q   |
321
#     | |   |
322
#     | r   |
323
#     | |   |
324
#     | s   |
325
#     | |   |
326
#     | t   |
327
#     | |   |
328
#     | u   |
329
#     | |   |
330
#     | v   |
331
#     | |   |
332
#     | w   |
333
#     | |   |
334
#     | x   |
335
#     |/ \ /
336
#     y   z
337
#
338
339
multiple_interesting_unique = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'],
340
    'd':['c'], 'e':['d'], 'f':['d'], 'g':['e'], 'h':['e'], 'i':['f'],
341
    'j':['g'], 'k':['g'], 'l':['h'], 'm':['i'], 'n':['k', 'l'],
342
    'o':['m'], 'p':['m', 'l'], 'q':['n', 'o'], 'r':['q'], 's':['r'],
343
    't':['s'], 'u':['t'], 'v':['u'], 'w':['v'], 'x':['w'],
344
    'y':['j', 'x'], 'z':['x', 'p']}
345
346
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
347
# Shortcut with extra root
348
# We have a long history shortcut, and an extra root, which is why we can't
349
# stop searchers based on seeing NULL_REVISION
350
#  NULL_REVISION
351
#       |   |
352
#       a   |
353
#       |\  |
354
#       b | |
355
#       | | |
356
#       c | |
357
#       | | |
358
#       d | g
359
#       |\|/
360
#       e f
361
shortcut_extra_root = {'a': [NULL_REVISION],
362
                       'b': ['a'],
363
                       'c': ['b'],
364
                       'd': ['c'],
365
                       'e': ['d'],
366
                       'f': ['a', 'd', 'g'],
367
                       'g': [NULL_REVISION],
368
                      }
369
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
370
#  NULL_REVISION
371
#       |
372
#       f
373
#       |
374
#       e
375
#      / \
376
#     b   d
377
#     | \ |
378
#     a   c
379
380
boundary = {'a': ['b'], 'c': ['b', 'd'], 'b':['e'], 'd':['e'], 'e': ['f'],
381
            'f':[NULL_REVISION]}
382
383
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
384
# A graph that contains a ghost
385
#  NULL_REVISION
386
#       |
387
#       f
388
#       |
389
#       e   g
390
#      / \ /
391
#     b   d
392
#     | \ |
393
#     a   c
394
395
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.
396
              'e': ['f'], 'f':[NULL_REVISION], NULL_REVISION:()}
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
397
3445.1.3 by John Arbash Meinel
Search from all of the known revisions.
398
# A graph that shows we can shortcut finding revnos when reaching them from the
399
# side.
400
#  NULL_REVISION
401
#       |
402
#       a
403
#       |
404
#       b
405
#       |
406
#       c
407
#       |
408
#       d
409
#       |
410
#       e
411
#      / \
412
#     f   g
413
#     |
414
#     h
415
#     |
416
#     i
417
418
with_tail = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'], 'e':['d'],
419
             'f':['e'], 'g':['e'], 'h':['f'], 'i':['h']}
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
420
421
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
422
class InstrumentedParentsProvider(object):
423
424
    def __init__(self, parents_provider):
425
        self.calls = []
426
        self._real_parents_provider = parents_provider
6015.24.3 by John Arbash Meinel
Implement get_cached_parent_map
427
        get_cached = getattr(parents_provider, 'get_cached_parent_map', None)
428
        if get_cached is not None:
429
            # Only expose the underlying 'get_cached_parent_map' function if
430
            # the wrapped provider has it.
431
            self.get_cached_parent_map = self._get_cached_parent_map
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
432
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
433
    def get_parent_map(self, nodes):
434
        self.calls.extend(nodes)
435
        return self._real_parents_provider.get_parent_map(nodes)
436
6015.24.3 by John Arbash Meinel
Implement get_cached_parent_map
437
    def _get_cached_parent_map(self, nodes):
438
        self.calls.append(('cached', sorted(nodes)))
439
        return self._real_parents_provider.get_cached_parent_map(nodes)
440
441
442
class SharedInstrumentedParentsProvider(object):
443
444
    def __init__(self, parents_provider, calls, info):
445
        self.calls = calls
446
        self.info = info
447
        self._real_parents_provider = parents_provider
448
        get_cached = getattr(parents_provider, 'get_cached_parent_map', None)
449
        if get_cached is not None:
450
            # Only expose the underlying 'get_cached_parent_map' function if
451
            # the wrapped provider has it.
452
            self.get_cached_parent_map = self._get_cached_parent_map
453
454
    def get_parent_map(self, nodes):
455
        self.calls.append((self.info, sorted(nodes)))
456
        return self._real_parents_provider.get_parent_map(nodes)
457
458
    def _get_cached_parent_map(self, nodes):
459
        self.calls.append((self.info, 'cached', sorted(nodes)))
460
        return self._real_parents_provider.get_cached_parent_map(nodes)
461
2490.2.25 by Aaron Bentley
Update from review
462
3445.1.2 by John Arbash Meinel
Handle when a known revision is an ancestor.
463
class TestGraphBase(tests.TestCase):
464
465
    def make_graph(self, ancestors):
466
        return _mod_graph.Graph(_mod_graph.DictParentsProvider(ancestors))
467
468
    def make_breaking_graph(self, ancestors, break_on):
469
        """Make a Graph that raises an exception if we hit a node."""
470
        g = self.make_graph(ancestors)
471
        orig_parent_map = g.get_parent_map
472
        def get_parent_map(keys):
473
            bad_keys = set(keys).intersection(break_on)
474
            if bad_keys:
475
                self.fail('key(s) %s was accessed' % (sorted(bad_keys),))
476
            return orig_parent_map(keys)
477
        g.get_parent_map = get_parent_map
478
        return g
479
480
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
481
class TestGraph(TestCaseWithMemoryTransport):
2490.2.1 by Aaron Bentley
Start work on GraphWalker
482
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
483
    def make_graph(self, ancestors):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
484
        return _mod_graph.Graph(_mod_graph.DictParentsProvider(ancestors))
2490.2.3 by Aaron Bentley
Implement new merge base picker
485
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
486
    def prepare_memory_tree(self, location):
487
        tree = self.make_branch_and_memory_tree(location)
2490.2.1 by Aaron Bentley
Start work on GraphWalker
488
        tree.lock_write()
489
        tree.add('.')
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
490
        return tree
491
492
    def build_ancestry(self, tree, ancestors):
2490.2.25 by Aaron Bentley
Update from review
493
        """Create an ancestry as specified by a graph dict
494
495
        :param tree: A tree to use
496
        :param ancestors: a dict of {node: [node_parent, ...]}
497
        """
2490.2.1 by Aaron Bentley
Start work on GraphWalker
498
        pending = [NULL_REVISION]
499
        descendants = {}
500
        for descendant, parents in ancestors.iteritems():
501
            for parent in parents:
502
                descendants.setdefault(parent, []).append(descendant)
503
        while len(pending) > 0:
504
            cur_node = pending.pop()
505
            for descendant in descendants.get(cur_node, []):
2490.2.3 by Aaron Bentley
Implement new merge base picker
506
                if tree.branch.repository.has_revision(descendant):
507
                    continue
2490.2.1 by Aaron Bentley
Start work on GraphWalker
508
                parents = [p for p in ancestors[descendant] if p is not
509
                           NULL_REVISION]
510
                if len([p for p in parents if not
511
                    tree.branch.repository.has_revision(p)]) > 0:
512
                    continue
513
                tree.set_parent_ids(parents)
2490.2.3 by Aaron Bentley
Implement new merge base picker
514
                if len(parents) > 0:
515
                    left_parent = parents[0]
516
                else:
517
                    left_parent = NULL_REVISION
2490.2.1 by Aaron Bentley
Start work on GraphWalker
518
                tree.branch.set_last_revision_info(
2490.2.3 by Aaron Bentley
Implement new merge base picker
519
                    len(tree.branch._lefthand_history(left_parent)),
520
                    left_parent)
2490.2.1 by Aaron Bentley
Start work on GraphWalker
521
                tree.commit(descendant, rev_id=descendant)
522
                pending.append(descendant)
2490.2.2 by Aaron Bentley
add minimal-common-ancestor calculation
523
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
524
    def test_lca(self):
2490.2.25 by Aaron Bentley
Update from review
525
        """Test finding least common ancestor.
2490.2.3 by Aaron Bentley
Implement new merge base picker
526
527
        ancestry_1 should always have a single common ancestor
528
        """
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
529
        graph = self.make_graph(ancestry_1)
2490.2.28 by Aaron Bentley
Fix handling of null revision
530
        self.assertRaises(errors.InvalidRevisionId, graph.find_lca, None)
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
531
        self.assertEqual(set([NULL_REVISION]),
532
                         graph.find_lca(NULL_REVISION, NULL_REVISION))
533
        self.assertEqual(set([NULL_REVISION]),
534
                         graph.find_lca(NULL_REVISION, 'rev1'))
535
        self.assertEqual(set(['rev1']), graph.find_lca('rev1', 'rev1'))
536
        self.assertEqual(set(['rev1']), graph.find_lca('rev2a', 'rev2b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
537
2520.4.104 by Aaron Bentley
Avoid infinite loop when there is no unique lca
538
    def test_no_unique_lca(self):
539
        """Test error when one revision is not in the graph"""
540
        graph = self.make_graph(ancestry_1)
541
        self.assertRaises(errors.NoCommonAncestor, graph.find_unique_lca,
542
                          'rev1', '1rev')
543
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
544
    def test_lca_criss_cross(self):
2490.2.25 by Aaron Bentley
Update from review
545
        """Test least-common-ancestor after a criss-cross merge."""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
546
        graph = self.make_graph(criss_cross)
2490.2.3 by Aaron Bentley
Implement new merge base picker
547
        self.assertEqual(set(['rev2a', 'rev2b']),
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
548
                         graph.find_lca('rev3a', 'rev3b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
549
        self.assertEqual(set(['rev2b']),
2490.2.25 by Aaron Bentley
Update from review
550
                         graph.find_lca('rev3a', 'rev3b', 'rev2b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
551
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
552
    def test_lca_shortcut(self):
2490.2.25 by Aaron Bentley
Update from review
553
        """Test least-common ancestor on this history shortcut"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
554
        graph = self.make_graph(history_shortcut)
555
        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
556
4332.3.4 by Robert Collins
Add a graph API for getting multiple distances to NULL at once.
557
    def test_lefthand_distance_smoke(self):
558
        """A simple does it work test for graph.lefthand_distance(keys)."""
559
        graph = self.make_graph(history_shortcut)
560
        distance_graph = graph.find_lefthand_distances(['rev3b', 'rev2a'])
561
        self.assertEqual({'rev2a': 2, 'rev3b': 3}, distance_graph)
562
4332.3.6 by Robert Collins
Teach graph.find_lefthand_distances about ghosts.
563
    def test_lefthand_distance_ghosts(self):
564
        """A simple does it work test for graph.lefthand_distance(keys)."""
565
        nodes = {'nonghost':[NULL_REVISION], 'toghost':['ghost']}
566
        graph = self.make_graph(nodes)
567
        distance_graph = graph.find_lefthand_distances(['nonghost', 'toghost'])
568
        self.assertEqual({'nonghost': 1, 'toghost': -1}, distance_graph)
569
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
570
    def test_recursive_unique_lca(self):
2490.2.25 by Aaron Bentley
Update from review
571
        """Test finding a unique least common ancestor.
2490.2.3 by Aaron Bentley
Implement new merge base picker
572
573
        ancestry_1 should always have a single common ancestor
574
        """
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
575
        graph = self.make_graph(ancestry_1)
576
        self.assertEqual(NULL_REVISION,
577
                         graph.find_unique_lca(NULL_REVISION, NULL_REVISION))
578
        self.assertEqual(NULL_REVISION,
579
                         graph.find_unique_lca(NULL_REVISION, 'rev1'))
580
        self.assertEqual('rev1', graph.find_unique_lca('rev1', 'rev1'))
581
        self.assertEqual('rev1', graph.find_unique_lca('rev2a', 'rev2b'))
1551.19.10 by Aaron Bentley
Merge now warns when it encounters a criss-cross
582
        self.assertEqual(('rev1', 1,),
583
                         graph.find_unique_lca('rev2a', 'rev2b',
584
                         count_steps=True))
2490.2.3 by Aaron Bentley
Implement new merge base picker
585
3377.3.1 by John Arbash Meinel
Bring in some of the changes from graph_update and graph_optimization
586
    def assertRemoveDescendants(self, expected, graph, revisions):
587
        parents = graph.get_parent_map(revisions)
588
        self.assertEqual(expected,
589
                         graph._remove_simple_descendants(revisions, parents))
590
591
    def test__remove_simple_descendants(self):
592
        graph = self.make_graph(ancestry_1)
593
        self.assertRemoveDescendants(set(['rev1']), graph,
594
            set(['rev1', 'rev2a', 'rev2b', 'rev3', 'rev4']))
595
596
    def test__remove_simple_descendants_disjoint(self):
597
        graph = self.make_graph(ancestry_1)
598
        self.assertRemoveDescendants(set(['rev1', 'rev3']), graph,
599
            set(['rev1', 'rev3']))
600
601
    def test__remove_simple_descendants_chain(self):
602
        graph = self.make_graph(ancestry_1)
603
        self.assertRemoveDescendants(set(['rev1']), graph,
604
            set(['rev1', 'rev2a', 'rev3']))
605
606
    def test__remove_simple_descendants_siblings(self):
607
        graph = self.make_graph(ancestry_1)
608
        self.assertRemoveDescendants(set(['rev2a', 'rev2b']), graph,
609
            set(['rev2a', 'rev2b', 'rev3']))
610
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
611
    def test_unique_lca_criss_cross(self):
612
        """Ensure we don't pick non-unique lcas in a criss-cross"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
613
        graph = self.make_graph(criss_cross)
614
        self.assertEqual('rev1', graph.find_unique_lca('rev3a', 'rev3b'))
1551.19.10 by Aaron Bentley
Merge now warns when it encounters a criss-cross
615
        lca, steps = graph.find_unique_lca('rev3a', 'rev3b', count_steps=True)
616
        self.assertEqual('rev1', lca)
617
        self.assertEqual(2, steps)
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
618
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
619
    def test_unique_lca_null_revision(self):
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
620
        """Ensure we pick NULL_REVISION when necessary"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
621
        graph = self.make_graph(criss_cross2)
622
        self.assertEqual('rev1b', graph.find_unique_lca('rev2a', 'rev1b'))
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
623
        self.assertEqual(NULL_REVISION,
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
624
                         graph.find_unique_lca('rev2a', 'rev2b'))
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
625
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
626
    def test_unique_lca_null_revision2(self):
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
627
        """Ensure we pick NULL_REVISION when necessary"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
628
        graph = self.make_graph(ancestry_2)
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
629
        self.assertEqual(NULL_REVISION,
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
630
                         graph.find_unique_lca('rev4a', 'rev1b'))
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
631
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
632
    def test_lca_double_shortcut(self):
633
        graph = self.make_graph(double_shortcut)
634
        self.assertEqual('c', graph.find_unique_lca('f', 'g'))
635
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
636
    def test_common_ancestor_two_repos(self):
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
637
        """Ensure we do unique_lca using data from two repos"""
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
638
        mainline_tree = self.prepare_memory_tree('mainline')
639
        self.build_ancestry(mainline_tree, mainline)
3010.1.6 by Robert Collins
Locking in test_graph.
640
        self.addCleanup(mainline_tree.unlock)
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
641
642
        # This is cheating, because the revisions in the graph are actually
643
        # different revisions, despite having the same revision-id.
644
        feature_tree = self.prepare_memory_tree('feature')
645
        self.build_ancestry(feature_tree, feature_branch)
3010.1.6 by Robert Collins
Locking in test_graph.
646
        self.addCleanup(feature_tree.unlock)
647
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
648
        graph = mainline_tree.branch.repository.get_graph(
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
649
            feature_tree.branch.repository)
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
650
        self.assertEqual('rev2b', graph.find_unique_lca('rev2a', 'rev3b'))
2490.2.23 by Aaron Bentley
Adapt find_borders to produce a graph difference
651
652
    def test_graph_difference(self):
653
        graph = self.make_graph(ancestry_1)
654
        self.assertEqual((set(), set()), graph.find_difference('rev1', 'rev1'))
655
        self.assertEqual((set(), set(['rev1'])),
656
                         graph.find_difference(NULL_REVISION, 'rev1'))
657
        self.assertEqual((set(['rev1']), set()),
658
                         graph.find_difference('rev1', NULL_REVISION))
659
        self.assertEqual((set(['rev2a', 'rev3']), set(['rev2b'])),
660
                         graph.find_difference('rev3', 'rev2b'))
661
        self.assertEqual((set(['rev4', 'rev3', 'rev2a']), set()),
662
                         graph.find_difference('rev4', 'rev2b'))
663
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
664
    def test_graph_difference_separate_ancestry(self):
665
        graph = self.make_graph(ancestry_2)
666
        self.assertEqual((set(['rev1a']), set(['rev1b'])),
667
                         graph.find_difference('rev1a', 'rev1b'))
668
        self.assertEqual((set(['rev1a', 'rev2a', 'rev3a', 'rev4a']),
669
                          set(['rev1b'])),
670
                         graph.find_difference('rev4a', 'rev1b'))
671
2490.2.23 by Aaron Bentley
Adapt find_borders to produce a graph difference
672
    def test_graph_difference_criss_cross(self):
673
        graph = self.make_graph(criss_cross)
674
        self.assertEqual((set(['rev3a']), set(['rev3b'])),
675
                         graph.find_difference('rev3a', 'rev3b'))
676
        self.assertEqual((set([]), set(['rev3b', 'rev2b'])),
677
                         graph.find_difference('rev2a', 'rev3b'))
2490.2.25 by Aaron Bentley
Update from review
678
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
679
    def test_graph_difference_extended_history(self):
680
        graph = self.make_graph(extended_history_shortcut)
681
        self.assertEqual((set(['e']), set(['f'])),
682
                         graph.find_difference('e', 'f'))
683
        self.assertEqual((set(['f']), set(['e'])),
684
                         graph.find_difference('f', 'e'))
685
686
    def test_graph_difference_double_shortcut(self):
687
        graph = self.make_graph(double_shortcut)
688
        self.assertEqual((set(['d', 'f']), set(['e', 'g'])),
689
                         graph.find_difference('f', 'g'))
690
691
    def test_graph_difference_complex_shortcut(self):
692
        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
693
        self.assertEqual((set(['m', 'i', 'e']), set(['n', 'h'])),
694
                         graph.find_difference('m', 'n'))
695
696
    def test_graph_difference_complex_shortcut2(self):
697
        graph = self.make_graph(complex_shortcut2)
3377.3.13 by John Arbash Meinel
Change _search_for_extra_common slightly.
698
        self.assertEqual((set(['t']), set(['j', 'u'])),
699
                         graph.find_difference('t', 'u'))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
700
701
    def test_graph_difference_shortcut_extra_root(self):
702
        graph = self.make_graph(shortcut_extra_root)
703
        self.assertEqual((set(['e']), set(['f', 'g'])),
704
                         graph.find_difference('e', 'f'))
705
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
706
    def test_iter_topo_order(self):
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
707
        graph = self.make_graph(ancestry_1)
708
        args = ['rev2a', 'rev3', 'rev1']
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
709
        topo_args = list(graph.iter_topo_order(args))
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
710
        self.assertEqual(set(args), set(topo_args))
711
        self.assertTrue(topo_args.index('rev2a') > topo_args.index('rev1'))
712
        self.assertTrue(topo_args.index('rev2a') < topo_args.index('rev3'))
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
713
714
    def test_is_ancestor(self):
715
        graph = self.make_graph(ancestry_1)
2653.2.3 by Aaron Bentley
correctly handle Graph.is_ancestor(x, x)
716
        self.assertEqual(True, graph.is_ancestor('null:', 'null:'))
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
717
        self.assertEqual(True, graph.is_ancestor('null:', 'rev1'))
718
        self.assertEqual(False, graph.is_ancestor('rev1', 'null:'))
719
        self.assertEqual(True, graph.is_ancestor('null:', 'rev4'))
720
        self.assertEqual(False, graph.is_ancestor('rev4', 'null:'))
721
        self.assertEqual(False, graph.is_ancestor('rev4', 'rev2b'))
722
        self.assertEqual(True, graph.is_ancestor('rev2b', 'rev4'))
723
        self.assertEqual(False, graph.is_ancestor('rev2b', 'rev3'))
724
        self.assertEqual(False, graph.is_ancestor('rev3', 'rev2b'))
725
        instrumented_provider = InstrumentedParentsProvider(graph)
726
        instrumented_graph = _mod_graph.Graph(instrumented_provider)
727
        instrumented_graph.is_ancestor('rev2a', 'rev2b')
728
        self.assertTrue('null:' not in instrumented_provider.calls)
729
3921.3.5 by Marius Kruger
extract graph.is_between from builtins.cmd_tags.run, and test it
730
    def test_is_between(self):
731
        graph = self.make_graph(ancestry_1)
732
        self.assertEqual(True, graph.is_between('null:', 'null:', 'null:'))
733
        self.assertEqual(True, graph.is_between('rev1', 'null:', 'rev1'))
734
        self.assertEqual(True, graph.is_between('rev1', 'rev1', 'rev4'))
735
        self.assertEqual(True, graph.is_between('rev4', 'rev1', 'rev4'))
736
        self.assertEqual(True, graph.is_between('rev3', 'rev1', 'rev4'))
737
        self.assertEqual(False, graph.is_between('rev4', 'rev1', 'rev3'))
738
        self.assertEqual(False, graph.is_between('rev1', 'rev2a', 'rev4'))
739
        self.assertEqual(False, graph.is_between('null:', 'rev1', 'rev4'))
740
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
741
    def test_is_ancestor_boundary(self):
742
        """Ensure that we avoid searching the whole graph.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
743
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
744
        This requires searching through b as a common ancestor, so we
745
        can identify that e is common.
746
        """
747
        graph = self.make_graph(boundary)
748
        instrumented_provider = InstrumentedParentsProvider(graph)
749
        graph = _mod_graph.Graph(instrumented_provider)
750
        self.assertFalse(graph.is_ancestor('a', 'c'))
751
        self.assertTrue('null:' not in instrumented_provider.calls)
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
752
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
753
    def test_iter_ancestry(self):
3228.4.10 by John Arbash Meinel
Respond to abentley's review comments.
754
        nodes = boundary.copy()
755
        nodes[NULL_REVISION] = ()
756
        graph = self.make_graph(nodes)
757
        expected = nodes.copy()
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
758
        expected.pop('a') # 'a' is not in the ancestry of 'c', all the
759
                          # other nodes are
3228.4.4 by John Arbash Meinel
Change iter_ancestry to take a group instead of a single node,
760
        self.assertEqual(expected, dict(graph.iter_ancestry(['c'])))
3228.4.10 by John Arbash Meinel
Respond to abentley's review comments.
761
        self.assertEqual(nodes, dict(graph.iter_ancestry(['a', 'c'])))
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
762
763
    def test_iter_ancestry_with_ghost(self):
764
        graph = self.make_graph(with_ghost)
765
        expected = with_ghost.copy()
766
        # '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.
767
        expected['g'] = None
3228.4.4 by John Arbash Meinel
Change iter_ancestry to take a group instead of a single node,
768
        self.assertEqual(expected, dict(graph.iter_ancestry(['a', 'c'])))
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
769
        expected.pop('a')
3228.4.4 by John Arbash Meinel
Change iter_ancestry to take a group instead of a single node,
770
        self.assertEqual(expected, dict(graph.iter_ancestry(['c'])))
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
771
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
772
    def test_filter_candidate_lca(self):
773
        """Test filter_candidate_lca for a corner case
774
1551.15.83 by Aaron Bentley
Update test documentation
775
        This tests the case where we encounter the end of iteration for 'e'
776
        in the same pass as we discover that 'd' is an ancestor of 'e', and
777
        therefore 'e' can't be an lca.
778
779
        To compensate for different dict orderings on other Python
780
        implementations, we mirror 'd' and 'e' with 'b' and 'a'.
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
781
        """
782
        # This test is sensitive to the iteration order of dicts.  It will
1551.15.82 by Aaron Bentley
Add symmetrical alternative to test case
783
        # pass incorrectly if 'e' and 'a' sort before 'c'
1551.15.84 by Aaron Bentley
Add ancestry graph for test case
784
        #
785
        # NULL_REVISION
786
        #     / \
787
        #    a   e
788
        #    |   |
789
        #    b   d
790
        #     \ /
791
        #      c
1551.15.82 by Aaron Bentley
Add symmetrical alternative to test case
792
        graph = self.make_graph({'c': ['b', 'd'], 'd': ['e'], 'b': ['a'],
793
                                 'a': [NULL_REVISION], 'e': [NULL_REVISION]})
2776.1.4 by Robert Collins
Trivial review feedback changes.
794
        self.assertEqual(set(['c']), graph.heads(['a', 'c', 'e']))
795
796
    def test_heads_null(self):
797
        graph = self.make_graph(ancestry_1)
798
        self.assertEqual(set(['null:']), graph.heads(['null:']))
799
        self.assertEqual(set(['rev1']), graph.heads(['null:', 'rev1']))
800
        self.assertEqual(set(['rev1']), graph.heads(['rev1', 'null:']))
801
        self.assertEqual(set(['rev1']), graph.heads(set(['rev1', 'null:'])))
802
        self.assertEqual(set(['rev1']), graph.heads(('rev1', 'null:')))
803
804
    def test_heads_one(self):
3052.5.5 by John Arbash Meinel
Special case Graph.heads() for NULL_REVISION rather than is_ancestor.
805
        # A single node will always be a head
2776.1.4 by Robert Collins
Trivial review feedback changes.
806
        graph = self.make_graph(ancestry_1)
807
        self.assertEqual(set(['null:']), graph.heads(['null:']))
808
        self.assertEqual(set(['rev1']), graph.heads(['rev1']))
809
        self.assertEqual(set(['rev2a']), graph.heads(['rev2a']))
810
        self.assertEqual(set(['rev2b']), graph.heads(['rev2b']))
811
        self.assertEqual(set(['rev3']), graph.heads(['rev3']))
812
        self.assertEqual(set(['rev4']), graph.heads(['rev4']))
813
814
    def test_heads_single(self):
815
        graph = self.make_graph(ancestry_1)
816
        self.assertEqual(set(['rev4']), graph.heads(['null:', 'rev4']))
817
        self.assertEqual(set(['rev2a']), graph.heads(['rev1', 'rev2a']))
818
        self.assertEqual(set(['rev2b']), graph.heads(['rev1', 'rev2b']))
819
        self.assertEqual(set(['rev3']), graph.heads(['rev1', 'rev3']))
820
        self.assertEqual(set(['rev4']), graph.heads(['rev1', 'rev4']))
821
        self.assertEqual(set(['rev4']), graph.heads(['rev2a', 'rev4']))
822
        self.assertEqual(set(['rev4']), graph.heads(['rev2b', 'rev4']))
823
        self.assertEqual(set(['rev4']), graph.heads(['rev3', 'rev4']))
824
825
    def test_heads_two_heads(self):
826
        graph = self.make_graph(ancestry_1)
827
        self.assertEqual(set(['rev2a', 'rev2b']),
828
                         graph.heads(['rev2a', 'rev2b']))
829
        self.assertEqual(set(['rev3', 'rev2b']),
830
                         graph.heads(['rev3', 'rev2b']))
831
832
    def test_heads_criss_cross(self):
833
        graph = self.make_graph(criss_cross)
834
        self.assertEqual(set(['rev2a']),
835
                         graph.heads(['rev2a', 'rev1']))
836
        self.assertEqual(set(['rev2b']),
837
                         graph.heads(['rev2b', 'rev1']))
838
        self.assertEqual(set(['rev3a']),
839
                         graph.heads(['rev3a', 'rev1']))
840
        self.assertEqual(set(['rev3b']),
841
                         graph.heads(['rev3b', 'rev1']))
842
        self.assertEqual(set(['rev2a', 'rev2b']),
843
                         graph.heads(['rev2a', 'rev2b']))
844
        self.assertEqual(set(['rev3a']),
845
                         graph.heads(['rev3a', 'rev2a']))
846
        self.assertEqual(set(['rev3a']),
847
                         graph.heads(['rev3a', 'rev2b']))
848
        self.assertEqual(set(['rev3a']),
849
                         graph.heads(['rev3a', 'rev2a', 'rev2b']))
850
        self.assertEqual(set(['rev3b']),
851
                         graph.heads(['rev3b', 'rev2a']))
852
        self.assertEqual(set(['rev3b']),
853
                         graph.heads(['rev3b', 'rev2b']))
854
        self.assertEqual(set(['rev3b']),
855
                         graph.heads(['rev3b', 'rev2a', 'rev2b']))
856
        self.assertEqual(set(['rev3a', 'rev3b']),
857
                         graph.heads(['rev3a', 'rev3b']))
858
        self.assertEqual(set(['rev3a', 'rev3b']),
859
                         graph.heads(['rev3a', 'rev3b', 'rev2a', 'rev2b']))
860
861
    def test_heads_shortcut(self):
862
        graph = self.make_graph(history_shortcut)
863
864
        self.assertEqual(set(['rev2a', 'rev2b', 'rev2c']),
865
                         graph.heads(['rev2a', 'rev2b', 'rev2c']))
866
        self.assertEqual(set(['rev3a', 'rev3b']),
867
                         graph.heads(['rev3a', 'rev3b']))
868
        self.assertEqual(set(['rev3a', 'rev3b']),
869
                         graph.heads(['rev2a', 'rev3a', 'rev3b']))
870
        self.assertEqual(set(['rev2a', 'rev3b']),
871
                         graph.heads(['rev2a', 'rev3b']))
872
        self.assertEqual(set(['rev2c', 'rev3a']),
873
                         graph.heads(['rev2c', 'rev3a']))
2921.3.1 by Robert Collins
* Graph ``heads()`` queries have been bugfixed to no longer access all
874
875
    def _run_heads_break_deeper(self, graph_dict, search):
876
        """Run heads on a graph-as-a-dict.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
877
2921.3.1 by Robert Collins
* Graph ``heads()`` queries have been bugfixed to no longer access all
878
        If the search asks for the parents of 'deeper' the test will fail.
879
        """
880
        class stub(object):
881
            pass
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
882
        def get_parent_map(keys):
883
            result = {}
884
            for key in keys:
885
                if key == 'deeper':
886
                    self.fail('key deeper was accessed')
887
                result[key] = graph_dict[key]
888
            return result
2921.3.1 by Robert Collins
* Graph ``heads()`` queries have been bugfixed to no longer access all
889
        an_obj = stub()
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
890
        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
891
        graph = _mod_graph.Graph(an_obj)
892
        return graph.heads(search)
893
894
    def test_heads_limits_search(self):
895
        # test that a heads query does not search all of history
896
        graph_dict = {
897
            'left':['common'],
898
            'right':['common'],
899
            'common':['deeper'],
900
        }
901
        self.assertEqual(set(['left', 'right']),
902
            self._run_heads_break_deeper(graph_dict, ['left', 'right']))
903
904
    def test_heads_limits_search_assymetric(self):
905
        # test that a heads query does not search all of history
906
        graph_dict = {
907
            'left':['midleft'],
908
            'midleft':['common'],
909
            'right':['common'],
910
            'common':['aftercommon'],
911
            'aftercommon':['deeper'],
912
        }
913
        self.assertEqual(set(['left', 'right']),
914
            self._run_heads_break_deeper(graph_dict, ['left', 'right']))
915
916
    def test_heads_limits_search_common_search_must_continue(self):
917
        # test that common nodes are still queried, preventing
918
        # all-the-way-to-origin behaviour in the following graph:
919
        graph_dict = {
920
            'h1':['shortcut', 'common1'],
921
            'h2':['common1'],
922
            'shortcut':['common2'],
923
            'common1':['common2'],
924
            'common2':['deeper'],
925
        }
926
        self.assertEqual(set(['h1', 'h2']),
927
            self._run_heads_break_deeper(graph_dict, ['h1', 'h2']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
928
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
929
    def test_breadth_first_search_start_ghosts(self):
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
930
        graph = self.make_graph({})
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
931
        # with_ghosts reports the ghosts
932
        search = graph._make_breadth_first_searcher(['a-ghost'])
933
        self.assertEqual((set(), set(['a-ghost'])), search.next_with_ghosts())
934
        self.assertRaises(StopIteration, search.next_with_ghosts)
935
        # next includes them
936
        search = graph._make_breadth_first_searcher(['a-ghost'])
937
        self.assertEqual(set(['a-ghost']), search.next())
938
        self.assertRaises(StopIteration, search.next)
939
940
    def test_breadth_first_search_deep_ghosts(self):
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
941
        graph = self.make_graph({
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
942
            'head':['present'],
943
            'present':['child', 'ghost'],
944
            'child':[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
945
            })
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
946
        # with_ghosts reports the ghosts
947
        search = graph._make_breadth_first_searcher(['head'])
948
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
949
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
950
        self.assertEqual((set(['child']), set(['ghost'])),
951
            search.next_with_ghosts())
952
        self.assertRaises(StopIteration, search.next_with_ghosts)
953
        # next includes them
954
        search = graph._make_breadth_first_searcher(['head'])
955
        self.assertEqual(set(['head']), search.next())
956
        self.assertEqual(set(['present']), search.next())
957
        self.assertEqual(set(['child', 'ghost']),
958
            search.next())
959
        self.assertRaises(StopIteration, search.next)
960
961
    def test_breadth_first_search_change_next_to_next_with_ghosts(self):
3177.3.3 by Robert Collins
Review feedback.
962
        # To make the API robust, we allow calling both next() and
963
        # next_with_ghosts() on the same searcher.
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
964
        graph = self.make_graph({
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
965
            'head':['present'],
966
            'present':['child', 'ghost'],
967
            'child':[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
968
            })
3177.3.3 by Robert Collins
Review feedback.
969
        # start with next_with_ghosts
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
970
        search = graph._make_breadth_first_searcher(['head'])
971
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
972
        self.assertEqual(set(['present']), search.next())
973
        self.assertEqual((set(['child']), set(['ghost'])),
974
            search.next_with_ghosts())
975
        self.assertRaises(StopIteration, search.next)
3177.3.3 by Robert Collins
Review feedback.
976
        # start with next
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
977
        search = graph._make_breadth_first_searcher(['head'])
978
        self.assertEqual(set(['head']), search.next())
979
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
980
        self.assertEqual(set(['child', 'ghost']),
981
            search.next())
982
        self.assertRaises(StopIteration, search.next_with_ghosts)
983
3177.3.2 by Robert Collins
Update graph searchers stop_searching_any and start_searching for next_with_ghosts.
984
    def test_breadth_first_change_search(self):
3177.3.3 by Robert Collins
Review feedback.
985
        # 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.
986
        graph = self.make_graph({
3177.3.2 by Robert Collins
Update graph searchers stop_searching_any and start_searching for next_with_ghosts.
987
            'head':['present'],
988
            'present':['stopped'],
989
            'other':['other_2'],
990
            'other_2':[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
991
            })
3177.3.2 by Robert Collins
Update graph searchers stop_searching_any and start_searching for next_with_ghosts.
992
        search = graph._make_breadth_first_searcher(['head'])
993
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
994
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
995
        self.assertEqual(set(['present']),
996
            search.stop_searching_any(['present']))
997
        self.assertEqual((set(['other']), set(['other_ghost'])),
998
            search.start_searching(['other', 'other_ghost']))
999
        self.assertEqual((set(['other_2']), set()), search.next_with_ghosts())
1000
        self.assertRaises(StopIteration, search.next_with_ghosts)
1001
        # next includes them
1002
        search = graph._make_breadth_first_searcher(['head'])
1003
        self.assertEqual(set(['head']), search.next())
1004
        self.assertEqual(set(['present']), search.next())
1005
        self.assertEqual(set(['present']),
1006
            search.stop_searching_any(['present']))
1007
        search.start_searching(['other', 'other_ghost'])
1008
        self.assertEqual(set(['other_2']), search.next())
1009
        self.assertRaises(StopIteration, search.next)
1010
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1011
    def assertSeenAndResult(self, instructions, search, next):
1012
        """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.
1013
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1014
        :param instructions: A list of tuples:
1015
            (seen, recipe, included_keys, starts, stops).
1016
            seen, recipe and included_keys are results to check on the search
1017
            and the searches get_result(). starts and stops are parameters to
1018
            pass to start_searching and stop_searching_any during each
1019
            iteration, if they are not None.
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1020
        :param search: The search to use.
1021
        :param next: A callable to advance the search.
1022
        """
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1023
        for seen, recipe, included_keys, starts, stops in instructions:
4152.1.2 by Robert Collins
Add streaming from a stacked branch when the sort order is compatible with doing so.
1024
            # Adjust for recipe contract changes that don't vary for all the
1025
            # current tests.
1026
            recipe = ('search',) + recipe
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1027
            next()
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
1028
            if starts is not None:
1029
                search.start_searching(starts)
1030
            if stops is not None:
1031
                search.stop_searching_any(stops)
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1032
            result = search.get_result()
1033
            self.assertEqual(recipe, result.get_recipe())
1034
            self.assertEqual(set(included_keys), result.get_keys())
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1035
            self.assertEqual(seen, search.seen)
1036
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1037
    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.
1038
        graph = self.make_graph({
1039
            'head':['child'],
1040
            'child':[NULL_REVISION],
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1041
            NULL_REVISION:[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1042
            })
1043
        search = graph._make_breadth_first_searcher(['head'])
1044
        # 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.
1045
        result = search.get_result()
4152.1.2 by Robert Collins
Add streaming from a stacked branch when the sort order is compatible with doing so.
1046
        self.assertEqual(('search', set(['head']), set(['head']), 0),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1047
            result.get_recipe())
1048
        self.assertEqual(set(), result.get_keys())
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1049
        self.assertEqual(set(), search.seen)
1050
        # using next:
1051
        expected = [
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1052
            (set(['head']), (set(['head']), set(['child']), 1),
1053
             ['head'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1054
            (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.
1055
             ['head', 'child'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1056
            (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.
1057
             ['head', 'child', NULL_REVISION], None, None),
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1058
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1059
        self.assertSeenAndResult(expected, search, search.next)
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1060
        # using next_with_ghosts:
1061
        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.
1062
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1063
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1064
    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.
1065
        graph = self.make_graph({
1066
            'head':['child'],
1067
            'child':[NULL_REVISION],
1068
            'otherhead':['otherchild'],
1069
            'otherchild':['excluded'],
1070
            'excluded':[NULL_REVISION],
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1071
            NULL_REVISION:[]
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
1072
            })
1073
        search = graph._make_breadth_first_searcher([])
1074
        # Starting with nothing and adding a search works:
1075
        search.start_searching(['head'])
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1076
        # head has been seen:
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1077
        result = search.get_result()
4152.1.2 by Robert Collins
Add streaming from a stacked branch when the sort order is compatible with doing so.
1078
        self.assertEqual(('search', set(['head']), set(['child']), 1),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1079
            result.get_recipe())
1080
        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.
1081
        self.assertEqual(set(['head']), search.seen)
1082
        # using next:
1083
        expected = [
1084
            # stop at child, and start a new search at otherhead:
1085
            # - otherhead counts as seen immediately when start_searching is
1086
            # called.
1087
            (set(['head', 'child', 'otherhead']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1088
             (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.
1089
             ['head', 'otherhead'], ['otherhead'], ['child']),
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
1090
            (set(['head', 'child', 'otherhead', 'otherchild']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1091
             (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.
1092
             ['head', 'otherhead', 'otherchild'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1093
            # stop searching excluded now
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
1094
            (set(['head', 'child', 'otherhead', 'otherchild', 'excluded']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1095
             (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.
1096
             ['head', 'otherhead', 'otherchild'], None, ['excluded']),
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
1097
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1098
        self.assertSeenAndResult(expected, search, search.next)
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
1099
        # using next_with_ghosts:
1100
        search = graph._make_breadth_first_searcher([])
1101
        search.start_searching(['head'])
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1102
        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.
1103
3184.2.1 by Robert Collins
Handle stopping ghosts in searches properly.
1104
    def test_breadth_first_stop_searching_not_queried(self):
1105
        # A client should be able to say 'stop node X' even if X has not been
1106
        # returned to the client.
1107
        graph = self.make_graph({
1108
            'head':['child', 'ghost1'],
1109
            'child':[NULL_REVISION],
1110
            NULL_REVISION:[],
1111
            })
1112
        search = graph._make_breadth_first_searcher(['head'])
1113
        expected = [
1114
            # NULL_REVISION and ghost1 have not been returned
4053.2.2 by Andrew Bennetts
Better fix, with test.
1115
            (set(['head']),
1116
             (set(['head']), set(['child', NULL_REVISION, 'ghost1']), 1),
3184.2.1 by Robert Collins
Handle stopping ghosts in searches properly.
1117
             ['head'], None, [NULL_REVISION, 'ghost1']),
1118
            # ghost1 has been returned, NULL_REVISION is to be returned in the
1119
            # next iteration.
1120
            (set(['head', 'child', 'ghost1']),
1121
             (set(['head']), set(['ghost1', NULL_REVISION]), 2),
1122
             ['head', 'child'], None, [NULL_REVISION, 'ghost1']),
1123
            ]
1124
        self.assertSeenAndResult(expected, search, search.next)
1125
        # using next_with_ghosts:
1126
        search = graph._make_breadth_first_searcher(['head'])
1127
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
1128
3808.1.1 by Andrew Bennetts
Possible fix for bug in new _walk_to_common_revisions.
1129
    def test_breadth_first_stop_searching_late(self):
1130
        # A client should be able to say 'stop node X' and have it excluded
1131
        # from the result even if X was seen in an older iteration of the
1132
        # search.
1133
        graph = self.make_graph({
1134
            'head':['middle'],
1135
            'middle':['child'],
1136
            'child':[NULL_REVISION],
1137
            NULL_REVISION:[],
1138
            })
1139
        search = graph._make_breadth_first_searcher(['head'])
1140
        expected = [
1141
            (set(['head']), (set(['head']), set(['middle']), 1),
1142
             ['head'], None, None),
1143
            (set(['head', 'middle']), (set(['head']), set(['child']), 2),
1144
             ['head', 'middle'], None, None),
1145
            # 'middle' came from the previous iteration, but we don't stop
1146
            # searching it until *after* advancing the searcher.
1147
            (set(['head', 'middle', 'child']),
3808.1.4 by John Arbash Meinel
make _walk_to_common responsible for stopping ancestors
1148
             (set(['head']), set(['middle', 'child']), 1),
3808.1.3 by Andrew Bennetts
Add another test, this one currently failing. Perhaps the current behaviour should be considered ok, rather than a failure?
1149
             ['head'], None, ['middle', 'child']),
1150
            ]
1151
        self.assertSeenAndResult(expected, search, search.next)
1152
        # using next_with_ghosts:
1153
        search = graph._make_breadth_first_searcher(['head'])
1154
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
1155
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1156
    def test_breadth_first_get_result_ghosts_are_excluded(self):
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1157
        graph = self.make_graph({
1158
            'head':['child', 'ghost'],
1159
            'child':[NULL_REVISION],
1160
            NULL_REVISION:[],
1161
            })
1162
        search = graph._make_breadth_first_searcher(['head'])
1163
        # using next:
1164
        expected = [
1165
            (set(['head']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1166
             (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.
1167
             ['head'], None, None),
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1168
            (set(['head', 'child', 'ghost']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1169
             (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.
1170
             ['head', 'child'], None, None),
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1171
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1172
        self.assertSeenAndResult(expected, search, search.next)
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1173
        # using next_with_ghosts:
1174
        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.
1175
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1176
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1177
    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.
1178
        graph = self.make_graph({
1179
            'head':['child'],
1180
            'child':[NULL_REVISION],
1181
            NULL_REVISION:[],
1182
            })
1183
        search = graph._make_breadth_first_searcher(['head'])
1184
        # using next:
1185
        expected = [
1186
            (set(['head', 'ghost']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1187
             (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.
1188
             ['head'], ['ghost'], None),
3184.1.4 by Robert Collins
Correctly exclude ghosts when ghosts are started on an existing search.
1189
            (set(['head', 'child', 'ghost']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1190
             (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.
1191
             ['head', 'child'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1192
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1193
        self.assertSeenAndResult(expected, search, search.next)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1194
        # using next_with_ghosts:
1195
        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.
1196
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1197
1198
    def test_breadth_first_revision_count_includes_NULL_REVISION(self):
1199
        graph = self.make_graph({
1200
            'head':[NULL_REVISION],
1201
            NULL_REVISION:[],
1202
            })
1203
        search = graph._make_breadth_first_searcher(['head'])
1204
        # using next:
1205
        expected = [
1206
            (set(['head']),
1207
             (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.
1208
             ['head'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1209
            (set(['head', NULL_REVISION]),
1210
             (set(['head']), set([]), 2),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1211
             ['head', NULL_REVISION], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1212
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1213
        self.assertSeenAndResult(expected, search, search.next)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1214
        # using next_with_ghosts:
1215
        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.
1216
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1217
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1218
    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.
1219
        # StopIteration should not invalid anything..
1220
        graph = self.make_graph({
1221
            'head':[NULL_REVISION],
1222
            NULL_REVISION:[],
1223
            })
1224
        search = graph._make_breadth_first_searcher(['head'])
1225
        # using next:
1226
        expected = [
1227
            (set(['head']),
1228
             (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.
1229
             ['head'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1230
            (set(['head', 'ghost', NULL_REVISION]),
1231
             (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.
1232
             ['head', NULL_REVISION], ['ghost'], None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1233
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1234
        self.assertSeenAndResult(expected, search, search.next)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1235
        self.assertRaises(StopIteration, search.next)
1236
        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.
1237
        result = search.get_result()
4152.1.2 by Robert Collins
Add streaming from a stacked branch when the sort order is compatible with doing so.
1238
        self.assertEqual(('search', 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.
1239
            result.get_recipe())
1240
        self.assertEqual(set(['head', NULL_REVISION]), result.get_keys())
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1241
        # using next_with_ghosts:
1242
        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.
1243
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1244
        self.assertRaises(StopIteration, search.next)
1245
        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.
1246
        result = search.get_result()
4152.1.2 by Robert Collins
Add streaming from a stacked branch when the sort order is compatible with doing so.
1247
        self.assertEqual(('search', 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.
1248
            result.get_recipe())
1249
        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.
1250
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1251
3445.1.2 by John Arbash Meinel
Handle when a known revision is an ancestor.
1252
class TestFindUniqueAncestors(TestGraphBase):
3377.3.24 by John Arbash Meinel
For some reason find_unique_ancestors is much slower than it should be.
1253
3377.3.21 by John Arbash Meinel
Simple brute-force implementation of find_unique_ancestors
1254
    def assertFindUniqueAncestors(self, graph, expected, node, common):
1255
        actual = graph.find_unique_ancestors(node, common)
1256
        self.assertEqual(expected, sorted(actual))
1257
1258
    def test_empty_set(self):
1259
        graph = self.make_graph(ancestry_1)
1260
        self.assertFindUniqueAncestors(graph, [], 'rev1', ['rev1'])
1261
        self.assertFindUniqueAncestors(graph, [], 'rev2b', ['rev2b'])
1262
        self.assertFindUniqueAncestors(graph, [], 'rev3', ['rev1', 'rev3'])
1263
1264
    def test_single_node(self):
1265
        graph = self.make_graph(ancestry_1)
1266
        self.assertFindUniqueAncestors(graph, ['rev2a'], 'rev2a', ['rev1'])
1267
        self.assertFindUniqueAncestors(graph, ['rev2b'], 'rev2b', ['rev1'])
1268
        self.assertFindUniqueAncestors(graph, ['rev3'], 'rev3', ['rev2a'])
1269
3377.3.24 by John Arbash Meinel
For some reason find_unique_ancestors is much slower than it should be.
1270
    def test_minimal_ancestry(self):
1271
        graph = self.make_breaking_graph(extended_history_shortcut,
1272
                                         [NULL_REVISION, 'a', 'b'])
1273
        self.assertFindUniqueAncestors(graph, ['e'], 'e', ['d'])
1274
3377.3.25 by John Arbash Meinel
A few more minimal ancestry checks
1275
        graph = self.make_breaking_graph(extended_history_shortcut,
1276
                                         ['b'])
1277
        self.assertFindUniqueAncestors(graph, ['f'], 'f', ['a', 'd'])
1278
1279
        graph = self.make_breaking_graph(complex_shortcut,
3377.3.27 by John Arbash Meinel
some simple updates
1280
                                         ['a', 'b'])
3377.3.25 by John Arbash Meinel
A few more minimal ancestry checks
1281
        self.assertFindUniqueAncestors(graph, ['h'], 'h', ['i'])
1282
        self.assertFindUniqueAncestors(graph, ['e', 'g', 'i'], 'i', ['h'])
3377.3.27 by John Arbash Meinel
some simple updates
1283
        self.assertFindUniqueAncestors(graph, ['h'], 'h', ['g'])
3377.3.26 by John Arbash Meinel
Found a graph leak.
1284
        self.assertFindUniqueAncestors(graph, ['h'], 'h', ['j'])
1285
3377.3.21 by John Arbash Meinel
Simple brute-force implementation of find_unique_ancestors
1286
    def test_in_ancestry(self):
1287
        graph = self.make_graph(ancestry_1)
1288
        self.assertFindUniqueAncestors(graph, [], 'rev1', ['rev3'])
1289
        self.assertFindUniqueAncestors(graph, [], 'rev2b', ['rev4'])
1290
1291
    def test_multiple_revisions(self):
1292
        graph = self.make_graph(ancestry_1)
1293
        self.assertFindUniqueAncestors(graph,
1294
            ['rev4'], 'rev4', ['rev3', 'rev2b'])
1295
        self.assertFindUniqueAncestors(graph,
1296
            ['rev2a', 'rev3', 'rev4'], 'rev4', ['rev2b'])
1297
3377.3.22 by John Arbash Meinel
include some tests using the complex graphs
1298
    def test_complex_shortcut(self):
1299
        graph = self.make_graph(complex_shortcut)
1300
        self.assertFindUniqueAncestors(graph,
1301
            ['h', 'n'], 'n', ['m'])
1302
        self.assertFindUniqueAncestors(graph,
1303
            ['e', 'i', 'm'], 'm', ['n'])
1304
1305
    def test_complex_shortcut2(self):
1306
        graph = self.make_graph(complex_shortcut2)
1307
        self.assertFindUniqueAncestors(graph,
1308
            ['j', 'u'], 'u', ['t'])
1309
        self.assertFindUniqueAncestors(graph,
1310
            ['t'], 't', ['u'])
1311
3377.3.35 by John Arbash Meinel
Add a test that exercises the multiple interesting unique code
1312
    def test_multiple_interesting_unique(self):
1313
        graph = self.make_graph(multiple_interesting_unique)
1314
        self.assertFindUniqueAncestors(graph,
1315
            ['j', 'y'], 'y', ['z'])
1316
        self.assertFindUniqueAncestors(graph,
1317
            ['p', 'z'], 'z', ['y'])
1318
3377.3.36 by John Arbash Meinel
Small updates, try to write a test for the race condition.
1319
    def test_racing_shortcuts(self):
1320
        graph = self.make_graph(racing_shortcuts)
1321
        self.assertFindUniqueAncestors(graph,
1322
            ['p', 'q', 'z'], 'z', ['y'])
1323
        self.assertFindUniqueAncestors(graph,
1324
            ['h', 'i', 'j', 'y'], 'j', ['z'])
1325
3377.3.21 by John Arbash Meinel
Simple brute-force implementation of find_unique_ancestors
1326
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1327
class TestGraphFindDistanceToNull(TestGraphBase):
3445.1.1 by John Arbash Meinel
Start working on a new Graph api to make finding revision numbers faster.
1328
    """Test an api that should be able to compute a revno"""
1329
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1330
    def assertFindDistance(self, revno, graph, target_id, known_ids):
1331
        """Assert the output of Graph.find_distance_to_null()"""
1332
        actual = graph.find_distance_to_null(target_id, known_ids)
3445.1.1 by John Arbash Meinel
Start working on a new Graph api to make finding revision numbers faster.
1333
        self.assertEqual(revno, actual)
1334
1335
    def test_nothing_known(self):
1336
        graph = self.make_graph(ancestry_1)
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1337
        self.assertFindDistance(0, graph, NULL_REVISION, [])
1338
        self.assertFindDistance(1, graph, 'rev1', [])
1339
        self.assertFindDistance(2, graph, 'rev2a', [])
1340
        self.assertFindDistance(2, graph, 'rev2b', [])
1341
        self.assertFindDistance(3, graph, 'rev3', [])
1342
        self.assertFindDistance(4, graph, 'rev4', [])
3445.1.1 by John Arbash Meinel
Start working on a new Graph api to make finding revision numbers faster.
1343
1344
    def test_rev_is_ghost(self):
1345
        graph = self.make_graph(ancestry_1)
1346
        e = self.assertRaises(errors.GhostRevisionsHaveNoRevno,
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1347
                              graph.find_distance_to_null, 'rev_missing', [])
3445.1.1 by John Arbash Meinel
Start working on a new Graph api to make finding revision numbers faster.
1348
        self.assertEqual('rev_missing', e.revision_id)
1349
        self.assertEqual('rev_missing', e.ghost_revision_id)
1350
1351
    def test_ancestor_is_ghost(self):
1352
        graph = self.make_graph({'rev':['parent']})
1353
        e = self.assertRaises(errors.GhostRevisionsHaveNoRevno,
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1354
                              graph.find_distance_to_null, 'rev', [])
3445.1.1 by John Arbash Meinel
Start working on a new Graph api to make finding revision numbers faster.
1355
        self.assertEqual('rev', e.revision_id)
1356
        self.assertEqual('parent', e.ghost_revision_id)
1357
3445.1.2 by John Arbash Meinel
Handle when a known revision is an ancestor.
1358
    def test_known_in_ancestry(self):
1359
        graph = self.make_graph(ancestry_1)
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1360
        self.assertFindDistance(2, graph, 'rev2a', [('rev1', 1)])
1361
        self.assertFindDistance(3, graph, 'rev3', [('rev2a', 2)])
3445.1.2 by John Arbash Meinel
Handle when a known revision is an ancestor.
1362
1363
    def test_known_in_ancestry_limits(self):
3445.1.3 by John Arbash Meinel
Search from all of the known revisions.
1364
        graph = self.make_breaking_graph(ancestry_1, ['rev1'])
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1365
        self.assertFindDistance(4, graph, 'rev4', [('rev3', 3)])
3445.1.2 by John Arbash Meinel
Handle when a known revision is an ancestor.
1366
3445.1.3 by John Arbash Meinel
Search from all of the known revisions.
1367
    def test_target_is_ancestor(self):
1368
        graph = self.make_graph(ancestry_1)
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1369
        self.assertFindDistance(2, graph, 'rev2a', [('rev3', 3)])
3445.1.3 by John Arbash Meinel
Search from all of the known revisions.
1370
1371
    def test_target_is_ancestor_limits(self):
1372
        """We shouldn't search all history if we run into ourselves"""
1373
        graph = self.make_breaking_graph(ancestry_1, ['rev1'])
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1374
        self.assertFindDistance(3, graph, 'rev3', [('rev4', 4)])
3445.1.3 by John Arbash Meinel
Search from all of the known revisions.
1375
1376
    def test_target_parallel_to_known_limits(self):
3445.1.8 by John Arbash Meinel
Clarity tweaks recommended by Ian
1377
        # Even though the known revision isn't part of the other ancestry, they
3445.1.3 by John Arbash Meinel
Search from all of the known revisions.
1378
        # eventually converge
1379
        graph = self.make_breaking_graph(with_tail, ['a'])
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1380
        self.assertFindDistance(6, graph, 'f', [('g', 6)])
1381
        self.assertFindDistance(7, graph, 'h', [('g', 6)])
1382
        self.assertFindDistance(8, graph, 'i', [('g', 6)])
1383
        self.assertFindDistance(6, graph, 'g', [('i', 8)])
3445.1.3 by John Arbash Meinel
Search from all of the known revisions.
1384
3445.1.1 by John Arbash Meinel
Start working on a new Graph api to make finding revision numbers faster.
1385
3514.2.8 by John Arbash Meinel
The insertion ordering into the weave has an impact on conflicts.
1386
class TestFindMergeOrder(TestGraphBase):
1387
1388
    def assertMergeOrder(self, expected, graph, tip, base_revisions):
1389
        self.assertEqual(expected, graph.find_merge_order(tip, base_revisions))
1390
1391
    def test_parents(self):
1392
        graph = self.make_graph(ancestry_1)
1393
        self.assertMergeOrder(['rev3', 'rev2b'], graph, 'rev4',
1394
                                                        ['rev3', 'rev2b'])
1395
        self.assertMergeOrder(['rev3', 'rev2b'], graph, 'rev4',
1396
                                                        ['rev2b', 'rev3'])
1397
1398
    def test_ancestors(self):
1399
        graph = self.make_graph(ancestry_1)
1400
        self.assertMergeOrder(['rev1', 'rev2b'], graph, 'rev4',
1401
                                                        ['rev1', 'rev2b'])
1402
        self.assertMergeOrder(['rev1', 'rev2b'], graph, 'rev4',
1403
                                                        ['rev2b', 'rev1'])
1404
1405
    def test_shortcut_one_ancestor(self):
1406
        # When we have enough info, we can stop searching
1407
        graph = self.make_breaking_graph(ancestry_1, ['rev3', 'rev2b', 'rev4'])
1408
        # Single ancestors shortcut right away
1409
        self.assertMergeOrder(['rev3'], graph, 'rev4', ['rev3'])
1410
1411
    def test_shortcut_after_one_ancestor(self):
1412
        graph = self.make_breaking_graph(ancestry_1, ['rev2a', 'rev2b'])
1413
        self.assertMergeOrder(['rev3', 'rev1'], graph, 'rev4', ['rev1', 'rev3'])
1414
1415
5365.6.1 by Aaron Bentley
Implement find_descendants.
1416
class TestFindDescendants(TestGraphBase):
1417
1418
    def test_find_descendants_rev1_rev3(self):
1419
        graph = self.make_graph(ancestry_1)
1420
        descendants = graph.find_descendants('rev1', 'rev3')
1421
        self.assertEqual(set(['rev1', 'rev2a', 'rev3']), descendants)
1422
1423
    def test_find_descendants_rev1_rev4(self):
1424
        graph = self.make_graph(ancestry_1)
1425
        descendants = graph.find_descendants('rev1', 'rev4')
1426
        self.assertEqual(set(['rev1', 'rev2a', 'rev2b', 'rev3', 'rev4']),
1427
                         descendants)
1428
1429
    def test_find_descendants_rev2a_rev4(self):
1430
        graph = self.make_graph(ancestry_1)
1431
        descendants = graph.find_descendants('rev2a', 'rev4')
1432
        self.assertEqual(set(['rev2a', 'rev3', 'rev4']), descendants)
1433
5365.6.3 by Aaron Bentley
Implement find_lefthand_merger.
1434
class TestFindLefthandMerger(TestGraphBase):
1435
1436
    def check_merger(self, result, ancestry, merged, tip):
1437
        graph = self.make_graph(ancestry)
1438
        self.assertEqual(result, graph.find_lefthand_merger(merged, tip))
1439
1440
    def test_find_lefthand_merger_rev2b(self):
1441
        self.check_merger('rev4', ancestry_1, 'rev2b', 'rev4')
1442
1443
    def test_find_lefthand_merger_rev2a(self):
1444
        self.check_merger('rev2a', ancestry_1, 'rev2a', 'rev4')
1445
1446
    def test_find_lefthand_merger_rev4(self):
1447
        self.check_merger(None, ancestry_1, 'rev4', 'rev2a')
1448
1449
    def test_find_lefthand_merger_f(self):
1450
        self.check_merger('i', complex_shortcut, 'f', 'm')
1451
1452
    def test_find_lefthand_merger_g(self):
1453
        self.check_merger('i', complex_shortcut, 'g', 'm')
1454
1455
    def test_find_lefthand_merger_h(self):
1456
        self.check_merger('n', complex_shortcut, 'h', 'n')
1457
5365.6.1 by Aaron Bentley
Implement find_descendants.
1458
1459
class TestGetChildMap(TestGraphBase):
1460
1461
    def test_get_child_map(self):
1462
        graph = self.make_graph(ancestry_1)
1463
        child_map = graph.get_child_map(['rev4', 'rev3', 'rev2a', 'rev2b'])
1464
        self.assertEqual({'rev1': ['rev2a', 'rev2b'],
1465
                          'rev2a': ['rev3'],
1466
                          'rev2b': ['rev4'],
1467
                          'rev3': ['rev4']},
1468
                          child_map)
1469
1470
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1471
class TestCachingParentsProvider(tests.TestCase):
4190.1.1 by Robert Collins
Negatively cache misses during read-locks in RemoteRepository.
1472
    """These tests run with:
1473
1474
    self.inst_pp, a recording parents provider with a graph of a->b, and b is a
1475
    ghost.
1476
    self.caching_pp, a CachingParentsProvider layered on inst_pp.
1477
    """
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1478
1479
    def setUp(self):
1480
        super(TestCachingParentsProvider, self).setUp()
6015.24.3 by John Arbash Meinel
Implement get_cached_parent_map
1481
        dict_pp = _mod_graph.DictParentsProvider({'a': ('b',)})
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1482
        self.inst_pp = InstrumentedParentsProvider(dict_pp)
1483
        self.caching_pp = _mod_graph.CachingParentsProvider(self.inst_pp)
1484
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1485
    def test_get_parent_map(self):
1486
        """Requesting the same revision should be returned from cache"""
3835.1.16 by Aaron Bentley
Updates from review
1487
        self.assertEqual({}, self.caching_pp._cache)
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1488
        self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
1489
        self.assertEqual(['a'], self.inst_pp.calls)
1490
        self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
1491
        # No new call, as it should have been returned from the cache
1492
        self.assertEqual(['a'], self.inst_pp.calls)
3835.1.16 by Aaron Bentley
Updates from review
1493
        self.assertEqual({'a':('b',)}, self.caching_pp._cache)
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1494
1495
    def test_get_parent_map_not_present(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1496
        """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()
1497
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1498
        self.assertEqual(['b'], self.inst_pp.calls)
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1499
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1500
        # No new calls
1501
        self.assertEqual(['b'], self.inst_pp.calls)
1502
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1503
    def test_get_parent_map_mixed(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1504
        """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()
1505
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1506
        self.assertEqual(['b'], self.inst_pp.calls)
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1507
        self.assertEqual({'a':('b',)},
1508
                         self.caching_pp.get_parent_map(['a', 'b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1509
        self.assertEqual(['b', 'a'], self.inst_pp.calls)
1510
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1511
    def test_get_parent_map_repeated(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1512
        """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()
1513
        self.assertEqual({'a':('b',)},
1514
                         self.caching_pp.get_parent_map(['b', 'a', 'b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1515
        # Use sorted because we don't care about the order, just that each is
1516
        # only present 1 time.
1517
        self.assertEqual(['a', 'b'], sorted(self.inst_pp.calls))
3514.2.14 by John Arbash Meinel
Bring in the code to collapse linear portions of the graph.
1518
4190.1.4 by Robert Collins
Cache ghosts when we can get them from a RemoteRepository in get_parent_map.
1519
    def test_note_missing_key(self):
1520
        """After noting that a key is missing it is cached."""
1521
        self.caching_pp.note_missing_key('b')
1522
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
1523
        self.assertEqual([], self.inst_pp.calls)
1524
        self.assertEqual(set(['b']), self.caching_pp.missing_keys)
1525
6015.24.3 by John Arbash Meinel
Implement get_cached_parent_map
1526
    def test_get_cached_parent_map(self):
1527
        self.assertEqual({}, self.caching_pp.get_cached_parent_map(['a']))
1528
        self.assertEqual([], self.inst_pp.calls)
1529
        self.assertEqual({'a': ('b',)}, self.caching_pp.get_parent_map(['a']))
1530
        self.assertEqual(['a'], self.inst_pp.calls)
1531
        self.assertEqual({'a': ('b',)},
1532
                         self.caching_pp.get_cached_parent_map(['a']))
1533
3514.2.14 by John Arbash Meinel
Bring in the code to collapse linear portions of the graph.
1534
3835.1.12 by Aaron Bentley
Unify CachingExtraParentsProvider and CachingParentsProvider.
1535
class TestCachingParentsProviderExtras(tests.TestCaseWithTransport):
3835.1.16 by Aaron Bentley
Updates from review
1536
    """Test the behaviour when parents are provided that were not requested."""
3835.1.10 by Aaron Bentley
Move CachingExtraParentsProvider to Graph
1537
1538
    def setUp(self):
3835.1.12 by Aaron Bentley
Unify CachingExtraParentsProvider and CachingParentsProvider.
1539
        super(TestCachingParentsProviderExtras, self).setUp()
3835.1.11 by Aaron Bentley
Rename FakeParentsProvider to ExtraParentsProvider
1540
        class ExtraParentsProvider(object):
3835.1.10 by Aaron Bentley
Move CachingExtraParentsProvider to Graph
1541
1542
            def get_parent_map(self, keys):
1543
                return {'rev1': [], 'rev2': ['rev1',]}
1544
3835.1.11 by Aaron Bentley
Rename FakeParentsProvider to ExtraParentsProvider
1545
        self.inst_pp = InstrumentedParentsProvider(ExtraParentsProvider())
3835.1.12 by Aaron Bentley
Unify CachingExtraParentsProvider and CachingParentsProvider.
1546
        self.caching_pp = _mod_graph.CachingParentsProvider(
1547
            get_parent_map=self.inst_pp.get_parent_map)
3835.1.10 by Aaron Bentley
Move CachingExtraParentsProvider to Graph
1548
1549
    def test_uncached(self):
3835.1.12 by Aaron Bentley
Unify CachingExtraParentsProvider and CachingParentsProvider.
1550
        self.caching_pp.disable_cache()
3835.1.10 by Aaron Bentley
Move CachingExtraParentsProvider to Graph
1551
        self.assertEqual({'rev1': []},
1552
                         self.caching_pp.get_parent_map(['rev1']))
1553
        self.assertEqual(['rev1'], self.inst_pp.calls)
3835.1.16 by Aaron Bentley
Updates from review
1554
        self.assertIs(None, self.caching_pp._cache)
1555
1556
    def test_cache_initially_empty(self):
1557
        self.assertEqual({}, self.caching_pp._cache)
3835.1.10 by Aaron Bentley
Move CachingExtraParentsProvider to Graph
1558
1559
    def test_cached(self):
1560
        self.assertEqual({'rev1': []},
1561
                         self.caching_pp.get_parent_map(['rev1']))
1562
        self.assertEqual(['rev1'], self.inst_pp.calls)
1563
        self.assertEqual({'rev1': [], 'rev2': ['rev1']},
3835.1.16 by Aaron Bentley
Updates from review
1564
                         self.caching_pp._cache)
3835.1.10 by Aaron Bentley
Move CachingExtraParentsProvider to Graph
1565
        self.assertEqual({'rev1': []},
1566
                          self.caching_pp.get_parent_map(['rev1']))
1567
        self.assertEqual(['rev1'], self.inst_pp.calls)
3835.1.16 by Aaron Bentley
Updates from review
1568
1569
    def test_disable_cache_clears_cache(self):
1570
        # Put something in the cache
1571
        self.caching_pp.get_parent_map(['rev1'])
1572
        self.assertEqual(2, len(self.caching_pp._cache))
3835.1.10 by Aaron Bentley
Move CachingExtraParentsProvider to Graph
1573
        self.caching_pp.disable_cache()
3835.1.16 by Aaron Bentley
Updates from review
1574
        self.assertIs(None, self.caching_pp._cache)
3835.1.10 by Aaron Bentley
Move CachingExtraParentsProvider to Graph
1575
3835.1.19 by Aaron Bentley
Raise exception when caching is enabled twice.
1576
    def test_enable_cache_raises(self):
3835.1.20 by Aaron Bentley
Change custom error to an AssertionError.
1577
        e = self.assertRaises(AssertionError, self.caching_pp.enable_cache)
3835.1.19 by Aaron Bentley
Raise exception when caching is enabled twice.
1578
        self.assertEqual('Cache enabled when already enabled.', str(e))
1579
3835.1.15 by Aaron Bentley
Allow miss caching to be disabled.
1580
    def test_cache_misses(self):
1581
        self.caching_pp.get_parent_map(['rev3'])
1582
        self.caching_pp.get_parent_map(['rev3'])
1583
        self.assertEqual(['rev3'], self.inst_pp.calls)
1584
1585
    def test_no_cache_misses(self):
3835.1.19 by Aaron Bentley
Raise exception when caching is enabled twice.
1586
        self.caching_pp.disable_cache()
3835.1.15 by Aaron Bentley
Allow miss caching to be disabled.
1587
        self.caching_pp.enable_cache(cache_misses=False)
1588
        self.caching_pp.get_parent_map(['rev3'])
1589
        self.caching_pp.get_parent_map(['rev3'])
1590
        self.assertEqual(['rev3', 'rev3'], self.inst_pp.calls)
1591
3835.1.10 by Aaron Bentley
Move CachingExtraParentsProvider to Graph
1592
    def test_cache_extras(self):
1593
        self.assertEqual({}, self.caching_pp.get_parent_map(['rev3']))
1594
        self.assertEqual({'rev2': ['rev1']},
1595
                         self.caching_pp.get_parent_map(['rev2']))
1596
        self.assertEqual(['rev3'], self.inst_pp.calls)
1597
6015.24.3 by John Arbash Meinel
Implement get_cached_parent_map
1598
    def test_extras_using_cached(self):
1599
        self.assertEqual({}, self.caching_pp.get_cached_parent_map(['rev3']))
1600
        self.assertEqual({}, self.caching_pp.get_parent_map(['rev3']))
1601
        self.assertEqual({'rev2': ['rev1']},
1602
                         self.caching_pp.get_cached_parent_map(['rev2']))
1603
        self.assertEqual(['rev3'], self.inst_pp.calls)
1604
1605
3835.1.10 by Aaron Bentley
Move CachingExtraParentsProvider to Graph
1606
3514.2.14 by John Arbash Meinel
Bring in the code to collapse linear portions of the graph.
1607
class TestCollapseLinearRegions(tests.TestCase):
1608
1609
    def assertCollapsed(self, collapsed, original):
1610
        self.assertEqual(collapsed,
1611
                         _mod_graph.collapse_linear_regions(original))
1612
1613
    def test_collapse_nothing(self):
1614
        d = {1:[2, 3], 2:[], 3:[]}
1615
        self.assertCollapsed(d, d)
1616
        d = {1:[2], 2:[3, 4], 3:[5], 4:[5], 5:[]}
1617
        self.assertCollapsed(d, d)
1618
1619
    def test_collapse_chain(self):
1620
        # Any time we have a linear chain, we should be able to collapse
1621
        d = {1:[2], 2:[3], 3:[4], 4:[5], 5:[]}
1622
        self.assertCollapsed({1:[5], 5:[]}, d)
1623
        d = {5:[4], 4:[3], 3:[2], 2:[1], 1:[]}
1624
        self.assertCollapsed({5:[1], 1:[]}, d)
1625
        d = {5:[3], 3:[4], 4:[1], 1:[2], 2:[]}
1626
        self.assertCollapsed({5:[2], 2:[]}, d)
1627
1628
    def test_collapse_with_multiple_children(self):
1629
        #    7
1630
        #    |
1631
        #    6
1632
        #   / \
1633
        #  4   5
1634
        #  |   |
3514.2.16 by John Arbash Meinel
Review feedback from Ian.
1635
        #  2   3
3514.2.14 by John Arbash Meinel
Bring in the code to collapse linear portions of the graph.
1636
        #   \ /
1637
        #    1
1638
        #
1639
        # 4 and 5 cannot be removed because 6 has 2 children
3514.2.16 by John Arbash Meinel
Review feedback from Ian.
1640
        # 2 and 3 cannot be removed because 1 has 2 parents
3514.2.14 by John Arbash Meinel
Bring in the code to collapse linear portions of the graph.
1641
        d = {1:[2, 3], 2:[4], 4:[6], 3:[5], 5:[6], 6:[7], 7:[]}
1642
        self.assertCollapsed(d, d)
4070.9.14 by Andrew Bennetts
Tweaks requested by Robert's review.
1643
1644
4819.2.3 by John Arbash Meinel
Add a GraphThunkIdsToKeys as a tested class.
1645
class TestGraphThunkIdsToKeys(tests.TestCase):
1646
1647
    def test_heads(self):
1648
        # A
1649
        # |\
1650
        # B C
1651
        # |/
1652
        # D
1653
        d = {('D',): [('B',), ('C',)], ('C',):[('A',)],
1654
             ('B',): [('A',)], ('A',): []}
1655
        g = _mod_graph.Graph(_mod_graph.DictParentsProvider(d))
1656
        graph_thunk = _mod_graph.GraphThunkIdsToKeys(g)
1657
        self.assertEqual(['D'], sorted(graph_thunk.heads(['D', 'A'])))
1658
        self.assertEqual(['D'], sorted(graph_thunk.heads(['D', 'B'])))
1659
        self.assertEqual(['D'], sorted(graph_thunk.heads(['D', 'C'])))
1660
        self.assertEqual(['B', 'C'], sorted(graph_thunk.heads(['B', 'C'])))
1661
5559.3.1 by Jelmer Vernooij
Add GraphThunkIdsToKeys.add_node.
1662
    def test_add_node(self):
1663
        d = {('C',):[('A',)], ('B',): [('A',)], ('A',): []}
1664
        g = _mod_graph.KnownGraph(d)
1665
        graph_thunk = _mod_graph.GraphThunkIdsToKeys(g)
1666
        graph_thunk.add_node("D", ["A", "C"])
1667
        self.assertEqual(['B', 'D'],
1668
            sorted(graph_thunk.heads(['D', 'B', 'A'])))
1669
5988.1.1 by Jelmer Vernooij
Fix GraphThunkIdsToKeys.merge_sort
1670
    def test_merge_sort(self):
1671
        d = {('C',):[('A',)], ('B',): [('A',)], ('A',): []}
1672
        g = _mod_graph.KnownGraph(d)
1673
        graph_thunk = _mod_graph.GraphThunkIdsToKeys(g)
1674
        graph_thunk.add_node("D", ["A", "C"])
1675
        self.assertEqual([('C', 0, (2,), False), ('A', 0, (1,), True)],
1676
            [(n.key, n.merge_depth, n.revno, n.end_of_merge)
1677
                 for n in graph_thunk.merge_sort('C')])
1678
4819.2.3 by John Arbash Meinel
Add a GraphThunkIdsToKeys as a tested class.
1679
4152.1.2 by Robert Collins
Add streaming from a stacked branch when the sort order is compatible with doing so.
1680
class TestPendingAncestryResultGetKeys(TestCaseWithMemoryTransport):
4070.9.14 by Andrew Bennetts
Tweaks requested by Robert's review.
1681
    """Tests for bzrlib.graph.PendingAncestryResult."""
1682
1683
    def test_get_keys(self):
1684
        builder = self.make_branch_builder('b')
1685
        builder.start_series()
1686
        builder.build_snapshot('rev-1', None, [
1687
            ('add', ('', 'root-id', 'directory', ''))])
1688
        builder.build_snapshot('rev-2', ['rev-1'], [])
1689
        builder.finish_series()
1690
        repo = builder.get_branch().repository
1691
        repo.lock_read()
1692
        self.addCleanup(repo.unlock)
4152.1.2 by Robert Collins
Add streaming from a stacked branch when the sort order is compatible with doing so.
1693
        result = _mod_graph.PendingAncestryResult(['rev-2'], repo)
1694
        self.assertEqual(set(['rev-1', 'rev-2']), set(result.get_keys()))
4070.9.14 by Andrew Bennetts
Tweaks requested by Robert's review.
1695
4343.3.11 by John Arbash Meinel
Change PendingAncestryResult to strip ghosts from .get_keys()
1696
    def test_get_keys_excludes_ghosts(self):
1697
        builder = self.make_branch_builder('b')
1698
        builder.start_series()
1699
        builder.build_snapshot('rev-1', None, [
1700
            ('add', ('', 'root-id', 'directory', ''))])
1701
        builder.build_snapshot('rev-2', ['rev-1', 'ghost'], [])
1702
        builder.finish_series()
1703
        repo = builder.get_branch().repository
1704
        repo.lock_read()
1705
        self.addCleanup(repo.unlock)
1706
        result = _mod_graph.PendingAncestryResult(['rev-2'], repo)
1707
        self.assertEqual(sorted(['rev-1', 'rev-2']), sorted(result.get_keys()))
1708
4098.1.1 by Andrew Bennetts
Fix a bug with how PendingAncestryResult.get_keys handles NULL_REVISION.
1709
    def test_get_keys_excludes_null(self):
1710
        # Make a 'graph' with an iter_ancestry that returns NULL_REVISION
1711
        # somewhere other than the last element, which can happen in real
1712
        # ancestries.
1713
        class StubGraph(object):
1714
            def iter_ancestry(self, keys):
1715
                return [(NULL_REVISION, ()), ('foo', (NULL_REVISION,))]
4152.1.2 by Robert Collins
Add streaming from a stacked branch when the sort order is compatible with doing so.
1716
        result = _mod_graph.PendingAncestryResult(['rev-3'], None)
1717
        result_keys = result._get_keys(StubGraph())
4098.1.1 by Andrew Bennetts
Fix a bug with how PendingAncestryResult.get_keys handles NULL_REVISION.
1718
        # Only the non-null keys from the ancestry appear.
4152.1.2 by Robert Collins
Add streaming from a stacked branch when the sort order is compatible with doing so.
1719
        self.assertEqual(set(['foo']), set(result_keys))
1720
1721
1722
class TestPendingAncestryResultRefine(TestGraphBase):
1723
1724
    def test_refine(self):
1725
        # Used when pulling from a stacked repository, so test some revisions
1726
        # being satisfied from the stacking branch.
1727
        g = self.make_graph(
1728
            {"tip":["mid"], "mid":["base"], "tag":["base"],
1729
             "base":[NULL_REVISION], NULL_REVISION:[]})
1730
        result = _mod_graph.PendingAncestryResult(['tip', 'tag'], None)
1731
        result = result.refine(set(['tip']), set(['mid']))
1732
        self.assertEqual(set(['mid', 'tag']), result.heads)
1733
        result = result.refine(set(['mid', 'tag', 'base']),
1734
            set([NULL_REVISION]))
1735
        self.assertEqual(set([NULL_REVISION]), result.heads)
1736
        self.assertTrue(result.is_empty())
1737
1738
1739
class TestSearchResultRefine(TestGraphBase):
1740
1741
    def test_refine(self):
1742
        # Used when pulling from a stacked repository, so test some revisions
1743
        # being satisfied from the stacking branch.
1744
        g = self.make_graph(
1745
            {"tip":["mid"], "mid":["base"], "tag":["base"],
1746
             "base":[NULL_REVISION], NULL_REVISION:[]})
1747
        result = _mod_graph.SearchResult(set(['tip', 'tag']),
1748
            set([NULL_REVISION]), 4, set(['tip', 'mid', 'tag', 'base']))
1749
        result = result.refine(set(['tip']), set(['mid']))
1750
        recipe = result.get_recipe()
1751
        # We should be starting from tag (original head) and mid (seen ref)
1752
        self.assertEqual(set(['mid', 'tag']), recipe[1])
1753
        # We should be stopping at NULL (original stop) and tip (seen head)
1754
        self.assertEqual(set([NULL_REVISION, 'tip']), recipe[2])
1755
        self.assertEqual(3, recipe[3])
1756
        result = result.refine(set(['mid', 'tag', 'base']),
1757
            set([NULL_REVISION]))
1758
        recipe = result.get_recipe()
1759
        # We should be starting from nothing (NULL was known as a cut point)
1760
        self.assertEqual(set([]), recipe[1])
1761
        # We should be stopping at NULL (original stop) and tip (seen head) and
1762
        # tag (seen head) and mid(seen mid-point head). We could come back and
1763
        # define this as not including mid, for minimal results, but it is
1764
        # still 'correct' to include mid, and simpler/easier.
1765
        self.assertEqual(set([NULL_REVISION, 'tip', 'tag', 'mid']), recipe[2])
1766
        self.assertEqual(0, recipe[3])
1767
        self.assertTrue(result.is_empty())
6015.23.3 by John Arbash Meinel
Start refactoring code into graph.py code for easier testing.
1768
1769
1770
class TestSearchResultFromParentMap(TestGraphBase):
1771
1772
    def assertSearchResult(self, start_keys, stop_keys, key_count, parent_map,
1773
                           missing_keys=()):
1774
        (start, stop, count) = _mod_graph.search_result_from_parent_map(
1775
            parent_map, missing_keys)
1776
        self.assertEqual((sorted(start_keys), sorted(stop_keys), key_count),
1777
                         (sorted(start), sorted(stop), count))
1778
1779
    def test_no_parents(self):
1780
        self.assertSearchResult([], [], 0, {})
1781
        self.assertSearchResult([], [], 0, None)
1782
1783
    def test_ancestry_1(self):
1784
        self.assertSearchResult(['rev4'], [NULL_REVISION], len(ancestry_1),
1785
                                ancestry_1)
1786
1787
    def test_ancestry_2(self):
1788
        self.assertSearchResult(['rev1b', 'rev4a'], [NULL_REVISION],
1789
                                len(ancestry_2), ancestry_2)
1790
        self.assertSearchResult(['rev1b', 'rev4a'], [],
1791
                                len(ancestry_2)+1, ancestry_2,
1792
                                missing_keys=[NULL_REVISION])
1793
1794
    def test_partial_search(self):
1795
        parent_map = dict((k,extended_history_shortcut[k])
1796
                          for k in ['e', 'f'])
1797
        self.assertSearchResult(['e', 'f'], ['d', 'a'], 2,
1798
                                parent_map)
1799
        parent_map.update((k,extended_history_shortcut[k])
1800
                          for k in ['d', 'a'])
1801
        self.assertSearchResult(['e', 'f'], ['c', NULL_REVISION], 4,
1802
                                parent_map)
1803
        parent_map['c'] = extended_history_shortcut['c']
1804
        self.assertSearchResult(['e', 'f'], ['b'], 6,
1805
                                parent_map, missing_keys=[NULL_REVISION])
1806
        parent_map['b'] = extended_history_shortcut['b']
1807
        self.assertSearchResult(['e', 'f'], [], 7,
1808
                                parent_map, missing_keys=[NULL_REVISION])
6015.23.5 by John Arbash Meinel
Implement something that seems to work for limited search recipies.
1809
1810
1811
class TestLimitedSearchResultFromParentMap(TestGraphBase):
1812
1813
    def assertSearchResult(self, start_keys, stop_keys, key_count, parent_map,
1814
                           missing_keys, tip_keys, depth):
1815
        (start, stop, count) = _mod_graph.limited_search_result_from_parent_map(
1816
            parent_map, missing_keys, tip_keys, depth)
1817
        self.assertEqual((sorted(start_keys), sorted(stop_keys), key_count),
1818
                         (sorted(start), sorted(stop), count))
1819
6015.23.17 by John Arbash Meinel
Code was relying on an empty parent map to yield an empty search.
1820
    def test_empty_ancestry(self):
1821
        self.assertSearchResult([], [], 0, {}, (), ['tip-rev-id'], 10)
1822
6015.23.5 by John Arbash Meinel
Implement something that seems to work for limited search recipies.
1823
    def test_ancestry_1(self):
1824
        self.assertSearchResult(['rev4'], ['rev1'], 4,
1825
                                ancestry_1, (), ['rev1'], 10)
1826
        self.assertSearchResult(['rev2a', 'rev2b'], ['rev1'], 2,
1827
                                ancestry_1, (), ['rev1'], 1)
1828
6015.23.6 by John Arbash Meinel
Test multiple heads, and test when a head is inside the search of another head.
1829
1830
    def test_multiple_heads(self):
1831
        self.assertSearchResult(['e', 'f'], ['a'], 5,
1832
                                extended_history_shortcut, (), ['a'], 10)
1833
        # Note that even though we only take 1 step back, we find 'f', which
1834
        # means the described search will still find d and c.
6015.23.7 by John Arbash Meinel
Run the search a 3rd time if we encounter heads.
1835
        self.assertSearchResult(['f'], ['a'], 4,
6015.23.6 by John Arbash Meinel
Test multiple heads, and test when a head is inside the search of another head.
1836
                                extended_history_shortcut, (), ['a'], 1)
6015.23.7 by John Arbash Meinel
Run the search a 3rd time if we encounter heads.
1837
        self.assertSearchResult(['f'], ['a'], 4,
6015.23.6 by John Arbash Meinel
Test multiple heads, and test when a head is inside the search of another head.
1838
                                extended_history_shortcut, (), ['a'], 2)
6104.1.1 by John Arbash Meinel
Merge fix for bug #388269 into trunk, resolve conflicts and add release notes.
1839
1840
6015.24.3 by John Arbash Meinel
Implement get_cached_parent_map
1841
class TestStackedParentsProvider(tests.TestCase):
1842
1843
    def setUp(self):
1844
        super(TestStackedParentsProvider, self).setUp()
1845
        self.calls = []
1846
1847
    def get_shared_provider(self, info, ancestry, has_cached):
1848
        pp = _mod_graph.DictParentsProvider(ancestry)
1849
        if has_cached:
1850
            pp.get_cached_parent_map = pp.get_parent_map
1851
        return SharedInstrumentedParentsProvider(pp, self.calls, info)
1852
1853
    def test_stacked_parents_provider(self):
1854
        parents1 = _mod_graph.DictParentsProvider({'rev2': ['rev3']})
1855
        parents2 = _mod_graph.DictParentsProvider({'rev1': ['rev4']})
1856
        stacked = _mod_graph.StackedParentsProvider([parents1, parents2])
1857
        self.assertEqual({'rev1':['rev4'], 'rev2':['rev3']},
1858
                         stacked.get_parent_map(['rev1', 'rev2']))
1859
        self.assertEqual({'rev2':['rev3'], 'rev1':['rev4']},
1860
                         stacked.get_parent_map(['rev2', 'rev1']))
1861
        self.assertEqual({'rev2':['rev3']},
1862
                         stacked.get_parent_map(['rev2', 'rev2']))
1863
        self.assertEqual({'rev1':['rev4']},
1864
                         stacked.get_parent_map(['rev1', 'rev1']))
1865
1866
    def test_stacked_parents_provider_overlapping(self):
1867
        # rev2 is availible in both providers.
1868
        # 1
1869
        # |
1870
        # 2
1871
        parents1 = _mod_graph.DictParentsProvider({'rev2': ['rev1']})
1872
        parents2 = _mod_graph.DictParentsProvider({'rev2': ['rev1']})
1873
        stacked = _mod_graph.StackedParentsProvider([parents1, parents2])
1874
        self.assertEqual({'rev2': ['rev1']},
1875
                         stacked.get_parent_map(['rev2']))
1876
1877
    def test_handles_no_get_cached_parent_map(self):
1878
        # this shows that we both handle when a provider doesn't implement
1879
        # get_cached_parent_map
1880
        pp1 = self.get_shared_provider('pp1', {'rev2': ('rev1',)},
1881
                                       has_cached=False)
1882
        pp2 = self.get_shared_provider('pp2', {'rev2': ('rev1',)},
1883
                                       has_cached=True)
1884
        stacked = _mod_graph.StackedParentsProvider([pp1, pp2])
1885
        self.assertEqual({'rev2': ('rev1',)}, stacked.get_parent_map(['rev2']))
1886
        # No call on 'pp1' because it doesn't provide get_cached_parent_map
1887
        self.assertEqual([('pp2', 'cached', ['rev2'])], self.calls)
1888
1889
    def test_query_order(self):
1890
        # We should call get_cached_parent_map on all providers before we call
1891
        # get_parent_map. Further, we should track what entries we have found,
1892
        # and not re-try them.
1893
        pp1 = self.get_shared_provider('pp1', {'a': ()}, has_cached=True)
1894
        pp2 = self.get_shared_provider('pp2', {'c': ('b',)}, has_cached=False)
1895
        pp3 = self.get_shared_provider('pp3', {'b': ('a',)}, has_cached=True)
1896
        stacked = _mod_graph.StackedParentsProvider([pp1, pp2, pp3])
1897
        self.assertEqual({'a': (), 'b': ('a',), 'c': ('b',)},
1898
                         stacked.get_parent_map(['a', 'b', 'c', 'd']))
1899
        self.assertEqual([('pp1', 'cached', ['a', 'b', 'c', 'd']),
1900
                          # No call to pp2, because it doesn't have cached
1901
                          ('pp3', 'cached', ['b', 'c', 'd']),
1902
                          ('pp1', ['c', 'd']),
1903
                          ('pp2', ['c', 'd']),
1904
                          ('pp3', ['d']),
1905
                         ], self.calls)