/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_graph.py

  • Committer: John Arbash Meinel
  • Date: 2008-05-01 20:10:42 UTC
  • mto: This revision was merged to the branch mainline in revision 3407.
  • Revision ID: john@arbash-meinel.com-20080501201042-ep7n9ix7ebe5nlpk
Fix up a couple of the tests

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
17
from bzrlib import (
 
18
    errors,
 
19
    graph as _mod_graph,
 
20
    symbol_versioning,
 
21
    tests,
 
22
    )
 
23
from bzrlib.revision import NULL_REVISION
 
24
from bzrlib.tests import TestCaseWithMemoryTransport
 
25
 
 
26
 
 
27
# Ancestry 1:
 
28
#
 
29
#  NULL_REVISION
 
30
#       |
 
31
#     rev1
 
32
#      /\
 
33
#  rev2a rev2b
 
34
#     |    |
 
35
#   rev3  /
 
36
#     |  /
 
37
#   rev4
 
38
ancestry_1 = {'rev1': [NULL_REVISION], 'rev2a': ['rev1'], 'rev2b': ['rev1'],
 
39
              'rev3': ['rev2a'], 'rev4': ['rev3', 'rev2b']}
 
40
 
 
41
 
 
42
# Ancestry 2:
 
43
#
 
44
#  NULL_REVISION
 
45
#    /    \
 
46
# rev1a  rev1b
 
47
#   |
 
48
# rev2a
 
49
#   |
 
50
# rev3a
 
51
#   |
 
52
# rev4a
 
53
ancestry_2 = {'rev1a': [NULL_REVISION], 'rev2a': ['rev1a'],
 
54
              'rev1b': [NULL_REVISION], 'rev3a': ['rev2a'], 'rev4a': ['rev3a']}
 
55
 
 
56
 
 
57
# Criss cross ancestry
 
58
#
 
59
#     NULL_REVISION
 
60
#         |
 
61
#        rev1
 
62
#        /  \
 
63
#    rev2a  rev2b
 
64
#       |\  /|
 
65
#       |  X |
 
66
#       |/  \|
 
67
#    rev3a  rev3b
 
68
criss_cross = {'rev1': [NULL_REVISION], 'rev2a': ['rev1'], 'rev2b': ['rev1'],
 
69
               'rev3a': ['rev2a', 'rev2b'], 'rev3b': ['rev2b', 'rev2a']}
 
70
 
 
71
 
 
72
# Criss-cross 2
 
73
#
 
74
#  NULL_REVISION
 
75
#    /   \
 
76
# rev1a  rev1b
 
77
#   |\   /|
 
78
#   | \ / |
 
79
#   |  X  |
 
80
#   | / \ |
 
81
#   |/   \|
 
82
# rev2a  rev2b
 
83
criss_cross2 = {'rev1a': [NULL_REVISION], 'rev1b': [NULL_REVISION],
 
84
                'rev2a': ['rev1a', 'rev1b'], 'rev2b': ['rev1b', 'rev1a']}
 
85
 
 
86
 
 
87
# Mainline:
 
88
#
 
89
#  NULL_REVISION
 
90
#       |
 
91
#      rev1
 
92
#      /  \
 
93
#      | rev2b
 
94
#      |  /
 
95
#     rev2a
 
96
mainline = {'rev1': [NULL_REVISION], 'rev2a': ['rev1', 'rev2b'],
 
97
            'rev2b': ['rev1']}
 
98
 
 
99
 
 
100
# feature branch:
 
101
#
 
102
#  NULL_REVISION
 
103
#       |
 
104
#      rev1
 
105
#       |
 
106
#     rev2b
 
107
#       |
 
108
#     rev3b
 
109
feature_branch = {'rev1': [NULL_REVISION],
 
110
                  'rev2b': ['rev1'], 'rev3b': ['rev2b']}
 
111
 
 
112
 
 
113
# History shortcut
 
114
#  NULL_REVISION
 
115
#       |
 
116
#     rev1------
 
117
#     /  \      \
 
118
#  rev2a rev2b rev2c
 
119
#    |  /   \   /
 
120
#  rev3a    rev3b
 
121
history_shortcut = {'rev1': [NULL_REVISION], 'rev2a': ['rev1'],
 
122
                    'rev2b': ['rev1'], 'rev2c': ['rev1'],
 
123
                    'rev3a': ['rev2a', 'rev2b'], 'rev3b': ['rev2b', 'rev2c']}
 
124
 
 
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 beginning 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
#     | 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 |
 
209
#     | |
 
210
#     f |
 
211
#     | |
 
212
#     g h
 
213
#     | |\
 
214
#     i | j
 
215
#     |\| |
 
216
#     | k |
 
217
#     | | |
 
218
#     | l |
 
219
#     | | |
 
220
#     | m |
 
221
#     | | |
 
222
#     | n |
 
223
#     | | |
 
224
#     | o |
 
225
#     | | |
 
226
#     | p |
 
227
#     | | |
 
228
#     | q |
 
229
#     | | |
 
230
#     | r |
 
231
#     | | |
 
232
#     | s |
 
233
#     | | |
 
234
#     |/|/
 
235
#     t u
 
236
complex_shortcut2 = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'],
 
237
                    'e':['d'], 'f':['e'],
 
238
                    'g':['f'], 'h':['d'], 'k':['h', 'i'], 'j':['h'],
 
239
                    'i':['g'], 'l':['k'], 'm':['l'],
 
240
                    'n':['m'], 't':['i', 's'], 'u':['s', 'j'],
 
241
                    'o':['n'], 'p':['o'], 'q':['p'],
 
242
                    'r':['q'], 's':['r'],
 
243
                    }
 
244
 
 
245
# Graph where different walkers will race to find the common and uncommon
 
246
# nodes.
 
247
#
 
248
# NULL_REVISION
 
249
#     |
 
250
#     a
 
251
#     |
 
252
#     b
 
253
#     |
 
254
#     c
 
255
#     |
 
256
#     d
 
257
#     |\
 
258
#     e k
 
259
#     | |
 
260
#     f-+-p
 
261
#     | | |
 
262
#     | l |
 
263
#     | | |
 
264
#     | m |
 
265
#     | |\|
 
266
#     g n q
 
267
#     |\| |
 
268
#     h o |
 
269
#     |/| |
 
270
#     i r |
 
271
#     | | |
 
272
#     | s |
 
273
#     | | |
 
274
#     | t |
 
275
#     | | |
 
276
#     | u |
 
277
#     | | |
 
278
#     | v |
 
279
#     | | |
 
280
#     | w |
 
281
#     | | |
 
282
#     | x |
 
283
#     | |\|
 
284
#     | y z
 
285
#     |/
 
286
#     j
 
287
#
 
288
# y is found to be common right away, but is the start of a long series of
 
289
# common commits.
 
290
# o is actually common, but the i-j shortcut makes it look like it is actually
 
291
# unique to j at first, you have to traverse all of y->o to find it.
 
292
# q,n give the walker from j a common point to stop searching, as does p,f.
 
293
# k-n exists so that the second pass still has nodes that are worth searching,
 
294
# rather than instantly cancelling the extra walker.
 
295
 
 
296
racing_shortcuts = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'],
 
297
    'e':['d'], 'f':['e'], 'g':['f'], 'h':['g'], 'i':['h', 'o'], 'j':['i', 'y'],
 
298
    'k':['d'], 'l':['k'], 'm':['l'], 'n':['m'], 'o':['n', 'g'], 'p':['f'],
 
299
    'q':['p', 'm'], 'r':['o'], 's':['r'], 't':['s'], 'u':['t'], 'v':['u'],
 
300
    'w':['v'], 'x':['w'], 'y':['x'], 'z':['x', 'q']}
 
301
 
 
302
 
 
303
# A graph with multiple nodes unique to one side.
 
304
#
 
305
# NULL_REVISION
 
306
#     |
 
307
#     a
 
308
#     |
 
309
#     b
 
310
#     |
 
311
#     c
 
312
#     |
 
313
#     d
 
314
#     |\
 
315
#     e f
 
316
#     |\ \
 
317
#     g h i
 
318
#     |\ \ \
 
319
#     j k l m
 
320
#     | |/ x|
 
321
#     | n o p
 
322
#     | |/  |
 
323
#     | q   |
 
324
#     | |   |
 
325
#     | r   |
 
326
#     | |   |
 
327
#     | s   |
 
328
#     | |   |
 
329
#     | t   |
 
330
#     | |   |
 
331
#     | u   |
 
332
#     | |   |
 
333
#     | v   |
 
334
#     | |   |
 
335
#     | w   |
 
336
#     | |   |
 
337
#     | x   |
 
338
#     |/ \ /
 
339
#     y   z
 
340
#
 
341
 
 
342
multiple_interesting_unique = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'],
 
343
    'd':['c'], 'e':['d'], 'f':['d'], 'g':['e'], 'h':['e'], 'i':['f'],
 
344
    'j':['g'], 'k':['g'], 'l':['h'], 'm':['i'], 'n':['k', 'l'],
 
345
    'o':['m'], 'p':['m', 'l'], 'q':['n', 'o'], 'r':['q'], 's':['r'],
 
346
    't':['s'], 'u':['t'], 'v':['u'], 'w':['v'], 'x':['w'],
 
347
    'y':['j', 'x'], 'z':['x', 'p']}
 
348
 
 
349
 
 
350
# Shortcut with extra root
 
351
# We have a long history shortcut, and an extra root, which is why we can't
 
352
# stop searchers based on seeing NULL_REVISION
 
353
#  NULL_REVISION
 
354
#       |   |
 
355
#       a   |
 
356
#       |\  |
 
357
#       b | |
 
358
#       | | |
 
359
#       c | |
 
360
#       | | |
 
361
#       d | g
 
362
#       |\|/
 
363
#       e f
 
364
shortcut_extra_root = {'a': [NULL_REVISION],
 
365
                       'b': ['a'],
 
366
                       'c': ['b'],
 
367
                       'd': ['c'],
 
368
                       'e': ['d'],
 
369
                       'f': ['a', 'd', 'g'],
 
370
                       'g': [NULL_REVISION],
 
371
                      }
 
372
 
 
373
#  NULL_REVISION
 
374
#       |
 
375
#       f
 
376
#       |
 
377
#       e
 
378
#      / \
 
379
#     b   d
 
380
#     | \ |
 
381
#     a   c
 
382
 
 
383
boundary = {'a': ['b'], 'c': ['b', 'd'], 'b':['e'], 'd':['e'], 'e': ['f'],
 
384
            'f':[NULL_REVISION]}
 
385
 
 
386
 
 
387
# A graph that contains a ghost
 
388
#  NULL_REVISION
 
389
#       |
 
390
#       f
 
391
#       |
 
392
#       e   g
 
393
#      / \ /
 
394
#     b   d
 
395
#     | \ |
 
396
#     a   c
 
397
 
 
398
with_ghost = {'a': ['b'], 'c': ['b', 'd'], 'b':['e'], 'd':['e', 'g'],
 
399
              'e': ['f'], 'f':[NULL_REVISION], NULL_REVISION:()}
 
400
 
 
401
 
 
402
 
 
403
class InstrumentedParentsProvider(object):
 
404
 
 
405
    def __init__(self, parents_provider):
 
406
        self.calls = []
 
407
        self._real_parents_provider = parents_provider
 
408
 
 
409
    def get_parent_map(self, nodes):
 
410
        self.calls.extend(nodes)
 
411
        return self._real_parents_provider.get_parent_map(nodes)
 
412
 
 
413
    def get_parent_map(self, nodes):
 
414
        self.calls.extend(nodes)
 
415
        return self._real_parents_provider.get_parent_map(nodes)
 
416
 
 
417
 
 
418
class TestGraph(TestCaseWithMemoryTransport):
 
419
 
 
420
    def make_graph(self, ancestors):
 
421
        return _mod_graph.Graph(_mod_graph.DictParentsProvider(ancestors))
 
422
 
 
423
    def prepare_memory_tree(self, location):
 
424
        tree = self.make_branch_and_memory_tree(location)
 
425
        tree.lock_write()
 
426
        tree.add('.')
 
427
        return tree
 
428
 
 
429
    def build_ancestry(self, tree, ancestors):
 
430
        """Create an ancestry as specified by a graph dict
 
431
 
 
432
        :param tree: A tree to use
 
433
        :param ancestors: a dict of {node: [node_parent, ...]}
 
434
        """
 
435
        pending = [NULL_REVISION]
 
436
        descendants = {}
 
437
        for descendant, parents in ancestors.iteritems():
 
438
            for parent in parents:
 
439
                descendants.setdefault(parent, []).append(descendant)
 
440
        while len(pending) > 0:
 
441
            cur_node = pending.pop()
 
442
            for descendant in descendants.get(cur_node, []):
 
443
                if tree.branch.repository.has_revision(descendant):
 
444
                    continue
 
445
                parents = [p for p in ancestors[descendant] if p is not
 
446
                           NULL_REVISION]
 
447
                if len([p for p in parents if not
 
448
                    tree.branch.repository.has_revision(p)]) > 0:
 
449
                    continue
 
450
                tree.set_parent_ids(parents)
 
451
                if len(parents) > 0:
 
452
                    left_parent = parents[0]
 
453
                else:
 
454
                    left_parent = NULL_REVISION
 
455
                tree.branch.set_last_revision_info(
 
456
                    len(tree.branch._lefthand_history(left_parent)),
 
457
                    left_parent)
 
458
                tree.commit(descendant, rev_id=descendant)
 
459
                pending.append(descendant)
 
460
 
 
461
    def test_lca(self):
 
462
        """Test finding least common ancestor.
 
463
 
 
464
        ancestry_1 should always have a single common ancestor
 
465
        """
 
466
        graph = self.make_graph(ancestry_1)
 
467
        self.assertRaises(errors.InvalidRevisionId, graph.find_lca, None)
 
468
        self.assertEqual(set([NULL_REVISION]),
 
469
                         graph.find_lca(NULL_REVISION, NULL_REVISION))
 
470
        self.assertEqual(set([NULL_REVISION]),
 
471
                         graph.find_lca(NULL_REVISION, 'rev1'))
 
472
        self.assertEqual(set(['rev1']), graph.find_lca('rev1', 'rev1'))
 
473
        self.assertEqual(set(['rev1']), graph.find_lca('rev2a', 'rev2b'))
 
474
 
 
475
    def test_no_unique_lca(self):
 
476
        """Test error when one revision is not in the graph"""
 
477
        graph = self.make_graph(ancestry_1)
 
478
        self.assertRaises(errors.NoCommonAncestor, graph.find_unique_lca,
 
479
                          'rev1', '1rev')
 
480
 
 
481
    def test_lca_criss_cross(self):
 
482
        """Test least-common-ancestor after a criss-cross merge."""
 
483
        graph = self.make_graph(criss_cross)
 
484
        self.assertEqual(set(['rev2a', 'rev2b']),
 
485
                         graph.find_lca('rev3a', 'rev3b'))
 
486
        self.assertEqual(set(['rev2b']),
 
487
                         graph.find_lca('rev3a', 'rev3b', 'rev2b'))
 
488
 
 
489
    def test_lca_shortcut(self):
 
490
        """Test least-common ancestor on this history shortcut"""
 
491
        graph = self.make_graph(history_shortcut)
 
492
        self.assertEqual(set(['rev2b']), graph.find_lca('rev3a', 'rev3b'))
 
493
 
 
494
    def test_recursive_unique_lca(self):
 
495
        """Test finding a unique least common ancestor.
 
496
 
 
497
        ancestry_1 should always have a single common ancestor
 
498
        """
 
499
        graph = self.make_graph(ancestry_1)
 
500
        self.assertEqual(NULL_REVISION,
 
501
                         graph.find_unique_lca(NULL_REVISION, NULL_REVISION))
 
502
        self.assertEqual(NULL_REVISION,
 
503
                         graph.find_unique_lca(NULL_REVISION, 'rev1'))
 
504
        self.assertEqual('rev1', graph.find_unique_lca('rev1', 'rev1'))
 
505
        self.assertEqual('rev1', graph.find_unique_lca('rev2a', 'rev2b'))
 
506
        self.assertEqual(('rev1', 1,),
 
507
                         graph.find_unique_lca('rev2a', 'rev2b',
 
508
                         count_steps=True))
 
509
 
 
510
    def assertRemoveDescendants(self, expected, graph, revisions):
 
511
        parents = graph.get_parent_map(revisions)
 
512
        self.assertEqual(expected,
 
513
                         graph._remove_simple_descendants(revisions, parents))
 
514
 
 
515
    def test__remove_simple_descendants(self):
 
516
        graph = self.make_graph(ancestry_1)
 
517
        self.assertRemoveDescendants(set(['rev1']), graph,
 
518
            set(['rev1', 'rev2a', 'rev2b', 'rev3', 'rev4']))
 
519
 
 
520
    def test__remove_simple_descendants_disjoint(self):
 
521
        graph = self.make_graph(ancestry_1)
 
522
        self.assertRemoveDescendants(set(['rev1', 'rev3']), graph,
 
523
            set(['rev1', 'rev3']))
 
524
 
 
525
    def test__remove_simple_descendants_chain(self):
 
526
        graph = self.make_graph(ancestry_1)
 
527
        self.assertRemoveDescendants(set(['rev1']), graph,
 
528
            set(['rev1', 'rev2a', 'rev3']))
 
529
 
 
530
    def test__remove_simple_descendants_siblings(self):
 
531
        graph = self.make_graph(ancestry_1)
 
532
        self.assertRemoveDescendants(set(['rev2a', 'rev2b']), graph,
 
533
            set(['rev2a', 'rev2b', 'rev3']))
 
534
 
 
535
    def test_unique_lca_criss_cross(self):
 
536
        """Ensure we don't pick non-unique lcas in a criss-cross"""
 
537
        graph = self.make_graph(criss_cross)
 
538
        self.assertEqual('rev1', graph.find_unique_lca('rev3a', 'rev3b'))
 
539
        lca, steps = graph.find_unique_lca('rev3a', 'rev3b', count_steps=True)
 
540
        self.assertEqual('rev1', lca)
 
541
        self.assertEqual(2, steps)
 
542
 
 
543
    def test_unique_lca_null_revision(self):
 
544
        """Ensure we pick NULL_REVISION when necessary"""
 
545
        graph = self.make_graph(criss_cross2)
 
546
        self.assertEqual('rev1b', graph.find_unique_lca('rev2a', 'rev1b'))
 
547
        self.assertEqual(NULL_REVISION,
 
548
                         graph.find_unique_lca('rev2a', 'rev2b'))
 
549
 
 
550
    def test_unique_lca_null_revision2(self):
 
551
        """Ensure we pick NULL_REVISION when necessary"""
 
552
        graph = self.make_graph(ancestry_2)
 
553
        self.assertEqual(NULL_REVISION,
 
554
                         graph.find_unique_lca('rev4a', 'rev1b'))
 
555
 
 
556
    def test_lca_double_shortcut(self):
 
557
        graph = self.make_graph(double_shortcut)
 
558
        self.assertEqual('c', graph.find_unique_lca('f', 'g'))
 
559
 
 
560
    def test_common_ancestor_two_repos(self):
 
561
        """Ensure we do unique_lca using data from two repos"""
 
562
        mainline_tree = self.prepare_memory_tree('mainline')
 
563
        self.build_ancestry(mainline_tree, mainline)
 
564
        self.addCleanup(mainline_tree.unlock)
 
565
 
 
566
        # This is cheating, because the revisions in the graph are actually
 
567
        # different revisions, despite having the same revision-id.
 
568
        feature_tree = self.prepare_memory_tree('feature')
 
569
        self.build_ancestry(feature_tree, feature_branch)
 
570
        self.addCleanup(feature_tree.unlock)
 
571
 
 
572
        graph = mainline_tree.branch.repository.get_graph(
 
573
            feature_tree.branch.repository)
 
574
        self.assertEqual('rev2b', graph.find_unique_lca('rev2a', 'rev3b'))
 
575
 
 
576
    def test_graph_difference(self):
 
577
        graph = self.make_graph(ancestry_1)
 
578
        self.assertEqual((set(), set()), graph.find_difference('rev1', 'rev1'))
 
579
        self.assertEqual((set(), set(['rev1'])),
 
580
                         graph.find_difference(NULL_REVISION, 'rev1'))
 
581
        self.assertEqual((set(['rev1']), set()),
 
582
                         graph.find_difference('rev1', NULL_REVISION))
 
583
        self.assertEqual((set(['rev2a', 'rev3']), set(['rev2b'])),
 
584
                         graph.find_difference('rev3', 'rev2b'))
 
585
        self.assertEqual((set(['rev4', 'rev3', 'rev2a']), set()),
 
586
                         graph.find_difference('rev4', 'rev2b'))
 
587
 
 
588
    def test_graph_difference_separate_ancestry(self):
 
589
        graph = self.make_graph(ancestry_2)
 
590
        self.assertEqual((set(['rev1a']), set(['rev1b'])),
 
591
                         graph.find_difference('rev1a', 'rev1b'))
 
592
        self.assertEqual((set(['rev1a', 'rev2a', 'rev3a', 'rev4a']),
 
593
                          set(['rev1b'])),
 
594
                         graph.find_difference('rev4a', 'rev1b'))
 
595
 
 
596
    def test_graph_difference_criss_cross(self):
 
597
        graph = self.make_graph(criss_cross)
 
598
        self.assertEqual((set(['rev3a']), set(['rev3b'])),
 
599
                         graph.find_difference('rev3a', 'rev3b'))
 
600
        self.assertEqual((set([]), set(['rev3b', 'rev2b'])),
 
601
                         graph.find_difference('rev2a', 'rev3b'))
 
602
 
 
603
    def test_graph_difference_extended_history(self):
 
604
        graph = self.make_graph(extended_history_shortcut)
 
605
        self.assertEqual((set(['e']), set(['f'])),
 
606
                         graph.find_difference('e', 'f'))
 
607
        self.assertEqual((set(['f']), set(['e'])),
 
608
                         graph.find_difference('f', 'e'))
 
609
 
 
610
    def test_graph_difference_double_shortcut(self):
 
611
        graph = self.make_graph(double_shortcut)
 
612
        self.assertEqual((set(['d', 'f']), set(['e', 'g'])),
 
613
                         graph.find_difference('f', 'g'))
 
614
 
 
615
    def test_graph_difference_complex_shortcut(self):
 
616
        graph = self.make_graph(complex_shortcut)
 
617
        self.assertEqual((set(['m', 'i', 'e']), set(['n', 'h'])),
 
618
                         graph.find_difference('m', 'n'))
 
619
 
 
620
    def test_graph_difference_complex_shortcut2(self):
 
621
        graph = self.make_graph(complex_shortcut2)
 
622
        self.assertEqual((set(['t']), set(['j', 'u'])),
 
623
                         graph.find_difference('t', 'u'))
 
624
 
 
625
    def test_graph_difference_shortcut_extra_root(self):
 
626
        graph = self.make_graph(shortcut_extra_root)
 
627
        self.assertEqual((set(['e']), set(['f', 'g'])),
 
628
                         graph.find_difference('e', 'f'))
 
629
 
 
630
    def test_stacked_parents_provider(self):
 
631
        parents1 = _mod_graph.DictParentsProvider({'rev2': ['rev3']})
 
632
        parents2 = _mod_graph.DictParentsProvider({'rev1': ['rev4']})
 
633
        stacked = _mod_graph._StackedParentsProvider([parents1, parents2])
 
634
        self.assertEqual({'rev1':['rev4'], 'rev2':['rev3']},
 
635
                         stacked.get_parent_map(['rev1', 'rev2']))
 
636
        self.assertEqual({'rev2':['rev3'], 'rev1':['rev4']},
 
637
                         stacked.get_parent_map(['rev2', 'rev1']))
 
638
        self.assertEqual({'rev2':['rev3']},
 
639
                         stacked.get_parent_map(['rev2', 'rev2']))
 
640
        self.assertEqual({'rev1':['rev4']},
 
641
                         stacked.get_parent_map(['rev1', 'rev1']))
 
642
 
 
643
    def test_iter_topo_order(self):
 
644
        graph = self.make_graph(ancestry_1)
 
645
        args = ['rev2a', 'rev3', 'rev1']
 
646
        topo_args = list(graph.iter_topo_order(args))
 
647
        self.assertEqual(set(args), set(topo_args))
 
648
        self.assertTrue(topo_args.index('rev2a') > topo_args.index('rev1'))
 
649
        self.assertTrue(topo_args.index('rev2a') < topo_args.index('rev3'))
 
650
 
 
651
    def test_is_ancestor(self):
 
652
        graph = self.make_graph(ancestry_1)
 
653
        self.assertEqual(True, graph.is_ancestor('null:', 'null:'))
 
654
        self.assertEqual(True, graph.is_ancestor('null:', 'rev1'))
 
655
        self.assertEqual(False, graph.is_ancestor('rev1', 'null:'))
 
656
        self.assertEqual(True, graph.is_ancestor('null:', 'rev4'))
 
657
        self.assertEqual(False, graph.is_ancestor('rev4', 'null:'))
 
658
        self.assertEqual(False, graph.is_ancestor('rev4', 'rev2b'))
 
659
        self.assertEqual(True, graph.is_ancestor('rev2b', 'rev4'))
 
660
        self.assertEqual(False, graph.is_ancestor('rev2b', 'rev3'))
 
661
        self.assertEqual(False, graph.is_ancestor('rev3', 'rev2b'))
 
662
        instrumented_provider = InstrumentedParentsProvider(graph)
 
663
        instrumented_graph = _mod_graph.Graph(instrumented_provider)
 
664
        instrumented_graph.is_ancestor('rev2a', 'rev2b')
 
665
        self.assertTrue('null:' not in instrumented_provider.calls)
 
666
 
 
667
    def test_is_ancestor_boundary(self):
 
668
        """Ensure that we avoid searching the whole graph.
 
669
        
 
670
        This requires searching through b as a common ancestor, so we
 
671
        can identify that e is common.
 
672
        """
 
673
        graph = self.make_graph(boundary)
 
674
        instrumented_provider = InstrumentedParentsProvider(graph)
 
675
        graph = _mod_graph.Graph(instrumented_provider)
 
676
        self.assertFalse(graph.is_ancestor('a', 'c'))
 
677
        self.assertTrue('null:' not in instrumented_provider.calls)
 
678
 
 
679
    def test_iter_ancestry(self):
 
680
        nodes = boundary.copy()
 
681
        nodes[NULL_REVISION] = ()
 
682
        graph = self.make_graph(nodes)
 
683
        expected = nodes.copy()
 
684
        expected.pop('a') # 'a' is not in the ancestry of 'c', all the
 
685
                          # other nodes are
 
686
        self.assertEqual(expected, dict(graph.iter_ancestry(['c'])))
 
687
        self.assertEqual(nodes, dict(graph.iter_ancestry(['a', 'c'])))
 
688
 
 
689
    def test_iter_ancestry_with_ghost(self):
 
690
        graph = self.make_graph(with_ghost)
 
691
        expected = with_ghost.copy()
 
692
        # 'a' is not in the ancestry of 'c', and 'g' is a ghost
 
693
        expected['g'] = None
 
694
        self.assertEqual(expected, dict(graph.iter_ancestry(['a', 'c'])))
 
695
        expected.pop('a') 
 
696
        self.assertEqual(expected, dict(graph.iter_ancestry(['c'])))
 
697
 
 
698
    def test_filter_candidate_lca(self):
 
699
        """Test filter_candidate_lca for a corner case
 
700
 
 
701
        This tests the case where we encounter the end of iteration for 'e'
 
702
        in the same pass as we discover that 'd' is an ancestor of 'e', and
 
703
        therefore 'e' can't be an lca.
 
704
 
 
705
        To compensate for different dict orderings on other Python
 
706
        implementations, we mirror 'd' and 'e' with 'b' and 'a'.
 
707
        """
 
708
        # This test is sensitive to the iteration order of dicts.  It will
 
709
        # pass incorrectly if 'e' and 'a' sort before 'c'
 
710
        #
 
711
        # NULL_REVISION
 
712
        #     / \
 
713
        #    a   e
 
714
        #    |   |
 
715
        #    b   d
 
716
        #     \ /
 
717
        #      c
 
718
        graph = self.make_graph({'c': ['b', 'd'], 'd': ['e'], 'b': ['a'],
 
719
                                 'a': [NULL_REVISION], 'e': [NULL_REVISION]})
 
720
        self.assertEqual(set(['c']), graph.heads(['a', 'c', 'e']))
 
721
 
 
722
    def test_heads_null(self):
 
723
        graph = self.make_graph(ancestry_1)
 
724
        self.assertEqual(set(['null:']), graph.heads(['null:']))
 
725
        self.assertEqual(set(['rev1']), graph.heads(['null:', 'rev1']))
 
726
        self.assertEqual(set(['rev1']), graph.heads(['rev1', 'null:']))
 
727
        self.assertEqual(set(['rev1']), graph.heads(set(['rev1', 'null:'])))
 
728
        self.assertEqual(set(['rev1']), graph.heads(('rev1', 'null:')))
 
729
 
 
730
    def test_heads_one(self):
 
731
        # A single node will always be a head
 
732
        graph = self.make_graph(ancestry_1)
 
733
        self.assertEqual(set(['null:']), graph.heads(['null:']))
 
734
        self.assertEqual(set(['rev1']), graph.heads(['rev1']))
 
735
        self.assertEqual(set(['rev2a']), graph.heads(['rev2a']))
 
736
        self.assertEqual(set(['rev2b']), graph.heads(['rev2b']))
 
737
        self.assertEqual(set(['rev3']), graph.heads(['rev3']))
 
738
        self.assertEqual(set(['rev4']), graph.heads(['rev4']))
 
739
 
 
740
    def test_heads_single(self):
 
741
        graph = self.make_graph(ancestry_1)
 
742
        self.assertEqual(set(['rev4']), graph.heads(['null:', 'rev4']))
 
743
        self.assertEqual(set(['rev2a']), graph.heads(['rev1', 'rev2a']))
 
744
        self.assertEqual(set(['rev2b']), graph.heads(['rev1', 'rev2b']))
 
745
        self.assertEqual(set(['rev3']), graph.heads(['rev1', 'rev3']))
 
746
        self.assertEqual(set(['rev4']), graph.heads(['rev1', 'rev4']))
 
747
        self.assertEqual(set(['rev4']), graph.heads(['rev2a', 'rev4']))
 
748
        self.assertEqual(set(['rev4']), graph.heads(['rev2b', 'rev4']))
 
749
        self.assertEqual(set(['rev4']), graph.heads(['rev3', 'rev4']))
 
750
 
 
751
    def test_heads_two_heads(self):
 
752
        graph = self.make_graph(ancestry_1)
 
753
        self.assertEqual(set(['rev2a', 'rev2b']),
 
754
                         graph.heads(['rev2a', 'rev2b']))
 
755
        self.assertEqual(set(['rev3', 'rev2b']),
 
756
                         graph.heads(['rev3', 'rev2b']))
 
757
 
 
758
    def test_heads_criss_cross(self):
 
759
        graph = self.make_graph(criss_cross)
 
760
        self.assertEqual(set(['rev2a']),
 
761
                         graph.heads(['rev2a', 'rev1']))
 
762
        self.assertEqual(set(['rev2b']),
 
763
                         graph.heads(['rev2b', 'rev1']))
 
764
        self.assertEqual(set(['rev3a']),
 
765
                         graph.heads(['rev3a', 'rev1']))
 
766
        self.assertEqual(set(['rev3b']),
 
767
                         graph.heads(['rev3b', 'rev1']))
 
768
        self.assertEqual(set(['rev2a', 'rev2b']),
 
769
                         graph.heads(['rev2a', 'rev2b']))
 
770
        self.assertEqual(set(['rev3a']),
 
771
                         graph.heads(['rev3a', 'rev2a']))
 
772
        self.assertEqual(set(['rev3a']),
 
773
                         graph.heads(['rev3a', 'rev2b']))
 
774
        self.assertEqual(set(['rev3a']),
 
775
                         graph.heads(['rev3a', 'rev2a', 'rev2b']))
 
776
        self.assertEqual(set(['rev3b']),
 
777
                         graph.heads(['rev3b', 'rev2a']))
 
778
        self.assertEqual(set(['rev3b']),
 
779
                         graph.heads(['rev3b', 'rev2b']))
 
780
        self.assertEqual(set(['rev3b']),
 
781
                         graph.heads(['rev3b', 'rev2a', 'rev2b']))
 
782
        self.assertEqual(set(['rev3a', 'rev3b']),
 
783
                         graph.heads(['rev3a', 'rev3b']))
 
784
        self.assertEqual(set(['rev3a', 'rev3b']),
 
785
                         graph.heads(['rev3a', 'rev3b', 'rev2a', 'rev2b']))
 
786
 
 
787
    def test_heads_shortcut(self):
 
788
        graph = self.make_graph(history_shortcut)
 
789
 
 
790
        self.assertEqual(set(['rev2a', 'rev2b', 'rev2c']),
 
791
                         graph.heads(['rev2a', 'rev2b', 'rev2c']))
 
792
        self.assertEqual(set(['rev3a', 'rev3b']),
 
793
                         graph.heads(['rev3a', 'rev3b']))
 
794
        self.assertEqual(set(['rev3a', 'rev3b']),
 
795
                         graph.heads(['rev2a', 'rev3a', 'rev3b']))
 
796
        self.assertEqual(set(['rev2a', 'rev3b']),
 
797
                         graph.heads(['rev2a', 'rev3b']))
 
798
        self.assertEqual(set(['rev2c', 'rev3a']),
 
799
                         graph.heads(['rev2c', 'rev3a']))
 
800
 
 
801
    def _run_heads_break_deeper(self, graph_dict, search):
 
802
        """Run heads on a graph-as-a-dict.
 
803
        
 
804
        If the search asks for the parents of 'deeper' the test will fail.
 
805
        """
 
806
        class stub(object):
 
807
            pass
 
808
        def get_parent_map(keys):
 
809
            result = {}
 
810
            for key in keys:
 
811
                if key == 'deeper':
 
812
                    self.fail('key deeper was accessed')
 
813
                result[key] = graph_dict[key]
 
814
            return result
 
815
        an_obj = stub()
 
816
        an_obj.get_parent_map = get_parent_map
 
817
        graph = _mod_graph.Graph(an_obj)
 
818
        return graph.heads(search)
 
819
 
 
820
    def test_heads_limits_search(self):
 
821
        # test that a heads query does not search all of history
 
822
        graph_dict = {
 
823
            'left':['common'],
 
824
            'right':['common'],
 
825
            'common':['deeper'],
 
826
        }
 
827
        self.assertEqual(set(['left', 'right']),
 
828
            self._run_heads_break_deeper(graph_dict, ['left', 'right']))
 
829
 
 
830
    def test_heads_limits_search_assymetric(self):
 
831
        # test that a heads query does not search all of history
 
832
        graph_dict = {
 
833
            'left':['midleft'],
 
834
            'midleft':['common'],
 
835
            'right':['common'],
 
836
            'common':['aftercommon'],
 
837
            'aftercommon':['deeper'],
 
838
        }
 
839
        self.assertEqual(set(['left', 'right']),
 
840
            self._run_heads_break_deeper(graph_dict, ['left', 'right']))
 
841
 
 
842
    def test_heads_limits_search_common_search_must_continue(self):
 
843
        # test that common nodes are still queried, preventing
 
844
        # all-the-way-to-origin behaviour in the following graph:
 
845
        graph_dict = {
 
846
            'h1':['shortcut', 'common1'],
 
847
            'h2':['common1'],
 
848
            'shortcut':['common2'],
 
849
            'common1':['common2'],
 
850
            'common2':['deeper'],
 
851
        }
 
852
        self.assertEqual(set(['h1', 'h2']),
 
853
            self._run_heads_break_deeper(graph_dict, ['h1', 'h2']))
 
854
 
 
855
    def test_breadth_first_search_start_ghosts(self):
 
856
        graph = self.make_graph({})
 
857
        # with_ghosts reports the ghosts
 
858
        search = graph._make_breadth_first_searcher(['a-ghost'])
 
859
        self.assertEqual((set(), set(['a-ghost'])), search.next_with_ghosts())
 
860
        self.assertRaises(StopIteration, search.next_with_ghosts)
 
861
        # next includes them
 
862
        search = graph._make_breadth_first_searcher(['a-ghost'])
 
863
        self.assertEqual(set(['a-ghost']), search.next())
 
864
        self.assertRaises(StopIteration, search.next)
 
865
 
 
866
    def test_breadth_first_search_deep_ghosts(self):
 
867
        graph = self.make_graph({
 
868
            'head':['present'],
 
869
            'present':['child', 'ghost'],
 
870
            'child':[],
 
871
            })
 
872
        # with_ghosts reports the ghosts
 
873
        search = graph._make_breadth_first_searcher(['head'])
 
874
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
 
875
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
 
876
        self.assertEqual((set(['child']), set(['ghost'])),
 
877
            search.next_with_ghosts())
 
878
        self.assertRaises(StopIteration, search.next_with_ghosts)
 
879
        # next includes them
 
880
        search = graph._make_breadth_first_searcher(['head'])
 
881
        self.assertEqual(set(['head']), search.next())
 
882
        self.assertEqual(set(['present']), search.next())
 
883
        self.assertEqual(set(['child', 'ghost']),
 
884
            search.next())
 
885
        self.assertRaises(StopIteration, search.next)
 
886
 
 
887
    def test_breadth_first_search_change_next_to_next_with_ghosts(self):
 
888
        # To make the API robust, we allow calling both next() and
 
889
        # next_with_ghosts() on the same searcher.
 
890
        graph = self.make_graph({
 
891
            'head':['present'],
 
892
            'present':['child', 'ghost'],
 
893
            'child':[],
 
894
            })
 
895
        # start with next_with_ghosts
 
896
        search = graph._make_breadth_first_searcher(['head'])
 
897
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
 
898
        self.assertEqual(set(['present']), search.next())
 
899
        self.assertEqual((set(['child']), set(['ghost'])),
 
900
            search.next_with_ghosts())
 
901
        self.assertRaises(StopIteration, search.next)
 
902
        # start with next
 
903
        search = graph._make_breadth_first_searcher(['head'])
 
904
        self.assertEqual(set(['head']), search.next())
 
905
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
 
906
        self.assertEqual(set(['child', 'ghost']),
 
907
            search.next())
 
908
        self.assertRaises(StopIteration, search.next_with_ghosts)
 
909
 
 
910
    def test_breadth_first_change_search(self):
 
911
        # Changing the search should work with both next and next_with_ghosts.
 
912
        graph = self.make_graph({
 
913
            'head':['present'],
 
914
            'present':['stopped'],
 
915
            'other':['other_2'],
 
916
            'other_2':[],
 
917
            })
 
918
        search = graph._make_breadth_first_searcher(['head'])
 
919
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
 
920
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
 
921
        self.assertEqual(set(['present']),
 
922
            search.stop_searching_any(['present']))
 
923
        self.assertEqual((set(['other']), set(['other_ghost'])),
 
924
            search.start_searching(['other', 'other_ghost']))
 
925
        self.assertEqual((set(['other_2']), set()), search.next_with_ghosts())
 
926
        self.assertRaises(StopIteration, search.next_with_ghosts)
 
927
        # next includes them
 
928
        search = graph._make_breadth_first_searcher(['head'])
 
929
        self.assertEqual(set(['head']), search.next())
 
930
        self.assertEqual(set(['present']), search.next())
 
931
        self.assertEqual(set(['present']),
 
932
            search.stop_searching_any(['present']))
 
933
        search.start_searching(['other', 'other_ghost'])
 
934
        self.assertEqual(set(['other_2']), search.next())
 
935
        self.assertRaises(StopIteration, search.next)
 
936
 
 
937
    def assertSeenAndResult(self, instructions, search, next):
 
938
        """Check the results of .seen and get_result() for a seach.
 
939
 
 
940
        :param instructions: A list of tuples:
 
941
            (seen, recipe, included_keys, starts, stops).
 
942
            seen, recipe and included_keys are results to check on the search
 
943
            and the searches get_result(). starts and stops are parameters to
 
944
            pass to start_searching and stop_searching_any during each
 
945
            iteration, if they are not None.
 
946
        :param search: The search to use.
 
947
        :param next: A callable to advance the search.
 
948
        """
 
949
        for seen, recipe, included_keys, starts, stops in instructions:
 
950
            next()
 
951
            if starts is not None:
 
952
                search.start_searching(starts)
 
953
            if stops is not None:
 
954
                search.stop_searching_any(stops)
 
955
            result = search.get_result()
 
956
            self.assertEqual(recipe, result.get_recipe())
 
957
            self.assertEqual(set(included_keys), result.get_keys())
 
958
            self.assertEqual(seen, search.seen)
 
959
 
 
960
    def test_breadth_first_get_result_excludes_current_pending(self):
 
961
        graph = self.make_graph({
 
962
            'head':['child'],
 
963
            'child':[NULL_REVISION],
 
964
            NULL_REVISION:[],
 
965
            })
 
966
        search = graph._make_breadth_first_searcher(['head'])
 
967
        # At the start, nothing has been seen, to its all excluded:
 
968
        result = search.get_result()
 
969
        self.assertEqual((set(['head']), set(['head']), 0),
 
970
            result.get_recipe())
 
971
        self.assertEqual(set(), result.get_keys())
 
972
        self.assertEqual(set(), search.seen)
 
973
        # using next:
 
974
        expected = [
 
975
            (set(['head']), (set(['head']), set(['child']), 1),
 
976
             ['head'], None, None),
 
977
            (set(['head', 'child']), (set(['head']), set([NULL_REVISION]), 2),
 
978
             ['head', 'child'], None, None),
 
979
            (set(['head', 'child', NULL_REVISION]), (set(['head']), set(), 3),
 
980
             ['head', 'child', NULL_REVISION], None, None),
 
981
            ]
 
982
        self.assertSeenAndResult(expected, search, search.next)
 
983
        # using next_with_ghosts:
 
984
        search = graph._make_breadth_first_searcher(['head'])
 
985
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
 
986
 
 
987
    def test_breadth_first_get_result_starts_stops(self):
 
988
        graph = self.make_graph({
 
989
            'head':['child'],
 
990
            'child':[NULL_REVISION],
 
991
            'otherhead':['otherchild'],
 
992
            'otherchild':['excluded'],
 
993
            'excluded':[NULL_REVISION],
 
994
            NULL_REVISION:[]
 
995
            })
 
996
        search = graph._make_breadth_first_searcher([])
 
997
        # Starting with nothing and adding a search works:
 
998
        search.start_searching(['head'])
 
999
        # head has been seen:
 
1000
        result = search.get_result()
 
1001
        self.assertEqual((set(['head']), set(['child']), 1),
 
1002
            result.get_recipe())
 
1003
        self.assertEqual(set(['head']), result.get_keys())
 
1004
        self.assertEqual(set(['head']), search.seen)
 
1005
        # using next:
 
1006
        expected = [
 
1007
            # stop at child, and start a new search at otherhead:
 
1008
            # - otherhead counts as seen immediately when start_searching is
 
1009
            # called.
 
1010
            (set(['head', 'child', 'otherhead']),
 
1011
             (set(['head', 'otherhead']), set(['child', 'otherchild']), 2),
 
1012
             ['head', 'otherhead'], ['otherhead'], ['child']),
 
1013
            (set(['head', 'child', 'otherhead', 'otherchild']),
 
1014
             (set(['head', 'otherhead']), set(['child', 'excluded']), 3),
 
1015
             ['head', 'otherhead', 'otherchild'], None, None),
 
1016
            # stop searching excluded now
 
1017
            (set(['head', 'child', 'otherhead', 'otherchild', 'excluded']),
 
1018
             (set(['head', 'otherhead']), set(['child', 'excluded']), 3),
 
1019
             ['head', 'otherhead', 'otherchild'], None, ['excluded']),
 
1020
            ]
 
1021
        self.assertSeenAndResult(expected, search, search.next)
 
1022
        # using next_with_ghosts:
 
1023
        search = graph._make_breadth_first_searcher([])
 
1024
        search.start_searching(['head'])
 
1025
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
 
1026
 
 
1027
    def test_breadth_first_stop_searching_not_queried(self):
 
1028
        # A client should be able to say 'stop node X' even if X has not been
 
1029
        # returned to the client.
 
1030
        graph = self.make_graph({
 
1031
            'head':['child', 'ghost1'],
 
1032
            'child':[NULL_REVISION],
 
1033
            NULL_REVISION:[],
 
1034
            })
 
1035
        search = graph._make_breadth_first_searcher(['head'])
 
1036
        expected = [
 
1037
            # NULL_REVISION and ghost1 have not been returned
 
1038
            (set(['head']), (set(['head']), set(['child', 'ghost1']), 1),
 
1039
             ['head'], None, [NULL_REVISION, 'ghost1']),
 
1040
            # ghost1 has been returned, NULL_REVISION is to be returned in the
 
1041
            # next iteration.
 
1042
            (set(['head', 'child', 'ghost1']),
 
1043
             (set(['head']), set(['ghost1', NULL_REVISION]), 2),
 
1044
             ['head', 'child'], None, [NULL_REVISION, 'ghost1']),
 
1045
            ]
 
1046
        self.assertSeenAndResult(expected, search, search.next)
 
1047
        # using next_with_ghosts:
 
1048
        search = graph._make_breadth_first_searcher(['head'])
 
1049
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
 
1050
 
 
1051
    def test_breadth_first_get_result_ghosts_are_excluded(self):
 
1052
        graph = self.make_graph({
 
1053
            'head':['child', 'ghost'],
 
1054
            'child':[NULL_REVISION],
 
1055
            NULL_REVISION:[],
 
1056
            })
 
1057
        search = graph._make_breadth_first_searcher(['head'])
 
1058
        # using next:
 
1059
        expected = [
 
1060
            (set(['head']),
 
1061
             (set(['head']), set(['ghost', 'child']), 1),
 
1062
             ['head'], None, None),
 
1063
            (set(['head', 'child', 'ghost']),
 
1064
             (set(['head']), set([NULL_REVISION, 'ghost']), 2),
 
1065
             ['head', 'child'], None, None),
 
1066
            ]
 
1067
        self.assertSeenAndResult(expected, search, search.next)
 
1068
        # using next_with_ghosts:
 
1069
        search = graph._make_breadth_first_searcher(['head'])
 
1070
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
 
1071
 
 
1072
    def test_breadth_first_get_result_starting_a_ghost_ghost_is_excluded(self):
 
1073
        graph = self.make_graph({
 
1074
            'head':['child'],
 
1075
            'child':[NULL_REVISION],
 
1076
            NULL_REVISION:[],
 
1077
            })
 
1078
        search = graph._make_breadth_first_searcher(['head'])
 
1079
        # using next:
 
1080
        expected = [
 
1081
            (set(['head', 'ghost']),
 
1082
             (set(['head', 'ghost']), set(['child', 'ghost']), 1),
 
1083
             ['head'], ['ghost'], None),
 
1084
            (set(['head', 'child', 'ghost']),
 
1085
             (set(['head', 'ghost']), set([NULL_REVISION, 'ghost']), 2),
 
1086
             ['head', 'child'], None, None),
 
1087
            ]
 
1088
        self.assertSeenAndResult(expected, search, search.next)
 
1089
        # using next_with_ghosts:
 
1090
        search = graph._make_breadth_first_searcher(['head'])
 
1091
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
 
1092
 
 
1093
    def test_breadth_first_revision_count_includes_NULL_REVISION(self):
 
1094
        graph = self.make_graph({
 
1095
            'head':[NULL_REVISION],
 
1096
            NULL_REVISION:[],
 
1097
            })
 
1098
        search = graph._make_breadth_first_searcher(['head'])
 
1099
        # using next:
 
1100
        expected = [
 
1101
            (set(['head']),
 
1102
             (set(['head']), set([NULL_REVISION]), 1),
 
1103
             ['head'], None, None),
 
1104
            (set(['head', NULL_REVISION]),
 
1105
             (set(['head']), set([]), 2),
 
1106
             ['head', NULL_REVISION], None, None),
 
1107
            ]
 
1108
        self.assertSeenAndResult(expected, search, search.next)
 
1109
        # using next_with_ghosts:
 
1110
        search = graph._make_breadth_first_searcher(['head'])
 
1111
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
 
1112
 
 
1113
    def test_breadth_first_search_get_result_after_StopIteration(self):
 
1114
        # StopIteration should not invalid anything..
 
1115
        graph = self.make_graph({
 
1116
            'head':[NULL_REVISION],
 
1117
            NULL_REVISION:[],
 
1118
            })
 
1119
        search = graph._make_breadth_first_searcher(['head'])
 
1120
        # using next:
 
1121
        expected = [
 
1122
            (set(['head']),
 
1123
             (set(['head']), set([NULL_REVISION]), 1),
 
1124
             ['head'], None, None),
 
1125
            (set(['head', 'ghost', NULL_REVISION]),
 
1126
             (set(['head', 'ghost']), set(['ghost']), 2),
 
1127
             ['head', NULL_REVISION], ['ghost'], None),
 
1128
            ]
 
1129
        self.assertSeenAndResult(expected, search, search.next)
 
1130
        self.assertRaises(StopIteration, search.next)
 
1131
        self.assertEqual(set(['head', 'ghost', NULL_REVISION]), search.seen)
 
1132
        result = search.get_result()
 
1133
        self.assertEqual((set(['ghost', 'head']), set(['ghost']), 2),
 
1134
            result.get_recipe())
 
1135
        self.assertEqual(set(['head', NULL_REVISION]), result.get_keys())
 
1136
        # using next_with_ghosts:
 
1137
        search = graph._make_breadth_first_searcher(['head'])
 
1138
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
 
1139
        self.assertRaises(StopIteration, search.next)
 
1140
        self.assertEqual(set(['head', 'ghost', NULL_REVISION]), search.seen)
 
1141
        result = search.get_result()
 
1142
        self.assertEqual((set(['ghost', 'head']), set(['ghost']), 2),
 
1143
            result.get_recipe())
 
1144
        self.assertEqual(set(['head', NULL_REVISION]), result.get_keys())
 
1145
 
 
1146
 
 
1147
class TestFindUniqueAncestors(tests.TestCase):
 
1148
 
 
1149
    def make_graph(self, ancestors):
 
1150
        return _mod_graph.Graph(_mod_graph.DictParentsProvider(ancestors))
 
1151
 
 
1152
    def make_breaking_graph(self, ancestors, break_on):
 
1153
        """Make a Graph that raises an exception if we hit a node."""
 
1154
        g = self.make_graph(ancestors)
 
1155
        orig_parent_map = g.get_parent_map
 
1156
        def get_parent_map(keys):
 
1157
            bad_keys = set(keys).intersection(break_on)
 
1158
            if bad_keys:
 
1159
                self.fail('key(s) %s was accessed' % (sorted(bad_keys),))
 
1160
            return orig_parent_map(keys)
 
1161
        g.get_parent_map = get_parent_map
 
1162
        return g
 
1163
 
 
1164
    def assertFindUniqueAncestors(self, graph, expected, node, common):
 
1165
        actual = graph.find_unique_ancestors(node, common)
 
1166
        self.assertEqual(expected, sorted(actual))
 
1167
 
 
1168
    def test_empty_set(self):
 
1169
        graph = self.make_graph(ancestry_1)
 
1170
        self.assertFindUniqueAncestors(graph, [], 'rev1', ['rev1'])
 
1171
        self.assertFindUniqueAncestors(graph, [], 'rev2b', ['rev2b'])
 
1172
        self.assertFindUniqueAncestors(graph, [], 'rev3', ['rev1', 'rev3'])
 
1173
 
 
1174
    def test_single_node(self):
 
1175
        graph = self.make_graph(ancestry_1)
 
1176
        self.assertFindUniqueAncestors(graph, ['rev2a'], 'rev2a', ['rev1'])
 
1177
        self.assertFindUniqueAncestors(graph, ['rev2b'], 'rev2b', ['rev1'])
 
1178
        self.assertFindUniqueAncestors(graph, ['rev3'], 'rev3', ['rev2a'])
 
1179
 
 
1180
    def test_minimal_ancestry(self):
 
1181
        graph = self.make_breaking_graph(extended_history_shortcut,
 
1182
                                         [NULL_REVISION, 'a', 'b'])
 
1183
        self.assertFindUniqueAncestors(graph, ['e'], 'e', ['d'])
 
1184
 
 
1185
        graph = self.make_breaking_graph(extended_history_shortcut,
 
1186
                                         ['b'])
 
1187
        self.assertFindUniqueAncestors(graph, ['f'], 'f', ['a', 'd'])
 
1188
 
 
1189
        graph = self.make_breaking_graph(complex_shortcut,
 
1190
                                         ['a', 'b'])
 
1191
        self.assertFindUniqueAncestors(graph, ['h'], 'h', ['i'])
 
1192
        self.assertFindUniqueAncestors(graph, ['e', 'g', 'i'], 'i', ['h'])
 
1193
        self.assertFindUniqueAncestors(graph, ['h'], 'h', ['g'])
 
1194
        self.assertFindUniqueAncestors(graph, ['h'], 'h', ['j'])
 
1195
 
 
1196
    def test_in_ancestry(self):
 
1197
        graph = self.make_graph(ancestry_1)
 
1198
        self.assertFindUniqueAncestors(graph, [], 'rev1', ['rev3'])
 
1199
        self.assertFindUniqueAncestors(graph, [], 'rev2b', ['rev4'])
 
1200
 
 
1201
    def test_multiple_revisions(self):
 
1202
        graph = self.make_graph(ancestry_1)
 
1203
        self.assertFindUniqueAncestors(graph,
 
1204
            ['rev4'], 'rev4', ['rev3', 'rev2b'])
 
1205
        self.assertFindUniqueAncestors(graph,
 
1206
            ['rev2a', 'rev3', 'rev4'], 'rev4', ['rev2b'])
 
1207
 
 
1208
    def test_complex_shortcut(self):
 
1209
        graph = self.make_graph(complex_shortcut)
 
1210
        self.assertFindUniqueAncestors(graph,
 
1211
            ['h', 'n'], 'n', ['m'])
 
1212
        self.assertFindUniqueAncestors(graph,
 
1213
            ['e', 'i', 'm'], 'm', ['n'])
 
1214
 
 
1215
    def test_complex_shortcut2(self):
 
1216
        graph = self.make_graph(complex_shortcut2)
 
1217
        self.assertFindUniqueAncestors(graph,
 
1218
            ['j', 'u'], 'u', ['t'])
 
1219
        self.assertFindUniqueAncestors(graph,
 
1220
            ['t'], 't', ['u'])
 
1221
 
 
1222
    def test_multiple_interesting_unique(self):
 
1223
        graph = self.make_graph(multiple_interesting_unique)
 
1224
        self.assertFindUniqueAncestors(graph,
 
1225
            ['j', 'y'], 'y', ['z'])
 
1226
        self.assertFindUniqueAncestors(graph,
 
1227
            ['p', 'z'], 'z', ['y'])
 
1228
 
 
1229
    def test_racing_shortcuts(self):
 
1230
        graph = self.make_graph(racing_shortcuts)
 
1231
        self.assertFindUniqueAncestors(graph,
 
1232
            ['p', 'q', 'z'], 'z', ['y'])
 
1233
        import pdb; pdb.set_trace()
 
1234
        self.assertFindUniqueAncestors(graph,
 
1235
            ['h', 'i', 'j', 'y'], 'j', ['z'])
 
1236
 
 
1237
 
 
1238
class TestCachingParentsProvider(tests.TestCase):
 
1239
 
 
1240
    def setUp(self):
 
1241
        super(TestCachingParentsProvider, self).setUp()
 
1242
        dict_pp = _mod_graph.DictParentsProvider({'a':('b',)})
 
1243
        self.inst_pp = InstrumentedParentsProvider(dict_pp)
 
1244
        self.caching_pp = _mod_graph.CachingParentsProvider(self.inst_pp)
 
1245
 
 
1246
    def test_get_parent_map(self):
 
1247
        """Requesting the same revision should be returned from cache"""
 
1248
        self.assertEqual({}, self.caching_pp._cache)
 
1249
        self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
 
1250
        self.assertEqual(['a'], self.inst_pp.calls)
 
1251
        self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
 
1252
        # No new call, as it should have been returned from the cache
 
1253
        self.assertEqual(['a'], self.inst_pp.calls)
 
1254
        self.assertEqual({'a':('b',)}, self.caching_pp._cache)
 
1255
 
 
1256
    def test_get_parent_map_not_present(self):
 
1257
        """The cache should also track when a revision doesn't exist"""
 
1258
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
 
1259
        self.assertEqual(['b'], self.inst_pp.calls)
 
1260
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
 
1261
        # No new calls
 
1262
        self.assertEqual(['b'], self.inst_pp.calls)
 
1263
        self.assertEqual({'b':None}, self.caching_pp._cache)
 
1264
 
 
1265
    def test_get_parent_map_mixed(self):
 
1266
        """Anything that can be returned from cache, should be"""
 
1267
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
 
1268
        self.assertEqual(['b'], self.inst_pp.calls)
 
1269
        self.assertEqual({'a':('b',)},
 
1270
                         self.caching_pp.get_parent_map(['a', 'b']))
 
1271
        self.assertEqual(['b', 'a'], self.inst_pp.calls)
 
1272
 
 
1273
    def test_get_parent_map_repeated(self):
 
1274
        """Asking for the same parent 2x will only forward 1 request."""
 
1275
        self.assertEqual({'a':('b',)},
 
1276
                         self.caching_pp.get_parent_map(['b', 'a', 'b']))
 
1277
        # Use sorted because we don't care about the order, just that each is
 
1278
        # only present 1 time.
 
1279
        self.assertEqual(['a', 'b'], sorted(self.inst_pp.calls))