/brz/remove-bazaar

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