/brz/remove-bazaar

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