~bzr-pqm/bzr/bzr.dev

2490.2.5 by Aaron Bentley
Use GraphWalker.unique_ancestor to determine merge base
1
# Copyright (C) 2007 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
2490.2.28 by Aaron Bentley
Fix handling of null revision
17
from bzrlib import (
18
    errors,
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
19
    graph as _mod_graph,
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
20
    symbol_versioning,
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
21
    tests,
2490.2.28 by Aaron Bentley
Fix handling of null revision
22
    )
2490.2.1 by Aaron Bentley
Start work on GraphWalker
23
from bzrlib.revision import NULL_REVISION
24
from bzrlib.tests import TestCaseWithMemoryTransport
25
2490.2.25 by Aaron Bentley
Update from review
26
27
# Ancestry 1:
28
#
29
#  NULL_REVISION
30
#       |
31
#     rev1
32
#      /\
33
#  rev2a rev2b
34
#     |    |
35
#   rev3  /
36
#     |  /
37
#   rev4
2490.2.2 by Aaron Bentley
add minimal-common-ancestor calculation
38
ancestry_1 = {'rev1': [NULL_REVISION], 'rev2a': ['rev1'], 'rev2b': ['rev1'],
39
              'rev3': ['rev2a'], 'rev4': ['rev3', 'rev2b']}
2490.2.25 by Aaron Bentley
Update from review
40
41
42
# Ancestry 2:
43
#
44
#  NULL_REVISION
45
#    /    \
46
# rev1a  rev1b
47
#   |
48
# rev2a
49
#   |
50
# rev3a
51
#   |
52
# rev4a
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
53
ancestry_2 = {'rev1a': [NULL_REVISION], 'rev2a': ['rev1a'],
54
              'rev1b': [NULL_REVISION], 'rev3a': ['rev2a'], 'rev4a': ['rev3a']}
2490.2.2 by Aaron Bentley
add minimal-common-ancestor calculation
55
2490.2.25 by Aaron Bentley
Update from review
56
57
# Criss cross ancestry
58
#
59
#     NULL_REVISION
60
#         |
61
#        rev1
62
#        /  \
63
#    rev2a  rev2b
64
#       |\  /|
65
#       |  X |
66
#       |/  \|
67
#    rev3a  rev3b
2490.2.3 by Aaron Bentley
Implement new merge base picker
68
criss_cross = {'rev1': [NULL_REVISION], 'rev2a': ['rev1'], 'rev2b': ['rev1'],
69
               'rev3a': ['rev2a', 'rev2b'], 'rev3b': ['rev2b', 'rev2a']}
70
2490.2.25 by Aaron Bentley
Update from review
71
72
# Criss-cross 2
73
#
74
#  NULL_REVISION
75
#    /   \
76
# rev1a  rev1b
77
#   |\   /|
78
#   | \ / |
79
#   |  X  |
80
#   | / \ |
81
#   |/   \|
82
# rev2a  rev2b
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
83
criss_cross2 = {'rev1a': [NULL_REVISION], 'rev1b': [NULL_REVISION],
84
                'rev2a': ['rev1a', 'rev1b'], 'rev2b': ['rev1b', 'rev1a']}
85
2490.2.25 by Aaron Bentley
Update from review
86
87
# Mainline:
88
#
89
#  NULL_REVISION
90
#       |
91
#      rev1
92
#      /  \
93
#      | rev2b
94
#      |  /
95
#     rev2a
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
96
mainline = {'rev1': [NULL_REVISION], 'rev2a': ['rev1', 'rev2b'],
97
            'rev2b': ['rev1']}
98
2490.2.25 by Aaron Bentley
Update from review
99
100
# feature branch:
101
#
102
#  NULL_REVISION
103
#       |
104
#      rev1
105
#       |
106
#     rev2b
107
#       |
108
#     rev3b
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
109
feature_branch = {'rev1': [NULL_REVISION],
110
                  'rev2b': ['rev1'], 'rev3b': ['rev2b']}
111
2490.2.25 by Aaron Bentley
Update from review
112
113
# History shortcut
114
#  NULL_REVISION
115
#       |
116
#     rev1------
117
#     /  \      \
118
#  rev2a rev2b rev2c
119
#    |  /   \   /
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
120
#  rev3a    rev3b
2490.2.9 by Aaron Bentley
Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors
121
history_shortcut = {'rev1': [NULL_REVISION], 'rev2a': ['rev1'],
122
                    'rev2b': ['rev1'], 'rev2c': ['rev1'],
123
                    'rev3a': ['rev2a', 'rev2b'], 'rev3b': ['rev2b', 'rev2c']}
124
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
125
# Extended history shortcut
126
#  NULL_REVISION
127
#       |
128
#       a
129
#       |\
130
#       b |
131
#       | |
132
#       c |
133
#       | |
134
#       d |
135
#       |\|
136
#       e f
137
extended_history_shortcut = {'a': [NULL_REVISION],
138
                             'b': ['a'],
139
                             'c': ['b'],
140
                             'd': ['c'],
141
                             'e': ['d'],
142
                             'f': ['a', 'd'],
143
                            }
144
145
# Double shortcut
146
# Both sides will see 'A' first, even though it is actually a decendent of a
147
# different common revision.
148
#
149
#  NULL_REVISION
150
#       |
151
#       a
152
#      /|\
153
#     / b \
154
#    /  |  \
155
#   |   c   |
156
#   |  / \  |
157
#   | d   e |
158
#   |/     \|
159
#   f       g
160
161
double_shortcut = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'],
162
                   'd':['c'], 'e':['c'], 'f':['a', 'd'],
163
                   'g':['a', 'e']}
164
165
# Complex shortcut
166
# This has a failure mode in that a shortcut will find some nodes in common,
167
# but the common searcher won't have time to find that one branch is actually
168
# in common. The extra nodes at the top are because we want to avoid
169
# walking off the graph. Specifically, node G should be considered common, but
170
# is likely to be seen by M long before the common searcher finds it.
171
#
172
# NULL_REVISION
173
#     |
174
#     a
175
#     |
176
#     b
177
#     |
178
#     c
179
#     |
180
#     d
181
#     |\
182
#     e f
183
#     | |\
184
#     i | h
185
#     |\| |
186
#     | g |
187
#     | | |
188
#     | j |
189
#     | | |
190
#     | k |
191
#     | | |
192
#     | l |
193
#     |/|/
194
#     m n
195
complex_shortcut = {'d':[NULL_REVISION],
196
                    'x':['d'], 'y':['x'],
197
                    'e':['y'], 'f':['d'], 'g':['f', 'i'], 'h':['f'],
198
                    'i':['e'], 'j':['g'], 'k':['j'],
199
                    'l':['k'], 'm':['i', 's'], 'n':['s', 'h'],
200
                    'o':['l'], 'p':['o'], 'q':['p'],
201
                    'r':['q'], 's':['r'],
202
                    }
203
204
# Shortcut with extra root
205
# We have a long history shortcut, and an extra root, which is why we can't
206
# stop searchers based on seeing NULL_REVISION
207
#  NULL_REVISION
208
#       |   |
209
#       a   |
210
#       |\  |
211
#       b | |
212
#       | | |
213
#       c | |
214
#       | | |
215
#       d | g
216
#       |\|/
217
#       e f
218
shortcut_extra_root = {'a': [NULL_REVISION],
219
                       'b': ['a'],
220
                       'c': ['b'],
221
                       'd': ['c'],
222
                       'e': ['d'],
223
                       'f': ['a', 'd', 'g'],
224
                       'g': [NULL_REVISION],
225
                      }
226
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
227
#  NULL_REVISION
228
#       |
229
#       f
230
#       |
231
#       e
232
#      / \
233
#     b   d
234
#     | \ |
235
#     a   c
236
237
boundary = {'a': ['b'], 'c': ['b', 'd'], 'b':['e'], 'd':['e'], 'e': ['f'],
238
            'f':[NULL_REVISION]}
239
240
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
241
# A graph that contains a ghost
242
#  NULL_REVISION
243
#       |
244
#       f
245
#       |
246
#       e   g
247
#      / \ /
248
#     b   d
249
#     | \ |
250
#     a   c
251
252
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.
253
              'e': ['f'], 'f':[NULL_REVISION], NULL_REVISION:()}
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
254
255
256
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
257
class InstrumentedParentsProvider(object):
258
259
    def __init__(self, parents_provider):
260
        self.calls = []
261
        self._real_parents_provider = parents_provider
262
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
263
    def get_parent_map(self, nodes):
264
        self.calls.extend(nodes)
265
        return self._real_parents_provider.get_parent_map(nodes)
266
2490.2.25 by Aaron Bentley
Update from review
267
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
268
class TestGraph(TestCaseWithMemoryTransport):
2490.2.1 by Aaron Bentley
Start work on GraphWalker
269
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
270
    def make_graph(self, ancestors):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
271
        return _mod_graph.Graph(_mod_graph.DictParentsProvider(ancestors))
2490.2.3 by Aaron Bentley
Implement new merge base picker
272
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
273
    def prepare_memory_tree(self, location):
274
        tree = self.make_branch_and_memory_tree(location)
2490.2.1 by Aaron Bentley
Start work on GraphWalker
275
        tree.lock_write()
276
        tree.add('.')
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
277
        return tree
278
279
    def build_ancestry(self, tree, ancestors):
2490.2.25 by Aaron Bentley
Update from review
280
        """Create an ancestry as specified by a graph dict
281
282
        :param tree: A tree to use
283
        :param ancestors: a dict of {node: [node_parent, ...]}
284
        """
2490.2.1 by Aaron Bentley
Start work on GraphWalker
285
        pending = [NULL_REVISION]
286
        descendants = {}
287
        for descendant, parents in ancestors.iteritems():
288
            for parent in parents:
289
                descendants.setdefault(parent, []).append(descendant)
290
        while len(pending) > 0:
291
            cur_node = pending.pop()
292
            for descendant in descendants.get(cur_node, []):
2490.2.3 by Aaron Bentley
Implement new merge base picker
293
                if tree.branch.repository.has_revision(descendant):
294
                    continue
2490.2.1 by Aaron Bentley
Start work on GraphWalker
295
                parents = [p for p in ancestors[descendant] if p is not
296
                           NULL_REVISION]
297
                if len([p for p in parents if not
298
                    tree.branch.repository.has_revision(p)]) > 0:
299
                    continue
300
                tree.set_parent_ids(parents)
2490.2.3 by Aaron Bentley
Implement new merge base picker
301
                if len(parents) > 0:
302
                    left_parent = parents[0]
303
                else:
304
                    left_parent = NULL_REVISION
2490.2.1 by Aaron Bentley
Start work on GraphWalker
305
                tree.branch.set_last_revision_info(
2490.2.3 by Aaron Bentley
Implement new merge base picker
306
                    len(tree.branch._lefthand_history(left_parent)),
307
                    left_parent)
2490.2.1 by Aaron Bentley
Start work on GraphWalker
308
                tree.commit(descendant, rev_id=descendant)
309
                pending.append(descendant)
2490.2.2 by Aaron Bentley
add minimal-common-ancestor calculation
310
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
311
    def test_lca(self):
2490.2.25 by Aaron Bentley
Update from review
312
        """Test finding least common ancestor.
2490.2.3 by Aaron Bentley
Implement new merge base picker
313
314
        ancestry_1 should always have a single common ancestor
315
        """
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
316
        graph = self.make_graph(ancestry_1)
2490.2.28 by Aaron Bentley
Fix handling of null revision
317
        self.assertRaises(errors.InvalidRevisionId, graph.find_lca, None)
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
318
        self.assertEqual(set([NULL_REVISION]),
319
                         graph.find_lca(NULL_REVISION, NULL_REVISION))
320
        self.assertEqual(set([NULL_REVISION]),
321
                         graph.find_lca(NULL_REVISION, 'rev1'))
322
        self.assertEqual(set(['rev1']), graph.find_lca('rev1', 'rev1'))
323
        self.assertEqual(set(['rev1']), graph.find_lca('rev2a', 'rev2b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
324
2520.4.104 by Aaron Bentley
Avoid infinite loop when there is no unique lca
325
    def test_no_unique_lca(self):
326
        """Test error when one revision is not in the graph"""
327
        graph = self.make_graph(ancestry_1)
328
        self.assertRaises(errors.NoCommonAncestor, graph.find_unique_lca,
329
                          'rev1', '1rev')
330
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
331
    def test_lca_criss_cross(self):
2490.2.25 by Aaron Bentley
Update from review
332
        """Test least-common-ancestor after a criss-cross merge."""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
333
        graph = self.make_graph(criss_cross)
2490.2.3 by Aaron Bentley
Implement new merge base picker
334
        self.assertEqual(set(['rev2a', 'rev2b']),
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
335
                         graph.find_lca('rev3a', 'rev3b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
336
        self.assertEqual(set(['rev2b']),
2490.2.25 by Aaron Bentley
Update from review
337
                         graph.find_lca('rev3a', 'rev3b', 'rev2b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
338
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
339
    def test_lca_shortcut(self):
2490.2.25 by Aaron Bentley
Update from review
340
        """Test least-common ancestor on this history shortcut"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
341
        graph = self.make_graph(history_shortcut)
342
        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
343
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
344
    def test_recursive_unique_lca(self):
2490.2.25 by Aaron Bentley
Update from review
345
        """Test finding a unique least common ancestor.
2490.2.3 by Aaron Bentley
Implement new merge base picker
346
347
        ancestry_1 should always have a single common ancestor
348
        """
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
349
        graph = self.make_graph(ancestry_1)
350
        self.assertEqual(NULL_REVISION,
351
                         graph.find_unique_lca(NULL_REVISION, NULL_REVISION))
352
        self.assertEqual(NULL_REVISION,
353
                         graph.find_unique_lca(NULL_REVISION, 'rev1'))
354
        self.assertEqual('rev1', graph.find_unique_lca('rev1', 'rev1'))
355
        self.assertEqual('rev1', graph.find_unique_lca('rev2a', 'rev2b'))
1551.19.10 by Aaron Bentley
Merge now warns when it encounters a criss-cross
356
        self.assertEqual(('rev1', 1,),
357
                         graph.find_unique_lca('rev2a', 'rev2b',
358
                         count_steps=True))
2490.2.3 by Aaron Bentley
Implement new merge base picker
359
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
360
    def test_unique_lca_criss_cross(self):
361
        """Ensure we don't pick non-unique lcas in a criss-cross"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
362
        graph = self.make_graph(criss_cross)
363
        self.assertEqual('rev1', graph.find_unique_lca('rev3a', 'rev3b'))
1551.19.10 by Aaron Bentley
Merge now warns when it encounters a criss-cross
364
        lca, steps = graph.find_unique_lca('rev3a', 'rev3b', count_steps=True)
365
        self.assertEqual('rev1', lca)
366
        self.assertEqual(2, steps)
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
367
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
368
    def test_unique_lca_null_revision(self):
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
369
        """Ensure we pick NULL_REVISION when necessary"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
370
        graph = self.make_graph(criss_cross2)
371
        self.assertEqual('rev1b', graph.find_unique_lca('rev2a', 'rev1b'))
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
372
        self.assertEqual(NULL_REVISION,
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
373
                         graph.find_unique_lca('rev2a', 'rev2b'))
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
374
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
375
    def test_unique_lca_null_revision2(self):
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
376
        """Ensure we pick NULL_REVISION when necessary"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
377
        graph = self.make_graph(ancestry_2)
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
378
        self.assertEqual(NULL_REVISION,
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
379
                         graph.find_unique_lca('rev4a', 'rev1b'))
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
380
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
381
    def test_lca_double_shortcut(self):
382
        graph = self.make_graph(double_shortcut)
383
        self.assertEqual('c', graph.find_unique_lca('f', 'g'))
384
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
385
    def test_common_ancestor_two_repos(self):
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
386
        """Ensure we do unique_lca using data from two repos"""
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
387
        mainline_tree = self.prepare_memory_tree('mainline')
388
        self.build_ancestry(mainline_tree, mainline)
3010.1.6 by Robert Collins
Locking in test_graph.
389
        self.addCleanup(mainline_tree.unlock)
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
390
391
        # This is cheating, because the revisions in the graph are actually
392
        # different revisions, despite having the same revision-id.
393
        feature_tree = self.prepare_memory_tree('feature')
394
        self.build_ancestry(feature_tree, feature_branch)
3010.1.6 by Robert Collins
Locking in test_graph.
395
        self.addCleanup(feature_tree.unlock)
396
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
397
        graph = mainline_tree.branch.repository.get_graph(
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
398
            feature_tree.branch.repository)
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
399
        self.assertEqual('rev2b', graph.find_unique_lca('rev2a', 'rev3b'))
2490.2.23 by Aaron Bentley
Adapt find_borders to produce a graph difference
400
401
    def test_graph_difference(self):
402
        graph = self.make_graph(ancestry_1)
403
        self.assertEqual((set(), set()), graph.find_difference('rev1', 'rev1'))
404
        self.assertEqual((set(), set(['rev1'])),
405
                         graph.find_difference(NULL_REVISION, 'rev1'))
406
        self.assertEqual((set(['rev1']), set()),
407
                         graph.find_difference('rev1', NULL_REVISION))
408
        self.assertEqual((set(['rev2a', 'rev3']), set(['rev2b'])),
409
                         graph.find_difference('rev3', 'rev2b'))
410
        self.assertEqual((set(['rev4', 'rev3', 'rev2a']), set()),
411
                         graph.find_difference('rev4', 'rev2b'))
412
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
413
    def test_graph_difference_separate_ancestry(self):
414
        graph = self.make_graph(ancestry_2)
415
        self.assertEqual((set(['rev1a']), set(['rev1b'])),
416
                         graph.find_difference('rev1a', 'rev1b'))
417
        self.assertEqual((set(['rev1a', 'rev2a', 'rev3a', 'rev4a']),
418
                          set(['rev1b'])),
419
                         graph.find_difference('rev4a', 'rev1b'))
420
2490.2.23 by Aaron Bentley
Adapt find_borders to produce a graph difference
421
    def test_graph_difference_criss_cross(self):
422
        graph = self.make_graph(criss_cross)
423
        self.assertEqual((set(['rev3a']), set(['rev3b'])),
424
                         graph.find_difference('rev3a', 'rev3b'))
425
        self.assertEqual((set([]), set(['rev3b', 'rev2b'])),
426
                         graph.find_difference('rev2a', 'rev3b'))
2490.2.25 by Aaron Bentley
Update from review
427
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
428
    def test_graph_difference_extended_history(self):
429
        graph = self.make_graph(extended_history_shortcut)
430
        self.expectFailure('find_difference cannot handle shortcuts',
431
            self.assertEqual, (set(['e']), set(['f'])),
432
                graph.find_difference('e', 'f'))
433
        self.assertEqual((set(['e']), set(['f'])),
434
                         graph.find_difference('e', 'f'))
435
        self.assertEqual((set(['f']), set(['e'])),
436
                         graph.find_difference('f', 'e'))
437
438
    def test_graph_difference_double_shortcut(self):
439
        graph = self.make_graph(double_shortcut)
440
        self.assertEqual((set(['d', 'f']), set(['e', 'g'])),
441
                         graph.find_difference('f', 'g'))
442
443
    def test_graph_difference_complex_shortcut(self):
444
        graph = self.make_graph(complex_shortcut)
445
        self.expectFailure('find_difference cannot handle shortcuts',
446
            self.assertEqual, (set(['m']), set(['h', 'n'])),
447
                graph.find_difference('m', 'n'))
448
        self.assertEqual((set(['m']), set(['h', 'n'])),
449
                         graph.find_difference('m', 'n'))
450
451
    def test_graph_difference_shortcut_extra_root(self):
452
        graph = self.make_graph(shortcut_extra_root)
453
        self.expectFailure('find_difference cannot handle shortcuts',
454
            self.assertEqual, (set(['e']), set(['f', 'g'])),
455
                graph.find_difference('e', 'f'))
456
        self.assertEqual((set(['e']), set(['f', 'g'])),
457
                         graph.find_difference('e', 'f'))
458
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
459
    def test_stacked_parents_provider(self):
460
        parents1 = _mod_graph.DictParentsProvider({'rev2': ['rev3']})
461
        parents2 = _mod_graph.DictParentsProvider({'rev1': ['rev4']})
462
        stacked = _mod_graph._StackedParentsProvider([parents1, parents2])
463
        self.assertEqual({'rev1':['rev4'], 'rev2':['rev3']},
464
                         stacked.get_parent_map(['rev1', 'rev2']))
465
        self.assertEqual({'rev2':['rev3'], 'rev1':['rev4']},
466
                         stacked.get_parent_map(['rev2', 'rev1']))
467
        self.assertEqual({'rev2':['rev3']},
468
                         stacked.get_parent_map(['rev2', 'rev2']))
469
        self.assertEqual({'rev1':['rev4']},
470
                         stacked.get_parent_map(['rev1', 'rev1']))
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
471
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
472
    def test_iter_topo_order(self):
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
473
        graph = self.make_graph(ancestry_1)
474
        args = ['rev2a', 'rev3', 'rev1']
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
475
        topo_args = list(graph.iter_topo_order(args))
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
476
        self.assertEqual(set(args), set(topo_args))
477
        self.assertTrue(topo_args.index('rev2a') > topo_args.index('rev1'))
478
        self.assertTrue(topo_args.index('rev2a') < topo_args.index('rev3'))
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
479
480
    def test_is_ancestor(self):
481
        graph = self.make_graph(ancestry_1)
2653.2.3 by Aaron Bentley
correctly handle Graph.is_ancestor(x, x)
482
        self.assertEqual(True, graph.is_ancestor('null:', 'null:'))
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
483
        self.assertEqual(True, graph.is_ancestor('null:', 'rev1'))
484
        self.assertEqual(False, graph.is_ancestor('rev1', 'null:'))
485
        self.assertEqual(True, graph.is_ancestor('null:', 'rev4'))
486
        self.assertEqual(False, graph.is_ancestor('rev4', 'null:'))
487
        self.assertEqual(False, graph.is_ancestor('rev4', 'rev2b'))
488
        self.assertEqual(True, graph.is_ancestor('rev2b', 'rev4'))
489
        self.assertEqual(False, graph.is_ancestor('rev2b', 'rev3'))
490
        self.assertEqual(False, graph.is_ancestor('rev3', 'rev2b'))
491
        instrumented_provider = InstrumentedParentsProvider(graph)
492
        instrumented_graph = _mod_graph.Graph(instrumented_provider)
493
        instrumented_graph.is_ancestor('rev2a', 'rev2b')
494
        self.assertTrue('null:' not in instrumented_provider.calls)
495
496
    def test_is_ancestor_boundary(self):
497
        """Ensure that we avoid searching the whole graph.
498
        
499
        This requires searching through b as a common ancestor, so we
500
        can identify that e is common.
501
        """
502
        graph = self.make_graph(boundary)
503
        instrumented_provider = InstrumentedParentsProvider(graph)
504
        graph = _mod_graph.Graph(instrumented_provider)
505
        self.assertFalse(graph.is_ancestor('a', 'c'))
506
        self.assertTrue('null:' not in instrumented_provider.calls)
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
507
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
508
    def test_iter_ancestry(self):
3228.4.10 by John Arbash Meinel
Respond to abentley's review comments.
509
        nodes = boundary.copy()
510
        nodes[NULL_REVISION] = ()
511
        graph = self.make_graph(nodes)
512
        expected = nodes.copy()
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
513
        expected.pop('a') # 'a' is not in the ancestry of 'c', all the
514
                          # other nodes are
3228.4.4 by John Arbash Meinel
Change iter_ancestry to take a group instead of a single node,
515
        self.assertEqual(expected, dict(graph.iter_ancestry(['c'])))
3228.4.10 by John Arbash Meinel
Respond to abentley's review comments.
516
        self.assertEqual(nodes, dict(graph.iter_ancestry(['a', 'c'])))
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
517
518
    def test_iter_ancestry_with_ghost(self):
519
        graph = self.make_graph(with_ghost)
520
        expected = with_ghost.copy()
521
        # '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.
522
        expected['g'] = None
3228.4.4 by John Arbash Meinel
Change iter_ancestry to take a group instead of a single node,
523
        self.assertEqual(expected, dict(graph.iter_ancestry(['a', 'c'])))
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
524
        expected.pop('a') 
3228.4.4 by John Arbash Meinel
Change iter_ancestry to take a group instead of a single node,
525
        self.assertEqual(expected, dict(graph.iter_ancestry(['c'])))
3228.4.2 by John Arbash Meinel
Add a Graph.iter_ancestry()
526
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
527
    def test_filter_candidate_lca(self):
528
        """Test filter_candidate_lca for a corner case
529
1551.15.83 by Aaron Bentley
Update test documentation
530
        This tests the case where we encounter the end of iteration for 'e'
531
        in the same pass as we discover that 'd' is an ancestor of 'e', and
532
        therefore 'e' can't be an lca.
533
534
        To compensate for different dict orderings on other Python
535
        implementations, we mirror 'd' and 'e' with 'b' and 'a'.
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
536
        """
537
        # This test is sensitive to the iteration order of dicts.  It will
1551.15.82 by Aaron Bentley
Add symmetrical alternative to test case
538
        # pass incorrectly if 'e' and 'a' sort before 'c'
1551.15.84 by Aaron Bentley
Add ancestry graph for test case
539
        #
540
        # NULL_REVISION
541
        #     / \
542
        #    a   e
543
        #    |   |
544
        #    b   d
545
        #     \ /
546
        #      c
1551.15.82 by Aaron Bentley
Add symmetrical alternative to test case
547
        graph = self.make_graph({'c': ['b', 'd'], 'd': ['e'], 'b': ['a'],
548
                                 'a': [NULL_REVISION], 'e': [NULL_REVISION]})
2776.1.4 by Robert Collins
Trivial review feedback changes.
549
        self.assertEqual(set(['c']), graph.heads(['a', 'c', 'e']))
550
551
    def test_heads_null(self):
552
        graph = self.make_graph(ancestry_1)
553
        self.assertEqual(set(['null:']), graph.heads(['null:']))
554
        self.assertEqual(set(['rev1']), graph.heads(['null:', 'rev1']))
555
        self.assertEqual(set(['rev1']), graph.heads(['rev1', 'null:']))
556
        self.assertEqual(set(['rev1']), graph.heads(set(['rev1', 'null:'])))
557
        self.assertEqual(set(['rev1']), graph.heads(('rev1', 'null:')))
558
559
    def test_heads_one(self):
3052.5.5 by John Arbash Meinel
Special case Graph.heads() for NULL_REVISION rather than is_ancestor.
560
        # A single node will always be a head
2776.1.4 by Robert Collins
Trivial review feedback changes.
561
        graph = self.make_graph(ancestry_1)
562
        self.assertEqual(set(['null:']), graph.heads(['null:']))
563
        self.assertEqual(set(['rev1']), graph.heads(['rev1']))
564
        self.assertEqual(set(['rev2a']), graph.heads(['rev2a']))
565
        self.assertEqual(set(['rev2b']), graph.heads(['rev2b']))
566
        self.assertEqual(set(['rev3']), graph.heads(['rev3']))
567
        self.assertEqual(set(['rev4']), graph.heads(['rev4']))
568
569
    def test_heads_single(self):
570
        graph = self.make_graph(ancestry_1)
571
        self.assertEqual(set(['rev4']), graph.heads(['null:', 'rev4']))
572
        self.assertEqual(set(['rev2a']), graph.heads(['rev1', 'rev2a']))
573
        self.assertEqual(set(['rev2b']), graph.heads(['rev1', 'rev2b']))
574
        self.assertEqual(set(['rev3']), graph.heads(['rev1', 'rev3']))
575
        self.assertEqual(set(['rev4']), graph.heads(['rev1', 'rev4']))
576
        self.assertEqual(set(['rev4']), graph.heads(['rev2a', 'rev4']))
577
        self.assertEqual(set(['rev4']), graph.heads(['rev2b', 'rev4']))
578
        self.assertEqual(set(['rev4']), graph.heads(['rev3', 'rev4']))
579
580
    def test_heads_two_heads(self):
581
        graph = self.make_graph(ancestry_1)
582
        self.assertEqual(set(['rev2a', 'rev2b']),
583
                         graph.heads(['rev2a', 'rev2b']))
584
        self.assertEqual(set(['rev3', 'rev2b']),
585
                         graph.heads(['rev3', 'rev2b']))
586
587
    def test_heads_criss_cross(self):
588
        graph = self.make_graph(criss_cross)
589
        self.assertEqual(set(['rev2a']),
590
                         graph.heads(['rev2a', 'rev1']))
591
        self.assertEqual(set(['rev2b']),
592
                         graph.heads(['rev2b', 'rev1']))
593
        self.assertEqual(set(['rev3a']),
594
                         graph.heads(['rev3a', 'rev1']))
595
        self.assertEqual(set(['rev3b']),
596
                         graph.heads(['rev3b', 'rev1']))
597
        self.assertEqual(set(['rev2a', 'rev2b']),
598
                         graph.heads(['rev2a', 'rev2b']))
599
        self.assertEqual(set(['rev3a']),
600
                         graph.heads(['rev3a', 'rev2a']))
601
        self.assertEqual(set(['rev3a']),
602
                         graph.heads(['rev3a', 'rev2b']))
603
        self.assertEqual(set(['rev3a']),
604
                         graph.heads(['rev3a', 'rev2a', 'rev2b']))
605
        self.assertEqual(set(['rev3b']),
606
                         graph.heads(['rev3b', 'rev2a']))
607
        self.assertEqual(set(['rev3b']),
608
                         graph.heads(['rev3b', 'rev2b']))
609
        self.assertEqual(set(['rev3b']),
610
                         graph.heads(['rev3b', 'rev2a', 'rev2b']))
611
        self.assertEqual(set(['rev3a', 'rev3b']),
612
                         graph.heads(['rev3a', 'rev3b']))
613
        self.assertEqual(set(['rev3a', 'rev3b']),
614
                         graph.heads(['rev3a', 'rev3b', 'rev2a', 'rev2b']))
615
616
    def test_heads_shortcut(self):
617
        graph = self.make_graph(history_shortcut)
618
619
        self.assertEqual(set(['rev2a', 'rev2b', 'rev2c']),
620
                         graph.heads(['rev2a', 'rev2b', 'rev2c']))
621
        self.assertEqual(set(['rev3a', 'rev3b']),
622
                         graph.heads(['rev3a', 'rev3b']))
623
        self.assertEqual(set(['rev3a', 'rev3b']),
624
                         graph.heads(['rev2a', 'rev3a', 'rev3b']))
625
        self.assertEqual(set(['rev2a', 'rev3b']),
626
                         graph.heads(['rev2a', 'rev3b']))
627
        self.assertEqual(set(['rev2c', 'rev3a']),
628
                         graph.heads(['rev2c', 'rev3a']))
2921.3.1 by Robert Collins
* Graph ``heads()`` queries have been bugfixed to no longer access all
629
630
    def _run_heads_break_deeper(self, graph_dict, search):
631
        """Run heads on a graph-as-a-dict.
632
        
633
        If the search asks for the parents of 'deeper' the test will fail.
634
        """
635
        class stub(object):
636
            pass
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
637
        def get_parent_map(keys):
638
            result = {}
639
            for key in keys:
640
                if key == 'deeper':
641
                    self.fail('key deeper was accessed')
642
                result[key] = graph_dict[key]
643
            return result
2921.3.1 by Robert Collins
* Graph ``heads()`` queries have been bugfixed to no longer access all
644
        an_obj = stub()
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
645
        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
646
        graph = _mod_graph.Graph(an_obj)
647
        return graph.heads(search)
648
649
    def test_heads_limits_search(self):
650
        # test that a heads query does not search all of history
651
        graph_dict = {
652
            'left':['common'],
653
            'right':['common'],
654
            'common':['deeper'],
655
        }
656
        self.assertEqual(set(['left', 'right']),
657
            self._run_heads_break_deeper(graph_dict, ['left', 'right']))
658
659
    def test_heads_limits_search_assymetric(self):
660
        # test that a heads query does not search all of history
661
        graph_dict = {
662
            'left':['midleft'],
663
            'midleft':['common'],
664
            'right':['common'],
665
            'common':['aftercommon'],
666
            'aftercommon':['deeper'],
667
        }
668
        self.assertEqual(set(['left', 'right']),
669
            self._run_heads_break_deeper(graph_dict, ['left', 'right']))
670
671
    def test_heads_limits_search_common_search_must_continue(self):
672
        # test that common nodes are still queried, preventing
673
        # all-the-way-to-origin behaviour in the following graph:
674
        graph_dict = {
675
            'h1':['shortcut', 'common1'],
676
            'h2':['common1'],
677
            'shortcut':['common2'],
678
            'common1':['common2'],
679
            'common2':['deeper'],
680
        }
681
        self.assertEqual(set(['h1', 'h2']),
682
            self._run_heads_break_deeper(graph_dict, ['h1', 'h2']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
683
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
684
    def test_breadth_first_search_start_ghosts(self):
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
685
        graph = self.make_graph({})
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
686
        # with_ghosts reports the ghosts
687
        search = graph._make_breadth_first_searcher(['a-ghost'])
688
        self.assertEqual((set(), set(['a-ghost'])), search.next_with_ghosts())
689
        self.assertRaises(StopIteration, search.next_with_ghosts)
690
        # next includes them
691
        search = graph._make_breadth_first_searcher(['a-ghost'])
692
        self.assertEqual(set(['a-ghost']), search.next())
693
        self.assertRaises(StopIteration, search.next)
694
695
    def test_breadth_first_search_deep_ghosts(self):
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
696
        graph = self.make_graph({
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
697
            'head':['present'],
698
            'present':['child', 'ghost'],
699
            'child':[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
700
            })
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
701
        # with_ghosts reports the ghosts
702
        search = graph._make_breadth_first_searcher(['head'])
703
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
704
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
705
        self.assertEqual((set(['child']), set(['ghost'])),
706
            search.next_with_ghosts())
707
        self.assertRaises(StopIteration, search.next_with_ghosts)
708
        # next includes them
709
        search = graph._make_breadth_first_searcher(['head'])
710
        self.assertEqual(set(['head']), search.next())
711
        self.assertEqual(set(['present']), search.next())
712
        self.assertEqual(set(['child', 'ghost']),
713
            search.next())
714
        self.assertRaises(StopIteration, search.next)
715
716
    def test_breadth_first_search_change_next_to_next_with_ghosts(self):
3177.3.3 by Robert Collins
Review feedback.
717
        # To make the API robust, we allow calling both next() and
718
        # next_with_ghosts() on the same searcher.
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
719
        graph = self.make_graph({
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
720
            'head':['present'],
721
            'present':['child', 'ghost'],
722
            'child':[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
723
            })
3177.3.3 by Robert Collins
Review feedback.
724
        # start with next_with_ghosts
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
725
        search = graph._make_breadth_first_searcher(['head'])
726
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
727
        self.assertEqual(set(['present']), search.next())
728
        self.assertEqual((set(['child']), set(['ghost'])),
729
            search.next_with_ghosts())
730
        self.assertRaises(StopIteration, search.next)
3177.3.3 by Robert Collins
Review feedback.
731
        # start with next
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
732
        search = graph._make_breadth_first_searcher(['head'])
733
        self.assertEqual(set(['head']), search.next())
734
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
735
        self.assertEqual(set(['child', 'ghost']),
736
            search.next())
737
        self.assertRaises(StopIteration, search.next_with_ghosts)
738
3177.3.2 by Robert Collins
Update graph searchers stop_searching_any and start_searching for next_with_ghosts.
739
    def test_breadth_first_change_search(self):
3177.3.3 by Robert Collins
Review feedback.
740
        # 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.
741
        graph = self.make_graph({
3177.3.2 by Robert Collins
Update graph searchers stop_searching_any and start_searching for next_with_ghosts.
742
            'head':['present'],
743
            'present':['stopped'],
744
            'other':['other_2'],
745
            'other_2':[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
746
            })
3177.3.2 by Robert Collins
Update graph searchers stop_searching_any and start_searching for next_with_ghosts.
747
        search = graph._make_breadth_first_searcher(['head'])
748
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
749
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
750
        self.assertEqual(set(['present']),
751
            search.stop_searching_any(['present']))
752
        self.assertEqual((set(['other']), set(['other_ghost'])),
753
            search.start_searching(['other', 'other_ghost']))
754
        self.assertEqual((set(['other_2']), set()), search.next_with_ghosts())
755
        self.assertRaises(StopIteration, search.next_with_ghosts)
756
        # next includes them
757
        search = graph._make_breadth_first_searcher(['head'])
758
        self.assertEqual(set(['head']), search.next())
759
        self.assertEqual(set(['present']), search.next())
760
        self.assertEqual(set(['present']),
761
            search.stop_searching_any(['present']))
762
        search.start_searching(['other', 'other_ghost'])
763
        self.assertEqual(set(['other_2']), search.next())
764
        self.assertRaises(StopIteration, search.next)
765
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
766
    def assertSeenAndResult(self, instructions, search, next):
767
        """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.
768
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
769
        :param instructions: A list of tuples:
770
            (seen, recipe, included_keys, starts, stops).
771
            seen, recipe and included_keys are results to check on the search
772
            and the searches get_result(). starts and stops are parameters to
773
            pass to start_searching and stop_searching_any during each
774
            iteration, if they are not None.
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
775
        :param search: The search to use.
776
        :param next: A callable to advance the search.
777
        """
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
778
        for seen, recipe, included_keys, starts, stops in instructions:
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
779
            next()
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
780
            if starts is not None:
781
                search.start_searching(starts)
782
            if stops is not None:
783
                search.stop_searching_any(stops)
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
784
            result = search.get_result()
785
            self.assertEqual(recipe, result.get_recipe())
786
            self.assertEqual(set(included_keys), result.get_keys())
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
787
            self.assertEqual(seen, search.seen)
788
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
789
    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.
790
        graph = self.make_graph({
791
            'head':['child'],
792
            'child':[NULL_REVISION],
3184.1.3 by Robert Collins
Automatically exclude ghosts.
793
            NULL_REVISION:[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
794
            })
795
        search = graph._make_breadth_first_searcher(['head'])
796
        # 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.
797
        result = search.get_result()
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
798
        self.assertEqual((set(['head']), set(['head']), 0),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
799
            result.get_recipe())
800
        self.assertEqual(set(), result.get_keys())
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
801
        self.assertEqual(set(), search.seen)
802
        # using next:
803
        expected = [
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
804
            (set(['head']), (set(['head']), set(['child']), 1),
805
             ['head'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
806
            (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.
807
             ['head', 'child'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
808
            (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.
809
             ['head', 'child', NULL_REVISION], None, None),
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
810
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
811
        self.assertSeenAndResult(expected, search, search.next)
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
812
        # using next_with_ghosts:
813
        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.
814
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
815
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
816
    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.
817
        graph = self.make_graph({
818
            'head':['child'],
819
            'child':[NULL_REVISION],
820
            'otherhead':['otherchild'],
821
            'otherchild':['excluded'],
822
            'excluded':[NULL_REVISION],
3184.1.3 by Robert Collins
Automatically exclude ghosts.
823
            NULL_REVISION:[]
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
824
            })
825
        search = graph._make_breadth_first_searcher([])
826
        # Starting with nothing and adding a search works:
827
        search.start_searching(['head'])
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
828
        # head has been seen:
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
829
        result = search.get_result()
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
830
        self.assertEqual((set(['head']), set(['child']), 1),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
831
            result.get_recipe())
832
        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.
833
        self.assertEqual(set(['head']), search.seen)
834
        # using next:
835
        expected = [
836
            # stop at child, and start a new search at otherhead:
837
            # - otherhead counts as seen immediately when start_searching is
838
            # called.
839
            (set(['head', 'child', 'otherhead']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
840
             (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.
841
             ['head', 'otherhead'], ['otherhead'], ['child']),
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
842
            (set(['head', 'child', 'otherhead', 'otherchild']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
843
             (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.
844
             ['head', 'otherhead', 'otherchild'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
845
            # stop searching excluded now
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
846
            (set(['head', 'child', 'otherhead', 'otherchild', 'excluded']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
847
             (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.
848
             ['head', 'otherhead', 'otherchild'], None, ['excluded']),
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
849
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
850
        self.assertSeenAndResult(expected, search, search.next)
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
851
        # using next_with_ghosts:
852
        search = graph._make_breadth_first_searcher([])
853
        search.start_searching(['head'])
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
854
        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.
855
3184.2.1 by Robert Collins
Handle stopping ghosts in searches properly.
856
    def test_breadth_first_stop_searching_not_queried(self):
857
        # A client should be able to say 'stop node X' even if X has not been
858
        # returned to the client.
859
        graph = self.make_graph({
860
            'head':['child', 'ghost1'],
861
            'child':[NULL_REVISION],
862
            NULL_REVISION:[],
863
            })
864
        search = graph._make_breadth_first_searcher(['head'])
865
        expected = [
866
            # NULL_REVISION and ghost1 have not been returned
867
            (set(['head']), (set(['head']), set(['child', 'ghost1']), 1),
868
             ['head'], None, [NULL_REVISION, 'ghost1']),
869
            # ghost1 has been returned, NULL_REVISION is to be returned in the
870
            # next iteration.
871
            (set(['head', 'child', 'ghost1']),
872
             (set(['head']), set(['ghost1', NULL_REVISION]), 2),
873
             ['head', 'child'], None, [NULL_REVISION, 'ghost1']),
874
            ]
875
        self.assertSeenAndResult(expected, search, search.next)
876
        # using next_with_ghosts:
877
        search = graph._make_breadth_first_searcher(['head'])
878
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
879
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
880
    def test_breadth_first_get_result_ghosts_are_excluded(self):
3184.1.3 by Robert Collins
Automatically exclude ghosts.
881
        graph = self.make_graph({
882
            'head':['child', 'ghost'],
883
            'child':[NULL_REVISION],
884
            NULL_REVISION:[],
885
            })
886
        search = graph._make_breadth_first_searcher(['head'])
887
        # using next:
888
        expected = [
889
            (set(['head']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
890
             (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.
891
             ['head'], None, None),
3184.1.3 by Robert Collins
Automatically exclude ghosts.
892
            (set(['head', 'child', 'ghost']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
893
             (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.
894
             ['head', 'child'], None, None),
3184.1.3 by Robert Collins
Automatically exclude ghosts.
895
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
896
        self.assertSeenAndResult(expected, search, search.next)
3184.1.3 by Robert Collins
Automatically exclude ghosts.
897
        # using next_with_ghosts:
898
        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.
899
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.3 by Robert Collins
Automatically exclude ghosts.
900
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
901
    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.
902
        graph = self.make_graph({
903
            'head':['child'],
904
            'child':[NULL_REVISION],
905
            NULL_REVISION:[],
906
            })
907
        search = graph._make_breadth_first_searcher(['head'])
908
        # using next:
909
        expected = [
910
            (set(['head', 'ghost']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
911
             (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.
912
             ['head'], ['ghost'], None),
3184.1.4 by Robert Collins
Correctly exclude ghosts when ghosts are started on an existing search.
913
            (set(['head', 'child', 'ghost']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
914
             (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.
915
             ['head', 'child'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
916
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
917
        self.assertSeenAndResult(expected, search, search.next)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
918
        # using next_with_ghosts:
919
        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.
920
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
921
922
    def test_breadth_first_revision_count_includes_NULL_REVISION(self):
923
        graph = self.make_graph({
924
            'head':[NULL_REVISION],
925
            NULL_REVISION:[],
926
            })
927
        search = graph._make_breadth_first_searcher(['head'])
928
        # using next:
929
        expected = [
930
            (set(['head']),
931
             (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.
932
             ['head'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
933
            (set(['head', NULL_REVISION]),
934
             (set(['head']), set([]), 2),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
935
             ['head', NULL_REVISION], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
936
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
937
        self.assertSeenAndResult(expected, search, search.next)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
938
        # using next_with_ghosts:
939
        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.
940
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
941
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
942
    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.
943
        # StopIteration should not invalid anything..
944
        graph = self.make_graph({
945
            'head':[NULL_REVISION],
946
            NULL_REVISION:[],
947
            })
948
        search = graph._make_breadth_first_searcher(['head'])
949
        # using next:
950
        expected = [
951
            (set(['head']),
952
             (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.
953
             ['head'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
954
            (set(['head', 'ghost', NULL_REVISION]),
955
             (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.
956
             ['head', NULL_REVISION], ['ghost'], None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
957
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
958
        self.assertSeenAndResult(expected, search, search.next)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
959
        self.assertRaises(StopIteration, search.next)
960
        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.
961
        result = search.get_result()
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
962
        self.assertEqual((set(['ghost', 'head']), set(['ghost']), 2),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
963
            result.get_recipe())
964
        self.assertEqual(set(['head', NULL_REVISION]), result.get_keys())
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
965
        # using next_with_ghosts:
966
        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.
967
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
968
        self.assertRaises(StopIteration, search.next)
969
        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.
970
        result = search.get_result()
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
971
        self.assertEqual((set(['ghost', 'head']), set(['ghost']), 2),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
972
            result.get_recipe())
973
        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.
974
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
975
976
class TestCachingParentsProvider(tests.TestCase):
977
978
    def setUp(self):
979
        super(TestCachingParentsProvider, self).setUp()
980
        dict_pp = _mod_graph.DictParentsProvider({'a':('b',)})
981
        self.inst_pp = InstrumentedParentsProvider(dict_pp)
982
        self.caching_pp = _mod_graph.CachingParentsProvider(self.inst_pp)
983
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
984
    def test_get_parent_map(self):
985
        """Requesting the same revision should be returned from cache"""
986
        self.assertEqual({}, self.caching_pp._cache)
987
        self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
988
        self.assertEqual(['a'], self.inst_pp.calls)
989
        self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
990
        # No new call, as it should have been returned from the cache
991
        self.assertEqual(['a'], self.inst_pp.calls)
992
        self.assertEqual({'a':('b',)}, self.caching_pp._cache)
993
994
    def test_get_parent_map_not_present(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
995
        """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()
996
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
997
        self.assertEqual(['b'], self.inst_pp.calls)
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
998
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
999
        # No new calls
1000
        self.assertEqual(['b'], self.inst_pp.calls)
1001
        self.assertEqual({'b':None}, self.caching_pp._cache)
1002
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1003
    def test_get_parent_map_mixed(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1004
        """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()
1005
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1006
        self.assertEqual(['b'], self.inst_pp.calls)
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1007
        self.assertEqual({'a':('b',)},
1008
                         self.caching_pp.get_parent_map(['a', 'b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1009
        self.assertEqual(['b', 'a'], self.inst_pp.calls)
1010
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
1011
    def test_get_parent_map_repeated(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1012
        """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()
1013
        self.assertEqual({'a':('b',)},
1014
                         self.caching_pp.get_parent_map(['b', 'a', 'b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
1015
        # Use sorted because we don't care about the order, just that each is
1016
        # only present 1 time.
1017
        self.assertEqual(['a', 'b'], sorted(self.inst_pp.calls))