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