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