~bzr-pqm/bzr/bzr.dev

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