/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
        # XXX: This seems valid, is there a reason to actually create a
256
        # repository and put things in it?
257
        return _mod_graph.Graph(_mod_graph.DictParentsProvider(ancestors))
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
258
        tree = self.prepare_memory_tree('.')
259
        self.build_ancestry(tree, ancestors)
3010.1.6 by Robert Collins
Locking in test_graph.
260
        self.addCleanup(tree.unlock)
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
261
        return tree.branch.repository.get_graph()
2490.2.3 by Aaron Bentley
Implement new merge base picker
262
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
263
    def prepare_memory_tree(self, location):
264
        tree = self.make_branch_and_memory_tree(location)
2490.2.1 by Aaron Bentley
Start work on GraphWalker
265
        tree.lock_write()
266
        tree.add('.')
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
267
        return tree
268
269
    def build_ancestry(self, tree, ancestors):
2490.2.25 by Aaron Bentley
Update from review
270
        """Create an ancestry as specified by a graph dict
271
272
        :param tree: A tree to use
273
        :param ancestors: a dict of {node: [node_parent, ...]}
274
        """
2490.2.1 by Aaron Bentley
Start work on GraphWalker
275
        pending = [NULL_REVISION]
276
        descendants = {}
277
        for descendant, parents in ancestors.iteritems():
278
            for parent in parents:
279
                descendants.setdefault(parent, []).append(descendant)
280
        while len(pending) > 0:
281
            cur_node = pending.pop()
282
            for descendant in descendants.get(cur_node, []):
2490.2.3 by Aaron Bentley
Implement new merge base picker
283
                if tree.branch.repository.has_revision(descendant):
284
                    continue
2490.2.1 by Aaron Bentley
Start work on GraphWalker
285
                parents = [p for p in ancestors[descendant] if p is not
286
                           NULL_REVISION]
287
                if len([p for p in parents if not
288
                    tree.branch.repository.has_revision(p)]) > 0:
289
                    continue
290
                tree.set_parent_ids(parents)
2490.2.3 by Aaron Bentley
Implement new merge base picker
291
                if len(parents) > 0:
292
                    left_parent = parents[0]
293
                else:
294
                    left_parent = NULL_REVISION
2490.2.1 by Aaron Bentley
Start work on GraphWalker
295
                tree.branch.set_last_revision_info(
2490.2.3 by Aaron Bentley
Implement new merge base picker
296
                    len(tree.branch._lefthand_history(left_parent)),
297
                    left_parent)
2490.2.1 by Aaron Bentley
Start work on GraphWalker
298
                tree.commit(descendant, rev_id=descendant)
299
                pending.append(descendant)
2490.2.2 by Aaron Bentley
add minimal-common-ancestor calculation
300
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
301
    def test_lca(self):
2490.2.25 by Aaron Bentley
Update from review
302
        """Test finding least common ancestor.
2490.2.3 by Aaron Bentley
Implement new merge base picker
303
304
        ancestry_1 should always have a single common ancestor
305
        """
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
306
        graph = self.make_graph(ancestry_1)
2490.2.28 by Aaron Bentley
Fix handling of null revision
307
        self.assertRaises(errors.InvalidRevisionId, graph.find_lca, None)
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
308
        self.assertEqual(set([NULL_REVISION]),
309
                         graph.find_lca(NULL_REVISION, NULL_REVISION))
310
        self.assertEqual(set([NULL_REVISION]),
311
                         graph.find_lca(NULL_REVISION, 'rev1'))
312
        self.assertEqual(set(['rev1']), graph.find_lca('rev1', 'rev1'))
313
        self.assertEqual(set(['rev1']), graph.find_lca('rev2a', 'rev2b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
314
2520.4.104 by Aaron Bentley
Avoid infinite loop when there is no unique lca
315
    def test_no_unique_lca(self):
316
        """Test error when one revision is not in the graph"""
317
        graph = self.make_graph(ancestry_1)
318
        self.assertRaises(errors.NoCommonAncestor, graph.find_unique_lca,
319
                          'rev1', '1rev')
320
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
321
    def test_lca_criss_cross(self):
2490.2.25 by Aaron Bentley
Update from review
322
        """Test least-common-ancestor after a criss-cross merge."""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
323
        graph = self.make_graph(criss_cross)
2490.2.3 by Aaron Bentley
Implement new merge base picker
324
        self.assertEqual(set(['rev2a', 'rev2b']),
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
325
                         graph.find_lca('rev3a', 'rev3b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
326
        self.assertEqual(set(['rev2b']),
2490.2.25 by Aaron Bentley
Update from review
327
                         graph.find_lca('rev3a', 'rev3b', 'rev2b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
328
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
329
    def test_lca_shortcut(self):
2490.2.25 by Aaron Bentley
Update from review
330
        """Test least-common ancestor on this history shortcut"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
331
        graph = self.make_graph(history_shortcut)
332
        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
333
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
334
    def test_recursive_unique_lca(self):
2490.2.25 by Aaron Bentley
Update from review
335
        """Test finding a unique least common ancestor.
2490.2.3 by Aaron Bentley
Implement new merge base picker
336
337
        ancestry_1 should always have a single common ancestor
338
        """
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
339
        graph = self.make_graph(ancestry_1)
340
        self.assertEqual(NULL_REVISION,
341
                         graph.find_unique_lca(NULL_REVISION, NULL_REVISION))
342
        self.assertEqual(NULL_REVISION,
343
                         graph.find_unique_lca(NULL_REVISION, 'rev1'))
344
        self.assertEqual('rev1', graph.find_unique_lca('rev1', 'rev1'))
345
        self.assertEqual('rev1', graph.find_unique_lca('rev2a', 'rev2b'))
1551.19.10 by Aaron Bentley
Merge now warns when it encounters a criss-cross
346
        self.assertEqual(('rev1', 1,),
347
                         graph.find_unique_lca('rev2a', 'rev2b',
348
                         count_steps=True))
2490.2.3 by Aaron Bentley
Implement new merge base picker
349
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
350
    def test_unique_lca_criss_cross(self):
351
        """Ensure we don't pick non-unique lcas in a criss-cross"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
352
        graph = self.make_graph(criss_cross)
353
        self.assertEqual('rev1', graph.find_unique_lca('rev3a', 'rev3b'))
1551.19.10 by Aaron Bentley
Merge now warns when it encounters a criss-cross
354
        lca, steps = graph.find_unique_lca('rev3a', 'rev3b', count_steps=True)
355
        self.assertEqual('rev1', lca)
356
        self.assertEqual(2, steps)
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
357
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
358
    def test_unique_lca_null_revision(self):
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
359
        """Ensure we pick NULL_REVISION when necessary"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
360
        graph = self.make_graph(criss_cross2)
361
        self.assertEqual('rev1b', graph.find_unique_lca('rev2a', 'rev1b'))
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('rev2a', 'rev2b'))
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
364
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
365
    def test_unique_lca_null_revision2(self):
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
366
        """Ensure we pick NULL_REVISION when necessary"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
367
        graph = self.make_graph(ancestry_2)
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
368
        self.assertEqual(NULL_REVISION,
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
369
                         graph.find_unique_lca('rev4a', 'rev1b'))
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
370
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
371
    def test_lca_double_shortcut(self):
372
        graph = self.make_graph(double_shortcut)
373
        self.assertEqual('c', graph.find_unique_lca('f', 'g'))
374
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
375
    def test_common_ancestor_two_repos(self):
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
376
        """Ensure we do unique_lca using data from two repos"""
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
377
        mainline_tree = self.prepare_memory_tree('mainline')
378
        self.build_ancestry(mainline_tree, mainline)
3010.1.6 by Robert Collins
Locking in test_graph.
379
        self.addCleanup(mainline_tree.unlock)
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
380
381
        # This is cheating, because the revisions in the graph are actually
382
        # different revisions, despite having the same revision-id.
383
        feature_tree = self.prepare_memory_tree('feature')
384
        self.build_ancestry(feature_tree, feature_branch)
3010.1.6 by Robert Collins
Locking in test_graph.
385
        self.addCleanup(feature_tree.unlock)
386
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
387
        graph = mainline_tree.branch.repository.get_graph(
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
388
            feature_tree.branch.repository)
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
389
        self.assertEqual('rev2b', graph.find_unique_lca('rev2a', 'rev3b'))
2490.2.23 by Aaron Bentley
Adapt find_borders to produce a graph difference
390
391
    def test_graph_difference(self):
392
        graph = self.make_graph(ancestry_1)
393
        self.assertEqual((set(), set()), graph.find_difference('rev1', 'rev1'))
394
        self.assertEqual((set(), set(['rev1'])),
395
                         graph.find_difference(NULL_REVISION, 'rev1'))
396
        self.assertEqual((set(['rev1']), set()),
397
                         graph.find_difference('rev1', NULL_REVISION))
398
        self.assertEqual((set(['rev2a', 'rev3']), set(['rev2b'])),
399
                         graph.find_difference('rev3', 'rev2b'))
400
        self.assertEqual((set(['rev4', 'rev3', 'rev2a']), set()),
401
                         graph.find_difference('rev4', 'rev2b'))
402
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
403
    def test_graph_difference_separate_ancestry(self):
404
        graph = self.make_graph(ancestry_2)
405
        self.assertEqual((set(['rev1a']), set(['rev1b'])),
406
                         graph.find_difference('rev1a', 'rev1b'))
407
        self.assertEqual((set(['rev1a', 'rev2a', 'rev3a', 'rev4a']),
408
                          set(['rev1b'])),
409
                         graph.find_difference('rev4a', 'rev1b'))
410
2490.2.23 by Aaron Bentley
Adapt find_borders to produce a graph difference
411
    def test_graph_difference_criss_cross(self):
412
        graph = self.make_graph(criss_cross)
413
        self.assertEqual((set(['rev3a']), set(['rev3b'])),
414
                         graph.find_difference('rev3a', 'rev3b'))
415
        self.assertEqual((set([]), set(['rev3b', 'rev2b'])),
416
                         graph.find_difference('rev2a', 'rev3b'))
2490.2.25 by Aaron Bentley
Update from review
417
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
418
    def test_graph_difference_extended_history(self):
419
        graph = self.make_graph(extended_history_shortcut)
420
        self.expectFailure('find_difference cannot handle shortcuts',
421
            self.assertEqual, (set(['e']), set(['f'])),
422
                graph.find_difference('e', 'f'))
423
        self.assertEqual((set(['e']), set(['f'])),
424
                         graph.find_difference('e', 'f'))
425
        self.assertEqual((set(['f']), set(['e'])),
426
                         graph.find_difference('f', 'e'))
427
428
    def test_graph_difference_double_shortcut(self):
429
        graph = self.make_graph(double_shortcut)
430
        self.assertEqual((set(['d', 'f']), set(['e', 'g'])),
431
                         graph.find_difference('f', 'g'))
432
433
    def test_graph_difference_complex_shortcut(self):
434
        graph = self.make_graph(complex_shortcut)
435
        self.expectFailure('find_difference cannot handle shortcuts',
436
            self.assertEqual, (set(['m']), set(['h', 'n'])),
437
                graph.find_difference('m', 'n'))
438
        self.assertEqual((set(['m']), set(['h', 'n'])),
439
                         graph.find_difference('m', 'n'))
440
441
    def test_graph_difference_shortcut_extra_root(self):
442
        graph = self.make_graph(shortcut_extra_root)
443
        self.expectFailure('find_difference cannot handle shortcuts',
444
            self.assertEqual, (set(['e']), set(['f', 'g'])),
445
                graph.find_difference('e', 'f'))
446
        self.assertEqual((set(['e']), set(['f', 'g'])),
447
                         graph.find_difference('e', 'f'))
448
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
449
    def test_stacked_parents_provider(self):
450
        parents1 = _mod_graph.DictParentsProvider({'rev2': ['rev3']})
451
        parents2 = _mod_graph.DictParentsProvider({'rev1': ['rev4']})
452
        stacked = _mod_graph._StackedParentsProvider([parents1, parents2])
453
        self.assertEqual({'rev1':['rev4'], 'rev2':['rev3']},
454
                         stacked.get_parent_map(['rev1', 'rev2']))
455
        self.assertEqual({'rev2':['rev3'], 'rev1':['rev4']},
456
                         stacked.get_parent_map(['rev2', 'rev1']))
457
        self.assertEqual({'rev2':['rev3']},
458
                         stacked.get_parent_map(['rev2', 'rev2']))
459
        self.assertEqual({'rev1':['rev4']},
460
                         stacked.get_parent_map(['rev1', 'rev1']))
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
461
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
462
    def test_iter_topo_order(self):
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
463
        graph = self.make_graph(ancestry_1)
464
        args = ['rev2a', 'rev3', 'rev1']
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
465
        topo_args = list(graph.iter_topo_order(args))
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
466
        self.assertEqual(set(args), set(topo_args))
467
        self.assertTrue(topo_args.index('rev2a') > topo_args.index('rev1'))
468
        self.assertTrue(topo_args.index('rev2a') < topo_args.index('rev3'))
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
469
470
    def test_is_ancestor(self):
471
        graph = self.make_graph(ancestry_1)
2653.2.3 by Aaron Bentley
correctly handle Graph.is_ancestor(x, x)
472
        self.assertEqual(True, graph.is_ancestor('null:', 'null:'))
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
473
        self.assertEqual(True, graph.is_ancestor('null:', 'rev1'))
474
        self.assertEqual(False, graph.is_ancestor('rev1', 'null:'))
475
        self.assertEqual(True, graph.is_ancestor('null:', 'rev4'))
476
        self.assertEqual(False, graph.is_ancestor('rev4', 'null:'))
477
        self.assertEqual(False, graph.is_ancestor('rev4', 'rev2b'))
478
        self.assertEqual(True, graph.is_ancestor('rev2b', 'rev4'))
479
        self.assertEqual(False, graph.is_ancestor('rev2b', 'rev3'))
480
        self.assertEqual(False, graph.is_ancestor('rev3', 'rev2b'))
481
        instrumented_provider = InstrumentedParentsProvider(graph)
482
        instrumented_graph = _mod_graph.Graph(instrumented_provider)
483
        instrumented_graph.is_ancestor('rev2a', 'rev2b')
484
        self.assertTrue('null:' not in instrumented_provider.calls)
485
486
    def test_is_ancestor_boundary(self):
487
        """Ensure that we avoid searching the whole graph.
488
        
489
        This requires searching through b as a common ancestor, so we
490
        can identify that e is common.
491
        """
492
        graph = self.make_graph(boundary)
493
        instrumented_provider = InstrumentedParentsProvider(graph)
494
        graph = _mod_graph.Graph(instrumented_provider)
495
        self.assertFalse(graph.is_ancestor('a', 'c'))
496
        self.assertTrue('null:' not in instrumented_provider.calls)
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
497
498
    def test_filter_candidate_lca(self):
499
        """Test filter_candidate_lca for a corner case
500
1551.15.83 by Aaron Bentley
Update test documentation
501
        This tests the case where we encounter the end of iteration for 'e'
502
        in the same pass as we discover that 'd' is an ancestor of 'e', and
503
        therefore 'e' can't be an lca.
504
505
        To compensate for different dict orderings on other Python
506
        implementations, we mirror 'd' and 'e' with 'b' and 'a'.
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
507
        """
508
        # This test is sensitive to the iteration order of dicts.  It will
1551.15.82 by Aaron Bentley
Add symmetrical alternative to test case
509
        # pass incorrectly if 'e' and 'a' sort before 'c'
1551.15.84 by Aaron Bentley
Add ancestry graph for test case
510
        #
511
        # NULL_REVISION
512
        #     / \
513
        #    a   e
514
        #    |   |
515
        #    b   d
516
        #     \ /
517
        #      c
1551.15.82 by Aaron Bentley
Add symmetrical alternative to test case
518
        graph = self.make_graph({'c': ['b', 'd'], 'd': ['e'], 'b': ['a'],
519
                                 'a': [NULL_REVISION], 'e': [NULL_REVISION]})
2776.1.4 by Robert Collins
Trivial review feedback changes.
520
        self.assertEqual(set(['c']), graph.heads(['a', 'c', 'e']))
521
522
    def test_heads_null(self):
523
        graph = self.make_graph(ancestry_1)
524
        self.assertEqual(set(['null:']), graph.heads(['null:']))
525
        self.assertEqual(set(['rev1']), graph.heads(['null:', 'rev1']))
526
        self.assertEqual(set(['rev1']), graph.heads(['rev1', 'null:']))
527
        self.assertEqual(set(['rev1']), graph.heads(set(['rev1', 'null:'])))
528
        self.assertEqual(set(['rev1']), graph.heads(('rev1', 'null:')))
529
530
    def test_heads_one(self):
3052.5.5 by John Arbash Meinel
Special case Graph.heads() for NULL_REVISION rather than is_ancestor.
531
        # A single node will always be a head
2776.1.4 by Robert Collins
Trivial review feedback changes.
532
        graph = self.make_graph(ancestry_1)
533
        self.assertEqual(set(['null:']), graph.heads(['null:']))
534
        self.assertEqual(set(['rev1']), graph.heads(['rev1']))
535
        self.assertEqual(set(['rev2a']), graph.heads(['rev2a']))
536
        self.assertEqual(set(['rev2b']), graph.heads(['rev2b']))
537
        self.assertEqual(set(['rev3']), graph.heads(['rev3']))
538
        self.assertEqual(set(['rev4']), graph.heads(['rev4']))
539
540
    def test_heads_single(self):
541
        graph = self.make_graph(ancestry_1)
542
        self.assertEqual(set(['rev4']), graph.heads(['null:', 'rev4']))
543
        self.assertEqual(set(['rev2a']), graph.heads(['rev1', 'rev2a']))
544
        self.assertEqual(set(['rev2b']), graph.heads(['rev1', 'rev2b']))
545
        self.assertEqual(set(['rev3']), graph.heads(['rev1', 'rev3']))
546
        self.assertEqual(set(['rev4']), graph.heads(['rev1', 'rev4']))
547
        self.assertEqual(set(['rev4']), graph.heads(['rev2a', 'rev4']))
548
        self.assertEqual(set(['rev4']), graph.heads(['rev2b', 'rev4']))
549
        self.assertEqual(set(['rev4']), graph.heads(['rev3', 'rev4']))
550
551
    def test_heads_two_heads(self):
552
        graph = self.make_graph(ancestry_1)
553
        self.assertEqual(set(['rev2a', 'rev2b']),
554
                         graph.heads(['rev2a', 'rev2b']))
555
        self.assertEqual(set(['rev3', 'rev2b']),
556
                         graph.heads(['rev3', 'rev2b']))
557
558
    def test_heads_criss_cross(self):
559
        graph = self.make_graph(criss_cross)
560
        self.assertEqual(set(['rev2a']),
561
                         graph.heads(['rev2a', 'rev1']))
562
        self.assertEqual(set(['rev2b']),
563
                         graph.heads(['rev2b', 'rev1']))
564
        self.assertEqual(set(['rev3a']),
565
                         graph.heads(['rev3a', 'rev1']))
566
        self.assertEqual(set(['rev3b']),
567
                         graph.heads(['rev3b', 'rev1']))
568
        self.assertEqual(set(['rev2a', 'rev2b']),
569
                         graph.heads(['rev2a', 'rev2b']))
570
        self.assertEqual(set(['rev3a']),
571
                         graph.heads(['rev3a', 'rev2a']))
572
        self.assertEqual(set(['rev3a']),
573
                         graph.heads(['rev3a', 'rev2b']))
574
        self.assertEqual(set(['rev3a']),
575
                         graph.heads(['rev3a', 'rev2a', 'rev2b']))
576
        self.assertEqual(set(['rev3b']),
577
                         graph.heads(['rev3b', 'rev2a']))
578
        self.assertEqual(set(['rev3b']),
579
                         graph.heads(['rev3b', 'rev2b']))
580
        self.assertEqual(set(['rev3b']),
581
                         graph.heads(['rev3b', 'rev2a', 'rev2b']))
582
        self.assertEqual(set(['rev3a', 'rev3b']),
583
                         graph.heads(['rev3a', 'rev3b']))
584
        self.assertEqual(set(['rev3a', 'rev3b']),
585
                         graph.heads(['rev3a', 'rev3b', 'rev2a', 'rev2b']))
586
587
    def test_heads_shortcut(self):
588
        graph = self.make_graph(history_shortcut)
589
590
        self.assertEqual(set(['rev2a', 'rev2b', 'rev2c']),
591
                         graph.heads(['rev2a', 'rev2b', 'rev2c']))
592
        self.assertEqual(set(['rev3a', 'rev3b']),
593
                         graph.heads(['rev3a', 'rev3b']))
594
        self.assertEqual(set(['rev3a', 'rev3b']),
595
                         graph.heads(['rev2a', 'rev3a', 'rev3b']))
596
        self.assertEqual(set(['rev2a', 'rev3b']),
597
                         graph.heads(['rev2a', 'rev3b']))
598
        self.assertEqual(set(['rev2c', 'rev3a']),
599
                         graph.heads(['rev2c', 'rev3a']))
2921.3.1 by Robert Collins
* Graph ``heads()`` queries have been bugfixed to no longer access all
600
601
    def _run_heads_break_deeper(self, graph_dict, search):
602
        """Run heads on a graph-as-a-dict.
603
        
604
        If the search asks for the parents of 'deeper' the test will fail.
605
        """
606
        class stub(object):
607
            pass
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
608
        def get_parent_map(keys):
609
            result = {}
610
            for key in keys:
611
                if key == 'deeper':
612
                    self.fail('key deeper was accessed')
613
                result[key] = graph_dict[key]
614
            return result
2921.3.1 by Robert Collins
* Graph ``heads()`` queries have been bugfixed to no longer access all
615
        an_obj = stub()
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
616
        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
617
        graph = _mod_graph.Graph(an_obj)
618
        return graph.heads(search)
619
620
    def test_heads_limits_search(self):
621
        # test that a heads query does not search all of history
622
        graph_dict = {
623
            'left':['common'],
624
            'right':['common'],
625
            'common':['deeper'],
626
        }
627
        self.assertEqual(set(['left', 'right']),
628
            self._run_heads_break_deeper(graph_dict, ['left', 'right']))
629
630
    def test_heads_limits_search_assymetric(self):
631
        # test that a heads query does not search all of history
632
        graph_dict = {
633
            'left':['midleft'],
634
            'midleft':['common'],
635
            'right':['common'],
636
            'common':['aftercommon'],
637
            'aftercommon':['deeper'],
638
        }
639
        self.assertEqual(set(['left', 'right']),
640
            self._run_heads_break_deeper(graph_dict, ['left', 'right']))
641
642
    def test_heads_limits_search_common_search_must_continue(self):
643
        # test that common nodes are still queried, preventing
644
        # all-the-way-to-origin behaviour in the following graph:
645
        graph_dict = {
646
            'h1':['shortcut', 'common1'],
647
            'h2':['common1'],
648
            'shortcut':['common2'],
649
            'common1':['common2'],
650
            'common2':['deeper'],
651
        }
652
        self.assertEqual(set(['h1', 'h2']),
653
            self._run_heads_break_deeper(graph_dict, ['h1', 'h2']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
654
655
656
class TestCachingParentsProvider(tests.TestCase):
657
658
    def setUp(self):
659
        super(TestCachingParentsProvider, self).setUp()
660
        dict_pp = _mod_graph.DictParentsProvider({'a':('b',)})
661
        self.inst_pp = InstrumentedParentsProvider(dict_pp)
662
        self.caching_pp = _mod_graph.CachingParentsProvider(self.inst_pp)
663
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
664
    def test_get_parent_map(self):
665
        """Requesting the same revision should be returned from cache"""
666
        self.assertEqual({}, self.caching_pp._cache)
667
        self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
668
        self.assertEqual(['a'], self.inst_pp.calls)
669
        self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
670
        # No new call, as it should have been returned from the cache
671
        self.assertEqual(['a'], self.inst_pp.calls)
672
        self.assertEqual({'a':('b',)}, self.caching_pp._cache)
673
674
    def test_get_parent_map_not_present(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
675
        """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()
676
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
677
        self.assertEqual(['b'], self.inst_pp.calls)
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
678
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
679
        # No new calls
680
        self.assertEqual(['b'], self.inst_pp.calls)
681
        self.assertEqual({'b':None}, self.caching_pp._cache)
682
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
683
    def test_get_parent_map_mixed(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
684
        """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()
685
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
686
        self.assertEqual(['b'], self.inst_pp.calls)
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
687
        self.assertEqual({'a':('b',)},
688
                         self.caching_pp.get_parent_map(['a', 'b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
689
        self.assertEqual(['b', 'a'], self.inst_pp.calls)
690
3099.3.3 by John Arbash Meinel
Deprecate get_parents() in favor of get_parent_map()
691
    def test_get_parent_map_repeated(self):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
692
        """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()
693
        self.assertEqual({'a':('b',)},
694
                         self.caching_pp.get_parent_map(['b', 'a', 'b']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
695
        # Use sorted because we don't care about the order, just that each is
696
        # only present 1 time.
697
        self.assertEqual(['a', 'b'], sorted(self.inst_pp.calls))