~bzr-pqm/bzr/bzr.dev

5557.1.7 by John Arbash Meinel
Merge in the bzr.dev 5582
1
# Copyright (C) 2007-2011 Canonical Ltd
2490.2.5 by Aaron Bentley
Use GraphWalker.unique_ancestor to determine merge base
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2490.2.5 by Aaron Bentley
Use GraphWalker.unique_ancestor to determine merge base
16
2490.2.28 by Aaron Bentley
Fix handling of null revision
17
from bzrlib import (
18
    errors,
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
19
    graph as _mod_graph,
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
20
    tests,
2490.2.28 by Aaron Bentley
Fix handling of null revision
21
    )
2490.2.1 by Aaron Bentley
Start work on GraphWalker
22
from bzrlib.revision import NULL_REVISION
23
from bzrlib.tests import TestCaseWithMemoryTransport
4371.3.18 by John Arbash Meinel
Change VF.annotate to use the new KnownGraph code.
24
from bzrlib.symbol_versioning import deprecated_in
2490.2.1 by Aaron Bentley
Start work on GraphWalker
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'],
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
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
4332.3.4 by Robert Collins
Add a graph API for getting multiple distances to NULL at once.
528
    def test_lefthand_distance_smoke(self):
529
        """A simple does it work test for graph.lefthand_distance(keys)."""
530
        graph = self.make_graph(history_shortcut)
531
        distance_graph = graph.find_lefthand_distances(['rev3b', 'rev2a'])
532
        self.assertEqual({'rev2a': 2, 'rev3b': 3}, distance_graph)
533
4332.3.6 by Robert Collins
Teach graph.find_lefthand_distances about ghosts.
534
    def test_lefthand_distance_ghosts(self):
535
        """A simple does it work test for graph.lefthand_distance(keys)."""
536
        nodes = {'nonghost':[NULL_REVISION], 'toghost':['ghost']}
537
        graph = self.make_graph(nodes)
538
        distance_graph = graph.find_lefthand_distances(['nonghost', 'toghost'])
539
        self.assertEqual({'nonghost': 1, 'toghost': -1}, distance_graph)
540
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
541
    def test_recursive_unique_lca(self):
2490.2.25 by Aaron Bentley
Update from review
542
        """Test finding a unique least common ancestor.
2490.2.3 by Aaron Bentley
Implement new merge base picker
543
544
        ancestry_1 should always have a single common ancestor
545
        """
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
546
        graph = self.make_graph(ancestry_1)
547
        self.assertEqual(NULL_REVISION,
548
                         graph.find_unique_lca(NULL_REVISION, NULL_REVISION))
549
        self.assertEqual(NULL_REVISION,
550
                         graph.find_unique_lca(NULL_REVISION, 'rev1'))
551
        self.assertEqual('rev1', graph.find_unique_lca('rev1', 'rev1'))
552
        self.assertEqual('rev1', graph.find_unique_lca('rev2a', 'rev2b'))
1551.19.10 by Aaron Bentley
Merge now warns when it encounters a criss-cross
553
        self.assertEqual(('rev1', 1,),
554
                         graph.find_unique_lca('rev2a', 'rev2b',
555
                         count_steps=True))
2490.2.3 by Aaron Bentley
Implement new merge base picker
556
3377.3.1 by John Arbash Meinel
Bring in some of the changes from graph_update and graph_optimization
557
    def assertRemoveDescendants(self, expected, graph, revisions):
558
        parents = graph.get_parent_map(revisions)
559
        self.assertEqual(expected,
560
                         graph._remove_simple_descendants(revisions, parents))
561
562
    def test__remove_simple_descendants(self):
563
        graph = self.make_graph(ancestry_1)
564
        self.assertRemoveDescendants(set(['rev1']), graph,
565
            set(['rev1', 'rev2a', 'rev2b', 'rev3', 'rev4']))
566
567
    def test__remove_simple_descendants_disjoint(self):
568
        graph = self.make_graph(ancestry_1)
569
        self.assertRemoveDescendants(set(['rev1', 'rev3']), graph,
570
            set(['rev1', 'rev3']))
571
572
    def test__remove_simple_descendants_chain(self):
573
        graph = self.make_graph(ancestry_1)
574
        self.assertRemoveDescendants(set(['rev1']), graph,
575
            set(['rev1', 'rev2a', 'rev3']))
576
577
    def test__remove_simple_descendants_siblings(self):
578
        graph = self.make_graph(ancestry_1)
579
        self.assertRemoveDescendants(set(['rev2a', 'rev2b']), graph,
580
            set(['rev2a', 'rev2b', 'rev3']))
581
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
582
    def test_unique_lca_criss_cross(self):
583
        """Ensure we don't pick non-unique lcas in a criss-cross"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
584
        graph = self.make_graph(criss_cross)
585
        self.assertEqual('rev1', graph.find_unique_lca('rev3a', 'rev3b'))
1551.19.10 by Aaron Bentley
Merge now warns when it encounters a criss-cross
586
        lca, steps = graph.find_unique_lca('rev3a', 'rev3b', count_steps=True)
587
        self.assertEqual('rev1', lca)
588
        self.assertEqual(2, steps)
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
589
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
590
    def test_unique_lca_null_revision(self):
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
591
        """Ensure we pick NULL_REVISION when necessary"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
592
        graph = self.make_graph(criss_cross2)
593
        self.assertEqual('rev1b', graph.find_unique_lca('rev2a', 'rev1b'))
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
594
        self.assertEqual(NULL_REVISION,
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
595
                         graph.find_unique_lca('rev2a', 'rev2b'))
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
596
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
597
    def test_unique_lca_null_revision2(self):
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
598
        """Ensure we pick NULL_REVISION when necessary"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
599
        graph = self.make_graph(ancestry_2)
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
600
        self.assertEqual(NULL_REVISION,
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
601
                         graph.find_unique_lca('rev4a', 'rev1b'))
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
602
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
603
    def test_lca_double_shortcut(self):
604
        graph = self.make_graph(double_shortcut)
605
        self.assertEqual('c', graph.find_unique_lca('f', 'g'))
606
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
607
    def test_common_ancestor_two_repos(self):
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
608
        """Ensure we do unique_lca using data from two repos"""
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
609
        mainline_tree = self.prepare_memory_tree('mainline')
610
        self.build_ancestry(mainline_tree, mainline)
3010.1.6 by Robert Collins
Locking in test_graph.
611
        self.addCleanup(mainline_tree.unlock)
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
612
613
        # This is cheating, because the revisions in the graph are actually
614
        # different revisions, despite having the same revision-id.
615
        feature_tree = self.prepare_memory_tree('feature')
616
        self.build_ancestry(feature_tree, feature_branch)
3010.1.6 by Robert Collins
Locking in test_graph.
617
        self.addCleanup(feature_tree.unlock)
618
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
619
        graph = mainline_tree.branch.repository.get_graph(
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
620
            feature_tree.branch.repository)
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
621
        self.assertEqual('rev2b', graph.find_unique_lca('rev2a', 'rev3b'))
2490.2.23 by Aaron Bentley
Adapt find_borders to produce a graph difference
622
623
    def test_graph_difference(self):
624
        graph = self.make_graph(ancestry_1)
625
        self.assertEqual((set(), set()), graph.find_difference('rev1', 'rev1'))
626
        self.assertEqual((set(), set(['rev1'])),
627
                         graph.find_difference(NULL_REVISION, 'rev1'))
628
        self.assertEqual((set(['rev1']), set()),
629
                         graph.find_difference('rev1', NULL_REVISION))
630
        self.assertEqual((set(['rev2a', 'rev3']), set(['rev2b'])),
631
                         graph.find_difference('rev3', 'rev2b'))
632
        self.assertEqual((set(['rev4', 'rev3', 'rev2a']), set()),
633
                         graph.find_difference('rev4', 'rev2b'))
634
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
635
    def test_graph_difference_separate_ancestry(self):
636
        graph = self.make_graph(ancestry_2)
637
        self.assertEqual((set(['rev1a']), set(['rev1b'])),
638
                         graph.find_difference('rev1a', 'rev1b'))
639
        self.assertEqual((set(['rev1a', 'rev2a', 'rev3a', 'rev4a']),
640
                          set(['rev1b'])),
641
                         graph.find_difference('rev4a', 'rev1b'))
642
2490.2.23 by Aaron Bentley
Adapt find_borders to produce a graph difference
643
    def test_graph_difference_criss_cross(self):
644
        graph = self.make_graph(criss_cross)
645
        self.assertEqual((set(['rev3a']), set(['rev3b'])),
646
                         graph.find_difference('rev3a', 'rev3b'))
647
        self.assertEqual((set([]), set(['rev3b', 'rev2b'])),
648
                         graph.find_difference('rev2a', 'rev3b'))
2490.2.25 by Aaron Bentley
Update from review
649
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
650
    def test_graph_difference_extended_history(self):
651
        graph = self.make_graph(extended_history_shortcut)
652
        self.assertEqual((set(['e']), set(['f'])),
653
                         graph.find_difference('e', 'f'))
654
        self.assertEqual((set(['f']), set(['e'])),
655
                         graph.find_difference('f', 'e'))
656
657
    def test_graph_difference_double_shortcut(self):
658
        graph = self.make_graph(double_shortcut)
659
        self.assertEqual((set(['d', 'f']), set(['e', 'g'])),
660
                         graph.find_difference('f', 'g'))
661
662
    def test_graph_difference_complex_shortcut(self):
663
        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
664
        self.assertEqual((set(['m', 'i', 'e']), set(['n', 'h'])),
665
                         graph.find_difference('m', 'n'))
666
667
    def test_graph_difference_complex_shortcut2(self):
668
        graph = self.make_graph(complex_shortcut2)
3377.3.13 by John Arbash Meinel
Change _search_for_extra_common slightly.
669
        self.assertEqual((set(['t']), set(['j', 'u'])),
670
                         graph.find_difference('t', 'u'))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
671
672
    def test_graph_difference_shortcut_extra_root(self):
673
        graph = self.make_graph(shortcut_extra_root)
674
        self.assertEqual((set(['e']), set(['f', 'g'])),
675
                         graph.find_difference('e', 'f'))
676
4379.3.1 by Gary van der Merwe
Add test for _StackedParentsProvider where there are overlapping revisions (revisions that are available in both providers.)
677
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
678
    def test_stacked_parents_provider(self):
679
        parents1 = _mod_graph.DictParentsProvider({'rev2': ['rev3']})
680
        parents2 = _mod_graph.DictParentsProvider({'rev1': ['rev4']})
4379.3.3 by Gary van der Merwe
Rename and add doc string for StackedParentsProvider.
681
        stacked = _mod_graph.StackedParentsProvider([parents1, parents2])
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
682
        self.assertEqual({'rev1':['rev4'], 'rev2':['rev3']},
683
                         stacked.get_parent_map(['rev1', 'rev2']))
684
        self.assertEqual({'rev2':['rev3'], 'rev1':['rev4']},
685
                         stacked.get_parent_map(['rev2', 'rev1']))
686
        self.assertEqual({'rev2':['rev3']},
687
                         stacked.get_parent_map(['rev2', 'rev2']))
688
        self.assertEqual({'rev1':['rev4']},
689
                         stacked.get_parent_map(['rev1', 'rev1']))
4379.3.1 by Gary van der Merwe
Add test for _StackedParentsProvider where there are overlapping revisions (revisions that are available in both providers.)
690
    
691
    def test_stacked_parents_provider_overlapping(self):
692
        # rev2 is availible in both providers.
693
        # 1
694
        # |
695
        # 2
4379.3.2 by Gary van der Merwe
Simplify the previous test.
696
        parents1 = _mod_graph.DictParentsProvider({'rev2': ['rev1']})
697
        parents2 = _mod_graph.DictParentsProvider({'rev2': ['rev1']})
4379.3.3 by Gary van der Merwe
Rename and add doc string for StackedParentsProvider.
698
        stacked = _mod_graph.StackedParentsProvider([parents1, parents2])
4379.3.2 by Gary van der Merwe
Simplify the previous test.
699
        self.assertEqual({'rev2': ['rev1']},
700
                         stacked.get_parent_map(['rev2']))
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
701
4379.3.6 by Gary van der Merwe
Test that _StackedParentsProvider is deprecated and still works. The deprecated version was also incorrect.
702
    def test__stacked_parents_provider_deprecated(self):
703
        parents1 = _mod_graph.DictParentsProvider({'rev2': ['rev3']})
704
        parents2 = _mod_graph.DictParentsProvider({'rev1': ['rev4']})
705
        stacked = self.applyDeprecated(deprecated_in((1, 16, 0)),
706
                    _mod_graph._StackedParentsProvider, [parents1, parents2])
707
        self.assertEqual({'rev1':['rev4'], 'rev2':['rev3']},
708
                         stacked.get_parent_map(['rev1', 'rev2']))
709
        self.assertEqual({'rev2':['rev3'], 'rev1':['rev4']},
710
                         stacked.get_parent_map(['rev2', 'rev1']))
711
        self.assertEqual({'rev2':['rev3']},
712
                         stacked.get_parent_map(['rev2', 'rev2']))
713
        self.assertEqual({'rev1':['rev4']},
714
                         stacked.get_parent_map(['rev1', 'rev1']))
715
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
716
    def test_iter_topo_order(self):
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
717
        graph = self.make_graph(ancestry_1)
718
        args = ['rev2a', 'rev3', 'rev1']
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
719
        topo_args = list(graph.iter_topo_order(args))
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
720
        self.assertEqual(set(args), set(topo_args))
721
        self.assertTrue(topo_args.index('rev2a') > topo_args.index('rev1'))
722
        self.assertTrue(topo_args.index('rev2a') < topo_args.index('rev3'))
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
723
724
    def test_is_ancestor(self):
725
        graph = self.make_graph(ancestry_1)
2653.2.3 by Aaron Bentley
correctly handle Graph.is_ancestor(x, x)
726
        self.assertEqual(True, graph.is_ancestor('null:', 'null:'))
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
727
        self.assertEqual(True, graph.is_ancestor('null:', 'rev1'))
728
        self.assertEqual(False, graph.is_ancestor('rev1', 'null:'))
729
        self.assertEqual(True, graph.is_ancestor('null:', 'rev4'))
730
        self.assertEqual(False, graph.is_ancestor('rev4', 'null:'))
731
        self.assertEqual(False, graph.is_ancestor('rev4', 'rev2b'))
732
        self.assertEqual(True, graph.is_ancestor('rev2b', 'rev4'))
733
        self.assertEqual(False, graph.is_ancestor('rev2b', 'rev3'))
734
        self.assertEqual(False, graph.is_ancestor('rev3', 'rev2b'))
735
        instrumented_provider = InstrumentedParentsProvider(graph)
736
        instrumented_graph = _mod_graph.Graph(instrumented_provider)
737
        instrumented_graph.is_ancestor('rev2a', 'rev2b')
738
        self.assertTrue('null:' not in instrumented_provider.calls)
739
3921.3.5 by Marius Kruger
extract graph.is_between from builtins.cmd_tags.run, and test it
740
    def test_is_between(self):
741
        graph = self.make_graph(ancestry_1)
742
        self.assertEqual(True, graph.is_between('null:', 'null:', 'null:'))
743
        self.assertEqual(True, graph.is_between('rev1', 'null:', 'rev1'))
744
        self.assertEqual(True, graph.is_between('rev1', 'rev1', 'rev4'))
745
        self.assertEqual(True, graph.is_between('rev4', 'rev1', 'rev4'))
746
        self.assertEqual(True, graph.is_between('rev3', 'rev1', 'rev4'))
747
        self.assertEqual(False, graph.is_between('rev4', 'rev1', 'rev3'))
748
        self.assertEqual(False, graph.is_between('rev1', 'rev2a', 'rev4'))
749
        self.assertEqual(False, graph.is_between('null:', 'rev1', 'rev4'))
750
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
751
    def test_is_ancestor_boundary(self):
752
        """Ensure that we avoid searching the whole graph.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
753
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
754
        This requires searching through b as a common ancestor, so we
755
        can identify that e is common.
756
        """
757
        graph = self.make_graph(boundary)
758
        instrumented_provider = InstrumentedParentsProvider(graph)
759
        graph = _mod_graph.Graph(instrumented_provider)
760
        self.assertFalse(graph.is_ancestor('a', 'c'))
761
        self.assertTrue('null:' not in instrumented_provider.calls)
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
762
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
763
    def test_iter_ancestry(self):
3228.4.10 by John Arbash Meinel
Respond to abentley's review comments.
764
        nodes = boundary.copy()
765
        nodes[NULL_REVISION] = ()
766
        graph = self.make_graph(nodes)
767
        expected = nodes.copy()
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
768
        expected.pop('a') # 'a' is not in the ancestry of 'c', all the
769
                          # other nodes are
3228.4.4 by John Arbash Meinel
Change iter_ancestry to take a group instead of a single node,
770
        self.assertEqual(expected, dict(graph.iter_ancestry(['c'])))
3228.4.10 by John Arbash Meinel
Respond to abentley's review comments.
771
        self.assertEqual(nodes, dict(graph.iter_ancestry(['a', 'c'])))
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
772
773
    def test_iter_ancestry_with_ghost(self):
774
        graph = self.make_graph(with_ghost)
775
        expected = with_ghost.copy()
776
        # '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.
777
        expected['g'] = None
3228.4.4 by John Arbash Meinel
Change iter_ancestry to take a group instead of a single node,
778
        self.assertEqual(expected, dict(graph.iter_ancestry(['a', 'c'])))
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
779
        expected.pop('a')
3228.4.4 by John Arbash Meinel
Change iter_ancestry to take a group instead of a single node,
780
        self.assertEqual(expected, dict(graph.iter_ancestry(['c'])))
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
781
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
782
    def test_filter_candidate_lca(self):
783
        """Test filter_candidate_lca for a corner case
784
1551.15.83 by Aaron Bentley
Update test documentation
785
        This tests the case where we encounter the end of iteration for 'e'
786
        in the same pass as we discover that 'd' is an ancestor of 'e', and
787
        therefore 'e' can't be an lca.
788
789
        To compensate for different dict orderings on other Python
790
        implementations, we mirror 'd' and 'e' with 'b' and 'a'.
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
791
        """
792
        # This test is sensitive to the iteration order of dicts.  It will
1551.15.82 by Aaron Bentley
Add symmetrical alternative to test case
793
        # pass incorrectly if 'e' and 'a' sort before 'c'
1551.15.84 by Aaron Bentley
Add ancestry graph for test case
794
        #
795
        # NULL_REVISION
796
        #     / \
797
        #    a   e
798
        #    |   |
799
        #    b   d
800
        #     \ /
801
        #      c
1551.15.82 by Aaron Bentley
Add symmetrical alternative to test case
802
        graph = self.make_graph({'c': ['b', 'd'], 'd': ['e'], 'b': ['a'],
803
                                 'a': [NULL_REVISION], 'e': [NULL_REVISION]})
2776.1.4 by Robert Collins
Trivial review feedback changes.
804
        self.assertEqual(set(['c']), graph.heads(['a', 'c', 'e']))
805
806
    def test_heads_null(self):
807
        graph = self.make_graph(ancestry_1)
808
        self.assertEqual(set(['null:']), graph.heads(['null:']))
809
        self.assertEqual(set(['rev1']), graph.heads(['null:', 'rev1']))
810
        self.assertEqual(set(['rev1']), graph.heads(['rev1', 'null:']))
811
        self.assertEqual(set(['rev1']), graph.heads(set(['rev1', 'null:'])))
812
        self.assertEqual(set(['rev1']), graph.heads(('rev1', 'null:')))
813
814
    def test_heads_one(self):
3052.5.5 by John Arbash Meinel
Special case Graph.heads() for NULL_REVISION rather than is_ancestor.
815
        # A single node will always be a head
2776.1.4 by Robert Collins
Trivial review feedback changes.
816
        graph = self.make_graph(ancestry_1)
817
        self.assertEqual(set(['null:']), graph.heads(['null:']))
818
        self.assertEqual(set(['rev1']), graph.heads(['rev1']))
819
        self.assertEqual(set(['rev2a']), graph.heads(['rev2a']))
820
        self.assertEqual(set(['rev2b']), graph.heads(['rev2b']))
821
        self.assertEqual(set(['rev3']), graph.heads(['rev3']))
822
        self.assertEqual(set(['rev4']), graph.heads(['rev4']))
823
824
    def test_heads_single(self):
825
        graph = self.make_graph(ancestry_1)
826
        self.assertEqual(set(['rev4']), graph.heads(['null:', 'rev4']))
827
        self.assertEqual(set(['rev2a']), graph.heads(['rev1', 'rev2a']))
828
        self.assertEqual(set(['rev2b']), graph.heads(['rev1', 'rev2b']))
829
        self.assertEqual(set(['rev3']), graph.heads(['rev1', 'rev3']))
830
        self.assertEqual(set(['rev4']), graph.heads(['rev1', 'rev4']))
831
        self.assertEqual(set(['rev4']), graph.heads(['rev2a', 'rev4']))
832
        self.assertEqual(set(['rev4']), graph.heads(['rev2b', 'rev4']))
833
        self.assertEqual(set(['rev4']), graph.heads(['rev3', 'rev4']))
834
835
    def test_heads_two_heads(self):
836
        graph = self.make_graph(ancestry_1)
837
        self.assertEqual(set(['rev2a', 'rev2b']),
838
                         graph.heads(['rev2a', 'rev2b']))
839
        self.assertEqual(set(['rev3', 'rev2b']),
840
                         graph.heads(['rev3', 'rev2b']))
841
842
    def test_heads_criss_cross(self):
843
        graph = self.make_graph(criss_cross)
844
        self.assertEqual(set(['rev2a']),
845
                         graph.heads(['rev2a', 'rev1']))
846
        self.assertEqual(set(['rev2b']),
847
                         graph.heads(['rev2b', 'rev1']))
848
        self.assertEqual(set(['rev3a']),
849
                         graph.heads(['rev3a', 'rev1']))
850
        self.assertEqual(set(['rev3b']),
851
                         graph.heads(['rev3b', 'rev1']))
852
        self.assertEqual(set(['rev2a', 'rev2b']),
853
                         graph.heads(['rev2a', 'rev2b']))
854
        self.assertEqual(set(['rev3a']),
855
                         graph.heads(['rev3a', 'rev2a']))
856
        self.assertEqual(set(['rev3a']),
857
                         graph.heads(['rev3a', 'rev2b']))
858
        self.assertEqual(set(['rev3a']),
859
                         graph.heads(['rev3a', 'rev2a', 'rev2b']))
860
        self.assertEqual(set(['rev3b']),
861
                         graph.heads(['rev3b', 'rev2a']))
862
        self.assertEqual(set(['rev3b']),
863
                         graph.heads(['rev3b', 'rev2b']))
864
        self.assertEqual(set(['rev3b']),
865
                         graph.heads(['rev3b', 'rev2a', 'rev2b']))
866
        self.assertEqual(set(['rev3a', 'rev3b']),
867
                         graph.heads(['rev3a', 'rev3b']))
868
        self.assertEqual(set(['rev3a', 'rev3b']),
869
                         graph.heads(['rev3a', 'rev3b', 'rev2a', 'rev2b']))
870
871
    def test_heads_shortcut(self):
872
        graph = self.make_graph(history_shortcut)
873
874
        self.assertEqual(set(['rev2a', 'rev2b', 'rev2c']),
875
                         graph.heads(['rev2a', 'rev2b', 'rev2c']))
876
        self.assertEqual(set(['rev3a', 'rev3b']),
877
                         graph.heads(['rev3a', 'rev3b']))
878
        self.assertEqual(set(['rev3a', 'rev3b']),
879
                         graph.heads(['rev2a', 'rev3a', 'rev3b']))
880
        self.assertEqual(set(['rev2a', 'rev3b']),
881
                         graph.heads(['rev2a', 'rev3b']))
882
        self.assertEqual(set(['rev2c', 'rev3a']),
883
                         graph.heads(['rev2c', 'rev3a']))
2921.3.1 by Robert Collins
* Graph ``heads()`` queries have been bugfixed to no longer access all
884
885
    def _run_heads_break_deeper(self, graph_dict, search):
886
        """Run heads on a graph-as-a-dict.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
887
2921.3.1 by Robert Collins
* Graph ``heads()`` queries have been bugfixed to no longer access all
888
        If the search asks for the parents of 'deeper' the test will fail.
889
        """
890
        class stub(object):
891
            pass
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
892
        def get_parent_map(keys):
893
            result = {}
894
            for key in keys:
895
                if key == 'deeper':
896
                    self.fail('key deeper was accessed')
897
                result[key] = graph_dict[key]
898
            return result
2921.3.1 by Robert Collins
* Graph ``heads()`` queries have been bugfixed to no longer access all
899
        an_obj = stub()
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
900
        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
901
        graph = _mod_graph.Graph(an_obj)
902
        return graph.heads(search)
903
904
    def test_heads_limits_search(self):
905
        # test that a heads query does not search all of history
906
        graph_dict = {
907
            'left':['common'],
908
            'right':['common'],
909
            'common':['deeper'],
910
        }
911
        self.assertEqual(set(['left', 'right']),
912
            self._run_heads_break_deeper(graph_dict, ['left', 'right']))
913
914
    def test_heads_limits_search_assymetric(self):
915
        # test that a heads query does not search all of history
916
        graph_dict = {
917
            'left':['midleft'],
918
            'midleft':['common'],
919
            'right':['common'],
920
            'common':['aftercommon'],
921
            'aftercommon':['deeper'],
922
        }
923
        self.assertEqual(set(['left', 'right']),
924
            self._run_heads_break_deeper(graph_dict, ['left', 'right']))
925
926
    def test_heads_limits_search_common_search_must_continue(self):
927
        # test that common nodes are still queried, preventing
928
        # all-the-way-to-origin behaviour in the following graph:
929
        graph_dict = {
930
            'h1':['shortcut', 'common1'],
931
            'h2':['common1'],
932
            'shortcut':['common2'],
933
            'common1':['common2'],
934
            'common2':['deeper'],
935
        }
936
        self.assertEqual(set(['h1', 'h2']),
937
            self._run_heads_break_deeper(graph_dict, ['h1', 'h2']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
938
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
939
    def test_breadth_first_search_start_ghosts(self):
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
940
        graph = self.make_graph({})
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
941
        # with_ghosts reports the ghosts
942
        search = graph._make_breadth_first_searcher(['a-ghost'])
943
        self.assertEqual((set(), set(['a-ghost'])), search.next_with_ghosts())
944
        self.assertRaises(StopIteration, search.next_with_ghosts)
945
        # next includes them
946
        search = graph._make_breadth_first_searcher(['a-ghost'])
947
        self.assertEqual(set(['a-ghost']), search.next())
948
        self.assertRaises(StopIteration, search.next)
949
950
    def test_breadth_first_search_deep_ghosts(self):
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
951
        graph = self.make_graph({
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
952
            'head':['present'],
953
            'present':['child', 'ghost'],
954
            'child':[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
955
            })
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
956
        # with_ghosts reports the ghosts
957
        search = graph._make_breadth_first_searcher(['head'])
958
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
959
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
960
        self.assertEqual((set(['child']), set(['ghost'])),
961
            search.next_with_ghosts())
962
        self.assertRaises(StopIteration, search.next_with_ghosts)
963
        # next includes them
964
        search = graph._make_breadth_first_searcher(['head'])
965
        self.assertEqual(set(['head']), search.next())
966
        self.assertEqual(set(['present']), search.next())
967
        self.assertEqual(set(['child', 'ghost']),
968
            search.next())
969
        self.assertRaises(StopIteration, search.next)
970
971
    def test_breadth_first_search_change_next_to_next_with_ghosts(self):
3177.3.3 by Robert Collins
Review feedback.
972
        # To make the API robust, we allow calling both next() and
973
        # next_with_ghosts() on the same searcher.
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
974
        graph = self.make_graph({
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
975
            'head':['present'],
976
            'present':['child', 'ghost'],
977
            'child':[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
978
            })
3177.3.3 by Robert Collins
Review feedback.
979
        # start with next_with_ghosts
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
980
        search = graph._make_breadth_first_searcher(['head'])
981
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
982
        self.assertEqual(set(['present']), search.next())
983
        self.assertEqual((set(['child']), set(['ghost'])),
984
            search.next_with_ghosts())
985
        self.assertRaises(StopIteration, search.next)
3177.3.3 by Robert Collins
Review feedback.
986
        # start with next
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
987
        search = graph._make_breadth_first_searcher(['head'])
988
        self.assertEqual(set(['head']), search.next())
989
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
990
        self.assertEqual(set(['child', 'ghost']),
991
            search.next())
992
        self.assertRaises(StopIteration, search.next_with_ghosts)
993
3177.3.2 by Robert Collins
Update graph searchers stop_searching_any and start_searching for next_with_ghosts.
994
    def test_breadth_first_change_search(self):
3177.3.3 by Robert Collins
Review feedback.
995
        # 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.
996
        graph = self.make_graph({
3177.3.2 by Robert Collins
Update graph searchers stop_searching_any and start_searching for next_with_ghosts.
997
            'head':['present'],
998
            'present':['stopped'],
999
            'other':['other_2'],
1000
            'other_2':[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1001
            })
3177.3.2 by Robert Collins
Update graph searchers stop_searching_any and start_searching for next_with_ghosts.
1002
        search = graph._make_breadth_first_searcher(['head'])
1003
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
1004
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
1005
        self.assertEqual(set(['present']),
1006
            search.stop_searching_any(['present']))
1007
        self.assertEqual((set(['other']), set(['other_ghost'])),
1008
            search.start_searching(['other', 'other_ghost']))
1009
        self.assertEqual((set(['other_2']), set()), search.next_with_ghosts())
1010
        self.assertRaises(StopIteration, search.next_with_ghosts)
1011
        # next includes them
1012
        search = graph._make_breadth_first_searcher(['head'])
1013
        self.assertEqual(set(['head']), search.next())
1014
        self.assertEqual(set(['present']), search.next())
1015
        self.assertEqual(set(['present']),
1016
            search.stop_searching_any(['present']))
1017
        search.start_searching(['other', 'other_ghost'])
1018
        self.assertEqual(set(['other_2']), search.next())
1019
        self.assertRaises(StopIteration, search.next)
1020
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1021
    def assertSeenAndResult(self, instructions, search, next):
1022
        """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.
1023
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1024
        :param instructions: A list of tuples:
1025
            (seen, recipe, included_keys, starts, stops).
1026
            seen, recipe and included_keys are results to check on the search
1027
            and the searches get_result(). starts and stops are parameters to
1028
            pass to start_searching and stop_searching_any during each
1029
            iteration, if they are not None.
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1030
        :param search: The search to use.
1031
        :param next: A callable to advance the search.
1032
        """
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1033
        for seen, recipe, included_keys, starts, stops in instructions:
4152.1.2 by Robert Collins
Add streaming from a stacked branch when the sort order is compatible with doing so.
1034
            # Adjust for recipe contract changes that don't vary for all the
1035
            # current tests.
1036
            recipe = ('search',) + recipe
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1037
            next()
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
1038
            if starts is not None:
1039
                search.start_searching(starts)
1040
            if stops is not None:
1041
                search.stop_searching_any(stops)
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1042
            result = search.get_result()
1043
            self.assertEqual(recipe, result.get_recipe())
1044
            self.assertEqual(set(included_keys), result.get_keys())
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1045
            self.assertEqual(seen, search.seen)
1046
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1047
    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.
1048
        graph = self.make_graph({
1049
            'head':['child'],
1050
            'child':[NULL_REVISION],
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1051
            NULL_REVISION:[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1052
            })
1053
        search = graph._make_breadth_first_searcher(['head'])
1054
        # 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.
1055
        result = search.get_result()
4152.1.2 by Robert Collins
Add streaming from a stacked branch when the sort order is compatible with doing so.
1056
        self.assertEqual(('search', set(['head']), set(['head']), 0),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1057
            result.get_recipe())
1058
        self.assertEqual(set(), result.get_keys())
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1059
        self.assertEqual(set(), search.seen)
1060
        # using next:
1061
        expected = [
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1062
            (set(['head']), (set(['head']), set(['child']), 1),
1063
             ['head'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1064
            (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.
1065
             ['head', 'child'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1066
            (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.
1067
             ['head', 'child', NULL_REVISION], None, None),
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1068
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1069
        self.assertSeenAndResult(expected, search, search.next)
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1070
        # using next_with_ghosts:
1071
        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.
1072
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
1073
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1074
    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.
1075
        graph = self.make_graph({
1076
            'head':['child'],
1077
            'child':[NULL_REVISION],
1078
            'otherhead':['otherchild'],
1079
            'otherchild':['excluded'],
1080
            'excluded':[NULL_REVISION],
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1081
            NULL_REVISION:[]
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
1082
            })
1083
        search = graph._make_breadth_first_searcher([])
1084
        # Starting with nothing and adding a search works:
1085
        search.start_searching(['head'])
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1086
        # head has been seen:
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1087
        result = search.get_result()
4152.1.2 by Robert Collins
Add streaming from a stacked branch when the sort order is compatible with doing so.
1088
        self.assertEqual(('search', set(['head']), set(['child']), 1),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1089
            result.get_recipe())
1090
        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.
1091
        self.assertEqual(set(['head']), search.seen)
1092
        # using next:
1093
        expected = [
1094
            # stop at child, and start a new search at otherhead:
1095
            # - otherhead counts as seen immediately when start_searching is
1096
            # called.
1097
            (set(['head', 'child', 'otherhead']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1098
             (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.
1099
             ['head', 'otherhead'], ['otherhead'], ['child']),
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
1100
            (set(['head', 'child', 'otherhead', 'otherchild']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1101
             (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.
1102
             ['head', 'otherhead', 'otherchild'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1103
            # stop searching excluded now
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
1104
            (set(['head', 'child', 'otherhead', 'otherchild', 'excluded']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1105
             (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.
1106
             ['head', 'otherhead', 'otherchild'], None, ['excluded']),
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
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.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
1109
        # using next_with_ghosts:
1110
        search = graph._make_breadth_first_searcher([])
1111
        search.start_searching(['head'])
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1112
        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.
1113
3184.2.1 by Robert Collins
Handle stopping ghosts in searches properly.
1114
    def test_breadth_first_stop_searching_not_queried(self):
1115
        # A client should be able to say 'stop node X' even if X has not been
1116
        # returned to the client.
1117
        graph = self.make_graph({
1118
            'head':['child', 'ghost1'],
1119
            'child':[NULL_REVISION],
1120
            NULL_REVISION:[],
1121
            })
1122
        search = graph._make_breadth_first_searcher(['head'])
1123
        expected = [
1124
            # NULL_REVISION and ghost1 have not been returned
4053.2.2 by Andrew Bennetts
Better fix, with test.
1125
            (set(['head']),
1126
             (set(['head']), set(['child', NULL_REVISION, 'ghost1']), 1),
3184.2.1 by Robert Collins
Handle stopping ghosts in searches properly.
1127
             ['head'], None, [NULL_REVISION, 'ghost1']),
1128
            # ghost1 has been returned, NULL_REVISION is to be returned in the
1129
            # next iteration.
1130
            (set(['head', 'child', 'ghost1']),
1131
             (set(['head']), set(['ghost1', NULL_REVISION]), 2),
1132
             ['head', 'child'], None, [NULL_REVISION, 'ghost1']),
1133
            ]
1134
        self.assertSeenAndResult(expected, search, search.next)
1135
        # using next_with_ghosts:
1136
        search = graph._make_breadth_first_searcher(['head'])
1137
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
1138
3808.1.1 by Andrew Bennetts
Possible fix for bug in new _walk_to_common_revisions.
1139
    def test_breadth_first_stop_searching_late(self):
1140
        # A client should be able to say 'stop node X' and have it excluded
1141
        # from the result even if X was seen in an older iteration of the
1142
        # search.
1143
        graph = self.make_graph({
1144
            'head':['middle'],
1145
            'middle':['child'],
1146
            'child':[NULL_REVISION],
1147
            NULL_REVISION:[],
1148
            })
1149
        search = graph._make_breadth_first_searcher(['head'])
1150
        expected = [
1151
            (set(['head']), (set(['head']), set(['middle']), 1),
1152
             ['head'], None, None),
1153
            (set(['head', 'middle']), (set(['head']), set(['child']), 2),
1154
             ['head', 'middle'], None, None),
1155
            # 'middle' came from the previous iteration, but we don't stop
1156
            # searching it until *after* advancing the searcher.
1157
            (set(['head', 'middle', 'child']),
3808.1.4 by John Arbash Meinel
make _walk_to_common responsible for stopping ancestors
1158
             (set(['head']), set(['middle', 'child']), 1),
3808.1.3 by Andrew Bennetts
Add another test, this one currently failing. Perhaps the current behaviour should be considered ok, rather than a failure?
1159
             ['head'], None, ['middle', 'child']),
1160
            ]
1161
        self.assertSeenAndResult(expected, search, search.next)
1162
        # using next_with_ghosts:
1163
        search = graph._make_breadth_first_searcher(['head'])
1164
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
1165
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1166
    def test_breadth_first_get_result_ghosts_are_excluded(self):
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1167
        graph = self.make_graph({
1168
            'head':['child', 'ghost'],
1169
            'child':[NULL_REVISION],
1170
            NULL_REVISION:[],
1171
            })
1172
        search = graph._make_breadth_first_searcher(['head'])
1173
        # using next:
1174
        expected = [
1175
            (set(['head']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1176
             (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.
1177
             ['head'], None, None),
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1178
            (set(['head', 'child', 'ghost']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1179
             (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.
1180
             ['head', 'child'], None, None),
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1181
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1182
        self.assertSeenAndResult(expected, search, search.next)
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1183
        # using next_with_ghosts:
1184
        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.
1185
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.3 by Robert Collins
Automatically exclude ghosts.
1186
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1187
    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.
1188
        graph = self.make_graph({
1189
            'head':['child'],
1190
            'child':[NULL_REVISION],
1191
            NULL_REVISION:[],
1192
            })
1193
        search = graph._make_breadth_first_searcher(['head'])
1194
        # using next:
1195
        expected = [
1196
            (set(['head', 'ghost']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1197
             (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.
1198
             ['head'], ['ghost'], None),
3184.1.4 by Robert Collins
Correctly exclude ghosts when ghosts are started on an existing search.
1199
            (set(['head', 'child', 'ghost']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1200
             (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.
1201
             ['head', 'child'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1202
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1203
        self.assertSeenAndResult(expected, search, search.next)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1204
        # using next_with_ghosts:
1205
        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.
1206
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1207
1208
    def test_breadth_first_revision_count_includes_NULL_REVISION(self):
1209
        graph = self.make_graph({
1210
            'head':[NULL_REVISION],
1211
            NULL_REVISION:[],
1212
            })
1213
        search = graph._make_breadth_first_searcher(['head'])
1214
        # using next:
1215
        expected = [
1216
            (set(['head']),
1217
             (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.
1218
             ['head'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1219
            (set(['head', NULL_REVISION]),
1220
             (set(['head']), set([]), 2),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1221
             ['head', NULL_REVISION], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1222
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1223
        self.assertSeenAndResult(expected, search, search.next)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1224
        # using next_with_ghosts:
1225
        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.
1226
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1227
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1228
    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.
1229
        # StopIteration should not invalid anything..
1230
        graph = self.make_graph({
1231
            'head':[NULL_REVISION],
1232
            NULL_REVISION:[],
1233
            })
1234
        search = graph._make_breadth_first_searcher(['head'])
1235
        # using next:
1236
        expected = [
1237
            (set(['head']),
1238
             (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.
1239
             ['head'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1240
            (set(['head', 'ghost', NULL_REVISION]),
1241
             (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.
1242
             ['head', NULL_REVISION], ['ghost'], None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1243
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1244
        self.assertSeenAndResult(expected, search, search.next)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1245
        self.assertRaises(StopIteration, search.next)
1246
        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.
1247
        result = search.get_result()
4152.1.2 by Robert Collins
Add streaming from a stacked branch when the sort order is compatible with doing so.
1248
        self.assertEqual(('search', set(['ghost', 'head']), set(['ghost']), 2),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1249
            result.get_recipe())
1250
        self.assertEqual(set(['head', NULL_REVISION]), result.get_keys())
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1251
        # using next_with_ghosts:
1252
        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.
1253
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
1254
        self.assertRaises(StopIteration, search.next)
1255
        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.
1256
        result = search.get_result()
4152.1.2 by Robert Collins
Add streaming from a stacked branch when the sort order is compatible with doing so.
1257
        self.assertEqual(('search', set(['ghost', 'head']), set(['ghost']), 2),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
1258
            result.get_recipe())
1259
        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.
1260
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1261
3445.1.2 by John Arbash Meinel
Handle when a known revision is an ancestor.
1262
class TestFindUniqueAncestors(TestGraphBase):
3377.3.24 by John Arbash Meinel
For some reason find_unique_ancestors is much slower than it should be.
1263
3377.3.21 by John Arbash Meinel
Simple brute-force implementation of find_unique_ancestors
1264
    def assertFindUniqueAncestors(self, graph, expected, node, common):
1265
        actual = graph.find_unique_ancestors(node, common)
1266
        self.assertEqual(expected, sorted(actual))
1267
1268
    def test_empty_set(self):
1269
        graph = self.make_graph(ancestry_1)
1270
        self.assertFindUniqueAncestors(graph, [], 'rev1', ['rev1'])
1271
        self.assertFindUniqueAncestors(graph, [], 'rev2b', ['rev2b'])
1272
        self.assertFindUniqueAncestors(graph, [], 'rev3', ['rev1', 'rev3'])
1273
1274
    def test_single_node(self):
1275
        graph = self.make_graph(ancestry_1)
1276
        self.assertFindUniqueAncestors(graph, ['rev2a'], 'rev2a', ['rev1'])
1277
        self.assertFindUniqueAncestors(graph, ['rev2b'], 'rev2b', ['rev1'])
1278
        self.assertFindUniqueAncestors(graph, ['rev3'], 'rev3', ['rev2a'])
1279
3377.3.24 by John Arbash Meinel
For some reason find_unique_ancestors is much slower than it should be.
1280
    def test_minimal_ancestry(self):
1281
        graph = self.make_breaking_graph(extended_history_shortcut,
1282
                                         [NULL_REVISION, 'a', 'b'])
1283
        self.assertFindUniqueAncestors(graph, ['e'], 'e', ['d'])
1284
3377.3.25 by John Arbash Meinel
A few more minimal ancestry checks
1285
        graph = self.make_breaking_graph(extended_history_shortcut,
1286
                                         ['b'])
1287
        self.assertFindUniqueAncestors(graph, ['f'], 'f', ['a', 'd'])
1288
1289
        graph = self.make_breaking_graph(complex_shortcut,
3377.3.27 by John Arbash Meinel
some simple updates
1290
                                         ['a', 'b'])
3377.3.25 by John Arbash Meinel
A few more minimal ancestry checks
1291
        self.assertFindUniqueAncestors(graph, ['h'], 'h', ['i'])
1292
        self.assertFindUniqueAncestors(graph, ['e', 'g', 'i'], 'i', ['h'])
3377.3.27 by John Arbash Meinel
some simple updates
1293
        self.assertFindUniqueAncestors(graph, ['h'], 'h', ['g'])
3377.3.26 by John Arbash Meinel
Found a graph leak.
1294
        self.assertFindUniqueAncestors(graph, ['h'], 'h', ['j'])
1295
3377.3.21 by John Arbash Meinel
Simple brute-force implementation of find_unique_ancestors
1296
    def test_in_ancestry(self):
1297
        graph = self.make_graph(ancestry_1)
1298
        self.assertFindUniqueAncestors(graph, [], 'rev1', ['rev3'])
1299
        self.assertFindUniqueAncestors(graph, [], 'rev2b', ['rev4'])
1300
1301
    def test_multiple_revisions(self):
1302
        graph = self.make_graph(ancestry_1)
1303
        self.assertFindUniqueAncestors(graph,
1304
            ['rev4'], 'rev4', ['rev3', 'rev2b'])
1305
        self.assertFindUniqueAncestors(graph,
1306
            ['rev2a', 'rev3', 'rev4'], 'rev4', ['rev2b'])
1307
3377.3.22 by John Arbash Meinel
include some tests using the complex graphs
1308
    def test_complex_shortcut(self):
1309
        graph = self.make_graph(complex_shortcut)
1310
        self.assertFindUniqueAncestors(graph,
1311
            ['h', 'n'], 'n', ['m'])
1312
        self.assertFindUniqueAncestors(graph,
1313
            ['e', 'i', 'm'], 'm', ['n'])
1314
1315
    def test_complex_shortcut2(self):
1316
        graph = self.make_graph(complex_shortcut2)
1317
        self.assertFindUniqueAncestors(graph,
1318
            ['j', 'u'], 'u', ['t'])
1319
        self.assertFindUniqueAncestors(graph,
1320
            ['t'], 't', ['u'])
1321
3377.3.35 by John Arbash Meinel
Add a test that exercises the multiple interesting unique code
1322
    def test_multiple_interesting_unique(self):
1323
        graph = self.make_graph(multiple_interesting_unique)
1324
        self.assertFindUniqueAncestors(graph,
1325
            ['j', 'y'], 'y', ['z'])
1326
        self.assertFindUniqueAncestors(graph,
1327
            ['p', 'z'], 'z', ['y'])
1328
3377.3.36 by John Arbash Meinel
Small updates, try to write a test for the race condition.
1329
    def test_racing_shortcuts(self):
1330
        graph = self.make_graph(racing_shortcuts)
1331
        self.assertFindUniqueAncestors(graph,
1332
            ['p', 'q', 'z'], 'z', ['y'])
1333
        self.assertFindUniqueAncestors(graph,
1334
            ['h', 'i', 'j', 'y'], 'j', ['z'])
1335
3377.3.21 by John Arbash Meinel
Simple brute-force implementation of find_unique_ancestors
1336
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1337
class TestGraphFindDistanceToNull(TestGraphBase):
3445.1.1 by John Arbash Meinel
Start working on a new Graph api to make finding revision numbers faster.
1338
    """Test an api that should be able to compute a revno"""
1339
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1340
    def assertFindDistance(self, revno, graph, target_id, known_ids):
1341
        """Assert the output of Graph.find_distance_to_null()"""
1342
        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.
1343
        self.assertEqual(revno, actual)
1344
1345
    def test_nothing_known(self):
1346
        graph = self.make_graph(ancestry_1)
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1347
        self.assertFindDistance(0, graph, NULL_REVISION, [])
1348
        self.assertFindDistance(1, graph, 'rev1', [])
1349
        self.assertFindDistance(2, graph, 'rev2a', [])
1350
        self.assertFindDistance(2, graph, 'rev2b', [])
1351
        self.assertFindDistance(3, graph, 'rev3', [])
1352
        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.
1353
1354
    def test_rev_is_ghost(self):
1355
        graph = self.make_graph(ancestry_1)
1356
        e = self.assertRaises(errors.GhostRevisionsHaveNoRevno,
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1357
                              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.
1358
        self.assertEqual('rev_missing', e.revision_id)
1359
        self.assertEqual('rev_missing', e.ghost_revision_id)
1360
1361
    def test_ancestor_is_ghost(self):
1362
        graph = self.make_graph({'rev':['parent']})
1363
        e = self.assertRaises(errors.GhostRevisionsHaveNoRevno,
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1364
                              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.
1365
        self.assertEqual('rev', e.revision_id)
1366
        self.assertEqual('parent', e.ghost_revision_id)
1367
3445.1.2 by John Arbash Meinel
Handle when a known revision is an ancestor.
1368
    def test_known_in_ancestry(self):
1369
        graph = self.make_graph(ancestry_1)
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1370
        self.assertFindDistance(2, graph, 'rev2a', [('rev1', 1)])
1371
        self.assertFindDistance(3, graph, 'rev3', [('rev2a', 2)])
3445.1.2 by John Arbash Meinel
Handle when a known revision is an ancestor.
1372
1373
    def test_known_in_ancestry_limits(self):
3445.1.3 by John Arbash Meinel
Search from all of the known revisions.
1374
        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'
1375
        self.assertFindDistance(4, graph, 'rev4', [('rev3', 3)])
3445.1.2 by John Arbash Meinel
Handle when a known revision is an ancestor.
1376
3445.1.3 by John Arbash Meinel
Search from all of the known revisions.
1377
    def test_target_is_ancestor(self):
1378
        graph = self.make_graph(ancestry_1)
3445.1.4 by John Arbash Meinel
Change the function to be called 'find_distance_to_null'
1379
        self.assertFindDistance(2, graph, 'rev2a', [('rev3', 3)])
3445.1.3 by John Arbash Meinel
Search from all of the known revisions.
1380
1381
    def test_target_is_ancestor_limits(self):
1382
        """We shouldn't search all history if we run into ourselves"""
1383
        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'
1384
        self.assertFindDistance(3, graph, 'rev3', [('rev4', 4)])
3445.1.3 by John Arbash Meinel
Search from all of the known revisions.
1385
1386
    def test_target_parallel_to_known_limits(self):
3445.1.8 by John Arbash Meinel
Clarity tweaks recommended by Ian
1387
        # 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.
1388
        # eventually converge
1389
        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'
1390
        self.assertFindDistance(6, graph, 'f', [('g', 6)])
1391
        self.assertFindDistance(7, graph, 'h', [('g', 6)])
1392
        self.assertFindDistance(8, graph, 'i', [('g', 6)])
1393
        self.assertFindDistance(6, graph, 'g', [('i', 8)])
3445.1.3 by John Arbash Meinel
Search from all of the known revisions.
1394
3445.1.1 by John Arbash Meinel
Start working on a new Graph api to make finding revision numbers faster.
1395
3514.2.8 by John Arbash Meinel
The insertion ordering into the weave has an impact on conflicts.
1396
class TestFindMergeOrder(TestGraphBase):
1397
1398
    def assertMergeOrder(self, expected, graph, tip, base_revisions):
1399
        self.assertEqual(expected, graph.find_merge_order(tip, base_revisions))
1400
1401
    def test_parents(self):
1402
        graph = self.make_graph(ancestry_1)
1403
        self.assertMergeOrder(['rev3', 'rev2b'], graph, 'rev4',
1404
                                                        ['rev3', 'rev2b'])
1405
        self.assertMergeOrder(['rev3', 'rev2b'], graph, 'rev4',
1406
                                                        ['rev2b', 'rev3'])
1407
1408
    def test_ancestors(self):
1409
        graph = self.make_graph(ancestry_1)
1410
        self.assertMergeOrder(['rev1', 'rev2b'], graph, 'rev4',
1411
                                                        ['rev1', 'rev2b'])
1412
        self.assertMergeOrder(['rev1', 'rev2b'], graph, 'rev4',
1413
                                                        ['rev2b', 'rev1'])
1414
1415
    def test_shortcut_one_ancestor(self):
1416
        # When we have enough info, we can stop searching
1417
        graph = self.make_breaking_graph(ancestry_1, ['rev3', 'rev2b', 'rev4'])
1418
        # Single ancestors shortcut right away
1419
        self.assertMergeOrder(['rev3'], graph, 'rev4', ['rev3'])
1420
1421
    def test_shortcut_after_one_ancestor(self):
1422
        graph = self.make_breaking_graph(ancestry_1, ['rev2a', 'rev2b'])
1423
        self.assertMergeOrder(['rev3', 'rev1'], graph, 'rev4', ['rev1', 'rev3'])
1424
1425
5365.6.1 by Aaron Bentley
Implement find_descendants.
1426
class TestFindDescendants(TestGraphBase):
1427
1428
    def test_find_descendants_rev1_rev3(self):
1429
        graph = self.make_graph(ancestry_1)
1430
        descendants = graph.find_descendants('rev1', 'rev3')
1431
        self.assertEqual(set(['rev1', 'rev2a', 'rev3']), descendants)
1432
1433
    def test_find_descendants_rev1_rev4(self):
1434
        graph = self.make_graph(ancestry_1)
1435
        descendants = graph.find_descendants('rev1', 'rev4')
1436
        self.assertEqual(set(['rev1', 'rev2a', 'rev2b', 'rev3', 'rev4']),
1437
                         descendants)
1438
1439
    def test_find_descendants_rev2a_rev4(self):
1440
        graph = self.make_graph(ancestry_1)
1441
        descendants = graph.find_descendants('rev2a', 'rev4')
1442
        self.assertEqual(set(['rev2a', 'rev3', 'rev4']), descendants)
1443
5365.6.3 by Aaron Bentley
Implement find_lefthand_merger.
1444
class TestFindLefthandMerger(TestGraphBase):
1445
1446
    def check_merger(self, result, ancestry, merged, tip):
1447
        graph = self.make_graph(ancestry)
1448
        self.assertEqual(result, graph.find_lefthand_merger(merged, tip))
1449
1450
    def test_find_lefthand_merger_rev2b(self):
1451
        self.check_merger('rev4', ancestry_1, 'rev2b', 'rev4')
1452
1453
    def test_find_lefthand_merger_rev2a(self):
1454
        self.check_merger('rev2a', ancestry_1, 'rev2a', 'rev4')
1455
1456
    def test_find_lefthand_merger_rev4(self):
1457
        self.check_merger(None, ancestry_1, 'rev4', 'rev2a')
1458
1459
    def test_find_lefthand_merger_f(self):
1460
        self.check_merger('i', complex_shortcut, 'f', 'm')
1461
1462
    def test_find_lefthand_merger_g(self):
1463
        self.check_merger('i', complex_shortcut, 'g', 'm')
1464
1465
    def test_find_lefthand_merger_h(self):
1466
        self.check_merger('n', complex_shortcut, 'h', 'n')
1467
5365.6.1 by Aaron Bentley
Implement find_descendants.
1468
1469
class TestGetChildMap(TestGraphBase):
1470
1471
    def test_get_child_map(self):
1472
        graph = self.make_graph(ancestry_1)
1473
        child_map = graph.get_child_map(['rev4', 'rev3', 'rev2a', 'rev2b'])
1474
        self.assertEqual({'rev1': ['rev2a', 'rev2b'],
1475
                          'rev2a': ['rev3'],
1476
                          'rev2b': ['rev4'],
1477
                          'rev3': ['rev4']},
1478
                          child_map)
1479
1480
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1481
class TestCachingParentsProvider(tests.TestCase):
4190.1.1 by Robert Collins
Negatively cache misses during read-locks in RemoteRepository.
1482
    """These tests run with:
1483
1484
    self.inst_pp, a recording parents provider with a graph of a->b, and b is a
1485
    ghost.
1486
    self.caching_pp, a CachingParentsProvider layered on inst_pp.
1487
    """
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1488
1489
    def setUp(self):
1490
        super(TestCachingParentsProvider, self).setUp()
1491
        dict_pp = _mod_graph.DictParentsProvider({'a':('b',)})
1492
        self.inst_pp = InstrumentedParentsProvider(dict_pp)
1493
        self.caching_pp = _mod_graph.CachingParentsProvider(self.inst_pp)
1494
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1495
    def test_get_parent_map(self):
1496
        """Requesting the same revision should be returned from cache"""
3835.1.16 by Aaron Bentley
Updates from review
1497
        self.assertEqual({}, self.caching_pp._cache)
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1498
        self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
1499
        self.assertEqual(['a'], self.inst_pp.calls)
1500
        self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
1501
        # No new call, as it should have been returned from the cache
1502
        self.assertEqual(['a'], self.inst_pp.calls)
3835.1.16 by Aaron Bentley
Updates from review
1503
        self.assertEqual({'a':('b',)}, self.caching_pp._cache)
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1504
1505
    def test_get_parent_map_not_present(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1506
        """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()
1507
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1508
        self.assertEqual(['b'], self.inst_pp.calls)
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1509
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1510
        # No new calls
1511
        self.assertEqual(['b'], self.inst_pp.calls)
1512
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1513
    def test_get_parent_map_mixed(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1514
        """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()
1515
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1516
        self.assertEqual(['b'], self.inst_pp.calls)
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1517
        self.assertEqual({'a':('b',)},
1518
                         self.caching_pp.get_parent_map(['a', 'b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1519
        self.assertEqual(['b', 'a'], self.inst_pp.calls)
1520
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1521
    def test_get_parent_map_repeated(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1522
        """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()
1523
        self.assertEqual({'a':('b',)},
1524
                         self.caching_pp.get_parent_map(['b', 'a', 'b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1525
        # Use sorted because we don't care about the order, just that each is
1526
        # only present 1 time.
1527
        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.
1528
4190.1.4 by Robert Collins
Cache ghosts when we can get them from a RemoteRepository in get_parent_map.
1529
    def test_note_missing_key(self):
1530
        """After noting that a key is missing it is cached."""
1531
        self.caching_pp.note_missing_key('b')
1532
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
1533
        self.assertEqual([], self.inst_pp.calls)
1534
        self.assertEqual(set(['b']), self.caching_pp.missing_keys)
1535
3514.2.14 by John Arbash Meinel
Bring in the code to collapse linear portions of the graph.
1536
3835.1.12 by Aaron Bentley
Unify CachingExtraParentsProvider and CachingParentsProvider.
1537
class TestCachingParentsProviderExtras(tests.TestCaseWithTransport):
3835.1.16 by Aaron Bentley
Updates from review
1538
    """Test the behaviour when parents are provided that were not requested."""
3835.1.10 by Aaron Bentley
Move CachingExtraParentsProvider to Graph
1539
1540
    def setUp(self):
3835.1.12 by Aaron Bentley
Unify CachingExtraParentsProvider and CachingParentsProvider.
1541
        super(TestCachingParentsProviderExtras, self).setUp()
3835.1.11 by Aaron Bentley
Rename FakeParentsProvider to ExtraParentsProvider
1542
        class ExtraParentsProvider(object):
3835.1.10 by Aaron Bentley
Move CachingExtraParentsProvider to Graph
1543
1544
            def get_parent_map(self, keys):
1545
                return {'rev1': [], 'rev2': ['rev1',]}
1546
3835.1.11 by Aaron Bentley
Rename FakeParentsProvider to ExtraParentsProvider
1547
        self.inst_pp = InstrumentedParentsProvider(ExtraParentsProvider())
3835.1.12 by Aaron Bentley
Unify CachingExtraParentsProvider and CachingParentsProvider.
1548
        self.caching_pp = _mod_graph.CachingParentsProvider(
1549
            get_parent_map=self.inst_pp.get_parent_map)
3835.1.10 by Aaron Bentley
Move CachingExtraParentsProvider to Graph
1550
1551
    def test_uncached(self):
3835.1.12 by Aaron Bentley
Unify CachingExtraParentsProvider and CachingParentsProvider.
1552
        self.caching_pp.disable_cache()
3835.1.10 by Aaron Bentley
Move CachingExtraParentsProvider to Graph
1553
        self.assertEqual({'rev1': []},
1554
                         self.caching_pp.get_parent_map(['rev1']))
1555
        self.assertEqual(['rev1'], self.inst_pp.calls)
3835.1.16 by Aaron Bentley
Updates from review
1556
        self.assertIs(None, self.caching_pp._cache)
1557
1558
    def test_cache_initially_empty(self):
1559
        self.assertEqual({}, self.caching_pp._cache)
3835.1.10 by Aaron Bentley
Move CachingExtraParentsProvider to Graph
1560
1561
    def test_cached(self):
1562
        self.assertEqual({'rev1': []},
1563
                         self.caching_pp.get_parent_map(['rev1']))
1564
        self.assertEqual(['rev1'], self.inst_pp.calls)
1565
        self.assertEqual({'rev1': [], 'rev2': ['rev1']},
3835.1.16 by Aaron Bentley
Updates from review
1566
                         self.caching_pp._cache)
3835.1.10 by Aaron Bentley
Move CachingExtraParentsProvider to Graph
1567
        self.assertEqual({'rev1': []},
1568
                          self.caching_pp.get_parent_map(['rev1']))
1569
        self.assertEqual(['rev1'], self.inst_pp.calls)
3835.1.16 by Aaron Bentley
Updates from review
1570
1571
    def test_disable_cache_clears_cache(self):
1572
        # Put something in the cache
1573
        self.caching_pp.get_parent_map(['rev1'])
1574
        self.assertEqual(2, len(self.caching_pp._cache))
3835.1.10 by Aaron Bentley
Move CachingExtraParentsProvider to Graph
1575
        self.caching_pp.disable_cache()
3835.1.16 by Aaron Bentley
Updates from review
1576
        self.assertIs(None, self.caching_pp._cache)
3835.1.10 by Aaron Bentley
Move CachingExtraParentsProvider to Graph
1577
3835.1.19 by Aaron Bentley
Raise exception when caching is enabled twice.
1578
    def test_enable_cache_raises(self):
3835.1.20 by Aaron Bentley
Change custom error to an AssertionError.
1579
        e = self.assertRaises(AssertionError, self.caching_pp.enable_cache)
3835.1.19 by Aaron Bentley
Raise exception when caching is enabled twice.
1580
        self.assertEqual('Cache enabled when already enabled.', str(e))
1581
3835.1.15 by Aaron Bentley
Allow miss caching to be disabled.
1582
    def test_cache_misses(self):
1583
        self.caching_pp.get_parent_map(['rev3'])
1584
        self.caching_pp.get_parent_map(['rev3'])
1585
        self.assertEqual(['rev3'], self.inst_pp.calls)
1586
1587
    def test_no_cache_misses(self):
3835.1.19 by Aaron Bentley
Raise exception when caching is enabled twice.
1588
        self.caching_pp.disable_cache()
3835.1.15 by Aaron Bentley
Allow miss caching to be disabled.
1589
        self.caching_pp.enable_cache(cache_misses=False)
1590
        self.caching_pp.get_parent_map(['rev3'])
1591
        self.caching_pp.get_parent_map(['rev3'])
1592
        self.assertEqual(['rev3', 'rev3'], self.inst_pp.calls)
1593
3835.1.10 by Aaron Bentley
Move CachingExtraParentsProvider to Graph
1594
    def test_cache_extras(self):
1595
        self.assertEqual({}, self.caching_pp.get_parent_map(['rev3']))
1596
        self.assertEqual({'rev2': ['rev1']},
1597
                         self.caching_pp.get_parent_map(['rev2']))
1598
        self.assertEqual(['rev3'], self.inst_pp.calls)
1599
1600
3514.2.14 by John Arbash Meinel
Bring in the code to collapse linear portions of the graph.
1601
class TestCollapseLinearRegions(tests.TestCase):
1602
1603
    def assertCollapsed(self, collapsed, original):
1604
        self.assertEqual(collapsed,
1605
                         _mod_graph.collapse_linear_regions(original))
1606
1607
    def test_collapse_nothing(self):
1608
        d = {1:[2, 3], 2:[], 3:[]}
1609
        self.assertCollapsed(d, d)
1610
        d = {1:[2], 2:[3, 4], 3:[5], 4:[5], 5:[]}
1611
        self.assertCollapsed(d, d)
1612
1613
    def test_collapse_chain(self):
1614
        # Any time we have a linear chain, we should be able to collapse
1615
        d = {1:[2], 2:[3], 3:[4], 4:[5], 5:[]}
1616
        self.assertCollapsed({1:[5], 5:[]}, d)
1617
        d = {5:[4], 4:[3], 3:[2], 2:[1], 1:[]}
1618
        self.assertCollapsed({5:[1], 1:[]}, d)
1619
        d = {5:[3], 3:[4], 4:[1], 1:[2], 2:[]}
1620
        self.assertCollapsed({5:[2], 2:[]}, d)
1621
1622
    def test_collapse_with_multiple_children(self):
1623
        #    7
1624
        #    |
1625
        #    6
1626
        #   / \
1627
        #  4   5
1628
        #  |   |
3514.2.16 by John Arbash Meinel
Review feedback from Ian.
1629
        #  2   3
3514.2.14 by John Arbash Meinel
Bring in the code to collapse linear portions of the graph.
1630
        #   \ /
1631
        #    1
1632
        #
1633
        # 4 and 5 cannot be removed because 6 has 2 children
3514.2.16 by John Arbash Meinel
Review feedback from Ian.
1634
        # 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.
1635
        d = {1:[2, 3], 2:[4], 4:[6], 3:[5], 5:[6], 6:[7], 7:[]}
1636
        self.assertCollapsed(d, d)
4070.9.14 by Andrew Bennetts
Tweaks requested by Robert's review.
1637
1638
4819.2.3 by John Arbash Meinel
Add a GraphThunkIdsToKeys as a tested class.
1639
class TestGraphThunkIdsToKeys(tests.TestCase):
1640
1641
    def test_heads(self):
1642
        # A
1643
        # |\
1644
        # B C
1645
        # |/
1646
        # D
1647
        d = {('D',): [('B',), ('C',)], ('C',):[('A',)],
1648
             ('B',): [('A',)], ('A',): []}
1649
        g = _mod_graph.Graph(_mod_graph.DictParentsProvider(d))
1650
        graph_thunk = _mod_graph.GraphThunkIdsToKeys(g)
1651
        self.assertEqual(['D'], sorted(graph_thunk.heads(['D', 'A'])))
1652
        self.assertEqual(['D'], sorted(graph_thunk.heads(['D', 'B'])))
1653
        self.assertEqual(['D'], sorted(graph_thunk.heads(['D', 'C'])))
1654
        self.assertEqual(['B', 'C'], sorted(graph_thunk.heads(['B', 'C'])))
1655
5559.3.1 by Jelmer Vernooij
Add GraphThunkIdsToKeys.add_node.
1656
    def test_add_node(self):
1657
        d = {('C',):[('A',)], ('B',): [('A',)], ('A',): []}
1658
        g = _mod_graph.KnownGraph(d)
1659
        graph_thunk = _mod_graph.GraphThunkIdsToKeys(g)
1660
        graph_thunk.add_node("D", ["A", "C"])
1661
        self.assertEqual(['B', 'D'],
1662
            sorted(graph_thunk.heads(['D', 'B', 'A'])))
1663
4819.2.3 by John Arbash Meinel
Add a GraphThunkIdsToKeys as a tested class.
1664
4152.1.2 by Robert Collins
Add streaming from a stacked branch when the sort order is compatible with doing so.
1665
class TestPendingAncestryResultGetKeys(TestCaseWithMemoryTransport):
4070.9.14 by Andrew Bennetts
Tweaks requested by Robert's review.
1666
    """Tests for bzrlib.graph.PendingAncestryResult."""
1667
1668
    def test_get_keys(self):
1669
        builder = self.make_branch_builder('b')
1670
        builder.start_series()
1671
        builder.build_snapshot('rev-1', None, [
1672
            ('add', ('', 'root-id', 'directory', ''))])
1673
        builder.build_snapshot('rev-2', ['rev-1'], [])
1674
        builder.finish_series()
1675
        repo = builder.get_branch().repository
1676
        repo.lock_read()
1677
        self.addCleanup(repo.unlock)
4152.1.2 by Robert Collins
Add streaming from a stacked branch when the sort order is compatible with doing so.
1678
        result = _mod_graph.PendingAncestryResult(['rev-2'], repo)
1679
        self.assertEqual(set(['rev-1', 'rev-2']), set(result.get_keys()))
4070.9.14 by Andrew Bennetts
Tweaks requested by Robert's review.
1680
4343.3.11 by John Arbash Meinel
Change PendingAncestryResult to strip ghosts from .get_keys()
1681
    def test_get_keys_excludes_ghosts(self):
1682
        builder = self.make_branch_builder('b')
1683
        builder.start_series()
1684
        builder.build_snapshot('rev-1', None, [
1685
            ('add', ('', 'root-id', 'directory', ''))])
1686
        builder.build_snapshot('rev-2', ['rev-1', 'ghost'], [])
1687
        builder.finish_series()
1688
        repo = builder.get_branch().repository
1689
        repo.lock_read()
1690
        self.addCleanup(repo.unlock)
1691
        result = _mod_graph.PendingAncestryResult(['rev-2'], repo)
1692
        self.assertEqual(sorted(['rev-1', 'rev-2']), sorted(result.get_keys()))
1693
4098.1.1 by Andrew Bennetts
Fix a bug with how PendingAncestryResult.get_keys handles NULL_REVISION.
1694
    def test_get_keys_excludes_null(self):
1695
        # Make a 'graph' with an iter_ancestry that returns NULL_REVISION
1696
        # somewhere other than the last element, which can happen in real
1697
        # ancestries.
1698
        class StubGraph(object):
1699
            def iter_ancestry(self, keys):
1700
                return [(NULL_REVISION, ()), ('foo', (NULL_REVISION,))]
4152.1.2 by Robert Collins
Add streaming from a stacked branch when the sort order is compatible with doing so.
1701
        result = _mod_graph.PendingAncestryResult(['rev-3'], None)
1702
        result_keys = result._get_keys(StubGraph())
4098.1.1 by Andrew Bennetts
Fix a bug with how PendingAncestryResult.get_keys handles NULL_REVISION.
1703
        # Only the non-null keys from the ancestry appear.
4152.1.2 by Robert Collins
Add streaming from a stacked branch when the sort order is compatible with doing so.
1704
        self.assertEqual(set(['foo']), set(result_keys))
1705
1706
1707
class TestPendingAncestryResultRefine(TestGraphBase):
1708
1709
    def test_refine(self):
1710
        # Used when pulling from a stacked repository, so test some revisions
1711
        # being satisfied from the stacking branch.
1712
        g = self.make_graph(
1713
            {"tip":["mid"], "mid":["base"], "tag":["base"],
1714
             "base":[NULL_REVISION], NULL_REVISION:[]})
1715
        result = _mod_graph.PendingAncestryResult(['tip', 'tag'], None)
1716
        result = result.refine(set(['tip']), set(['mid']))
1717
        self.assertEqual(set(['mid', 'tag']), result.heads)
1718
        result = result.refine(set(['mid', 'tag', 'base']),
1719
            set([NULL_REVISION]))
1720
        self.assertEqual(set([NULL_REVISION]), result.heads)
1721
        self.assertTrue(result.is_empty())
1722
1723
1724
class TestSearchResultRefine(TestGraphBase):
1725
1726
    def test_refine(self):
1727
        # Used when pulling from a stacked repository, so test some revisions
1728
        # being satisfied from the stacking branch.
1729
        g = self.make_graph(
1730
            {"tip":["mid"], "mid":["base"], "tag":["base"],
1731
             "base":[NULL_REVISION], NULL_REVISION:[]})
1732
        result = _mod_graph.SearchResult(set(['tip', 'tag']),
1733
            set([NULL_REVISION]), 4, set(['tip', 'mid', 'tag', 'base']))
1734
        result = result.refine(set(['tip']), set(['mid']))
1735
        recipe = result.get_recipe()
1736
        # We should be starting from tag (original head) and mid (seen ref)
1737
        self.assertEqual(set(['mid', 'tag']), recipe[1])
1738
        # We should be stopping at NULL (original stop) and tip (seen head)
1739
        self.assertEqual(set([NULL_REVISION, 'tip']), recipe[2])
1740
        self.assertEqual(3, recipe[3])
1741
        result = result.refine(set(['mid', 'tag', 'base']),
1742
            set([NULL_REVISION]))
1743
        recipe = result.get_recipe()
1744
        # We should be starting from nothing (NULL was known as a cut point)
1745
        self.assertEqual(set([]), recipe[1])
1746
        # We should be stopping at NULL (original stop) and tip (seen head) and
1747
        # tag (seen head) and mid(seen mid-point head). We could come back and
1748
        # define this as not including mid, for minimal results, but it is
1749
        # still 'correct' to include mid, and simpler/easier.
1750
        self.assertEqual(set([NULL_REVISION, 'tip', 'tag', 'mid']), recipe[2])
1751
        self.assertEqual(0, recipe[3])
1752
        self.assertTrue(result.is_empty())