~bzr-pqm/bzr/bzr.dev

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
3445.1.3 by John Arbash Meinel
Search from all of the known revisions.
399
# A graph that shows we can shortcut finding revnos when reaching them from the
400
# side.
401
#  NULL_REVISION
402
#       |
403
#       a
404
#       |
405
#       b
406
#       |
407
#       c
408
#       |
409
#       d
410
#       |
411
#       e
412
#      / \
413
#     f   g
414
#     |
415
#     h
416
#     |
417
#     i
418
419
with_tail = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'], 'e':['d'],
420
             'f':['e'], 'g':['e'], 'h':['f'], 'i':['h']}
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
421
422
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
423
class InstrumentedParentsProvider(object):
424
425
    def __init__(self, parents_provider):
426
        self.calls = []
427
        self._real_parents_provider = parents_provider
428
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
429
    def get_parent_map(self, nodes):
430
        self.calls.extend(nodes)
431
        return self._real_parents_provider.get_parent_map(nodes)
432
2490.2.25 by Aaron Bentley
Update from review
433
3445.1.2 by John Arbash Meinel
Handle when a known revision is an ancestor.
434
class TestGraphBase(tests.TestCase):
435
436
    def make_graph(self, ancestors):
437
        return _mod_graph.Graph(_mod_graph.DictParentsProvider(ancestors))
438
439
    def make_breaking_graph(self, ancestors, break_on):
440
        """Make a Graph that raises an exception if we hit a node."""
441
        g = self.make_graph(ancestors)
442
        orig_parent_map = g.get_parent_map
443
        def get_parent_map(keys):
444
            bad_keys = set(keys).intersection(break_on)
445
            if bad_keys:
446
                self.fail('key(s) %s was accessed' % (sorted(bad_keys),))
447
            return orig_parent_map(keys)
448
        g.get_parent_map = get_parent_map
449
        return g
450
451
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
452
class TestGraph(TestCaseWithMemoryTransport):
2490.2.1 by Aaron Bentley
Start work on GraphWalker
453
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
454
    def make_graph(self, ancestors):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
455
        return _mod_graph.Graph(_mod_graph.DictParentsProvider(ancestors))
2490.2.3 by Aaron Bentley
Implement new merge base picker
456
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
457
    def prepare_memory_tree(self, location):
458
        tree = self.make_branch_and_memory_tree(location)
2490.2.1 by Aaron Bentley
Start work on GraphWalker
459
        tree.lock_write()
460
        tree.add('.')
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
461
        return tree
462
463
    def build_ancestry(self, tree, ancestors):
2490.2.25 by Aaron Bentley
Update from review
464
        """Create an ancestry as specified by a graph dict
465
466
        :param tree: A tree to use
467
        :param ancestors: a dict of {node: [node_parent, ...]}
468
        """
2490.2.1 by Aaron Bentley
Start work on GraphWalker
469
        pending = [NULL_REVISION]
470
        descendants = {}
471
        for descendant, parents in ancestors.iteritems():
472
            for parent in parents:
473
                descendants.setdefault(parent, []).append(descendant)
474
        while len(pending) > 0:
475
            cur_node = pending.pop()
476
            for descendant in descendants.get(cur_node, []):
2490.2.3 by Aaron Bentley
Implement new merge base picker
477
                if tree.branch.repository.has_revision(descendant):
478
                    continue
2490.2.1 by Aaron Bentley
Start work on GraphWalker
479
                parents = [p for p in ancestors[descendant] if p is not
480
                           NULL_REVISION]
481
                if len([p for p in parents if not
482
                    tree.branch.repository.has_revision(p)]) > 0:
483
                    continue
484
                tree.set_parent_ids(parents)
2490.2.3 by Aaron Bentley
Implement new merge base picker
485
                if len(parents) > 0:
486
                    left_parent = parents[0]
487
                else:
488
                    left_parent = NULL_REVISION
2490.2.1 by Aaron Bentley
Start work on GraphWalker
489
                tree.branch.set_last_revision_info(
2490.2.3 by Aaron Bentley
Implement new merge base picker
490
                    len(tree.branch._lefthand_history(left_parent)),
491
                    left_parent)
2490.2.1 by Aaron Bentley
Start work on GraphWalker
492
                tree.commit(descendant, rev_id=descendant)
493
                pending.append(descendant)
2490.2.2 by Aaron Bentley
add minimal-common-ancestor calculation
494
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
495
    def test_lca(self):
2490.2.25 by Aaron Bentley
Update from review
496
        """Test finding least common ancestor.
2490.2.3 by Aaron Bentley
Implement new merge base picker
497
498
        ancestry_1 should always have a single common ancestor
499
        """
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
500
        graph = self.make_graph(ancestry_1)
2490.2.28 by Aaron Bentley
Fix handling of null revision
501
        self.assertRaises(errors.InvalidRevisionId, graph.find_lca, None)
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
502
        self.assertEqual(set([NULL_REVISION]),
503
                         graph.find_lca(NULL_REVISION, NULL_REVISION))
504
        self.assertEqual(set([NULL_REVISION]),
505
                         graph.find_lca(NULL_REVISION, 'rev1'))
506
        self.assertEqual(set(['rev1']), graph.find_lca('rev1', 'rev1'))
507
        self.assertEqual(set(['rev1']), graph.find_lca('rev2a', 'rev2b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
508
2520.4.104 by Aaron Bentley
Avoid infinite loop when there is no unique lca
509
    def test_no_unique_lca(self):
510
        """Test error when one revision is not in the graph"""
511
        graph = self.make_graph(ancestry_1)
512
        self.assertRaises(errors.NoCommonAncestor, graph.find_unique_lca,
513
                          'rev1', '1rev')
514
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
515
    def test_lca_criss_cross(self):
2490.2.25 by Aaron Bentley
Update from review
516
        """Test least-common-ancestor after a criss-cross merge."""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
517
        graph = self.make_graph(criss_cross)
2490.2.3 by Aaron Bentley
Implement new merge base picker
518
        self.assertEqual(set(['rev2a', 'rev2b']),
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
519
                         graph.find_lca('rev3a', 'rev3b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
520
        self.assertEqual(set(['rev2b']),
2490.2.25 by Aaron Bentley
Update from review
521
                         graph.find_lca('rev3a', 'rev3b', 'rev2b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
522
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
523
    def test_lca_shortcut(self):
2490.2.25 by Aaron Bentley
Update from review
524
        """Test least-common ancestor on this history shortcut"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
525
        graph = self.make_graph(history_shortcut)
526
        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
527
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
528
    def test_recursive_unique_lca(self):
2490.2.25 by Aaron Bentley
Update from review
529
        """Test finding a unique least common ancestor.
2490.2.3 by Aaron Bentley
Implement new merge base picker
530
531
        ancestry_1 should always have a single common ancestor
532
        """
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
533
        graph = self.make_graph(ancestry_1)
534
        self.assertEqual(NULL_REVISION,
535
                         graph.find_unique_lca(NULL_REVISION, NULL_REVISION))
536
        self.assertEqual(NULL_REVISION,
537
                         graph.find_unique_lca(NULL_REVISION, 'rev1'))
538
        self.assertEqual('rev1', graph.find_unique_lca('rev1', 'rev1'))
539
        self.assertEqual('rev1', graph.find_unique_lca('rev2a', 'rev2b'))
1551.19.10 by Aaron Bentley
Merge now warns when it encounters a criss-cross
540
        self.assertEqual(('rev1', 1,),
541
                         graph.find_unique_lca('rev2a', 'rev2b',
542
                         count_steps=True))
2490.2.3 by Aaron Bentley
Implement new merge base picker
543
3377.3.1 by John Arbash Meinel
Bring in some of the changes from graph_update and graph_optimization
544
    def assertRemoveDescendants(self, expected, graph, revisions):
545
        parents = graph.get_parent_map(revisions)
546
        self.assertEqual(expected,
547
                         graph._remove_simple_descendants(revisions, parents))
548
549
    def test__remove_simple_descendants(self):
550
        graph = self.make_graph(ancestry_1)
551
        self.assertRemoveDescendants(set(['rev1']), graph,
552
            set(['rev1', 'rev2a', 'rev2b', 'rev3', 'rev4']))
553
554
    def test__remove_simple_descendants_disjoint(self):
555
        graph = self.make_graph(ancestry_1)
556
        self.assertRemoveDescendants(set(['rev1', 'rev3']), graph,
557
            set(['rev1', 'rev3']))
558
559
    def test__remove_simple_descendants_chain(self):
560
        graph = self.make_graph(ancestry_1)
561
        self.assertRemoveDescendants(set(['rev1']), graph,
562
            set(['rev1', 'rev2a', 'rev3']))
563
564
    def test__remove_simple_descendants_siblings(self):
565
        graph = self.make_graph(ancestry_1)
566
        self.assertRemoveDescendants(set(['rev2a', 'rev2b']), graph,
567
            set(['rev2a', 'rev2b', 'rev3']))
568
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
569
    def test_unique_lca_criss_cross(self):
570
        """Ensure we don't pick non-unique lcas in a criss-cross"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
571
        graph = self.make_graph(criss_cross)
572
        self.assertEqual('rev1', graph.find_unique_lca('rev3a', 'rev3b'))
1551.19.10 by Aaron Bentley
Merge now warns when it encounters a criss-cross
573
        lca, steps = graph.find_unique_lca('rev3a', 'rev3b', count_steps=True)
574
        self.assertEqual('rev1', lca)
575
        self.assertEqual(2, steps)
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
576
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
577
    def test_unique_lca_null_revision(self):
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
578
        """Ensure we pick NULL_REVISION when necessary"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
579
        graph = self.make_graph(criss_cross2)
580
        self.assertEqual('rev1b', graph.find_unique_lca('rev2a', 'rev1b'))
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
581
        self.assertEqual(NULL_REVISION,
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
582
                         graph.find_unique_lca('rev2a', 'rev2b'))
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
583
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
584
    def test_unique_lca_null_revision2(self):
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
585
        """Ensure we pick NULL_REVISION when necessary"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
586
        graph = self.make_graph(ancestry_2)
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
587
        self.assertEqual(NULL_REVISION,
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
588
                         graph.find_unique_lca('rev4a', 'rev1b'))
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
589
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
590
    def test_lca_double_shortcut(self):
591
        graph = self.make_graph(double_shortcut)
592
        self.assertEqual('c', graph.find_unique_lca('f', 'g'))
593
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
594
    def test_common_ancestor_two_repos(self):
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
595
        """Ensure we do unique_lca using data from two repos"""
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
596
        mainline_tree = self.prepare_memory_tree('mainline')
597
        self.build_ancestry(mainline_tree, mainline)
3010.1.6 by Robert Collins
Locking in test_graph.
598
        self.addCleanup(mainline_tree.unlock)
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
599
600
        # This is cheating, because the revisions in the graph are actually
601
        # different revisions, despite having the same revision-id.
602
        feature_tree = self.prepare_memory_tree('feature')
603
        self.build_ancestry(feature_tree, feature_branch)
3010.1.6 by Robert Collins
Locking in test_graph.
604
        self.addCleanup(feature_tree.unlock)
605
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
606
        graph = mainline_tree.branch.repository.get_graph(
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
607
            feature_tree.branch.repository)
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
608
        self.assertEqual('rev2b', graph.find_unique_lca('rev2a', 'rev3b'))
2490.2.23 by Aaron Bentley
Adapt find_borders to produce a graph difference
609
610
    def test_graph_difference(self):
611
        graph = self.make_graph(ancestry_1)
612
        self.assertEqual((set(), set()), graph.find_difference('rev1', 'rev1'))
613
        self.assertEqual((set(), set(['rev1'])),
614
                         graph.find_difference(NULL_REVISION, 'rev1'))
615
        self.assertEqual((set(['rev1']), set()),
616
                         graph.find_difference('rev1', NULL_REVISION))
617
        self.assertEqual((set(['rev2a', 'rev3']), set(['rev2b'])),
618
                         graph.find_difference('rev3', 'rev2b'))
619
        self.assertEqual((set(['rev4', 'rev3', 'rev2a']), set()),
620
                         graph.find_difference('rev4', 'rev2b'))
621
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
622
    def test_graph_difference_separate_ancestry(self):
623
        graph = self.make_graph(ancestry_2)
624
        self.assertEqual((set(['rev1a']), set(['rev1b'])),
625
                         graph.find_difference('rev1a', 'rev1b'))
626
        self.assertEqual((set(['rev1a', 'rev2a', 'rev3a', 'rev4a']),
627
                          set(['rev1b'])),
628
                         graph.find_difference('rev4a', 'rev1b'))
629
2490.2.23 by Aaron Bentley
Adapt find_borders to produce a graph difference
630
    def test_graph_difference_criss_cross(self):
631
        graph = self.make_graph(criss_cross)
632
        self.assertEqual((set(['rev3a']), set(['rev3b'])),
633
                         graph.find_difference('rev3a', 'rev3b'))
634
        self.assertEqual((set([]), set(['rev3b', 'rev2b'])),
635
                         graph.find_difference('rev2a', 'rev3b'))
2490.2.25 by Aaron Bentley
Update from review
636
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
637
    def test_graph_difference_extended_history(self):
638
        graph = self.make_graph(extended_history_shortcut)
639
        self.assertEqual((set(['e']), set(['f'])),
640
                         graph.find_difference('e', 'f'))
641
        self.assertEqual((set(['f']), set(['e'])),
642
                         graph.find_difference('f', 'e'))
643
644
    def test_graph_difference_double_shortcut(self):
645
        graph = self.make_graph(double_shortcut)
646
        self.assertEqual((set(['d', 'f']), set(['e', 'g'])),
647
                         graph.find_difference('f', 'g'))
648
649
    def test_graph_difference_complex_shortcut(self):
650
        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
651
        self.assertEqual((set(['m', 'i', 'e']), set(['n', 'h'])),
652
                         graph.find_difference('m', 'n'))
653
654
    def test_graph_difference_complex_shortcut2(self):
655
        graph = self.make_graph(complex_shortcut2)
3377.3.13 by John Arbash Meinel
Change _search_for_extra_common slightly.
656
        self.assertEqual((set(['t']), set(['j', 'u'])),
657
                         graph.find_difference('t', 'u'))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
658
659
    def test_graph_difference_shortcut_extra_root(self):
660
        graph = self.make_graph(shortcut_extra_root)
661
        self.assertEqual((set(['e']), set(['f', 'g'])),
662
                         graph.find_difference('e', 'f'))
663
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
664
    def test_stacked_parents_provider(self):
665
        parents1 = _mod_graph.DictParentsProvider({'rev2': ['rev3']})
666
        parents2 = _mod_graph.DictParentsProvider({'rev1': ['rev4']})
667
        stacked = _mod_graph._StackedParentsProvider([parents1, parents2])
668
        self.assertEqual({'rev1':['rev4'], 'rev2':['rev3']},
669
                         stacked.get_parent_map(['rev1', 'rev2']))
670
        self.assertEqual({'rev2':['rev3'], 'rev1':['rev4']},
671
                         stacked.get_parent_map(['rev2', 'rev1']))
672
        self.assertEqual({'rev2':['rev3']},
673
                         stacked.get_parent_map(['rev2', 'rev2']))
674
        self.assertEqual({'rev1':['rev4']},
675
                         stacked.get_parent_map(['rev1', 'rev1']))
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
676
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
677
    def test_iter_topo_order(self):
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
678
        graph = self.make_graph(ancestry_1)
679
        args = ['rev2a', 'rev3', 'rev1']
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
680
        topo_args = list(graph.iter_topo_order(args))
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
681
        self.assertEqual(set(args), set(topo_args))
682
        self.assertTrue(topo_args.index('rev2a') > topo_args.index('rev1'))
683
        self.assertTrue(topo_args.index('rev2a') < topo_args.index('rev3'))
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
684
685
    def test_is_ancestor(self):
686
        graph = self.make_graph(ancestry_1)
2653.2.3 by Aaron Bentley
correctly handle Graph.is_ancestor(x, x)
687
        self.assertEqual(True, graph.is_ancestor('null:', 'null:'))
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
688
        self.assertEqual(True, graph.is_ancestor('null:', 'rev1'))
689
        self.assertEqual(False, graph.is_ancestor('rev1', 'null:'))
690
        self.assertEqual(True, graph.is_ancestor('null:', 'rev4'))
691
        self.assertEqual(False, graph.is_ancestor('rev4', 'null:'))
692
        self.assertEqual(False, graph.is_ancestor('rev4', 'rev2b'))
693
        self.assertEqual(True, graph.is_ancestor('rev2b', 'rev4'))
694
        self.assertEqual(False, graph.is_ancestor('rev2b', 'rev3'))
695
        self.assertEqual(False, graph.is_ancestor('rev3', 'rev2b'))
696
        instrumented_provider = InstrumentedParentsProvider(graph)
697
        instrumented_graph = _mod_graph.Graph(instrumented_provider)
698
        instrumented_graph.is_ancestor('rev2a', 'rev2b')
699
        self.assertTrue('null:' not in instrumented_provider.calls)
700
701
    def test_is_ancestor_boundary(self):
702
        """Ensure that we avoid searching the whole graph.
703
        
704
        This requires searching through b as a common ancestor, so we
705
        can identify that e is common.
706
        """
707
        graph = self.make_graph(boundary)
708
        instrumented_provider = InstrumentedParentsProvider(graph)
709
        graph = _mod_graph.Graph(instrumented_provider)
710
        self.assertFalse(graph.is_ancestor('a', 'c'))
711
        self.assertTrue('null:' not in instrumented_provider.calls)
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
712
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
713
    def test_iter_ancestry(self):
3228.4.10 by John Arbash Meinel
Respond to abentley's review comments.
714
        nodes = boundary.copy()
715
        nodes[NULL_REVISION] = ()
716
        graph = self.make_graph(nodes)
717
        expected = nodes.copy()
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
718
        expected.pop('a') # 'a' is not in the ancestry of 'c', all the
719
                          # other nodes are
3228.4.4 by John Arbash Meinel
Change iter_ancestry to take a group instead of a single node,
720
        self.assertEqual(expected, dict(graph.iter_ancestry(['c'])))
3228.4.10 by John Arbash Meinel
Respond to abentley's review comments.
721
        self.assertEqual(nodes, dict(graph.iter_ancestry(['a', 'c'])))
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
722
723
    def test_iter_ancestry_with_ghost(self):
724
        graph = self.make_graph(with_ghost)
725
        expected = with_ghost.copy()
726
        # '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.
727
        expected['g'] = None
3228.4.4 by John Arbash Meinel
Change iter_ancestry to take a group instead of a single node,
728
        self.assertEqual(expected, dict(graph.iter_ancestry(['a', 'c'])))
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
729
        expected.pop('a') 
3228.4.4 by John Arbash Meinel
Change iter_ancestry to take a group instead of a single node,
730
        self.assertEqual(expected, dict(graph.iter_ancestry(['c'])))
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
731
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
732
    def test_filter_candidate_lca(self):
733
        """Test filter_candidate_lca for a corner case
734
1551.15.83 by Aaron Bentley
Update test documentation
735
        This tests the case where we encounter the end of iteration for 'e'
736
        in the same pass as we discover that 'd' is an ancestor of 'e', and
737
        therefore 'e' can't be an lca.
738
739
        To compensate for different dict orderings on other Python
740
        implementations, we mirror 'd' and 'e' with 'b' and 'a'.
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
741
        """
742
        # This test is sensitive to the iteration order of dicts.  It will
1551.15.82 by Aaron Bentley
Add symmetrical alternative to test case
743
        # pass incorrectly if 'e' and 'a' sort before 'c'
1551.15.84 by Aaron Bentley
Add ancestry graph for test case
744
        #
745
        # NULL_REVISION
746
        #     / \
747
        #    a   e
748
        #    |   |
749
        #    b   d
750
        #     \ /
751
        #      c
1551.15.82 by Aaron Bentley
Add symmetrical alternative to test case
752
        graph = self.make_graph({'c': ['b', 'd'], 'd': ['e'], 'b': ['a'],
753
                                 'a': [NULL_REVISION], 'e': [NULL_REVISION]})
2776.1.4 by Robert Collins
Trivial review feedback changes.
754
        self.assertEqual(set(['c']), graph.heads(['a', 'c', 'e']))
755
756
    def test_heads_null(self):
757
        graph = self.make_graph(ancestry_1)
758
        self.assertEqual(set(['null:']), graph.heads(['null:']))
759
        self.assertEqual(set(['rev1']), graph.heads(['null:', 'rev1']))
760
        self.assertEqual(set(['rev1']), graph.heads(['rev1', 'null:']))
761
        self.assertEqual(set(['rev1']), graph.heads(set(['rev1', 'null:'])))
762
        self.assertEqual(set(['rev1']), graph.heads(('rev1', 'null:')))
763
764
    def test_heads_one(self):
3052.5.5 by John Arbash Meinel
Special case Graph.heads() for NULL_REVISION rather than is_ancestor.
765
        # A single node will always be a head
2776.1.4 by Robert Collins
Trivial review feedback changes.
766
        graph = self.make_graph(ancestry_1)
767
        self.assertEqual(set(['null:']), graph.heads(['null:']))
768
        self.assertEqual(set(['rev1']), graph.heads(['rev1']))
769
        self.assertEqual(set(['rev2a']), graph.heads(['rev2a']))
770
        self.assertEqual(set(['rev2b']), graph.heads(['rev2b']))
771
        self.assertEqual(set(['rev3']), graph.heads(['rev3']))
772
        self.assertEqual(set(['rev4']), graph.heads(['rev4']))
773
774
    def test_heads_single(self):
775
        graph = self.make_graph(ancestry_1)
776
        self.assertEqual(set(['rev4']), graph.heads(['null:', 'rev4']))
777
        self.assertEqual(set(['rev2a']), graph.heads(['rev1', 'rev2a']))
778
        self.assertEqual(set(['rev2b']), graph.heads(['rev1', 'rev2b']))
779
        self.assertEqual(set(['rev3']), graph.heads(['rev1', 'rev3']))
780
        self.assertEqual(set(['rev4']), graph.heads(['rev1', 'rev4']))
781
        self.assertEqual(set(['rev4']), graph.heads(['rev2a', 'rev4']))
782
        self.assertEqual(set(['rev4']), graph.heads(['rev2b', 'rev4']))
783
        self.assertEqual(set(['rev4']), graph.heads(['rev3', 'rev4']))
784
785
    def test_heads_two_heads(self):
786
        graph = self.make_graph(ancestry_1)
787
        self.assertEqual(set(['rev2a', 'rev2b']),
788
                         graph.heads(['rev2a', 'rev2b']))
789
        self.assertEqual(set(['rev3', 'rev2b']),
790
                         graph.heads(['rev3', 'rev2b']))
791
792
    def test_heads_criss_cross(self):
793
        graph = self.make_graph(criss_cross)
794
        self.assertEqual(set(['rev2a']),
795
                         graph.heads(['rev2a', 'rev1']))
796
        self.assertEqual(set(['rev2b']),
797
                         graph.heads(['rev2b', 'rev1']))
798
        self.assertEqual(set(['rev3a']),
799
                         graph.heads(['rev3a', 'rev1']))
800
        self.assertEqual(set(['rev3b']),
801
                         graph.heads(['rev3b', 'rev1']))
802
        self.assertEqual(set(['rev2a', 'rev2b']),
803
                         graph.heads(['rev2a', 'rev2b']))
804
        self.assertEqual(set(['rev3a']),
805
                         graph.heads(['rev3a', 'rev2a']))
806
        self.assertEqual(set(['rev3a']),
807
                         graph.heads(['rev3a', 'rev2b']))
808
        self.assertEqual(set(['rev3a']),
809
                         graph.heads(['rev3a', 'rev2a', 'rev2b']))
810
        self.assertEqual(set(['rev3b']),
811
                         graph.heads(['rev3b', 'rev2a']))
812
        self.assertEqual(set(['rev3b']),
813
                         graph.heads(['rev3b', 'rev2b']))
814
        self.assertEqual(set(['rev3b']),
815
                         graph.heads(['rev3b', 'rev2a', 'rev2b']))
816
        self.assertEqual(set(['rev3a', 'rev3b']),
817
                         graph.heads(['rev3a', 'rev3b']))
818
        self.assertEqual(set(['rev3a', 'rev3b']),
819
                         graph.heads(['rev3a', 'rev3b', 'rev2a', 'rev2b']))
820
821
    def test_heads_shortcut(self):
822
        graph = self.make_graph(history_shortcut)
823
824
        self.assertEqual(set(['rev2a', 'rev2b', 'rev2c']),
825
                         graph.heads(['rev2a', 'rev2b', 'rev2c']))
826
        self.assertEqual(set(['rev3a', 'rev3b']),
827
                         graph.heads(['rev3a', 'rev3b']))
828
        self.assertEqual(set(['rev3a', 'rev3b']),
829
                         graph.heads(['rev2a', 'rev3a', 'rev3b']))
830
        self.assertEqual(set(['rev2a', 'rev3b']),
831
                         graph.heads(['rev2a', 'rev3b']))
832
        self.assertEqual(set(['rev2c', 'rev3a']),
833
                         graph.heads(['rev2c', 'rev3a']))
2921.3.1 by Robert Collins
* Graph ``heads()`` queries have been bugfixed to no longer access all
834
835
    def _run_heads_break_deeper(self, graph_dict, search):
836
        """Run heads on a graph-as-a-dict.
837
        
838
        If the search asks for the parents of 'deeper' the test will fail.
839
        """
840
        class stub(object):
841
            pass
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
842
        def get_parent_map(keys):
843
            result = {}
844
            for key in keys:
845
                if key == 'deeper':
846
                    self.fail('key deeper was accessed')
847
                result[key] = graph_dict[key]
848
            return result
2921.3.1 by Robert Collins
* Graph ``heads()`` queries have been bugfixed to no longer access all
849
        an_obj = stub()
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
850
        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
851
        graph = _mod_graph.Graph(an_obj)
852
        return graph.heads(search)
853
854
    def test_heads_limits_search(self):
855
        # test that a heads query does not search all of history
856
        graph_dict = {
857
            'left':['common'],
858
            'right':['common'],
859
            'common':['deeper'],
860
        }
861
        self.assertEqual(set(['left', 'right']),
862
            self._run_heads_break_deeper(graph_dict, ['left', 'right']))
863
864
    def test_heads_limits_search_assymetric(self):
865
        # test that a heads query does not search all of history
866
        graph_dict = {
867
            'left':['midleft'],
868
            'midleft':['common'],
869
            'right':['common'],
870
            'common':['aftercommon'],
871
            'aftercommon':['deeper'],
872
        }
873
        self.assertEqual(set(['left', 'right']),
874
            self._run_heads_break_deeper(graph_dict, ['left', 'right']))
875
876
    def test_heads_limits_search_common_search_must_continue(self):
877
        # test that common nodes are still queried, preventing
878
        # all-the-way-to-origin behaviour in the following graph:
879
        graph_dict = {
880
            'h1':['shortcut', 'common1'],
881
            'h2':['common1'],
882
            'shortcut':['common2'],
883
            'common1':['common2'],
884
            'common2':['deeper'],
885
        }
886
        self.assertEqual(set(['h1', 'h2']),
887
            self._run_heads_break_deeper(graph_dict, ['h1', 'h2']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
888
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
889
    def test_breadth_first_search_start_ghosts(self):
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
        # with_ghosts reports the ghosts
892
        search = graph._make_breadth_first_searcher(['a-ghost'])
893
        self.assertEqual((set(), set(['a-ghost'])), search.next_with_ghosts())
894
        self.assertRaises(StopIteration, search.next_with_ghosts)
895
        # next includes them
896
        search = graph._make_breadth_first_searcher(['a-ghost'])
897
        self.assertEqual(set(['a-ghost']), search.next())
898
        self.assertRaises(StopIteration, search.next)
899
900
    def test_breadth_first_search_deep_ghosts(self):
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
901
        graph = self.make_graph({
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
902
            'head':['present'],
903
            'present':['child', 'ghost'],
904
            'child':[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
905
            })
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
906
        # with_ghosts reports the ghosts
907
        search = graph._make_breadth_first_searcher(['head'])
908
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
909
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
910
        self.assertEqual((set(['child']), set(['ghost'])),
911
            search.next_with_ghosts())
912
        self.assertRaises(StopIteration, search.next_with_ghosts)
913
        # next includes them
914
        search = graph._make_breadth_first_searcher(['head'])
915
        self.assertEqual(set(['head']), search.next())
916
        self.assertEqual(set(['present']), search.next())
917
        self.assertEqual(set(['child', 'ghost']),
918
            search.next())
919
        self.assertRaises(StopIteration, search.next)
920
921
    def test_breadth_first_search_change_next_to_next_with_ghosts(self):
3177.3.3 by Robert Collins
Review feedback.
922
        # To make the API robust, we allow calling both next() and
923
        # next_with_ghosts() on the same searcher.
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
924
        graph = self.make_graph({
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
925
            'head':['present'],
926
            'present':['child', 'ghost'],
927
            'child':[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
928
            })
3177.3.3 by Robert Collins
Review feedback.
929
        # start with next_with_ghosts
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
930
        search = graph._make_breadth_first_searcher(['head'])
931
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
932
        self.assertEqual(set(['present']), search.next())
933
        self.assertEqual((set(['child']), set(['ghost'])),
934
            search.next_with_ghosts())
935
        self.assertRaises(StopIteration, search.next)
3177.3.3 by Robert Collins
Review feedback.
936
        # start with next
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
937
        search = graph._make_breadth_first_searcher(['head'])
938
        self.assertEqual(set(['head']), search.next())
939
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
940
        self.assertEqual(set(['child', 'ghost']),
941
            search.next())
942
        self.assertRaises(StopIteration, search.next_with_ghosts)
943
3177.3.2 by Robert Collins
Update graph searchers stop_searching_any and start_searching for next_with_ghosts.
944
    def test_breadth_first_change_search(self):
3177.3.3 by Robert Collins
Review feedback.
945
        # 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.
946
        graph = self.make_graph({
3177.3.2 by Robert Collins
Update graph searchers stop_searching_any and start_searching for next_with_ghosts.
947
            'head':['present'],
948
            'present':['stopped'],
949
            'other':['other_2'],
950
            'other_2':[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
951
            })
3177.3.2 by Robert Collins
Update graph searchers stop_searching_any and start_searching for next_with_ghosts.
952
        search = graph._make_breadth_first_searcher(['head'])
953
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
954
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
955
        self.assertEqual(set(['present']),
956
            search.stop_searching_any(['present']))
957
        self.assertEqual((set(['other']), set(['other_ghost'])),
958
            search.start_searching(['other', 'other_ghost']))
959
        self.assertEqual((set(['other_2']), set()), search.next_with_ghosts())
960
        self.assertRaises(StopIteration, search.next_with_ghosts)
961
        # next includes them
962
        search = graph._make_breadth_first_searcher(['head'])
963
        self.assertEqual(set(['head']), search.next())
964
        self.assertEqual(set(['present']), search.next())
965
        self.assertEqual(set(['present']),
966
            search.stop_searching_any(['present']))
967
        search.start_searching(['other', 'other_ghost'])
968
        self.assertEqual(set(['other_2']), search.next())
969
        self.assertRaises(StopIteration, search.next)
970
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
971
    def assertSeenAndResult(self, instructions, search, next):
972
        """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.
973
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
974
        :param instructions: A list of tuples:
975
            (seen, recipe, included_keys, starts, stops).
976
            seen, recipe and included_keys are results to check on the search
977
            and the searches get_result(). starts and stops are parameters to
978
            pass to start_searching and stop_searching_any during each
979
            iteration, if they are not None.
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
980
        :param search: The search to use.
981
        :param next: A callable to advance the search.
982
        """
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
983
        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.
984
            next()
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
985
            if starts is not None:
986
                search.start_searching(starts)
987
            if stops is not None:
988
                search.stop_searching_any(stops)
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
989
            result = search.get_result()
990
            self.assertEqual(recipe, result.get_recipe())
991
            self.assertEqual(set(included_keys), result.get_keys())
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
992
            self.assertEqual(seen, search.seen)
993
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
994
    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.
995
        graph = self.make_graph({
996
            'head':['child'],
997
            'child':[NULL_REVISION],
3184.1.3 by Robert Collins
Automatically exclude ghosts.
998
            NULL_REVISION:[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
999
            })
1000
        search = graph._make_breadth_first_searcher(['head'])
1001
        # 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.
1002
        result = search.get_result()
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1003
        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.
1004
            result.get_recipe())
1005
        self.assertEqual(set(), result.get_keys())
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1006
        self.assertEqual(set(), search.seen)
1007
        # using next:
1008
        expected = [
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1009
            (set(['head']), (set(['head']), set(['child']), 1),
1010
             ['head'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1011
            (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.
1012
             ['head', 'child'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1013
            (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.
1014
             ['head', 'child', NULL_REVISION], None, None),
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1015
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1016
        self.assertSeenAndResult(expected, search, search.next)
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1017
        # using next_with_ghosts:
1018
        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.
1019
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1020
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1021
    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.
1022
        graph = self.make_graph({
1023
            'head':['child'],
1024
            'child':[NULL_REVISION],
1025
            'otherhead':['otherchild'],
1026
            'otherchild':['excluded'],
1027
            'excluded':[NULL_REVISION],
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1028
            NULL_REVISION:[]
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
1029
            })
1030
        search = graph._make_breadth_first_searcher([])
1031
        # Starting with nothing and adding a search works:
1032
        search.start_searching(['head'])
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1033
        # head has been seen:
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1034
        result = search.get_result()
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1035
        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.
1036
            result.get_recipe())
1037
        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.
1038
        self.assertEqual(set(['head']), search.seen)
1039
        # using next:
1040
        expected = [
1041
            # stop at child, and start a new search at otherhead:
1042
            # - otherhead counts as seen immediately when start_searching is
1043
            # called.
1044
            (set(['head', 'child', 'otherhead']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1045
             (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.
1046
             ['head', 'otherhead'], ['otherhead'], ['child']),
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
1047
            (set(['head', 'child', 'otherhead', 'otherchild']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1048
             (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.
1049
             ['head', 'otherhead', 'otherchild'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1050
            # stop searching excluded now
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
1051
            (set(['head', 'child', 'otherhead', 'otherchild', 'excluded']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1052
             (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.
1053
             ['head', 'otherhead', 'otherchild'], None, ['excluded']),
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
1054
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1055
        self.assertSeenAndResult(expected, search, search.next)
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
1056
        # using next_with_ghosts:
1057
        search = graph._make_breadth_first_searcher([])
1058
        search.start_searching(['head'])
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1059
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
1060
3184.2.1 by Robert Collins
Handle stopping ghosts in searches properly.
1061
    def test_breadth_first_stop_searching_not_queried(self):
1062
        # A client should be able to say 'stop node X' even if X has not been
1063
        # returned to the client.
1064
        graph = self.make_graph({
1065
            'head':['child', 'ghost1'],
1066
            'child':[NULL_REVISION],
1067
            NULL_REVISION:[],
1068
            })
1069
        search = graph._make_breadth_first_searcher(['head'])
1070
        expected = [
1071
            # NULL_REVISION and ghost1 have not been returned
1072
            (set(['head']), (set(['head']), set(['child', 'ghost1']), 1),
1073
             ['head'], None, [NULL_REVISION, 'ghost1']),
1074
            # ghost1 has been returned, NULL_REVISION is to be returned in the
1075
            # next iteration.
1076
            (set(['head', 'child', 'ghost1']),
1077
             (set(['head']), set(['ghost1', NULL_REVISION]), 2),
1078
             ['head', 'child'], None, [NULL_REVISION, 'ghost1']),
1079
            ]
1080
        self.assertSeenAndResult(expected, search, search.next)
1081
        # using next_with_ghosts:
1082
        search = graph._make_breadth_first_searcher(['head'])
1083
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
1084
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1085
    def test_breadth_first_get_result_ghosts_are_excluded(self):
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1086
        graph = self.make_graph({
1087
            'head':['child', 'ghost'],
1088
            'child':[NULL_REVISION],
1089
            NULL_REVISION:[],
1090
            })
1091
        search = graph._make_breadth_first_searcher(['head'])
1092
        # using next:
1093
        expected = [
1094
            (set(['head']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1095
             (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.
1096
             ['head'], None, None),
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1097
            (set(['head', 'child', 'ghost']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1098
             (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.
1099
             ['head', 'child'], None, None),
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1100
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1101
        self.assertSeenAndResult(expected, search, search.next)
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1102
        # using next_with_ghosts:
1103
        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.
1104
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1105
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1106
    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.
1107
        graph = self.make_graph({
1108
            'head':['child'],
1109
            'child':[NULL_REVISION],
1110
            NULL_REVISION:[],
1111
            })
1112
        search = graph._make_breadth_first_searcher(['head'])
1113
        # using next:
1114
        expected = [
1115
            (set(['head', 'ghost']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1116
             (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.
1117
             ['head'], ['ghost'], None),
3184.1.4 by Robert Collins
Correctly exclude ghosts when ghosts are started on an existing search.
1118
            (set(['head', 'child', 'ghost']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1119
             (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.
1120
             ['head', 'child'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1121
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1122
        self.assertSeenAndResult(expected, search, search.next)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1123
        # using next_with_ghosts:
1124
        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.
1125
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1126
1127
    def test_breadth_first_revision_count_includes_NULL_REVISION(self):
1128
        graph = self.make_graph({
1129
            'head':[NULL_REVISION],
1130
            NULL_REVISION:[],
1131
            })
1132
        search = graph._make_breadth_first_searcher(['head'])
1133
        # using next:
1134
        expected = [
1135
            (set(['head']),
1136
             (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.
1137
             ['head'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1138
            (set(['head', NULL_REVISION]),
1139
             (set(['head']), set([]), 2),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1140
             ['head', NULL_REVISION], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1141
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1142
        self.assertSeenAndResult(expected, search, search.next)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1143
        # using next_with_ghosts:
1144
        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.
1145
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1146
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1147
    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.
1148
        # StopIteration should not invalid anything..
1149
        graph = self.make_graph({
1150
            'head':[NULL_REVISION],
1151
            NULL_REVISION:[],
1152
            })
1153
        search = graph._make_breadth_first_searcher(['head'])
1154
        # using next:
1155
        expected = [
1156
            (set(['head']),
1157
             (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.
1158
             ['head'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1159
            (set(['head', 'ghost', NULL_REVISION]),
1160
             (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.
1161
             ['head', NULL_REVISION], ['ghost'], None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1162
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1163
        self.assertSeenAndResult(expected, search, search.next)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1164
        self.assertRaises(StopIteration, search.next)
1165
        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.
1166
        result = search.get_result()
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1167
        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.
1168
            result.get_recipe())
1169
        self.assertEqual(set(['head', NULL_REVISION]), result.get_keys())
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1170
        # using next_with_ghosts:
1171
        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.
1172
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1173
        self.assertRaises(StopIteration, search.next)
1174
        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.
1175
        result = search.get_result()
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1176
        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.
1177
            result.get_recipe())
1178
        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.
1179
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1180
3445.1.2 by John Arbash Meinel
Handle when a known revision is an ancestor.
1181
class TestFindUniqueAncestors(TestGraphBase):
3377.3.24 by John Arbash Meinel
For some reason find_unique_ancestors is much slower than it should be.
1182
3377.3.21 by John Arbash Meinel
Simple brute-force implementation of find_unique_ancestors
1183
    def assertFindUniqueAncestors(self, graph, expected, node, common):
1184
        actual = graph.find_unique_ancestors(node, common)
1185
        self.assertEqual(expected, sorted(actual))
1186
1187
    def test_empty_set(self):
1188
        graph = self.make_graph(ancestry_1)
1189
        self.assertFindUniqueAncestors(graph, [], 'rev1', ['rev1'])
1190
        self.assertFindUniqueAncestors(graph, [], 'rev2b', ['rev2b'])
1191
        self.assertFindUniqueAncestors(graph, [], 'rev3', ['rev1', 'rev3'])
1192
1193
    def test_single_node(self):
1194
        graph = self.make_graph(ancestry_1)
1195
        self.assertFindUniqueAncestors(graph, ['rev2a'], 'rev2a', ['rev1'])
1196
        self.assertFindUniqueAncestors(graph, ['rev2b'], 'rev2b', ['rev1'])
1197
        self.assertFindUniqueAncestors(graph, ['rev3'], 'rev3', ['rev2a'])
1198
3377.3.24 by John Arbash Meinel
For some reason find_unique_ancestors is much slower than it should be.
1199
    def test_minimal_ancestry(self):
1200
        graph = self.make_breaking_graph(extended_history_shortcut,
1201
                                         [NULL_REVISION, 'a', 'b'])
1202
        self.assertFindUniqueAncestors(graph, ['e'], 'e', ['d'])
1203
3377.3.25 by John Arbash Meinel
A few more minimal ancestry checks
1204
        graph = self.make_breaking_graph(extended_history_shortcut,
1205
                                         ['b'])
1206
        self.assertFindUniqueAncestors(graph, ['f'], 'f', ['a', 'd'])
1207
1208
        graph = self.make_breaking_graph(complex_shortcut,
3377.3.27 by John Arbash Meinel
some simple updates
1209
                                         ['a', 'b'])
3377.3.25 by John Arbash Meinel
A few more minimal ancestry checks
1210
        self.assertFindUniqueAncestors(graph, ['h'], 'h', ['i'])
1211
        self.assertFindUniqueAncestors(graph, ['e', 'g', 'i'], 'i', ['h'])
3377.3.27 by John Arbash Meinel
some simple updates
1212
        self.assertFindUniqueAncestors(graph, ['h'], 'h', ['g'])
3377.3.26 by John Arbash Meinel
Found a graph leak.
1213
        self.assertFindUniqueAncestors(graph, ['h'], 'h', ['j'])
1214
3377.3.21 by John Arbash Meinel
Simple brute-force implementation of find_unique_ancestors
1215
    def test_in_ancestry(self):
1216
        graph = self.make_graph(ancestry_1)
1217
        self.assertFindUniqueAncestors(graph, [], 'rev1', ['rev3'])
1218
        self.assertFindUniqueAncestors(graph, [], 'rev2b', ['rev4'])
1219
1220
    def test_multiple_revisions(self):
1221
        graph = self.make_graph(ancestry_1)
1222
        self.assertFindUniqueAncestors(graph,
1223
            ['rev4'], 'rev4', ['rev3', 'rev2b'])
1224
        self.assertFindUniqueAncestors(graph,
1225
            ['rev2a', 'rev3', 'rev4'], 'rev4', ['rev2b'])
1226
3377.3.22 by John Arbash Meinel
include some tests using the complex graphs
1227
    def test_complex_shortcut(self):
1228
        graph = self.make_graph(complex_shortcut)
1229
        self.assertFindUniqueAncestors(graph,
1230
            ['h', 'n'], 'n', ['m'])
1231
        self.assertFindUniqueAncestors(graph,
1232
            ['e', 'i', 'm'], 'm', ['n'])
1233
1234
    def test_complex_shortcut2(self):
1235
        graph = self.make_graph(complex_shortcut2)
1236
        self.assertFindUniqueAncestors(graph,
1237
            ['j', 'u'], 'u', ['t'])
1238
        self.assertFindUniqueAncestors(graph,
1239
            ['t'], 't', ['u'])
1240
3377.3.35 by John Arbash Meinel
Add a test that exercises the multiple interesting unique code
1241
    def test_multiple_interesting_unique(self):
1242
        graph = self.make_graph(multiple_interesting_unique)
1243
        self.assertFindUniqueAncestors(graph,
1244
            ['j', 'y'], 'y', ['z'])
1245
        self.assertFindUniqueAncestors(graph,
1246
            ['p', 'z'], 'z', ['y'])
1247
3377.3.36 by John Arbash Meinel
Small updates, try to write a test for the race condition.
1248
    def test_racing_shortcuts(self):
1249
        graph = self.make_graph(racing_shortcuts)
1250
        self.assertFindUniqueAncestors(graph,
1251
            ['p', 'q', 'z'], 'z', ['y'])
1252
        self.assertFindUniqueAncestors(graph,
1253
            ['h', 'i', 'j', 'y'], 'j', ['z'])
1254
3377.3.21 by John Arbash Meinel
Simple brute-force implementation of find_unique_ancestors
1255
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1256
class TestGraphFindDistanceToNull(TestGraphBase):
3445.1.1 by John Arbash Meinel
Start working on a new Graph api to make finding revision numbers faster.
1257
    """Test an api that should be able to compute a revno"""
1258
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1259
    def assertFindDistance(self, revno, graph, target_id, known_ids):
1260
        """Assert the output of Graph.find_distance_to_null()"""
1261
        actual = graph.find_distance_to_null(target_id, known_ids)
3445.1.1 by John Arbash Meinel
Start working on a new Graph api to make finding revision numbers faster.
1262
        self.assertEqual(revno, actual)
1263
1264
    def test_nothing_known(self):
1265
        graph = self.make_graph(ancestry_1)
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1266
        self.assertFindDistance(0, graph, NULL_REVISION, [])
1267
        self.assertFindDistance(1, graph, 'rev1', [])
1268
        self.assertFindDistance(2, graph, 'rev2a', [])
1269
        self.assertFindDistance(2, graph, 'rev2b', [])
1270
        self.assertFindDistance(3, graph, 'rev3', [])
1271
        self.assertFindDistance(4, graph, 'rev4', [])
3445.1.1 by John Arbash Meinel
Start working on a new Graph api to make finding revision numbers faster.
1272
1273
    def test_rev_is_ghost(self):
1274
        graph = self.make_graph(ancestry_1)
1275
        e = self.assertRaises(errors.GhostRevisionsHaveNoRevno,
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1276
                              graph.find_distance_to_null, 'rev_missing', [])
3445.1.1 by John Arbash Meinel
Start working on a new Graph api to make finding revision numbers faster.
1277
        self.assertEqual('rev_missing', e.revision_id)
1278
        self.assertEqual('rev_missing', e.ghost_revision_id)
1279
1280
    def test_ancestor_is_ghost(self):
1281
        graph = self.make_graph({'rev':['parent']})
1282
        e = self.assertRaises(errors.GhostRevisionsHaveNoRevno,
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1283
                              graph.find_distance_to_null, 'rev', [])
3445.1.1 by John Arbash Meinel
Start working on a new Graph api to make finding revision numbers faster.
1284
        self.assertEqual('rev', e.revision_id)
1285
        self.assertEqual('parent', e.ghost_revision_id)
1286
3445.1.2 by John Arbash Meinel
Handle when a known revision is an ancestor.
1287
    def test_known_in_ancestry(self):
1288
        graph = self.make_graph(ancestry_1)
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1289
        self.assertFindDistance(2, graph, 'rev2a', [('rev1', 1)])
1290
        self.assertFindDistance(3, graph, 'rev3', [('rev2a', 2)])
3445.1.2 by John Arbash Meinel
Handle when a known revision is an ancestor.
1291
1292
    def test_known_in_ancestry_limits(self):
3445.1.3 by John Arbash Meinel
Search from all of the known revisions.
1293
        graph = self.make_breaking_graph(ancestry_1, ['rev1'])
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1294
        self.assertFindDistance(4, graph, 'rev4', [('rev3', 3)])
3445.1.2 by John Arbash Meinel
Handle when a known revision is an ancestor.
1295
3445.1.3 by John Arbash Meinel
Search from all of the known revisions.
1296
    def test_target_is_ancestor(self):
1297
        graph = self.make_graph(ancestry_1)
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1298
        self.assertFindDistance(2, graph, 'rev2a', [('rev3', 3)])
3445.1.3 by John Arbash Meinel
Search from all of the known revisions.
1299
1300
    def test_target_is_ancestor_limits(self):
1301
        """We shouldn't search all history if we run into ourselves"""
1302
        graph = self.make_breaking_graph(ancestry_1, ['rev1'])
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1303
        self.assertFindDistance(3, graph, 'rev3', [('rev4', 4)])
3445.1.3 by John Arbash Meinel
Search from all of the known revisions.
1304
1305
    def test_target_parallel_to_known_limits(self):
3445.1.8 by John Arbash Meinel
Clarity tweaks recommended by Ian
1306
        # Even though the known revision isn't part of the other ancestry, they
3445.1.3 by John Arbash Meinel
Search from all of the known revisions.
1307
        # eventually converge
1308
        graph = self.make_breaking_graph(with_tail, ['a'])
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1309
        self.assertFindDistance(6, graph, 'f', [('g', 6)])
1310
        self.assertFindDistance(7, graph, 'h', [('g', 6)])
1311
        self.assertFindDistance(8, graph, 'i', [('g', 6)])
1312
        self.assertFindDistance(6, graph, 'g', [('i', 8)])
3445.1.3 by John Arbash Meinel
Search from all of the known revisions.
1313
3445.1.1 by John Arbash Meinel
Start working on a new Graph api to make finding revision numbers faster.
1314
3514.2.8 by John Arbash Meinel
The insertion ordering into the weave has an impact on conflicts.
1315
class TestFindMergeOrder(TestGraphBase):
1316
1317
    def assertMergeOrder(self, expected, graph, tip, base_revisions):
1318
        self.assertEqual(expected, graph.find_merge_order(tip, base_revisions))
1319
1320
    def test_parents(self):
1321
        graph = self.make_graph(ancestry_1)
1322
        self.assertMergeOrder(['rev3', 'rev2b'], graph, 'rev4',
1323
                                                        ['rev3', 'rev2b'])
1324
        self.assertMergeOrder(['rev3', 'rev2b'], graph, 'rev4',
1325
                                                        ['rev2b', 'rev3'])
1326
1327
    def test_ancestors(self):
1328
        graph = self.make_graph(ancestry_1)
1329
        self.assertMergeOrder(['rev1', 'rev2b'], graph, 'rev4',
1330
                                                        ['rev1', 'rev2b'])
1331
        self.assertMergeOrder(['rev1', 'rev2b'], graph, 'rev4',
1332
                                                        ['rev2b', 'rev1'])
1333
1334
    def test_shortcut_one_ancestor(self):
1335
        # When we have enough info, we can stop searching
1336
        graph = self.make_breaking_graph(ancestry_1, ['rev3', 'rev2b', 'rev4'])
1337
        # Single ancestors shortcut right away
1338
        self.assertMergeOrder(['rev3'], graph, 'rev4', ['rev3'])
1339
1340
    def test_shortcut_after_one_ancestor(self):
1341
        graph = self.make_breaking_graph(ancestry_1, ['rev2a', 'rev2b'])
1342
        self.assertMergeOrder(['rev3', 'rev1'], graph, 'rev4', ['rev1', 'rev3'])
1343
1344
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1345
class TestCachingParentsProvider(tests.TestCase):
1346
1347
    def setUp(self):
1348
        super(TestCachingParentsProvider, self).setUp()
1349
        dict_pp = _mod_graph.DictParentsProvider({'a':('b',)})
1350
        self.inst_pp = InstrumentedParentsProvider(dict_pp)
1351
        self.caching_pp = _mod_graph.CachingParentsProvider(self.inst_pp)
1352
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1353
    def test_get_parent_map(self):
1354
        """Requesting the same revision should be returned from cache"""
1355
        self.assertEqual({}, self.caching_pp._cache)
1356
        self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
1357
        self.assertEqual(['a'], self.inst_pp.calls)
1358
        self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
1359
        # No new call, as it should have been returned from the cache
1360
        self.assertEqual(['a'], self.inst_pp.calls)
1361
        self.assertEqual({'a':('b',)}, self.caching_pp._cache)
1362
1363
    def test_get_parent_map_not_present(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1364
        """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()
1365
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1366
        self.assertEqual(['b'], self.inst_pp.calls)
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1367
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1368
        # No new calls
1369
        self.assertEqual(['b'], self.inst_pp.calls)
1370
        self.assertEqual({'b':None}, self.caching_pp._cache)
1371
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1372
    def test_get_parent_map_mixed(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1373
        """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()
1374
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1375
        self.assertEqual(['b'], self.inst_pp.calls)
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1376
        self.assertEqual({'a':('b',)},
1377
                         self.caching_pp.get_parent_map(['a', 'b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1378
        self.assertEqual(['b', 'a'], self.inst_pp.calls)
1379
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1380
    def test_get_parent_map_repeated(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1381
        """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()
1382
        self.assertEqual({'a':('b',)},
1383
                         self.caching_pp.get_parent_map(['b', 'a', 'b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1384
        # Use sorted because we don't care about the order, just that each is
1385
        # only present 1 time.
1386
        self.assertEqual(['a', 'b'], sorted(self.inst_pp.calls))
3514.2.14 by John Arbash Meinel
Bring in the code to collapse linear portions of the graph.
1387
1388
1389
class TestCollapseLinearRegions(tests.TestCase):
1390
1391
    def assertCollapsed(self, collapsed, original):
1392
        self.assertEqual(collapsed,
1393
                         _mod_graph.collapse_linear_regions(original))
1394
1395
    def test_collapse_nothing(self):
1396
        d = {1:[2, 3], 2:[], 3:[]}
1397
        self.assertCollapsed(d, d)
1398
        d = {1:[2], 2:[3, 4], 3:[5], 4:[5], 5:[]}
1399
        self.assertCollapsed(d, d)
1400
1401
    def test_collapse_chain(self):
1402
        # Any time we have a linear chain, we should be able to collapse
1403
        d = {1:[2], 2:[3], 3:[4], 4:[5], 5:[]}
1404
        self.assertCollapsed({1:[5], 5:[]}, d)
1405
        d = {5:[4], 4:[3], 3:[2], 2:[1], 1:[]}
1406
        self.assertCollapsed({5:[1], 1:[]}, d)
1407
        d = {5:[3], 3:[4], 4:[1], 1:[2], 2:[]}
1408
        self.assertCollapsed({5:[2], 2:[]}, d)
1409
1410
    def test_collapse_with_multiple_children(self):
1411
        #    7
1412
        #    |
1413
        #    6
1414
        #   / \
1415
        #  4   5
1416
        #  |   |
3514.2.16 by John Arbash Meinel
Review feedback from Ian.
1417
        #  2   3
3514.2.14 by John Arbash Meinel
Bring in the code to collapse linear portions of the graph.
1418
        #   \ /
1419
        #    1
1420
        #
1421
        # 4 and 5 cannot be removed because 6 has 2 children
3514.2.16 by John Arbash Meinel
Review feedback from Ian.
1422
        # 2 and 3 cannot be removed because 1 has 2 parents
3514.2.14 by John Arbash Meinel
Bring in the code to collapse linear portions of the graph.
1423
        d = {1:[2, 3], 2:[4], 4:[6], 3:[5], 5:[6], 6:[7], 7:[]}
1424
        self.assertCollapsed(d, d)