~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_graph.py

  • Committer: Andrew Bennetts
  • Date: 2008-05-21 11:58:09 UTC
  • mto: (3452.2.9 inter-remote-pack)
  • mto: This revision was merged to the branch mainline in revision 3511.
  • Revision ID: andrew.bennetts@canonical.com-20080521115809-6cw3t8gn4qm0bpg9
Remove a bit more debugging cruft.

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