/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.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
20
    tests,
2490.2.28 by Aaron Bentley
Fix handling of null revision
21
    )
2490.2.1 by Aaron Bentley
Start work on GraphWalker
22
from bzrlib.revision import NULL_REVISION
23
from bzrlib.tests import TestCaseWithMemoryTransport
24
2490.2.25 by Aaron Bentley
Update from review
25
26
# Ancestry 1:
27
#
28
#  NULL_REVISION
29
#       |
30
#     rev1
31
#      /\
32
#  rev2a rev2b
33
#     |    |
34
#   rev3  /
35
#     |  /
36
#   rev4
2490.2.2 by Aaron Bentley
add minimal-common-ancestor calculation
37
ancestry_1 = {'rev1': [NULL_REVISION], 'rev2a': ['rev1'], 'rev2b': ['rev1'],
38
              'rev3': ['rev2a'], 'rev4': ['rev3', 'rev2b']}
2490.2.25 by Aaron Bentley
Update from review
39
40
41
# Ancestry 2:
42
#
43
#  NULL_REVISION
44
#    /    \
45
# rev1a  rev1b
46
#   |
47
# rev2a
48
#   |
49
# rev3a
50
#   |
51
# rev4a
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
52
ancestry_2 = {'rev1a': [NULL_REVISION], 'rev2a': ['rev1a'],
53
              'rev1b': [NULL_REVISION], 'rev3a': ['rev2a'], 'rev4a': ['rev3a']}
2490.2.2 by Aaron Bentley
add minimal-common-ancestor calculation
54
2490.2.25 by Aaron Bentley
Update from review
55
56
# Criss cross ancestry
57
#
58
#     NULL_REVISION
59
#         |
60
#        rev1
61
#        /  \
62
#    rev2a  rev2b
63
#       |\  /|
64
#       |  X |
65
#       |/  \|
66
#    rev3a  rev3b
2490.2.3 by Aaron Bentley
Implement new merge base picker
67
criss_cross = {'rev1': [NULL_REVISION], 'rev2a': ['rev1'], 'rev2b': ['rev1'],
68
               'rev3a': ['rev2a', 'rev2b'], 'rev3b': ['rev2b', 'rev2a']}
69
2490.2.25 by Aaron Bentley
Update from review
70
71
# Criss-cross 2
72
#
73
#  NULL_REVISION
74
#    /   \
75
# rev1a  rev1b
76
#   |\   /|
77
#   | \ / |
78
#   |  X  |
79
#   | / \ |
80
#   |/   \|
81
# rev2a  rev2b
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
82
criss_cross2 = {'rev1a': [NULL_REVISION], 'rev1b': [NULL_REVISION],
83
                'rev2a': ['rev1a', 'rev1b'], 'rev2b': ['rev1b', 'rev1a']}
84
2490.2.25 by Aaron Bentley
Update from review
85
86
# Mainline:
87
#
88
#  NULL_REVISION
89
#       |
90
#      rev1
91
#      /  \
92
#      | rev2b
93
#      |  /
94
#     rev2a
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
95
mainline = {'rev1': [NULL_REVISION], 'rev2a': ['rev1', 'rev2b'],
96
            'rev2b': ['rev1']}
97
2490.2.25 by Aaron Bentley
Update from review
98
99
# feature branch:
100
#
101
#  NULL_REVISION
102
#       |
103
#      rev1
104
#       |
105
#     rev2b
106
#       |
107
#     rev3b
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
108
feature_branch = {'rev1': [NULL_REVISION],
109
                  'rev2b': ['rev1'], 'rev3b': ['rev2b']}
110
2490.2.25 by Aaron Bentley
Update from review
111
112
# History shortcut
113
#  NULL_REVISION
114
#       |
115
#     rev1------
116
#     /  \      \
117
#  rev2a rev2b rev2c
118
#    |  /   \   /
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
119
#  rev3a    rev3b
2490.2.9 by Aaron Bentley
Fix minimal common ancestor algorithm for non-minimal perhipheral ancestors
120
history_shortcut = {'rev1': [NULL_REVISION], 'rev2a': ['rev1'],
121
                    'rev2b': ['rev1'], 'rev2c': ['rev1'],
122
                    'rev3a': ['rev2a', 'rev2b'], 'rev3b': ['rev2b', 'rev2c']}
123
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
124
# Extended history shortcut
125
#  NULL_REVISION
126
#       |
127
#       a
128
#       |\
129
#       b |
130
#       | |
131
#       c |
132
#       | |
133
#       d |
134
#       |\|
135
#       e f
136
extended_history_shortcut = {'a': [NULL_REVISION],
137
                             'b': ['a'],
138
                             'c': ['b'],
139
                             'd': ['c'],
140
                             'e': ['d'],
141
                             'f': ['a', 'd'],
142
                            }
143
144
# Double shortcut
145
# Both sides will see 'A' first, even though it is actually a decendent of a
146
# different common revision.
147
#
148
#  NULL_REVISION
149
#       |
150
#       a
151
#      /|\
152
#     / b \
153
#    /  |  \
154
#   |   c   |
155
#   |  / \  |
156
#   | d   e |
157
#   |/     \|
158
#   f       g
159
160
double_shortcut = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'],
161
                   'd':['c'], 'e':['c'], 'f':['a', 'd'],
162
                   'g':['a', 'e']}
163
164
# Complex shortcut
165
# This has a failure mode in that a shortcut will find some nodes in common,
166
# but the common searcher won't have time to find that one branch is actually
167
# in common. The extra nodes at the top are because we want to avoid
168
# walking off the graph. Specifically, node G should be considered common, but
169
# is likely to be seen by M long before the common searcher finds it.
170
#
171
# NULL_REVISION
172
#     |
173
#     a
174
#     |
175
#     b
176
#     |
177
#     c
178
#     |
179
#     d
180
#     |\
181
#     e f
182
#     | |\
183
#     i | h
184
#     |\| |
185
#     | g |
186
#     | | |
187
#     | j |
188
#     | | |
189
#     | k |
190
#     | | |
191
#     | l |
192
#     |/|/
193
#     m n
194
complex_shortcut = {'d':[NULL_REVISION],
195
                    'x':['d'], 'y':['x'],
196
                    'e':['y'], 'f':['d'], 'g':['f', 'i'], 'h':['f'],
197
                    'i':['e'], 'j':['g'], 'k':['j'],
198
                    'l':['k'], 'm':['i', 's'], 'n':['s', 'h'],
199
                    'o':['l'], 'p':['o'], 'q':['p'],
200
                    'r':['q'], 's':['r'],
201
                    }
202
203
# Shortcut with extra root
204
# We have a long history shortcut, and an extra root, which is why we can't
205
# stop searchers based on seeing NULL_REVISION
206
#  NULL_REVISION
207
#       |   |
208
#       a   |
209
#       |\  |
210
#       b | |
211
#       | | |
212
#       c | |
213
#       | | |
214
#       d | g
215
#       |\|/
216
#       e f
217
shortcut_extra_root = {'a': [NULL_REVISION],
218
                       'b': ['a'],
219
                       'c': ['b'],
220
                       'd': ['c'],
221
                       'e': ['d'],
222
                       'f': ['a', 'd', 'g'],
223
                       'g': [NULL_REVISION],
224
                      }
225
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
226
#  NULL_REVISION
227
#       |
228
#       f
229
#       |
230
#       e
231
#      / \
232
#     b   d
233
#     | \ |
234
#     a   c
235
236
boundary = {'a': ['b'], 'c': ['b', 'd'], 'b':['e'], 'd':['e'], 'e': ['f'],
237
            'f':[NULL_REVISION]}
238
239
240
class InstrumentedParentsProvider(object):
241
242
    def __init__(self, parents_provider):
243
        self.calls = []
244
        self._real_parents_provider = parents_provider
245
246
    def get_parents(self, nodes):
247
        self.calls.extend(nodes)
248
        return self._real_parents_provider.get_parents(nodes)
249
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
250
    def get_parent_map(self, nodes):
251
        self.calls.extend(nodes)
252
        return self._real_parents_provider.get_parent_map(nodes)
253
2490.2.25 by Aaron Bentley
Update from review
254
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
255
class TestGraph(TestCaseWithMemoryTransport):
2490.2.1 by Aaron Bentley
Start work on GraphWalker
256
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
257
    def make_graph(self, ancestors):
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
258
        # XXX: This seems valid, is there a reason to actually create a
259
        # repository and put things in it?
260
        return _mod_graph.Graph(_mod_graph.DictParentsProvider(ancestors))
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
261
        tree = self.prepare_memory_tree('.')
262
        self.build_ancestry(tree, ancestors)
3010.1.6 by Robert Collins
Locking in test_graph.
263
        self.addCleanup(tree.unlock)
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
264
        return tree.branch.repository.get_graph()
2490.2.3 by Aaron Bentley
Implement new merge base picker
265
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
266
    def prepare_memory_tree(self, location):
267
        tree = self.make_branch_and_memory_tree(location)
2490.2.1 by Aaron Bentley
Start work on GraphWalker
268
        tree.lock_write()
269
        tree.add('.')
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
270
        return tree
271
272
    def build_ancestry(self, tree, ancestors):
2490.2.25 by Aaron Bentley
Update from review
273
        """Create an ancestry as specified by a graph dict
274
275
        :param tree: A tree to use
276
        :param ancestors: a dict of {node: [node_parent, ...]}
277
        """
2490.2.1 by Aaron Bentley
Start work on GraphWalker
278
        pending = [NULL_REVISION]
279
        descendants = {}
280
        for descendant, parents in ancestors.iteritems():
281
            for parent in parents:
282
                descendants.setdefault(parent, []).append(descendant)
283
        while len(pending) > 0:
284
            cur_node = pending.pop()
285
            for descendant in descendants.get(cur_node, []):
2490.2.3 by Aaron Bentley
Implement new merge base picker
286
                if tree.branch.repository.has_revision(descendant):
287
                    continue
2490.2.1 by Aaron Bentley
Start work on GraphWalker
288
                parents = [p for p in ancestors[descendant] if p is not
289
                           NULL_REVISION]
290
                if len([p for p in parents if not
291
                    tree.branch.repository.has_revision(p)]) > 0:
292
                    continue
293
                tree.set_parent_ids(parents)
2490.2.3 by Aaron Bentley
Implement new merge base picker
294
                if len(parents) > 0:
295
                    left_parent = parents[0]
296
                else:
297
                    left_parent = NULL_REVISION
2490.2.1 by Aaron Bentley
Start work on GraphWalker
298
                tree.branch.set_last_revision_info(
2490.2.3 by Aaron Bentley
Implement new merge base picker
299
                    len(tree.branch._lefthand_history(left_parent)),
300
                    left_parent)
2490.2.1 by Aaron Bentley
Start work on GraphWalker
301
                tree.commit(descendant, rev_id=descendant)
302
                pending.append(descendant)
2490.2.2 by Aaron Bentley
add minimal-common-ancestor calculation
303
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
304
    def test_lca(self):
2490.2.25 by Aaron Bentley
Update from review
305
        """Test finding least common ancestor.
2490.2.3 by Aaron Bentley
Implement new merge base picker
306
307
        ancestry_1 should always have a single common ancestor
308
        """
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
309
        graph = self.make_graph(ancestry_1)
2490.2.28 by Aaron Bentley
Fix handling of null revision
310
        self.assertRaises(errors.InvalidRevisionId, graph.find_lca, None)
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
311
        self.assertEqual(set([NULL_REVISION]),
312
                         graph.find_lca(NULL_REVISION, NULL_REVISION))
313
        self.assertEqual(set([NULL_REVISION]),
314
                         graph.find_lca(NULL_REVISION, 'rev1'))
315
        self.assertEqual(set(['rev1']), graph.find_lca('rev1', 'rev1'))
316
        self.assertEqual(set(['rev1']), graph.find_lca('rev2a', 'rev2b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
317
2520.4.104 by Aaron Bentley
Avoid infinite loop when there is no unique lca
318
    def test_no_unique_lca(self):
319
        """Test error when one revision is not in the graph"""
320
        graph = self.make_graph(ancestry_1)
321
        self.assertRaises(errors.NoCommonAncestor, graph.find_unique_lca,
322
                          'rev1', '1rev')
323
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
324
    def test_lca_criss_cross(self):
2490.2.25 by Aaron Bentley
Update from review
325
        """Test least-common-ancestor after a criss-cross merge."""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
326
        graph = self.make_graph(criss_cross)
2490.2.3 by Aaron Bentley
Implement new merge base picker
327
        self.assertEqual(set(['rev2a', 'rev2b']),
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
328
                         graph.find_lca('rev3a', 'rev3b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
329
        self.assertEqual(set(['rev2b']),
2490.2.25 by Aaron Bentley
Update from review
330
                         graph.find_lca('rev3a', 'rev3b', 'rev2b'))
2490.2.3 by Aaron Bentley
Implement new merge base picker
331
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
332
    def test_lca_shortcut(self):
2490.2.25 by Aaron Bentley
Update from review
333
        """Test least-common ancestor on this history shortcut"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
334
        graph = self.make_graph(history_shortcut)
335
        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
336
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
337
    def test_recursive_unique_lca(self):
2490.2.25 by Aaron Bentley
Update from review
338
        """Test finding a unique least common ancestor.
2490.2.3 by Aaron Bentley
Implement new merge base picker
339
340
        ancestry_1 should always have a single common ancestor
341
        """
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
342
        graph = self.make_graph(ancestry_1)
343
        self.assertEqual(NULL_REVISION,
344
                         graph.find_unique_lca(NULL_REVISION, NULL_REVISION))
345
        self.assertEqual(NULL_REVISION,
346
                         graph.find_unique_lca(NULL_REVISION, 'rev1'))
347
        self.assertEqual('rev1', graph.find_unique_lca('rev1', 'rev1'))
348
        self.assertEqual('rev1', graph.find_unique_lca('rev2a', 'rev2b'))
1551.19.10 by Aaron Bentley
Merge now warns when it encounters a criss-cross
349
        self.assertEqual(('rev1', 1,),
350
                         graph.find_unique_lca('rev2a', 'rev2b',
351
                         count_steps=True))
2490.2.3 by Aaron Bentley
Implement new merge base picker
352
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
353
    def test_unique_lca_criss_cross(self):
354
        """Ensure we don't pick non-unique lcas in a criss-cross"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
355
        graph = self.make_graph(criss_cross)
356
        self.assertEqual('rev1', graph.find_unique_lca('rev3a', 'rev3b'))
1551.19.10 by Aaron Bentley
Merge now warns when it encounters a criss-cross
357
        lca, steps = graph.find_unique_lca('rev3a', 'rev3b', count_steps=True)
358
        self.assertEqual('rev1', lca)
359
        self.assertEqual(2, steps)
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
360
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
361
    def test_unique_lca_null_revision(self):
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
362
        """Ensure we pick NULL_REVISION when necessary"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
363
        graph = self.make_graph(criss_cross2)
364
        self.assertEqual('rev1b', graph.find_unique_lca('rev2a', 'rev1b'))
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
365
        self.assertEqual(NULL_REVISION,
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
366
                         graph.find_unique_lca('rev2a', 'rev2b'))
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
367
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
368
    def test_unique_lca_null_revision2(self):
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
369
        """Ensure we pick NULL_REVISION when necessary"""
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
370
        graph = self.make_graph(ancestry_2)
2490.2.4 by Aaron Bentley
More tests for unique common ancestor
371
        self.assertEqual(NULL_REVISION,
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
372
                         graph.find_unique_lca('rev4a', 'rev1b'))
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
373
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
374
    def test_lca_double_shortcut(self):
375
        graph = self.make_graph(double_shortcut)
376
        self.assertEqual('c', graph.find_unique_lca('f', 'g'))
377
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
378
    def test_common_ancestor_two_repos(self):
2490.2.13 by Aaron Bentley
Update distinct -> lowest, refactor, add ParentsProvider concept
379
        """Ensure we do unique_lca using data from two repos"""
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
380
        mainline_tree = self.prepare_memory_tree('mainline')
381
        self.build_ancestry(mainline_tree, mainline)
3010.1.6 by Robert Collins
Locking in test_graph.
382
        self.addCleanup(mainline_tree.unlock)
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
383
384
        # This is cheating, because the revisions in the graph are actually
385
        # different revisions, despite having the same revision-id.
386
        feature_tree = self.prepare_memory_tree('feature')
387
        self.build_ancestry(feature_tree, feature_branch)
3010.1.6 by Robert Collins
Locking in test_graph.
388
        self.addCleanup(feature_tree.unlock)
389
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
390
        graph = mainline_tree.branch.repository.get_graph(
2490.2.6 by Aaron Bentley
Use new common-ancestor code everywhere
391
            feature_tree.branch.repository)
2490.2.21 by Aaron Bentley
Rename graph to deprecated_graph
392
        self.assertEqual('rev2b', graph.find_unique_lca('rev2a', 'rev3b'))
2490.2.23 by Aaron Bentley
Adapt find_borders to produce a graph difference
393
394
    def test_graph_difference(self):
395
        graph = self.make_graph(ancestry_1)
396
        self.assertEqual((set(), set()), graph.find_difference('rev1', 'rev1'))
397
        self.assertEqual((set(), set(['rev1'])),
398
                         graph.find_difference(NULL_REVISION, 'rev1'))
399
        self.assertEqual((set(['rev1']), set()),
400
                         graph.find_difference('rev1', NULL_REVISION))
401
        self.assertEqual((set(['rev2a', 'rev3']), set(['rev2b'])),
402
                         graph.find_difference('rev3', 'rev2b'))
403
        self.assertEqual((set(['rev4', 'rev3', 'rev2a']), set()),
404
                         graph.find_difference('rev4', 'rev2b'))
405
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
406
    def test_graph_difference_separate_ancestry(self):
407
        graph = self.make_graph(ancestry_2)
408
        self.assertEqual((set(['rev1a']), set(['rev1b'])),
409
                         graph.find_difference('rev1a', 'rev1b'))
410
        self.assertEqual((set(['rev1a', 'rev2a', 'rev3a', 'rev4a']),
411
                          set(['rev1b'])),
412
                         graph.find_difference('rev4a', 'rev1b'))
413
2490.2.23 by Aaron Bentley
Adapt find_borders to produce a graph difference
414
    def test_graph_difference_criss_cross(self):
415
        graph = self.make_graph(criss_cross)
416
        self.assertEqual((set(['rev3a']), set(['rev3b'])),
417
                         graph.find_difference('rev3a', 'rev3b'))
418
        self.assertEqual((set([]), set(['rev3b', 'rev2b'])),
419
                         graph.find_difference('rev2a', 'rev3b'))
2490.2.25 by Aaron Bentley
Update from review
420
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
421
    def test_graph_difference_extended_history(self):
422
        graph = self.make_graph(extended_history_shortcut)
423
        self.expectFailure('find_difference cannot handle shortcuts',
424
            self.assertEqual, (set(['e']), set(['f'])),
425
                graph.find_difference('e', 'f'))
426
        self.assertEqual((set(['e']), set(['f'])),
427
                         graph.find_difference('e', 'f'))
428
        self.assertEqual((set(['f']), set(['e'])),
429
                         graph.find_difference('f', 'e'))
430
431
    def test_graph_difference_double_shortcut(self):
432
        graph = self.make_graph(double_shortcut)
433
        self.assertEqual((set(['d', 'f']), set(['e', 'g'])),
434
                         graph.find_difference('f', 'g'))
435
436
    def test_graph_difference_complex_shortcut(self):
437
        graph = self.make_graph(complex_shortcut)
438
        self.expectFailure('find_difference cannot handle shortcuts',
439
            self.assertEqual, (set(['m']), set(['h', 'n'])),
440
                graph.find_difference('m', 'n'))
441
        self.assertEqual((set(['m']), set(['h', 'n'])),
442
                         graph.find_difference('m', 'n'))
443
444
    def test_graph_difference_shortcut_extra_root(self):
445
        graph = self.make_graph(shortcut_extra_root)
446
        self.expectFailure('find_difference cannot handle shortcuts',
447
            self.assertEqual, (set(['e']), set(['f', 'g'])),
448
                graph.find_difference('e', 'f'))
449
        self.assertEqual((set(['e']), set(['f', 'g'])),
450
                         graph.find_difference('e', 'f'))
451
2490.2.25 by Aaron Bentley
Update from review
452
    def test_stacked_parents_provider(self):
453
2988.1.3 by Robert Collins
Add a new repositoy method _generate_text_key_index for use by reconcile/check.
454
        parents1 = _mod_graph.DictParentsProvider({'rev2': ['rev3']})
455
        parents2 = _mod_graph.DictParentsProvider({'rev1': ['rev4']})
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
456
        stacked = _mod_graph._StackedParentsProvider([parents1, parents2])
2490.2.25 by Aaron Bentley
Update from review
457
        self.assertEqual([['rev4',], ['rev3']],
458
                         stacked.get_parents(['rev1', 'rev2']))
459
        self.assertEqual([['rev3',], ['rev4']],
460
                         stacked.get_parents(['rev2', 'rev1']))
461
        self.assertEqual([['rev3',], ['rev3']],
462
                         stacked.get_parents(['rev2', 'rev2']))
463
        self.assertEqual([['rev4',], ['rev4']],
464
                         stacked.get_parents(['rev1', 'rev1']))
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
465
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
466
    def test_iter_topo_order(self):
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
467
        graph = self.make_graph(ancestry_1)
468
        args = ['rev2a', 'rev3', 'rev1']
2490.2.31 by Aaron Bentley
Fix iter_topo_order to permit un-included parents
469
        topo_args = list(graph.iter_topo_order(args))
2490.2.30 by Aaron Bentley
Add functionality for tsorting graphs
470
        self.assertEqual(set(args), set(topo_args))
471
        self.assertTrue(topo_args.index('rev2a') > topo_args.index('rev1'))
472
        self.assertTrue(topo_args.index('rev2a') < topo_args.index('rev3'))
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
473
474
    def test_is_ancestor(self):
475
        graph = self.make_graph(ancestry_1)
2653.2.3 by Aaron Bentley
correctly handle Graph.is_ancestor(x, x)
476
        self.assertEqual(True, graph.is_ancestor('null:', 'null:'))
2653.2.1 by Aaron Bentley
Implement Graph.is_ancestor
477
        self.assertEqual(True, graph.is_ancestor('null:', 'rev1'))
478
        self.assertEqual(False, graph.is_ancestor('rev1', 'null:'))
479
        self.assertEqual(True, graph.is_ancestor('null:', 'rev4'))
480
        self.assertEqual(False, graph.is_ancestor('rev4', 'null:'))
481
        self.assertEqual(False, graph.is_ancestor('rev4', 'rev2b'))
482
        self.assertEqual(True, graph.is_ancestor('rev2b', 'rev4'))
483
        self.assertEqual(False, graph.is_ancestor('rev2b', 'rev3'))
484
        self.assertEqual(False, graph.is_ancestor('rev3', 'rev2b'))
485
        instrumented_provider = InstrumentedParentsProvider(graph)
486
        instrumented_graph = _mod_graph.Graph(instrumented_provider)
487
        instrumented_graph.is_ancestor('rev2a', 'rev2b')
488
        self.assertTrue('null:' not in instrumented_provider.calls)
489
490
    def test_is_ancestor_boundary(self):
491
        """Ensure that we avoid searching the whole graph.
492
        
493
        This requires searching through b as a common ancestor, so we
494
        can identify that e is common.
495
        """
496
        graph = self.make_graph(boundary)
497
        instrumented_provider = InstrumentedParentsProvider(graph)
498
        graph = _mod_graph.Graph(instrumented_provider)
499
        self.assertFalse(graph.is_ancestor('a', 'c'))
500
        self.assertTrue('null:' not in instrumented_provider.calls)
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
501
502
    def test_filter_candidate_lca(self):
503
        """Test filter_candidate_lca for a corner case
504
1551.15.83 by Aaron Bentley
Update test documentation
505
        This tests the case where we encounter the end of iteration for 'e'
506
        in the same pass as we discover that 'd' is an ancestor of 'e', and
507
        therefore 'e' can't be an lca.
508
509
        To compensate for different dict orderings on other Python
510
        implementations, we mirror 'd' and 'e' with 'b' and 'a'.
1551.15.78 by Aaron Bentley
Fix KeyError in filter_candidate_lca
511
        """
512
        # This test is sensitive to the iteration order of dicts.  It will
1551.15.82 by Aaron Bentley
Add symmetrical alternative to test case
513
        # pass incorrectly if 'e' and 'a' sort before 'c'
1551.15.84 by Aaron Bentley
Add ancestry graph for test case
514
        #
515
        # NULL_REVISION
516
        #     / \
517
        #    a   e
518
        #    |   |
519
        #    b   d
520
        #     \ /
521
        #      c
1551.15.82 by Aaron Bentley
Add symmetrical alternative to test case
522
        graph = self.make_graph({'c': ['b', 'd'], 'd': ['e'], 'b': ['a'],
523
                                 'a': [NULL_REVISION], 'e': [NULL_REVISION]})
2776.1.4 by Robert Collins
Trivial review feedback changes.
524
        self.assertEqual(set(['c']), graph.heads(['a', 'c', 'e']))
525
526
    def test_heads_null(self):
527
        graph = self.make_graph(ancestry_1)
528
        self.assertEqual(set(['null:']), graph.heads(['null:']))
529
        self.assertEqual(set(['rev1']), graph.heads(['null:', 'rev1']))
530
        self.assertEqual(set(['rev1']), graph.heads(['rev1', 'null:']))
531
        self.assertEqual(set(['rev1']), graph.heads(set(['rev1', 'null:'])))
532
        self.assertEqual(set(['rev1']), graph.heads(('rev1', 'null:')))
533
534
    def test_heads_one(self):
3052.5.5 by John Arbash Meinel
Special case Graph.heads() for NULL_REVISION rather than is_ancestor.
535
        # A single node will always be a head
2776.1.4 by Robert Collins
Trivial review feedback changes.
536
        graph = self.make_graph(ancestry_1)
537
        self.assertEqual(set(['null:']), graph.heads(['null:']))
538
        self.assertEqual(set(['rev1']), graph.heads(['rev1']))
539
        self.assertEqual(set(['rev2a']), graph.heads(['rev2a']))
540
        self.assertEqual(set(['rev2b']), graph.heads(['rev2b']))
541
        self.assertEqual(set(['rev3']), graph.heads(['rev3']))
542
        self.assertEqual(set(['rev4']), graph.heads(['rev4']))
543
544
    def test_heads_single(self):
545
        graph = self.make_graph(ancestry_1)
546
        self.assertEqual(set(['rev4']), graph.heads(['null:', 'rev4']))
547
        self.assertEqual(set(['rev2a']), graph.heads(['rev1', 'rev2a']))
548
        self.assertEqual(set(['rev2b']), graph.heads(['rev1', 'rev2b']))
549
        self.assertEqual(set(['rev3']), graph.heads(['rev1', 'rev3']))
550
        self.assertEqual(set(['rev4']), graph.heads(['rev1', 'rev4']))
551
        self.assertEqual(set(['rev4']), graph.heads(['rev2a', 'rev4']))
552
        self.assertEqual(set(['rev4']), graph.heads(['rev2b', 'rev4']))
553
        self.assertEqual(set(['rev4']), graph.heads(['rev3', 'rev4']))
554
555
    def test_heads_two_heads(self):
556
        graph = self.make_graph(ancestry_1)
557
        self.assertEqual(set(['rev2a', 'rev2b']),
558
                         graph.heads(['rev2a', 'rev2b']))
559
        self.assertEqual(set(['rev3', 'rev2b']),
560
                         graph.heads(['rev3', 'rev2b']))
561
562
    def test_heads_criss_cross(self):
563
        graph = self.make_graph(criss_cross)
564
        self.assertEqual(set(['rev2a']),
565
                         graph.heads(['rev2a', 'rev1']))
566
        self.assertEqual(set(['rev2b']),
567
                         graph.heads(['rev2b', 'rev1']))
568
        self.assertEqual(set(['rev3a']),
569
                         graph.heads(['rev3a', 'rev1']))
570
        self.assertEqual(set(['rev3b']),
571
                         graph.heads(['rev3b', 'rev1']))
572
        self.assertEqual(set(['rev2a', 'rev2b']),
573
                         graph.heads(['rev2a', 'rev2b']))
574
        self.assertEqual(set(['rev3a']),
575
                         graph.heads(['rev3a', 'rev2a']))
576
        self.assertEqual(set(['rev3a']),
577
                         graph.heads(['rev3a', 'rev2b']))
578
        self.assertEqual(set(['rev3a']),
579
                         graph.heads(['rev3a', 'rev2a', 'rev2b']))
580
        self.assertEqual(set(['rev3b']),
581
                         graph.heads(['rev3b', 'rev2a']))
582
        self.assertEqual(set(['rev3b']),
583
                         graph.heads(['rev3b', 'rev2b']))
584
        self.assertEqual(set(['rev3b']),
585
                         graph.heads(['rev3b', 'rev2a', 'rev2b']))
586
        self.assertEqual(set(['rev3a', 'rev3b']),
587
                         graph.heads(['rev3a', 'rev3b']))
588
        self.assertEqual(set(['rev3a', 'rev3b']),
589
                         graph.heads(['rev3a', 'rev3b', 'rev2a', 'rev2b']))
590
591
    def test_heads_shortcut(self):
592
        graph = self.make_graph(history_shortcut)
593
594
        self.assertEqual(set(['rev2a', 'rev2b', 'rev2c']),
595
                         graph.heads(['rev2a', 'rev2b', 'rev2c']))
596
        self.assertEqual(set(['rev3a', 'rev3b']),
597
                         graph.heads(['rev3a', 'rev3b']))
598
        self.assertEqual(set(['rev3a', 'rev3b']),
599
                         graph.heads(['rev2a', 'rev3a', 'rev3b']))
600
        self.assertEqual(set(['rev2a', 'rev3b']),
601
                         graph.heads(['rev2a', 'rev3b']))
602
        self.assertEqual(set(['rev2c', 'rev3a']),
603
                         graph.heads(['rev2c', 'rev3a']))
2921.3.1 by Robert Collins
* Graph ``heads()`` queries have been bugfixed to no longer access all
604
605
    def _run_heads_break_deeper(self, graph_dict, search):
606
        """Run heads on a graph-as-a-dict.
607
        
608
        If the search asks for the parents of 'deeper' the test will fail.
609
        """
610
        class stub(object):
611
            pass
612
        def get_parents(keys):
613
            result = []
614
            for key in keys:
615
                if key == 'deeper':
616
                    self.fail('key deeper was accessed')
617
                result.append(graph_dict[key])
618
            return result
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
619
        def get_parent_map(keys):
620
            result = {}
621
            for key in keys:
622
                if key == 'deeper':
623
                    self.fail('key deeper was accessed')
624
                result[key] = graph_dict[key]
625
            return result
2921.3.1 by Robert Collins
* Graph ``heads()`` queries have been bugfixed to no longer access all
626
        an_obj = stub()
627
        an_obj.get_parents = get_parents
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
628
        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
629
        graph = _mod_graph.Graph(an_obj)
630
        return graph.heads(search)
631
632
    def test_heads_limits_search(self):
633
        # test that a heads query does not search all of history
634
        graph_dict = {
635
            'left':['common'],
636
            'right':['common'],
637
            'common':['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_assymetric(self):
643
        # test that a heads query does not search all of history
644
        graph_dict = {
645
            'left':['midleft'],
646
            'midleft':['common'],
647
            'right':['common'],
648
            'common':['aftercommon'],
649
            'aftercommon':['deeper'],
650
        }
651
        self.assertEqual(set(['left', 'right']),
652
            self._run_heads_break_deeper(graph_dict, ['left', 'right']))
653
654
    def test_heads_limits_search_common_search_must_continue(self):
655
        # test that common nodes are still queried, preventing
656
        # all-the-way-to-origin behaviour in the following graph:
657
        graph_dict = {
658
            'h1':['shortcut', 'common1'],
659
            'h2':['common1'],
660
            'shortcut':['common2'],
661
            'common1':['common2'],
662
            'common2':['deeper'],
663
        }
664
        self.assertEqual(set(['h1', 'h2']),
665
            self._run_heads_break_deeper(graph_dict, ['h1', 'h2']))
3099.3.1 by John Arbash Meinel
Implement get_parent_map for ParentProviders
666
667
668
class TestCachingParentsProvider(tests.TestCase):
669
670
    def setUp(self):
671
        super(TestCachingParentsProvider, self).setUp()
672
        dict_pp = _mod_graph.DictParentsProvider({'a':('b',)})
673
        self.inst_pp = InstrumentedParentsProvider(dict_pp)
674
        self.caching_pp = _mod_graph.CachingParentsProvider(self.inst_pp)
675
676
    def test_get_parents(self):
677
        """Requesting the same revision should be returned from cache"""
678
        self.assertEqual({}, self.caching_pp._cache)
679
        self.assertEqual([('b',)], self.caching_pp.get_parents(['a']))
680
        self.assertEqual(['a'], self.inst_pp.calls)
681
        self.assertEqual([('b',)], self.caching_pp.get_parents(['a']))
682
        # No new call, as it should have been returned from the cache
683
        self.assertEqual(['a'], self.inst_pp.calls)
684
        self.assertEqual({'a':('b',)}, self.caching_pp._cache)
685
686
    def test_get_parents_not_present(self):
687
        """The cache should also track when a revision doesn't exist"""
688
        self.assertEqual([None], self.caching_pp.get_parents(['b']))
689
        self.assertEqual(['b'], self.inst_pp.calls)
690
        self.assertEqual([None], self.caching_pp.get_parents(['b']))
691
        # No new calls
692
        self.assertEqual(['b'], self.inst_pp.calls)
693
        self.assertEqual({'b':None}, self.caching_pp._cache)
694
695
    def test_get_parents_mixed(self):
696
        """Anything that can be returned from cache, should be"""
697
        self.assertEqual([None], self.caching_pp.get_parents(['b']))
698
        self.assertEqual(['b'], self.inst_pp.calls)
699
        self.assertEqual([('b',), None],
700
                         self.caching_pp.get_parents(['a', 'b']))
701
        self.assertEqual(['b', 'a'], self.inst_pp.calls)
702
703
    def test_get_parents_repeated(self):
704
        """Asking for the same parent 2x will only forward 1 request."""
705
        self.assertEqual([None, ('b',), None],
706
                         self.caching_pp.get_parents(['b', 'a', 'b']))
707
        # Use sorted because we don't care about the order, just that each is
708
        # only present 1 time.
709
        self.assertEqual(['a', 'b'], sorted(self.inst_pp.calls))