/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
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
241
class InstrumentedParentsProvider(object):
242
243
    def __init__(self, parents_provider):
244
        self.calls = []
245
        self._real_parents_provider = parents_provider
246
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
247
    def get_parent_map(self, nodes):
248
        self.calls.extend(nodes)
249
        return self._real_parents_provider.get_parent_map(nodes)
250
2490.2.25 by Aaron Bentley
Update from review
251
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
252
class TestGraph(TestCaseWithMemoryTransport):
2490.2.1 by Aaron Bentley
Start work on GraphWalker
253
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
254
    def make_graph(self, ancestors):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
255
        return _mod_graph.Graph(_mod_graph.DictParentsProvider(ancestors))
2490.2.3 by Aaron Bentley
Implement new merge base picker
256
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
257
    def prepare_memory_tree(self, location):
258
        tree = self.make_branch_and_memory_tree(location)
2490.2.1 by Aaron Bentley
Start work on GraphWalker
259
        tree.lock_write()
260
        tree.add('.')
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
261
        return tree
262
263
    def build_ancestry(self, tree, ancestors):
2490.2.25 by Aaron Bentley
Update from review
264
        """Create an ancestry as specified by a graph dict
265
266
        :param tree: A tree to use
267
        :param ancestors: a dict of {node: [node_parent, ...]}
268
        """
2490.2.1 by Aaron Bentley
Start work on GraphWalker
269
        pending = [NULL_REVISION]
270
        descendants = {}
271
        for descendant, parents in ancestors.iteritems():
272
            for parent in parents:
273
                descendants.setdefault(parent, []).append(descendant)
274
        while len(pending) > 0:
275
            cur_node = pending.pop()
276
            for descendant in descendants.get(cur_node, []):
2490.2.3 by Aaron Bentley
Implement new merge base picker
277
                if tree.branch.repository.has_revision(descendant):
278
                    continue
2490.2.1 by Aaron Bentley
Start work on GraphWalker
279
                parents = [p for p in ancestors[descendant] if p is not
280
                           NULL_REVISION]
281
                if len([p for p in parents if not
282
                    tree.branch.repository.has_revision(p)]) > 0:
283
                    continue
284
                tree.set_parent_ids(parents)
2490.2.3 by Aaron Bentley
Implement new merge base picker
285
                if len(parents) > 0:
286
                    left_parent = parents[0]
287
                else:
288
                    left_parent = NULL_REVISION
2490.2.1 by Aaron Bentley
Start work on GraphWalker
289
                tree.branch.set_last_revision_info(
2490.2.3 by Aaron Bentley
Implement new merge base picker
290
                    len(tree.branch._lefthand_history(left_parent)),
291
                    left_parent)
2490.2.1 by Aaron Bentley
Start work on GraphWalker
292
                tree.commit(descendant, rev_id=descendant)
293
                pending.append(descendant)
2490.2.2 by Aaron Bentley
add minimal-common-ancestor calculation
294
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
295
    def test_lca(self):
2490.2.25 by Aaron Bentley
Update from review
296
        """Test finding least common ancestor.
2490.2.3 by Aaron Bentley
Implement new merge base picker
297
298
        ancestry_1 should always have a single common ancestor
299
        """
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
300
        graph = self.make_graph(ancestry_1)
2490.2.28 by Aaron Bentley
Fix handling of null revision
301
        self.assertRaises(errors.InvalidRevisionId, graph.find_lca, None)
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
302
        self.assertEqual(set([NULL_REVISION]),
303
                         graph.find_lca(NULL_REVISION, NULL_REVISION))
304
        self.assertEqual(set([NULL_REVISION]),
305
                         graph.find_lca(NULL_REVISION, 'rev1'))
306
        self.assertEqual(set(['rev1']), graph.find_lca('rev1', 'rev1'))
307
        self.assertEqual(set(['rev1']), graph.find_lca('rev2a', 'rev2b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
308
2520.4.104 by Aaron Bentley
Avoid infinite loop when there is no unique lca
309
    def test_no_unique_lca(self):
310
        """Test error when one revision is not in the graph"""
311
        graph = self.make_graph(ancestry_1)
312
        self.assertRaises(errors.NoCommonAncestor, graph.find_unique_lca,
313
                          'rev1', '1rev')
314
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
315
    def test_lca_criss_cross(self):
2490.2.25 by Aaron Bentley
Update from review
316
        """Test least-common-ancestor after a criss-cross merge."""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
317
        graph = self.make_graph(criss_cross)
2490.2.3 by Aaron Bentley
Implement new merge base picker
318
        self.assertEqual(set(['rev2a', 'rev2b']),
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
319
                         graph.find_lca('rev3a', 'rev3b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
320
        self.assertEqual(set(['rev2b']),
2490.2.25 by Aaron Bentley
Update from review
321
                         graph.find_lca('rev3a', 'rev3b', 'rev2b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
322
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
323
    def test_lca_shortcut(self):
2490.2.25 by Aaron Bentley
Update from review
324
        """Test least-common ancestor on this history shortcut"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
325
        graph = self.make_graph(history_shortcut)
326
        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
327
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
328
    def test_recursive_unique_lca(self):
2490.2.25 by Aaron Bentley
Update from review
329
        """Test finding a unique least common ancestor.
2490.2.3 by Aaron Bentley
Implement new merge base picker
330
331
        ancestry_1 should always have a single common ancestor
332
        """
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
333
        graph = self.make_graph(ancestry_1)
334
        self.assertEqual(NULL_REVISION,
335
                         graph.find_unique_lca(NULL_REVISION, NULL_REVISION))
336
        self.assertEqual(NULL_REVISION,
337
                         graph.find_unique_lca(NULL_REVISION, 'rev1'))
338
        self.assertEqual('rev1', graph.find_unique_lca('rev1', 'rev1'))
339
        self.assertEqual('rev1', graph.find_unique_lca('rev2a', 'rev2b'))
1551.19.10 by Aaron Bentley
Merge now warns when it encounters a criss-cross
340
        self.assertEqual(('rev1', 1,),
341
                         graph.find_unique_lca('rev2a', 'rev2b',
342
                         count_steps=True))
2490.2.3 by Aaron Bentley
Implement new merge base picker
343
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
344
    def test_unique_lca_criss_cross(self):
345
        """Ensure we don't pick non-unique lcas in a criss-cross"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
346
        graph = self.make_graph(criss_cross)
347
        self.assertEqual('rev1', graph.find_unique_lca('rev3a', 'rev3b'))
1551.19.10 by Aaron Bentley
Merge now warns when it encounters a criss-cross
348
        lca, steps = graph.find_unique_lca('rev3a', 'rev3b', count_steps=True)
349
        self.assertEqual('rev1', lca)
350
        self.assertEqual(2, steps)
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
351
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
352
    def test_unique_lca_null_revision(self):
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
353
        """Ensure we pick NULL_REVISION when necessary"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
354
        graph = self.make_graph(criss_cross2)
355
        self.assertEqual('rev1b', graph.find_unique_lca('rev2a', 'rev1b'))
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
356
        self.assertEqual(NULL_REVISION,
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
357
                         graph.find_unique_lca('rev2a', 'rev2b'))
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
358
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
359
    def test_unique_lca_null_revision2(self):
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
360
        """Ensure we pick NULL_REVISION when necessary"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
361
        graph = self.make_graph(ancestry_2)
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
362
        self.assertEqual(NULL_REVISION,
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
363
                         graph.find_unique_lca('rev4a', 'rev1b'))
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
364
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
365
    def test_lca_double_shortcut(self):
366
        graph = self.make_graph(double_shortcut)
367
        self.assertEqual('c', graph.find_unique_lca('f', 'g'))
368
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
369
    def test_common_ancestor_two_repos(self):
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
370
        """Ensure we do unique_lca using data from two repos"""
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
371
        mainline_tree = self.prepare_memory_tree('mainline')
372
        self.build_ancestry(mainline_tree, mainline)
3010.1.6 by Robert Collins
Locking in test_graph.
373
        self.addCleanup(mainline_tree.unlock)
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
374
375
        # This is cheating, because the revisions in the graph are actually
376
        # different revisions, despite having the same revision-id.
377
        feature_tree = self.prepare_memory_tree('feature')
378
        self.build_ancestry(feature_tree, feature_branch)
3010.1.6 by Robert Collins
Locking in test_graph.
379
        self.addCleanup(feature_tree.unlock)
380
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
381
        graph = mainline_tree.branch.repository.get_graph(
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
382
            feature_tree.branch.repository)
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
383
        self.assertEqual('rev2b', graph.find_unique_lca('rev2a', 'rev3b'))
2490.2.23 by Aaron Bentley
Adapt find_borders to produce a graph difference
384
385
    def test_graph_difference(self):
386
        graph = self.make_graph(ancestry_1)
387
        self.assertEqual((set(), set()), graph.find_difference('rev1', 'rev1'))
388
        self.assertEqual((set(), set(['rev1'])),
389
                         graph.find_difference(NULL_REVISION, 'rev1'))
390
        self.assertEqual((set(['rev1']), set()),
391
                         graph.find_difference('rev1', NULL_REVISION))
392
        self.assertEqual((set(['rev2a', 'rev3']), set(['rev2b'])),
393
                         graph.find_difference('rev3', 'rev2b'))
394
        self.assertEqual((set(['rev4', 'rev3', 'rev2a']), set()),
395
                         graph.find_difference('rev4', 'rev2b'))
396
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
397
    def test_graph_difference_separate_ancestry(self):
398
        graph = self.make_graph(ancestry_2)
399
        self.assertEqual((set(['rev1a']), set(['rev1b'])),
400
                         graph.find_difference('rev1a', 'rev1b'))
401
        self.assertEqual((set(['rev1a', 'rev2a', 'rev3a', 'rev4a']),
402
                          set(['rev1b'])),
403
                         graph.find_difference('rev4a', 'rev1b'))
404
2490.2.23 by Aaron Bentley
Adapt find_borders to produce a graph difference
405
    def test_graph_difference_criss_cross(self):
406
        graph = self.make_graph(criss_cross)
407
        self.assertEqual((set(['rev3a']), set(['rev3b'])),
408
                         graph.find_difference('rev3a', 'rev3b'))
409
        self.assertEqual((set([]), set(['rev3b', 'rev2b'])),
410
                         graph.find_difference('rev2a', 'rev3b'))
2490.2.25 by Aaron Bentley
Update from review
411
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
412
    def test_graph_difference_extended_history(self):
413
        graph = self.make_graph(extended_history_shortcut)
414
        self.expectFailure('find_difference cannot handle shortcuts',
415
            self.assertEqual, (set(['e']), set(['f'])),
416
                graph.find_difference('e', 'f'))
417
        self.assertEqual((set(['e']), set(['f'])),
418
                         graph.find_difference('e', 'f'))
419
        self.assertEqual((set(['f']), set(['e'])),
420
                         graph.find_difference('f', 'e'))
421
422
    def test_graph_difference_double_shortcut(self):
423
        graph = self.make_graph(double_shortcut)
424
        self.assertEqual((set(['d', 'f']), set(['e', 'g'])),
425
                         graph.find_difference('f', 'g'))
426
427
    def test_graph_difference_complex_shortcut(self):
428
        graph = self.make_graph(complex_shortcut)
429
        self.expectFailure('find_difference cannot handle shortcuts',
430
            self.assertEqual, (set(['m']), set(['h', 'n'])),
431
                graph.find_difference('m', 'n'))
432
        self.assertEqual((set(['m']), set(['h', 'n'])),
433
                         graph.find_difference('m', 'n'))
434
435
    def test_graph_difference_shortcut_extra_root(self):
436
        graph = self.make_graph(shortcut_extra_root)
437
        self.expectFailure('find_difference cannot handle shortcuts',
438
            self.assertEqual, (set(['e']), set(['f', 'g'])),
439
                graph.find_difference('e', 'f'))
440
        self.assertEqual((set(['e']), set(['f', 'g'])),
441
                         graph.find_difference('e', 'f'))
442
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
443
    def test_stacked_parents_provider(self):
444
        parents1 = _mod_graph.DictParentsProvider({'rev2': ['rev3']})
445
        parents2 = _mod_graph.DictParentsProvider({'rev1': ['rev4']})
446
        stacked = _mod_graph._StackedParentsProvider([parents1, parents2])
447
        self.assertEqual({'rev1':['rev4'], 'rev2':['rev3']},
448
                         stacked.get_parent_map(['rev1', 'rev2']))
449
        self.assertEqual({'rev2':['rev3'], 'rev1':['rev4']},
450
                         stacked.get_parent_map(['rev2', 'rev1']))
451
        self.assertEqual({'rev2':['rev3']},
452
                         stacked.get_parent_map(['rev2', 'rev2']))
453
        self.assertEqual({'rev1':['rev4']},
454
                         stacked.get_parent_map(['rev1', 'rev1']))
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
455
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
456
    def test_iter_topo_order(self):
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
457
        graph = self.make_graph(ancestry_1)
458
        args = ['rev2a', 'rev3', 'rev1']
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
459
        topo_args = list(graph.iter_topo_order(args))
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
460
        self.assertEqual(set(args), set(topo_args))
461
        self.assertTrue(topo_args.index('rev2a') > topo_args.index('rev1'))
462
        self.assertTrue(topo_args.index('rev2a') < topo_args.index('rev3'))
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
463
464
    def test_is_ancestor(self):
465
        graph = self.make_graph(ancestry_1)
2653.2.3 by Aaron Bentley
correctly handle Graph.is_ancestor(x, x)
466
        self.assertEqual(True, graph.is_ancestor('null:', 'null:'))
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
467
        self.assertEqual(True, graph.is_ancestor('null:', 'rev1'))
468
        self.assertEqual(False, graph.is_ancestor('rev1', 'null:'))
469
        self.assertEqual(True, graph.is_ancestor('null:', 'rev4'))
470
        self.assertEqual(False, graph.is_ancestor('rev4', 'null:'))
471
        self.assertEqual(False, graph.is_ancestor('rev4', 'rev2b'))
472
        self.assertEqual(True, graph.is_ancestor('rev2b', 'rev4'))
473
        self.assertEqual(False, graph.is_ancestor('rev2b', 'rev3'))
474
        self.assertEqual(False, graph.is_ancestor('rev3', 'rev2b'))
475
        instrumented_provider = InstrumentedParentsProvider(graph)
476
        instrumented_graph = _mod_graph.Graph(instrumented_provider)
477
        instrumented_graph.is_ancestor('rev2a', 'rev2b')
478
        self.assertTrue('null:' not in instrumented_provider.calls)
479
480
    def test_is_ancestor_boundary(self):
481
        """Ensure that we avoid searching the whole graph.
482
        
483
        This requires searching through b as a common ancestor, so we
484
        can identify that e is common.
485
        """
486
        graph = self.make_graph(boundary)
487
        instrumented_provider = InstrumentedParentsProvider(graph)
488
        graph = _mod_graph.Graph(instrumented_provider)
489
        self.assertFalse(graph.is_ancestor('a', 'c'))
490
        self.assertTrue('null:' not in instrumented_provider.calls)
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
491
492
    def test_filter_candidate_lca(self):
493
        """Test filter_candidate_lca for a corner case
494
1551.15.83 by Aaron Bentley
Update test documentation
495
        This tests the case where we encounter the end of iteration for 'e'
496
        in the same pass as we discover that 'd' is an ancestor of 'e', and
497
        therefore 'e' can't be an lca.
498
499
        To compensate for different dict orderings on other Python
500
        implementations, we mirror 'd' and 'e' with 'b' and 'a'.
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
501
        """
502
        # This test is sensitive to the iteration order of dicts.  It will
1551.15.82 by Aaron Bentley
Add symmetrical alternative to test case
503
        # pass incorrectly if 'e' and 'a' sort before 'c'
1551.15.84 by Aaron Bentley
Add ancestry graph for test case
504
        #
505
        # NULL_REVISION
506
        #     / \
507
        #    a   e
508
        #    |   |
509
        #    b   d
510
        #     \ /
511
        #      c
1551.15.82 by Aaron Bentley
Add symmetrical alternative to test case
512
        graph = self.make_graph({'c': ['b', 'd'], 'd': ['e'], 'b': ['a'],
513
                                 'a': [NULL_REVISION], 'e': [NULL_REVISION]})
2776.1.4 by Robert Collins
Trivial review feedback changes.
514
        self.assertEqual(set(['c']), graph.heads(['a', 'c', 'e']))
515
516
    def test_heads_null(self):
517
        graph = self.make_graph(ancestry_1)
518
        self.assertEqual(set(['null:']), graph.heads(['null:']))
519
        self.assertEqual(set(['rev1']), graph.heads(['null:', 'rev1']))
520
        self.assertEqual(set(['rev1']), graph.heads(['rev1', 'null:']))
521
        self.assertEqual(set(['rev1']), graph.heads(set(['rev1', 'null:'])))
522
        self.assertEqual(set(['rev1']), graph.heads(('rev1', 'null:')))
523
524
    def test_heads_one(self):
3052.5.5 by John Arbash Meinel
Special case Graph.heads() for NULL_REVISION rather than is_ancestor.
525
        # A single node will always be a head
2776.1.4 by Robert Collins
Trivial review feedback changes.
526
        graph = self.make_graph(ancestry_1)
527
        self.assertEqual(set(['null:']), graph.heads(['null:']))
528
        self.assertEqual(set(['rev1']), graph.heads(['rev1']))
529
        self.assertEqual(set(['rev2a']), graph.heads(['rev2a']))
530
        self.assertEqual(set(['rev2b']), graph.heads(['rev2b']))
531
        self.assertEqual(set(['rev3']), graph.heads(['rev3']))
532
        self.assertEqual(set(['rev4']), graph.heads(['rev4']))
533
534
    def test_heads_single(self):
535
        graph = self.make_graph(ancestry_1)
536
        self.assertEqual(set(['rev4']), graph.heads(['null:', 'rev4']))
537
        self.assertEqual(set(['rev2a']), graph.heads(['rev1', 'rev2a']))
538
        self.assertEqual(set(['rev2b']), graph.heads(['rev1', 'rev2b']))
539
        self.assertEqual(set(['rev3']), graph.heads(['rev1', 'rev3']))
540
        self.assertEqual(set(['rev4']), graph.heads(['rev1', 'rev4']))
541
        self.assertEqual(set(['rev4']), graph.heads(['rev2a', 'rev4']))
542
        self.assertEqual(set(['rev4']), graph.heads(['rev2b', 'rev4']))
543
        self.assertEqual(set(['rev4']), graph.heads(['rev3', 'rev4']))
544
545
    def test_heads_two_heads(self):
546
        graph = self.make_graph(ancestry_1)
547
        self.assertEqual(set(['rev2a', 'rev2b']),
548
                         graph.heads(['rev2a', 'rev2b']))
549
        self.assertEqual(set(['rev3', 'rev2b']),
550
                         graph.heads(['rev3', 'rev2b']))
551
552
    def test_heads_criss_cross(self):
553
        graph = self.make_graph(criss_cross)
554
        self.assertEqual(set(['rev2a']),
555
                         graph.heads(['rev2a', 'rev1']))
556
        self.assertEqual(set(['rev2b']),
557
                         graph.heads(['rev2b', 'rev1']))
558
        self.assertEqual(set(['rev3a']),
559
                         graph.heads(['rev3a', 'rev1']))
560
        self.assertEqual(set(['rev3b']),
561
                         graph.heads(['rev3b', 'rev1']))
562
        self.assertEqual(set(['rev2a', 'rev2b']),
563
                         graph.heads(['rev2a', 'rev2b']))
564
        self.assertEqual(set(['rev3a']),
565
                         graph.heads(['rev3a', 'rev2a']))
566
        self.assertEqual(set(['rev3a']),
567
                         graph.heads(['rev3a', 'rev2b']))
568
        self.assertEqual(set(['rev3a']),
569
                         graph.heads(['rev3a', 'rev2a', 'rev2b']))
570
        self.assertEqual(set(['rev3b']),
571
                         graph.heads(['rev3b', 'rev2a']))
572
        self.assertEqual(set(['rev3b']),
573
                         graph.heads(['rev3b', 'rev2b']))
574
        self.assertEqual(set(['rev3b']),
575
                         graph.heads(['rev3b', 'rev2a', 'rev2b']))
576
        self.assertEqual(set(['rev3a', 'rev3b']),
577
                         graph.heads(['rev3a', 'rev3b']))
578
        self.assertEqual(set(['rev3a', 'rev3b']),
579
                         graph.heads(['rev3a', 'rev3b', 'rev2a', 'rev2b']))
580
581
    def test_heads_shortcut(self):
582
        graph = self.make_graph(history_shortcut)
583
584
        self.assertEqual(set(['rev2a', 'rev2b', 'rev2c']),
585
                         graph.heads(['rev2a', 'rev2b', 'rev2c']))
586
        self.assertEqual(set(['rev3a', 'rev3b']),
587
                         graph.heads(['rev3a', 'rev3b']))
588
        self.assertEqual(set(['rev3a', 'rev3b']),
589
                         graph.heads(['rev2a', 'rev3a', 'rev3b']))
590
        self.assertEqual(set(['rev2a', 'rev3b']),
591
                         graph.heads(['rev2a', 'rev3b']))
592
        self.assertEqual(set(['rev2c', 'rev3a']),
593
                         graph.heads(['rev2c', 'rev3a']))
2921.3.1 by Robert Collins
* Graph ``heads()`` queries have been bugfixed to no longer access all
594
595
    def _run_heads_break_deeper(self, graph_dict, search):
596
        """Run heads on a graph-as-a-dict.
597
        
598
        If the search asks for the parents of 'deeper' the test will fail.
599
        """
600
        class stub(object):
601
            pass
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
602
        def get_parent_map(keys):
603
            result = {}
604
            for key in keys:
605
                if key == 'deeper':
606
                    self.fail('key deeper was accessed')
607
                result[key] = graph_dict[key]
608
            return result
2921.3.1 by Robert Collins
* Graph ``heads()`` queries have been bugfixed to no longer access all
609
        an_obj = stub()
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
610
        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
611
        graph = _mod_graph.Graph(an_obj)
612
        return graph.heads(search)
613
614
    def test_heads_limits_search(self):
615
        # test that a heads query does not search all of history
616
        graph_dict = {
617
            'left':['common'],
618
            'right':['common'],
619
            'common':['deeper'],
620
        }
621
        self.assertEqual(set(['left', 'right']),
622
            self._run_heads_break_deeper(graph_dict, ['left', 'right']))
623
624
    def test_heads_limits_search_assymetric(self):
625
        # test that a heads query does not search all of history
626
        graph_dict = {
627
            'left':['midleft'],
628
            'midleft':['common'],
629
            'right':['common'],
630
            'common':['aftercommon'],
631
            'aftercommon':['deeper'],
632
        }
633
        self.assertEqual(set(['left', 'right']),
634
            self._run_heads_break_deeper(graph_dict, ['left', 'right']))
635
636
    def test_heads_limits_search_common_search_must_continue(self):
637
        # test that common nodes are still queried, preventing
638
        # all-the-way-to-origin behaviour in the following graph:
639
        graph_dict = {
640
            'h1':['shortcut', 'common1'],
641
            'h2':['common1'],
642
            'shortcut':['common2'],
643
            'common1':['common2'],
644
            'common2':['deeper'],
645
        }
646
        self.assertEqual(set(['h1', 'h2']),
647
            self._run_heads_break_deeper(graph_dict, ['h1', 'h2']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
648
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
649
    def test_breadth_first_search_start_ghosts(self):
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
650
        graph = self.make_graph({})
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
651
        # with_ghosts reports the ghosts
652
        search = graph._make_breadth_first_searcher(['a-ghost'])
653
        self.assertEqual((set(), set(['a-ghost'])), search.next_with_ghosts())
654
        self.assertRaises(StopIteration, search.next_with_ghosts)
655
        # next includes them
656
        search = graph._make_breadth_first_searcher(['a-ghost'])
657
        self.assertEqual(set(['a-ghost']), search.next())
658
        self.assertRaises(StopIteration, search.next)
659
660
    def test_breadth_first_search_deep_ghosts(self):
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
661
        graph = self.make_graph({
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
662
            'head':['present'],
663
            'present':['child', 'ghost'],
664
            'child':[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
665
            })
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
666
        # with_ghosts reports the ghosts
667
        search = graph._make_breadth_first_searcher(['head'])
668
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
669
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
670
        self.assertEqual((set(['child']), set(['ghost'])),
671
            search.next_with_ghosts())
672
        self.assertRaises(StopIteration, search.next_with_ghosts)
673
        # next includes them
674
        search = graph._make_breadth_first_searcher(['head'])
675
        self.assertEqual(set(['head']), search.next())
676
        self.assertEqual(set(['present']), search.next())
677
        self.assertEqual(set(['child', 'ghost']),
678
            search.next())
679
        self.assertRaises(StopIteration, search.next)
680
681
    def test_breadth_first_search_change_next_to_next_with_ghosts(self):
3177.3.3 by Robert Collins
Review feedback.
682
        # To make the API robust, we allow calling both next() and
683
        # next_with_ghosts() on the same searcher.
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
684
        graph = self.make_graph({
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
685
            'head':['present'],
686
            'present':['child', 'ghost'],
687
            'child':[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
688
            })
3177.3.3 by Robert Collins
Review feedback.
689
        # start with next_with_ghosts
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
690
        search = graph._make_breadth_first_searcher(['head'])
691
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
692
        self.assertEqual(set(['present']), search.next())
693
        self.assertEqual((set(['child']), set(['ghost'])),
694
            search.next_with_ghosts())
695
        self.assertRaises(StopIteration, search.next)
3177.3.3 by Robert Collins
Review feedback.
696
        # start with next
3177.3.1 by Robert Collins
* New method ``next_with_ghosts`` on the Graph breadth-first-search objects
697
        search = graph._make_breadth_first_searcher(['head'])
698
        self.assertEqual(set(['head']), search.next())
699
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
700
        self.assertEqual(set(['child', 'ghost']),
701
            search.next())
702
        self.assertRaises(StopIteration, search.next_with_ghosts)
703
3177.3.2 by Robert Collins
Update graph searchers stop_searching_any and start_searching for next_with_ghosts.
704
    def test_breadth_first_change_search(self):
3177.3.3 by Robert Collins
Review feedback.
705
        # 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.
706
        graph = self.make_graph({
3177.3.2 by Robert Collins
Update graph searchers stop_searching_any and start_searching for next_with_ghosts.
707
            'head':['present'],
708
            'present':['stopped'],
709
            'other':['other_2'],
710
            'other_2':[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
711
            })
3177.3.2 by Robert Collins
Update graph searchers stop_searching_any and start_searching for next_with_ghosts.
712
        search = graph._make_breadth_first_searcher(['head'])
713
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
714
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
715
        self.assertEqual(set(['present']),
716
            search.stop_searching_any(['present']))
717
        self.assertEqual((set(['other']), set(['other_ghost'])),
718
            search.start_searching(['other', 'other_ghost']))
719
        self.assertEqual((set(['other_2']), set()), search.next_with_ghosts())
720
        self.assertRaises(StopIteration, search.next_with_ghosts)
721
        # next includes them
722
        search = graph._make_breadth_first_searcher(['head'])
723
        self.assertEqual(set(['head']), search.next())
724
        self.assertEqual(set(['present']), search.next())
725
        self.assertEqual(set(['present']),
726
            search.stop_searching_any(['present']))
727
        search.start_searching(['other', 'other_ghost'])
728
        self.assertEqual(set(['other_2']), search.next())
729
        self.assertRaises(StopIteration, search.next)
730
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
731
    def assertSeenAndResult(self, instructions, search, next):
732
        """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.
733
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
734
        :param instructions: A list of tuples:
735
            (seen, recipe, included_keys, starts, stops).
736
            seen, recipe and included_keys are results to check on the search
737
            and the searches get_result(). starts and stops are parameters to
738
            pass to start_searching and stop_searching_any during each
739
            iteration, if they are not None.
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
740
        :param search: The search to use.
741
        :param next: A callable to advance the search.
742
        """
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
743
        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.
744
            next()
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
745
            if starts is not None:
746
                search.start_searching(starts)
747
            if stops is not None:
748
                search.stop_searching_any(stops)
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
749
            result = search.get_result()
750
            self.assertEqual(recipe, result.get_recipe())
751
            self.assertEqual(set(included_keys), result.get_keys())
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
752
            self.assertEqual(seen, search.seen)
753
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
754
    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.
755
        graph = self.make_graph({
756
            'head':['child'],
757
            'child':[NULL_REVISION],
3184.1.3 by Robert Collins
Automatically exclude ghosts.
758
            NULL_REVISION:[],
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
759
            })
760
        search = graph._make_breadth_first_searcher(['head'])
761
        # 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.
762
        result = search.get_result()
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
763
        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.
764
            result.get_recipe())
765
        self.assertEqual(set(), result.get_keys())
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
766
        self.assertEqual(set(), search.seen)
767
        # using next:
768
        expected = [
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
769
            (set(['head']), (set(['head']), set(['child']), 1),
770
             ['head'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
771
            (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.
772
             ['head', 'child'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
773
            (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.
774
             ['head', 'child', NULL_REVISION], None, None),
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
775
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
776
        self.assertSeenAndResult(expected, search, search.next)
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
777
        # using next_with_ghosts:
778
        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.
779
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.1 by Robert Collins
Add basic get_recipe to the graph breadth first searcher.
780
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
781
    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.
782
        graph = self.make_graph({
783
            'head':['child'],
784
            'child':[NULL_REVISION],
785
            'otherhead':['otherchild'],
786
            'otherchild':['excluded'],
787
            'excluded':[NULL_REVISION],
3184.1.3 by Robert Collins
Automatically exclude ghosts.
788
            NULL_REVISION:[]
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
789
            })
790
        search = graph._make_breadth_first_searcher([])
791
        # Starting with nothing and adding a search works:
792
        search.start_searching(['head'])
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
793
        # head has been seen:
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
794
        result = search.get_result()
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
795
        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.
796
            result.get_recipe())
797
        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.
798
        self.assertEqual(set(['head']), search.seen)
799
        # using next:
800
        expected = [
801
            # stop at child, and start a new search at otherhead:
802
            # - otherhead counts as seen immediately when start_searching is
803
            # called.
804
            (set(['head', 'child', 'otherhead']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
805
             (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.
806
             ['head', 'otherhead'], ['otherhead'], ['child']),
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
807
            (set(['head', 'child', 'otherhead', 'otherchild']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
808
             (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.
809
             ['head', 'otherhead', 'otherchild'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
810
            # stop searching excluded now
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
811
            (set(['head', 'child', 'otherhead', 'otherchild', 'excluded']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
812
             (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.
813
             ['head', 'otherhead', 'otherchild'], None, ['excluded']),
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
814
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
815
        self.assertSeenAndResult(expected, search, search.next)
3184.1.2 by Robert Collins
Add tests for starting and stopping searches in combination with get_recipe.
816
        # using next_with_ghosts:
817
        search = graph._make_breadth_first_searcher([])
818
        search.start_searching(['head'])
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
819
        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.
820
3184.2.1 by Robert Collins
Handle stopping ghosts in searches properly.
821
    def test_breadth_first_stop_searching_not_queried(self):
822
        # A client should be able to say 'stop node X' even if X has not been
823
        # returned to the client.
824
        graph = self.make_graph({
825
            'head':['child', 'ghost1'],
826
            'child':[NULL_REVISION],
827
            NULL_REVISION:[],
828
            })
829
        search = graph._make_breadth_first_searcher(['head'])
830
        expected = [
831
            # NULL_REVISION and ghost1 have not been returned
832
            (set(['head']), (set(['head']), set(['child', 'ghost1']), 1),
833
             ['head'], None, [NULL_REVISION, 'ghost1']),
834
            # ghost1 has been returned, NULL_REVISION is to be returned in the
835
            # next iteration.
836
            (set(['head', 'child', 'ghost1']),
837
             (set(['head']), set(['ghost1', NULL_REVISION]), 2),
838
             ['head', 'child'], None, [NULL_REVISION, 'ghost1']),
839
            ]
840
        self.assertSeenAndResult(expected, search, search.next)
841
        # using next_with_ghosts:
842
        search = graph._make_breadth_first_searcher(['head'])
843
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
844
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
845
    def test_breadth_first_get_result_ghosts_are_excluded(self):
3184.1.3 by Robert Collins
Automatically exclude ghosts.
846
        graph = self.make_graph({
847
            'head':['child', 'ghost'],
848
            'child':[NULL_REVISION],
849
            NULL_REVISION:[],
850
            })
851
        search = graph._make_breadth_first_searcher(['head'])
852
        # using next:
853
        expected = [
854
            (set(['head']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
855
             (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.
856
             ['head'], None, None),
3184.1.3 by Robert Collins
Automatically exclude ghosts.
857
            (set(['head', 'child', 'ghost']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
858
             (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.
859
             ['head', 'child'], None, None),
3184.1.3 by Robert Collins
Automatically exclude ghosts.
860
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
861
        self.assertSeenAndResult(expected, search, search.next)
3184.1.3 by Robert Collins
Automatically exclude ghosts.
862
        # using next_with_ghosts:
863
        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.
864
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.3 by Robert Collins
Automatically exclude ghosts.
865
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
866
    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.
867
        graph = self.make_graph({
868
            'head':['child'],
869
            'child':[NULL_REVISION],
870
            NULL_REVISION:[],
871
            })
872
        search = graph._make_breadth_first_searcher(['head'])
873
        # using next:
874
        expected = [
875
            (set(['head', 'ghost']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
876
             (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.
877
             ['head'], ['ghost'], None),
3184.1.4 by Robert Collins
Correctly exclude ghosts when ghosts are started on an existing search.
878
            (set(['head', 'child', 'ghost']),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
879
             (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.
880
             ['head', 'child'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
881
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
882
        self.assertSeenAndResult(expected, search, search.next)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
883
        # using next_with_ghosts:
884
        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.
885
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
886
887
    def test_breadth_first_revision_count_includes_NULL_REVISION(self):
888
        graph = self.make_graph({
889
            'head':[NULL_REVISION],
890
            NULL_REVISION:[],
891
            })
892
        search = graph._make_breadth_first_searcher(['head'])
893
        # using next:
894
        expected = [
895
            (set(['head']),
896
             (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.
897
             ['head'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
898
            (set(['head', NULL_REVISION]),
899
             (set(['head']), set([]), 2),
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
900
             ['head', NULL_REVISION], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
901
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
902
        self.assertSeenAndResult(expected, search, search.next)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
903
        # using next_with_ghosts:
904
        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.
905
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
906
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
907
    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.
908
        # StopIteration should not invalid anything..
909
        graph = self.make_graph({
910
            'head':[NULL_REVISION],
911
            NULL_REVISION:[],
912
            })
913
        search = graph._make_breadth_first_searcher(['head'])
914
        # using next:
915
        expected = [
916
            (set(['head']),
917
             (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.
918
             ['head'], None, None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
919
            (set(['head', 'ghost', NULL_REVISION]),
920
             (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.
921
             ['head', NULL_REVISION], ['ghost'], None),
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
922
            ]
3184.1.6 by Robert Collins
Create a SearchResult object which can be used as a replacement for sets.
923
        self.assertSeenAndResult(expected, search, search.next)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
924
        self.assertRaises(StopIteration, search.next)
925
        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.
926
        result = search.get_result()
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
927
        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.
928
            result.get_recipe())
929
        self.assertEqual(set(['head', NULL_REVISION]), result.get_keys())
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
930
        # using next_with_ghosts:
931
        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.
932
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
933
        self.assertRaises(StopIteration, search.next)
934
        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.
935
        result = search.get_result()
3184.1.5 by Robert Collins
Record the number of found revisions for cross checking.
936
        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.
937
            result.get_recipe())
938
        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.
939
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
940
941
class TestCachingParentsProvider(tests.TestCase):
942
943
    def setUp(self):
944
        super(TestCachingParentsProvider, self).setUp()
945
        dict_pp = _mod_graph.DictParentsProvider({'a':('b',)})
946
        self.inst_pp = InstrumentedParentsProvider(dict_pp)
947
        self.caching_pp = _mod_graph.CachingParentsProvider(self.inst_pp)
948
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
949
    def test_get_parent_map(self):
950
        """Requesting the same revision should be returned from cache"""
951
        self.assertEqual({}, self.caching_pp._cache)
952
        self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
953
        self.assertEqual(['a'], self.inst_pp.calls)
954
        self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
955
        # No new call, as it should have been returned from the cache
956
        self.assertEqual(['a'], self.inst_pp.calls)
957
        self.assertEqual({'a':('b',)}, self.caching_pp._cache)
958
959
    def test_get_parent_map_not_present(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
960
        """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()
961
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
962
        self.assertEqual(['b'], self.inst_pp.calls)
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
963
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
964
        # No new calls
965
        self.assertEqual(['b'], self.inst_pp.calls)
966
        self.assertEqual({'b':None}, self.caching_pp._cache)
967
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
968
    def test_get_parent_map_mixed(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
969
        """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()
970
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
971
        self.assertEqual(['b'], self.inst_pp.calls)
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
972
        self.assertEqual({'a':('b',)},
973
                         self.caching_pp.get_parent_map(['a', 'b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
974
        self.assertEqual(['b', 'a'], self.inst_pp.calls)
975
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
976
    def test_get_parent_map_repeated(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
977
        """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()
978
        self.assertEqual({'a':('b',)},
979
                         self.caching_pp.get_parent_map(['b', 'a', 'b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
980
        # Use sorted because we don't care about the order, just that each is
981
        # only present 1 time.
982
        self.assertEqual(['a', 'b'], sorted(self.inst_pp.calls))