~bzr-pqm/bzr/bzr.dev

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