~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_graph.py

  • Committer: Robert Collins
  • Date: 2005-10-17 11:41:07 UTC
  • mfrom: (1442.1.60)
  • Revision ID: robertc@robertcollins.net-20051017114107-f5586285d825c105
Merge in first part of GPG support.

This adds check_signatures config support, triams back the transport api
to be leaner and easier to hook in suffixes - non primary streams in the store
associated with the fileid that primary data is stored in, a gpg module which
will encapsulate all signing and checking operations.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2011 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
from bzrlib import (
18
 
    errors,
19
 
    graph as _mod_graph,
20
 
    tests,
21
 
    )
22
 
from bzrlib.revision import NULL_REVISION
23
 
from bzrlib.tests import TestCaseWithMemoryTransport
24
 
 
25
 
 
26
 
# Ancestry 1:
27
 
#
28
 
#  NULL_REVISION
29
 
#       |
30
 
#     rev1
31
 
#      /\
32
 
#  rev2a rev2b
33
 
#     |    |
34
 
#   rev3  /
35
 
#     |  /
36
 
#   rev4
37
 
ancestry_1 = {'rev1': [NULL_REVISION], 'rev2a': ['rev1'], 'rev2b': ['rev1'],
38
 
              'rev3': ['rev2a'], 'rev4': ['rev3', 'rev2b']}
39
 
 
40
 
 
41
 
# Ancestry 2:
42
 
#
43
 
#  NULL_REVISION
44
 
#    /    \
45
 
# rev1a  rev1b
46
 
#   |
47
 
# rev2a
48
 
#   |
49
 
# rev3a
50
 
#   |
51
 
# rev4a
52
 
ancestry_2 = {'rev1a': [NULL_REVISION], 'rev2a': ['rev1a'],
53
 
              'rev1b': [NULL_REVISION], 'rev3a': ['rev2a'], 'rev4a': ['rev3a']}
54
 
 
55
 
 
56
 
# Criss cross ancestry
57
 
#
58
 
#     NULL_REVISION
59
 
#         |
60
 
#        rev1
61
 
#        /  \
62
 
#    rev2a  rev2b
63
 
#       |\  /|
64
 
#       |  X |
65
 
#       |/  \|
66
 
#    rev3a  rev3b
67
 
criss_cross = {'rev1': [NULL_REVISION], 'rev2a': ['rev1'], 'rev2b': ['rev1'],
68
 
               'rev3a': ['rev2a', 'rev2b'], 'rev3b': ['rev2b', 'rev2a']}
69
 
 
70
 
 
71
 
# Criss-cross 2
72
 
#
73
 
#  NULL_REVISION
74
 
#    /   \
75
 
# rev1a  rev1b
76
 
#   |\   /|
77
 
#   | \ / |
78
 
#   |  X  |
79
 
#   | / \ |
80
 
#   |/   \|
81
 
# rev2a  rev2b
82
 
criss_cross2 = {'rev1a': [NULL_REVISION], 'rev1b': [NULL_REVISION],
83
 
                'rev2a': ['rev1a', 'rev1b'], 'rev2b': ['rev1b', 'rev1a']}
84
 
 
85
 
 
86
 
# Mainline:
87
 
#
88
 
#  NULL_REVISION
89
 
#       |
90
 
#      rev1
91
 
#      /  \
92
 
#      | rev2b
93
 
#      |  /
94
 
#     rev2a
95
 
mainline = {'rev1': [NULL_REVISION], 'rev2a': ['rev1', 'rev2b'],
96
 
            'rev2b': ['rev1']}
97
 
 
98
 
 
99
 
# feature branch:
100
 
#
101
 
#  NULL_REVISION
102
 
#       |
103
 
#      rev1
104
 
#       |
105
 
#     rev2b
106
 
#       |
107
 
#     rev3b
108
 
feature_branch = {'rev1': [NULL_REVISION],
109
 
                  'rev2b': ['rev1'], 'rev3b': ['rev2b']}
110
 
 
111
 
 
112
 
# History shortcut
113
 
#  NULL_REVISION
114
 
#       |
115
 
#     rev1------
116
 
#     /  \      \
117
 
#  rev2a rev2b rev2c
118
 
#    |  /   \   /
119
 
#  rev3a    rev3b
120
 
history_shortcut = {'rev1': [NULL_REVISION], 'rev2a': ['rev1'],
121
 
                    'rev2b': ['rev1'], 'rev2c': ['rev1'],
122
 
                    'rev3a': ['rev2a', 'rev2b'], 'rev3b': ['rev2b', 'rev2c']}
123
 
 
124
 
# Extended history shortcut
125
 
#  NULL_REVISION
126
 
#       |
127
 
#       a
128
 
#       |\
129
 
#       b |
130
 
#       | |
131
 
#       c |
132
 
#       | |
133
 
#       d |
134
 
#       |\|
135
 
#       e f
136
 
extended_history_shortcut = {'a': [NULL_REVISION],
137
 
                             'b': ['a'],
138
 
                             'c': ['b'],
139
 
                             'd': ['c'],
140
 
                             'e': ['d'],
141
 
                             'f': ['a', 'd'],
142
 
                            }
143
 
 
144
 
# Double shortcut
145
 
# Both sides will see 'A' first, even though it is actually a decendent of a
146
 
# different common revision.
147
 
#
148
 
#  NULL_REVISION
149
 
#       |
150
 
#       a
151
 
#      /|\
152
 
#     / b \
153
 
#    /  |  \
154
 
#   |   c   |
155
 
#   |  / \  |
156
 
#   | d   e |
157
 
#   |/     \|
158
 
#   f       g
159
 
 
160
 
double_shortcut = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'],
161
 
                   'd':['c'], 'e':['c'], 'f':['a', 'd'],
162
 
                   'g':['a', 'e']}
163
 
 
164
 
# Complex shortcut
165
 
# This has a failure mode in that a shortcut will find some nodes in common,
166
 
# but the common searcher won't have time to find that one branch is actually
167
 
# in common. The extra nodes at the beginning are because we want to avoid
168
 
# walking off the graph. Specifically, node G should be considered common, but
169
 
# is likely to be seen by M long before the common searcher finds it.
170
 
#
171
 
# NULL_REVISION
172
 
#     |
173
 
#     a
174
 
#     |
175
 
#     b
176
 
#     |
177
 
#     c
178
 
#     |
179
 
#     d
180
 
#     |\
181
 
#     e f
182
 
#     | |\
183
 
#     | g h
184
 
#     |/| |
185
 
#     i j |
186
 
#     | | |
187
 
#     | k |
188
 
#     | | |
189
 
#     | l |
190
 
#     |/|/
191
 
#     m n
192
 
complex_shortcut = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'],
193
 
                    'e':['d'], 'f':['d'], 'g':['f'], 'h':['f'],
194
 
                    'i':['e', 'g'], 'j':['g'], 'k':['j'],
195
 
                    'l':['k'], 'm':['i', 'l'], 'n':['l', 'h']}
196
 
 
197
 
# NULL_REVISION
198
 
#     |
199
 
#     a
200
 
#     |
201
 
#     b
202
 
#     |
203
 
#     c
204
 
#     |
205
 
#     d
206
 
#     |\
207
 
#     e |
208
 
#     | |
209
 
#     f |
210
 
#     | |
211
 
#     g h
212
 
#     | |\
213
 
#     i | j
214
 
#     |\| |
215
 
#     | k |
216
 
#     | | |
217
 
#     | l |
218
 
#     | | |
219
 
#     | m |
220
 
#     | | |
221
 
#     | n |
222
 
#     | | |
223
 
#     | o |
224
 
#     | | |
225
 
#     | p |
226
 
#     | | |
227
 
#     | q |
228
 
#     | | |
229
 
#     | r |
230
 
#     | | |
231
 
#     | s |
232
 
#     | | |
233
 
#     |/|/
234
 
#     t u
235
 
complex_shortcut2 = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'],
236
 
                    'e':['d'], 'f':['e'], 'g':['f'], 'h':['d'], 'i':['g'],
237
 
                    'j':['h'], 'k':['h', 'i'], 'l':['k'], 'm':['l'], 'n':['m'],
238
 
                    'o':['n'], 'p':['o'], 'q':['p'], 'r':['q'], 's':['r'],
239
 
                    't':['i', 's'], 'u':['s', 'j'],
240
 
                    }
241
 
 
242
 
# Graph where different walkers will race to find the common and uncommon
243
 
# nodes.
244
 
#
245
 
# NULL_REVISION
246
 
#     |
247
 
#     a
248
 
#     |
249
 
#     b
250
 
#     |
251
 
#     c
252
 
#     |
253
 
#     d
254
 
#     |\
255
 
#     e k
256
 
#     | |
257
 
#     f-+-p
258
 
#     | | |
259
 
#     | l |
260
 
#     | | |
261
 
#     | m |
262
 
#     | |\|
263
 
#     g n q
264
 
#     |\| |
265
 
#     h o |
266
 
#     |/| |
267
 
#     i r |
268
 
#     | | |
269
 
#     | s |
270
 
#     | | |
271
 
#     | t |
272
 
#     | | |
273
 
#     | u |
274
 
#     | | |
275
 
#     | v |
276
 
#     | | |
277
 
#     | w |
278
 
#     | | |
279
 
#     | x |
280
 
#     | |\|
281
 
#     | y z
282
 
#     |/
283
 
#     j
284
 
#
285
 
# x is found to be common right away, but is the start of a long series of
286
 
# common commits.
287
 
# o is actually common, but the i-j shortcut makes it look like it is actually
288
 
# unique to j at first, you have to traverse all of x->o to find it.
289
 
# q,m gives the walker from j a common point to stop searching, as does p,f.
290
 
# k-n exists so that the second pass still has nodes that are worth searching,
291
 
# rather than instantly cancelling the extra walker.
292
 
 
293
 
racing_shortcuts = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'],
294
 
    'e':['d'], 'f':['e'], 'g':['f'], 'h':['g'], 'i':['h', 'o'], 'j':['i', 'y'],
295
 
    'k':['d'], 'l':['k'], 'm':['l'], 'n':['m'], 'o':['n', 'g'], 'p':['f'],
296
 
    'q':['p', 'm'], 'r':['o'], 's':['r'], 't':['s'], 'u':['t'], 'v':['u'],
297
 
    'w':['v'], 'x':['w'], 'y':['x'], 'z':['x', 'q']}
298
 
 
299
 
 
300
 
# A graph with multiple nodes unique to one side.
301
 
#
302
 
# NULL_REVISION
303
 
#     |
304
 
#     a
305
 
#     |
306
 
#     b
307
 
#     |
308
 
#     c
309
 
#     |
310
 
#     d
311
 
#     |\
312
 
#     e f
313
 
#     |\ \
314
 
#     g h i
315
 
#     |\ \ \
316
 
#     j k l m
317
 
#     | |/ x|
318
 
#     | n o p
319
 
#     | |/  |
320
 
#     | q   |
321
 
#     | |   |
322
 
#     | r   |
323
 
#     | |   |
324
 
#     | s   |
325
 
#     | |   |
326
 
#     | t   |
327
 
#     | |   |
328
 
#     | u   |
329
 
#     | |   |
330
 
#     | v   |
331
 
#     | |   |
332
 
#     | w   |
333
 
#     | |   |
334
 
#     | x   |
335
 
#     |/ \ /
336
 
#     y   z
337
 
#
338
 
 
339
 
multiple_interesting_unique = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'],
340
 
    'd':['c'], 'e':['d'], 'f':['d'], 'g':['e'], 'h':['e'], 'i':['f'],
341
 
    'j':['g'], 'k':['g'], 'l':['h'], 'm':['i'], 'n':['k', 'l'],
342
 
    'o':['m'], 'p':['m', 'l'], 'q':['n', 'o'], 'r':['q'], 's':['r'],
343
 
    't':['s'], 'u':['t'], 'v':['u'], 'w':['v'], 'x':['w'],
344
 
    'y':['j', 'x'], 'z':['x', 'p']}
345
 
 
346
 
 
347
 
# Shortcut with extra root
348
 
# We have a long history shortcut, and an extra root, which is why we can't
349
 
# stop searchers based on seeing NULL_REVISION
350
 
#  NULL_REVISION
351
 
#       |   |
352
 
#       a   |
353
 
#       |\  |
354
 
#       b | |
355
 
#       | | |
356
 
#       c | |
357
 
#       | | |
358
 
#       d | g
359
 
#       |\|/
360
 
#       e f
361
 
shortcut_extra_root = {'a': [NULL_REVISION],
362
 
                       'b': ['a'],
363
 
                       'c': ['b'],
364
 
                       'd': ['c'],
365
 
                       'e': ['d'],
366
 
                       'f': ['a', 'd', 'g'],
367
 
                       'g': [NULL_REVISION],
368
 
                      }
369
 
 
370
 
#  NULL_REVISION
371
 
#       |
372
 
#       f
373
 
#       |
374
 
#       e
375
 
#      / \
376
 
#     b   d
377
 
#     | \ |
378
 
#     a   c
379
 
 
380
 
boundary = {'a': ['b'], 'c': ['b', 'd'], 'b':['e'], 'd':['e'], 'e': ['f'],
381
 
            'f':[NULL_REVISION]}
382
 
 
383
 
 
384
 
# A graph that contains a ghost
385
 
#  NULL_REVISION
386
 
#       |
387
 
#       f
388
 
#       |
389
 
#       e   g
390
 
#      / \ /
391
 
#     b   d
392
 
#     | \ |
393
 
#     a   c
394
 
 
395
 
with_ghost = {'a': ['b'], 'c': ['b', 'd'], 'b':['e'], 'd':['e', 'g'],
396
 
              'e': ['f'], 'f':[NULL_REVISION], NULL_REVISION:()}
397
 
 
398
 
# A graph that shows we can shortcut finding revnos when reaching them from the
399
 
# side.
400
 
#  NULL_REVISION
401
 
#       |
402
 
#       a
403
 
#       |
404
 
#       b
405
 
#       |
406
 
#       c
407
 
#       |
408
 
#       d
409
 
#       |
410
 
#       e
411
 
#      / \
412
 
#     f   g
413
 
#     |
414
 
#     h
415
 
#     |
416
 
#     i
417
 
 
418
 
with_tail = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'], 'e':['d'],
419
 
             'f':['e'], 'g':['e'], 'h':['f'], 'i':['h']}
420
 
 
421
 
 
422
 
class InstrumentedParentsProvider(object):
423
 
 
424
 
    def __init__(self, parents_provider):
425
 
        self.calls = []
426
 
        self._real_parents_provider = parents_provider
427
 
 
428
 
    def get_parent_map(self, nodes):
429
 
        self.calls.extend(nodes)
430
 
        return self._real_parents_provider.get_parent_map(nodes)
431
 
 
432
 
 
433
 
class TestGraphBase(tests.TestCase):
434
 
 
435
 
    def make_graph(self, ancestors):
436
 
        return _mod_graph.Graph(_mod_graph.DictParentsProvider(ancestors))
437
 
 
438
 
    def make_breaking_graph(self, ancestors, break_on):
439
 
        """Make a Graph that raises an exception if we hit a node."""
440
 
        g = self.make_graph(ancestors)
441
 
        orig_parent_map = g.get_parent_map
442
 
        def get_parent_map(keys):
443
 
            bad_keys = set(keys).intersection(break_on)
444
 
            if bad_keys:
445
 
                self.fail('key(s) %s was accessed' % (sorted(bad_keys),))
446
 
            return orig_parent_map(keys)
447
 
        g.get_parent_map = get_parent_map
448
 
        return g
449
 
 
450
 
 
451
 
class TestGraph(TestCaseWithMemoryTransport):
452
 
 
453
 
    def make_graph(self, ancestors):
454
 
        return _mod_graph.Graph(_mod_graph.DictParentsProvider(ancestors))
455
 
 
456
 
    def prepare_memory_tree(self, location):
457
 
        tree = self.make_branch_and_memory_tree(location)
458
 
        tree.lock_write()
459
 
        tree.add('.')
460
 
        return tree
461
 
 
462
 
    def build_ancestry(self, tree, ancestors):
463
 
        """Create an ancestry as specified by a graph dict
464
 
 
465
 
        :param tree: A tree to use
466
 
        :param ancestors: a dict of {node: [node_parent, ...]}
467
 
        """
468
 
        pending = [NULL_REVISION]
469
 
        descendants = {}
470
 
        for descendant, parents in ancestors.iteritems():
471
 
            for parent in parents:
472
 
                descendants.setdefault(parent, []).append(descendant)
473
 
        while len(pending) > 0:
474
 
            cur_node = pending.pop()
475
 
            for descendant in descendants.get(cur_node, []):
476
 
                if tree.branch.repository.has_revision(descendant):
477
 
                    continue
478
 
                parents = [p for p in ancestors[descendant] if p is not
479
 
                           NULL_REVISION]
480
 
                if len([p for p in parents if not
481
 
                    tree.branch.repository.has_revision(p)]) > 0:
482
 
                    continue
483
 
                tree.set_parent_ids(parents)
484
 
                if len(parents) > 0:
485
 
                    left_parent = parents[0]
486
 
                else:
487
 
                    left_parent = NULL_REVISION
488
 
                tree.branch.set_last_revision_info(
489
 
                    len(tree.branch._lefthand_history(left_parent)),
490
 
                    left_parent)
491
 
                tree.commit(descendant, rev_id=descendant)
492
 
                pending.append(descendant)
493
 
 
494
 
    def test_lca(self):
495
 
        """Test finding least common ancestor.
496
 
 
497
 
        ancestry_1 should always have a single common ancestor
498
 
        """
499
 
        graph = self.make_graph(ancestry_1)
500
 
        self.assertRaises(errors.InvalidRevisionId, graph.find_lca, None)
501
 
        self.assertEqual(set([NULL_REVISION]),
502
 
                         graph.find_lca(NULL_REVISION, NULL_REVISION))
503
 
        self.assertEqual(set([NULL_REVISION]),
504
 
                         graph.find_lca(NULL_REVISION, 'rev1'))
505
 
        self.assertEqual(set(['rev1']), graph.find_lca('rev1', 'rev1'))
506
 
        self.assertEqual(set(['rev1']), graph.find_lca('rev2a', 'rev2b'))
507
 
 
508
 
    def test_no_unique_lca(self):
509
 
        """Test error when one revision is not in the graph"""
510
 
        graph = self.make_graph(ancestry_1)
511
 
        self.assertRaises(errors.NoCommonAncestor, graph.find_unique_lca,
512
 
                          'rev1', '1rev')
513
 
 
514
 
    def test_lca_criss_cross(self):
515
 
        """Test least-common-ancestor after a criss-cross merge."""
516
 
        graph = self.make_graph(criss_cross)
517
 
        self.assertEqual(set(['rev2a', 'rev2b']),
518
 
                         graph.find_lca('rev3a', 'rev3b'))
519
 
        self.assertEqual(set(['rev2b']),
520
 
                         graph.find_lca('rev3a', 'rev3b', 'rev2b'))
521
 
 
522
 
    def test_lca_shortcut(self):
523
 
        """Test least-common ancestor on this history shortcut"""
524
 
        graph = self.make_graph(history_shortcut)
525
 
        self.assertEqual(set(['rev2b']), graph.find_lca('rev3a', 'rev3b'))
526
 
 
527
 
    def test_lefthand_distance_smoke(self):
528
 
        """A simple does it work test for graph.lefthand_distance(keys)."""
529
 
        graph = self.make_graph(history_shortcut)
530
 
        distance_graph = graph.find_lefthand_distances(['rev3b', 'rev2a'])
531
 
        self.assertEqual({'rev2a': 2, 'rev3b': 3}, distance_graph)
532
 
 
533
 
    def test_lefthand_distance_ghosts(self):
534
 
        """A simple does it work test for graph.lefthand_distance(keys)."""
535
 
        nodes = {'nonghost':[NULL_REVISION], 'toghost':['ghost']}
536
 
        graph = self.make_graph(nodes)
537
 
        distance_graph = graph.find_lefthand_distances(['nonghost', 'toghost'])
538
 
        self.assertEqual({'nonghost': 1, 'toghost': -1}, distance_graph)
539
 
 
540
 
    def test_recursive_unique_lca(self):
541
 
        """Test finding a unique least common ancestor.
542
 
 
543
 
        ancestry_1 should always have a single common ancestor
544
 
        """
545
 
        graph = self.make_graph(ancestry_1)
546
 
        self.assertEqual(NULL_REVISION,
547
 
                         graph.find_unique_lca(NULL_REVISION, NULL_REVISION))
548
 
        self.assertEqual(NULL_REVISION,
549
 
                         graph.find_unique_lca(NULL_REVISION, 'rev1'))
550
 
        self.assertEqual('rev1', graph.find_unique_lca('rev1', 'rev1'))
551
 
        self.assertEqual('rev1', graph.find_unique_lca('rev2a', 'rev2b'))
552
 
        self.assertEqual(('rev1', 1,),
553
 
                         graph.find_unique_lca('rev2a', 'rev2b',
554
 
                         count_steps=True))
555
 
 
556
 
    def assertRemoveDescendants(self, expected, graph, revisions):
557
 
        parents = graph.get_parent_map(revisions)
558
 
        self.assertEqual(expected,
559
 
                         graph._remove_simple_descendants(revisions, parents))
560
 
 
561
 
    def test__remove_simple_descendants(self):
562
 
        graph = self.make_graph(ancestry_1)
563
 
        self.assertRemoveDescendants(set(['rev1']), graph,
564
 
            set(['rev1', 'rev2a', 'rev2b', 'rev3', 'rev4']))
565
 
 
566
 
    def test__remove_simple_descendants_disjoint(self):
567
 
        graph = self.make_graph(ancestry_1)
568
 
        self.assertRemoveDescendants(set(['rev1', 'rev3']), graph,
569
 
            set(['rev1', 'rev3']))
570
 
 
571
 
    def test__remove_simple_descendants_chain(self):
572
 
        graph = self.make_graph(ancestry_1)
573
 
        self.assertRemoveDescendants(set(['rev1']), graph,
574
 
            set(['rev1', 'rev2a', 'rev3']))
575
 
 
576
 
    def test__remove_simple_descendants_siblings(self):
577
 
        graph = self.make_graph(ancestry_1)
578
 
        self.assertRemoveDescendants(set(['rev2a', 'rev2b']), graph,
579
 
            set(['rev2a', 'rev2b', 'rev3']))
580
 
 
581
 
    def test_unique_lca_criss_cross(self):
582
 
        """Ensure we don't pick non-unique lcas in a criss-cross"""
583
 
        graph = self.make_graph(criss_cross)
584
 
        self.assertEqual('rev1', graph.find_unique_lca('rev3a', 'rev3b'))
585
 
        lca, steps = graph.find_unique_lca('rev3a', 'rev3b', count_steps=True)
586
 
        self.assertEqual('rev1', lca)
587
 
        self.assertEqual(2, steps)
588
 
 
589
 
    def test_unique_lca_null_revision(self):
590
 
        """Ensure we pick NULL_REVISION when necessary"""
591
 
        graph = self.make_graph(criss_cross2)
592
 
        self.assertEqual('rev1b', graph.find_unique_lca('rev2a', 'rev1b'))
593
 
        self.assertEqual(NULL_REVISION,
594
 
                         graph.find_unique_lca('rev2a', 'rev2b'))
595
 
 
596
 
    def test_unique_lca_null_revision2(self):
597
 
        """Ensure we pick NULL_REVISION when necessary"""
598
 
        graph = self.make_graph(ancestry_2)
599
 
        self.assertEqual(NULL_REVISION,
600
 
                         graph.find_unique_lca('rev4a', 'rev1b'))
601
 
 
602
 
    def test_lca_double_shortcut(self):
603
 
        graph = self.make_graph(double_shortcut)
604
 
        self.assertEqual('c', graph.find_unique_lca('f', 'g'))
605
 
 
606
 
    def test_common_ancestor_two_repos(self):
607
 
        """Ensure we do unique_lca using data from two repos"""
608
 
        mainline_tree = self.prepare_memory_tree('mainline')
609
 
        self.build_ancestry(mainline_tree, mainline)
610
 
        self.addCleanup(mainline_tree.unlock)
611
 
 
612
 
        # This is cheating, because the revisions in the graph are actually
613
 
        # different revisions, despite having the same revision-id.
614
 
        feature_tree = self.prepare_memory_tree('feature')
615
 
        self.build_ancestry(feature_tree, feature_branch)
616
 
        self.addCleanup(feature_tree.unlock)
617
 
 
618
 
        graph = mainline_tree.branch.repository.get_graph(
619
 
            feature_tree.branch.repository)
620
 
        self.assertEqual('rev2b', graph.find_unique_lca('rev2a', 'rev3b'))
621
 
 
622
 
    def test_graph_difference(self):
623
 
        graph = self.make_graph(ancestry_1)
624
 
        self.assertEqual((set(), set()), graph.find_difference('rev1', 'rev1'))
625
 
        self.assertEqual((set(), set(['rev1'])),
626
 
                         graph.find_difference(NULL_REVISION, 'rev1'))
627
 
        self.assertEqual((set(['rev1']), set()),
628
 
                         graph.find_difference('rev1', NULL_REVISION))
629
 
        self.assertEqual((set(['rev2a', 'rev3']), set(['rev2b'])),
630
 
                         graph.find_difference('rev3', 'rev2b'))
631
 
        self.assertEqual((set(['rev4', 'rev3', 'rev2a']), set()),
632
 
                         graph.find_difference('rev4', 'rev2b'))
633
 
 
634
 
    def test_graph_difference_separate_ancestry(self):
635
 
        graph = self.make_graph(ancestry_2)
636
 
        self.assertEqual((set(['rev1a']), set(['rev1b'])),
637
 
                         graph.find_difference('rev1a', 'rev1b'))
638
 
        self.assertEqual((set(['rev1a', 'rev2a', 'rev3a', 'rev4a']),
639
 
                          set(['rev1b'])),
640
 
                         graph.find_difference('rev4a', 'rev1b'))
641
 
 
642
 
    def test_graph_difference_criss_cross(self):
643
 
        graph = self.make_graph(criss_cross)
644
 
        self.assertEqual((set(['rev3a']), set(['rev3b'])),
645
 
                         graph.find_difference('rev3a', 'rev3b'))
646
 
        self.assertEqual((set([]), set(['rev3b', 'rev2b'])),
647
 
                         graph.find_difference('rev2a', 'rev3b'))
648
 
 
649
 
    def test_graph_difference_extended_history(self):
650
 
        graph = self.make_graph(extended_history_shortcut)
651
 
        self.assertEqual((set(['e']), set(['f'])),
652
 
                         graph.find_difference('e', 'f'))
653
 
        self.assertEqual((set(['f']), set(['e'])),
654
 
                         graph.find_difference('f', 'e'))
655
 
 
656
 
    def test_graph_difference_double_shortcut(self):
657
 
        graph = self.make_graph(double_shortcut)
658
 
        self.assertEqual((set(['d', 'f']), set(['e', 'g'])),
659
 
                         graph.find_difference('f', 'g'))
660
 
 
661
 
    def test_graph_difference_complex_shortcut(self):
662
 
        graph = self.make_graph(complex_shortcut)
663
 
        self.assertEqual((set(['m', 'i', 'e']), set(['n', 'h'])),
664
 
                         graph.find_difference('m', 'n'))
665
 
 
666
 
    def test_graph_difference_complex_shortcut2(self):
667
 
        graph = self.make_graph(complex_shortcut2)
668
 
        self.assertEqual((set(['t']), set(['j', 'u'])),
669
 
                         graph.find_difference('t', 'u'))
670
 
 
671
 
    def test_graph_difference_shortcut_extra_root(self):
672
 
        graph = self.make_graph(shortcut_extra_root)
673
 
        self.assertEqual((set(['e']), set(['f', 'g'])),
674
 
                         graph.find_difference('e', 'f'))
675
 
 
676
 
 
677
 
    def test_stacked_parents_provider(self):
678
 
        parents1 = _mod_graph.DictParentsProvider({'rev2': ['rev3']})
679
 
        parents2 = _mod_graph.DictParentsProvider({'rev1': ['rev4']})
680
 
        stacked = _mod_graph.StackedParentsProvider([parents1, parents2])
681
 
        self.assertEqual({'rev1':['rev4'], 'rev2':['rev3']},
682
 
                         stacked.get_parent_map(['rev1', 'rev2']))
683
 
        self.assertEqual({'rev2':['rev3'], 'rev1':['rev4']},
684
 
                         stacked.get_parent_map(['rev2', 'rev1']))
685
 
        self.assertEqual({'rev2':['rev3']},
686
 
                         stacked.get_parent_map(['rev2', 'rev2']))
687
 
        self.assertEqual({'rev1':['rev4']},
688
 
                         stacked.get_parent_map(['rev1', 'rev1']))
689
 
    
690
 
    def test_stacked_parents_provider_overlapping(self):
691
 
        # rev2 is availible in both providers.
692
 
        # 1
693
 
        # |
694
 
        # 2
695
 
        parents1 = _mod_graph.DictParentsProvider({'rev2': ['rev1']})
696
 
        parents2 = _mod_graph.DictParentsProvider({'rev2': ['rev1']})
697
 
        stacked = _mod_graph.StackedParentsProvider([parents1, parents2])
698
 
        self.assertEqual({'rev2': ['rev1']},
699
 
                         stacked.get_parent_map(['rev2']))
700
 
 
701
 
    def test_iter_topo_order(self):
702
 
        graph = self.make_graph(ancestry_1)
703
 
        args = ['rev2a', 'rev3', 'rev1']
704
 
        topo_args = list(graph.iter_topo_order(args))
705
 
        self.assertEqual(set(args), set(topo_args))
706
 
        self.assertTrue(topo_args.index('rev2a') > topo_args.index('rev1'))
707
 
        self.assertTrue(topo_args.index('rev2a') < topo_args.index('rev3'))
708
 
 
709
 
    def test_is_ancestor(self):
710
 
        graph = self.make_graph(ancestry_1)
711
 
        self.assertEqual(True, graph.is_ancestor('null:', 'null:'))
712
 
        self.assertEqual(True, graph.is_ancestor('null:', 'rev1'))
713
 
        self.assertEqual(False, graph.is_ancestor('rev1', 'null:'))
714
 
        self.assertEqual(True, graph.is_ancestor('null:', 'rev4'))
715
 
        self.assertEqual(False, graph.is_ancestor('rev4', 'null:'))
716
 
        self.assertEqual(False, graph.is_ancestor('rev4', 'rev2b'))
717
 
        self.assertEqual(True, graph.is_ancestor('rev2b', 'rev4'))
718
 
        self.assertEqual(False, graph.is_ancestor('rev2b', 'rev3'))
719
 
        self.assertEqual(False, graph.is_ancestor('rev3', 'rev2b'))
720
 
        instrumented_provider = InstrumentedParentsProvider(graph)
721
 
        instrumented_graph = _mod_graph.Graph(instrumented_provider)
722
 
        instrumented_graph.is_ancestor('rev2a', 'rev2b')
723
 
        self.assertTrue('null:' not in instrumented_provider.calls)
724
 
 
725
 
    def test_is_between(self):
726
 
        graph = self.make_graph(ancestry_1)
727
 
        self.assertEqual(True, graph.is_between('null:', 'null:', 'null:'))
728
 
        self.assertEqual(True, graph.is_between('rev1', 'null:', 'rev1'))
729
 
        self.assertEqual(True, graph.is_between('rev1', 'rev1', 'rev4'))
730
 
        self.assertEqual(True, graph.is_between('rev4', 'rev1', 'rev4'))
731
 
        self.assertEqual(True, graph.is_between('rev3', 'rev1', 'rev4'))
732
 
        self.assertEqual(False, graph.is_between('rev4', 'rev1', 'rev3'))
733
 
        self.assertEqual(False, graph.is_between('rev1', 'rev2a', 'rev4'))
734
 
        self.assertEqual(False, graph.is_between('null:', 'rev1', 'rev4'))
735
 
 
736
 
    def test_is_ancestor_boundary(self):
737
 
        """Ensure that we avoid searching the whole graph.
738
 
 
739
 
        This requires searching through b as a common ancestor, so we
740
 
        can identify that e is common.
741
 
        """
742
 
        graph = self.make_graph(boundary)
743
 
        instrumented_provider = InstrumentedParentsProvider(graph)
744
 
        graph = _mod_graph.Graph(instrumented_provider)
745
 
        self.assertFalse(graph.is_ancestor('a', 'c'))
746
 
        self.assertTrue('null:' not in instrumented_provider.calls)
747
 
 
748
 
    def test_iter_ancestry(self):
749
 
        nodes = boundary.copy()
750
 
        nodes[NULL_REVISION] = ()
751
 
        graph = self.make_graph(nodes)
752
 
        expected = nodes.copy()
753
 
        expected.pop('a') # 'a' is not in the ancestry of 'c', all the
754
 
                          # other nodes are
755
 
        self.assertEqual(expected, dict(graph.iter_ancestry(['c'])))
756
 
        self.assertEqual(nodes, dict(graph.iter_ancestry(['a', 'c'])))
757
 
 
758
 
    def test_iter_ancestry_with_ghost(self):
759
 
        graph = self.make_graph(with_ghost)
760
 
        expected = with_ghost.copy()
761
 
        # 'a' is not in the ancestry of 'c', and 'g' is a ghost
762
 
        expected['g'] = None
763
 
        self.assertEqual(expected, dict(graph.iter_ancestry(['a', 'c'])))
764
 
        expected.pop('a')
765
 
        self.assertEqual(expected, dict(graph.iter_ancestry(['c'])))
766
 
 
767
 
    def test_filter_candidate_lca(self):
768
 
        """Test filter_candidate_lca for a corner case
769
 
 
770
 
        This tests the case where we encounter the end of iteration for 'e'
771
 
        in the same pass as we discover that 'd' is an ancestor of 'e', and
772
 
        therefore 'e' can't be an lca.
773
 
 
774
 
        To compensate for different dict orderings on other Python
775
 
        implementations, we mirror 'd' and 'e' with 'b' and 'a'.
776
 
        """
777
 
        # This test is sensitive to the iteration order of dicts.  It will
778
 
        # pass incorrectly if 'e' and 'a' sort before 'c'
779
 
        #
780
 
        # NULL_REVISION
781
 
        #     / \
782
 
        #    a   e
783
 
        #    |   |
784
 
        #    b   d
785
 
        #     \ /
786
 
        #      c
787
 
        graph = self.make_graph({'c': ['b', 'd'], 'd': ['e'], 'b': ['a'],
788
 
                                 'a': [NULL_REVISION], 'e': [NULL_REVISION]})
789
 
        self.assertEqual(set(['c']), graph.heads(['a', 'c', 'e']))
790
 
 
791
 
    def test_heads_null(self):
792
 
        graph = self.make_graph(ancestry_1)
793
 
        self.assertEqual(set(['null:']), graph.heads(['null:']))
794
 
        self.assertEqual(set(['rev1']), graph.heads(['null:', 'rev1']))
795
 
        self.assertEqual(set(['rev1']), graph.heads(['rev1', 'null:']))
796
 
        self.assertEqual(set(['rev1']), graph.heads(set(['rev1', 'null:'])))
797
 
        self.assertEqual(set(['rev1']), graph.heads(('rev1', 'null:')))
798
 
 
799
 
    def test_heads_one(self):
800
 
        # A single node will always be a head
801
 
        graph = self.make_graph(ancestry_1)
802
 
        self.assertEqual(set(['null:']), graph.heads(['null:']))
803
 
        self.assertEqual(set(['rev1']), graph.heads(['rev1']))
804
 
        self.assertEqual(set(['rev2a']), graph.heads(['rev2a']))
805
 
        self.assertEqual(set(['rev2b']), graph.heads(['rev2b']))
806
 
        self.assertEqual(set(['rev3']), graph.heads(['rev3']))
807
 
        self.assertEqual(set(['rev4']), graph.heads(['rev4']))
808
 
 
809
 
    def test_heads_single(self):
810
 
        graph = self.make_graph(ancestry_1)
811
 
        self.assertEqual(set(['rev4']), graph.heads(['null:', 'rev4']))
812
 
        self.assertEqual(set(['rev2a']), graph.heads(['rev1', 'rev2a']))
813
 
        self.assertEqual(set(['rev2b']), graph.heads(['rev1', 'rev2b']))
814
 
        self.assertEqual(set(['rev3']), graph.heads(['rev1', 'rev3']))
815
 
        self.assertEqual(set(['rev4']), graph.heads(['rev1', 'rev4']))
816
 
        self.assertEqual(set(['rev4']), graph.heads(['rev2a', 'rev4']))
817
 
        self.assertEqual(set(['rev4']), graph.heads(['rev2b', 'rev4']))
818
 
        self.assertEqual(set(['rev4']), graph.heads(['rev3', 'rev4']))
819
 
 
820
 
    def test_heads_two_heads(self):
821
 
        graph = self.make_graph(ancestry_1)
822
 
        self.assertEqual(set(['rev2a', 'rev2b']),
823
 
                         graph.heads(['rev2a', 'rev2b']))
824
 
        self.assertEqual(set(['rev3', 'rev2b']),
825
 
                         graph.heads(['rev3', 'rev2b']))
826
 
 
827
 
    def test_heads_criss_cross(self):
828
 
        graph = self.make_graph(criss_cross)
829
 
        self.assertEqual(set(['rev2a']),
830
 
                         graph.heads(['rev2a', 'rev1']))
831
 
        self.assertEqual(set(['rev2b']),
832
 
                         graph.heads(['rev2b', 'rev1']))
833
 
        self.assertEqual(set(['rev3a']),
834
 
                         graph.heads(['rev3a', 'rev1']))
835
 
        self.assertEqual(set(['rev3b']),
836
 
                         graph.heads(['rev3b', 'rev1']))
837
 
        self.assertEqual(set(['rev2a', 'rev2b']),
838
 
                         graph.heads(['rev2a', 'rev2b']))
839
 
        self.assertEqual(set(['rev3a']),
840
 
                         graph.heads(['rev3a', 'rev2a']))
841
 
        self.assertEqual(set(['rev3a']),
842
 
                         graph.heads(['rev3a', 'rev2b']))
843
 
        self.assertEqual(set(['rev3a']),
844
 
                         graph.heads(['rev3a', 'rev2a', 'rev2b']))
845
 
        self.assertEqual(set(['rev3b']),
846
 
                         graph.heads(['rev3b', 'rev2a']))
847
 
        self.assertEqual(set(['rev3b']),
848
 
                         graph.heads(['rev3b', 'rev2b']))
849
 
        self.assertEqual(set(['rev3b']),
850
 
                         graph.heads(['rev3b', 'rev2a', 'rev2b']))
851
 
        self.assertEqual(set(['rev3a', 'rev3b']),
852
 
                         graph.heads(['rev3a', 'rev3b']))
853
 
        self.assertEqual(set(['rev3a', 'rev3b']),
854
 
                         graph.heads(['rev3a', 'rev3b', 'rev2a', 'rev2b']))
855
 
 
856
 
    def test_heads_shortcut(self):
857
 
        graph = self.make_graph(history_shortcut)
858
 
 
859
 
        self.assertEqual(set(['rev2a', 'rev2b', 'rev2c']),
860
 
                         graph.heads(['rev2a', 'rev2b', 'rev2c']))
861
 
        self.assertEqual(set(['rev3a', 'rev3b']),
862
 
                         graph.heads(['rev3a', 'rev3b']))
863
 
        self.assertEqual(set(['rev3a', 'rev3b']),
864
 
                         graph.heads(['rev2a', 'rev3a', 'rev3b']))
865
 
        self.assertEqual(set(['rev2a', 'rev3b']),
866
 
                         graph.heads(['rev2a', 'rev3b']))
867
 
        self.assertEqual(set(['rev2c', 'rev3a']),
868
 
                         graph.heads(['rev2c', 'rev3a']))
869
 
 
870
 
    def _run_heads_break_deeper(self, graph_dict, search):
871
 
        """Run heads on a graph-as-a-dict.
872
 
 
873
 
        If the search asks for the parents of 'deeper' the test will fail.
874
 
        """
875
 
        class stub(object):
876
 
            pass
877
 
        def get_parent_map(keys):
878
 
            result = {}
879
 
            for key in keys:
880
 
                if key == 'deeper':
881
 
                    self.fail('key deeper was accessed')
882
 
                result[key] = graph_dict[key]
883
 
            return result
884
 
        an_obj = stub()
885
 
        an_obj.get_parent_map = get_parent_map
886
 
        graph = _mod_graph.Graph(an_obj)
887
 
        return graph.heads(search)
888
 
 
889
 
    def test_heads_limits_search(self):
890
 
        # test that a heads query does not search all of history
891
 
        graph_dict = {
892
 
            'left':['common'],
893
 
            'right':['common'],
894
 
            'common':['deeper'],
895
 
        }
896
 
        self.assertEqual(set(['left', 'right']),
897
 
            self._run_heads_break_deeper(graph_dict, ['left', 'right']))
898
 
 
899
 
    def test_heads_limits_search_assymetric(self):
900
 
        # test that a heads query does not search all of history
901
 
        graph_dict = {
902
 
            'left':['midleft'],
903
 
            'midleft':['common'],
904
 
            'right':['common'],
905
 
            'common':['aftercommon'],
906
 
            'aftercommon':['deeper'],
907
 
        }
908
 
        self.assertEqual(set(['left', 'right']),
909
 
            self._run_heads_break_deeper(graph_dict, ['left', 'right']))
910
 
 
911
 
    def test_heads_limits_search_common_search_must_continue(self):
912
 
        # test that common nodes are still queried, preventing
913
 
        # all-the-way-to-origin behaviour in the following graph:
914
 
        graph_dict = {
915
 
            'h1':['shortcut', 'common1'],
916
 
            'h2':['common1'],
917
 
            'shortcut':['common2'],
918
 
            'common1':['common2'],
919
 
            'common2':['deeper'],
920
 
        }
921
 
        self.assertEqual(set(['h1', 'h2']),
922
 
            self._run_heads_break_deeper(graph_dict, ['h1', 'h2']))
923
 
 
924
 
    def test_breadth_first_search_start_ghosts(self):
925
 
        graph = self.make_graph({})
926
 
        # with_ghosts reports the ghosts
927
 
        search = graph._make_breadth_first_searcher(['a-ghost'])
928
 
        self.assertEqual((set(), set(['a-ghost'])), search.next_with_ghosts())
929
 
        self.assertRaises(StopIteration, search.next_with_ghosts)
930
 
        # next includes them
931
 
        search = graph._make_breadth_first_searcher(['a-ghost'])
932
 
        self.assertEqual(set(['a-ghost']), search.next())
933
 
        self.assertRaises(StopIteration, search.next)
934
 
 
935
 
    def test_breadth_first_search_deep_ghosts(self):
936
 
        graph = self.make_graph({
937
 
            'head':['present'],
938
 
            'present':['child', 'ghost'],
939
 
            'child':[],
940
 
            })
941
 
        # with_ghosts reports the ghosts
942
 
        search = graph._make_breadth_first_searcher(['head'])
943
 
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
944
 
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
945
 
        self.assertEqual((set(['child']), set(['ghost'])),
946
 
            search.next_with_ghosts())
947
 
        self.assertRaises(StopIteration, search.next_with_ghosts)
948
 
        # next includes them
949
 
        search = graph._make_breadth_first_searcher(['head'])
950
 
        self.assertEqual(set(['head']), search.next())
951
 
        self.assertEqual(set(['present']), search.next())
952
 
        self.assertEqual(set(['child', 'ghost']),
953
 
            search.next())
954
 
        self.assertRaises(StopIteration, search.next)
955
 
 
956
 
    def test_breadth_first_search_change_next_to_next_with_ghosts(self):
957
 
        # To make the API robust, we allow calling both next() and
958
 
        # next_with_ghosts() on the same searcher.
959
 
        graph = self.make_graph({
960
 
            'head':['present'],
961
 
            'present':['child', 'ghost'],
962
 
            'child':[],
963
 
            })
964
 
        # start with next_with_ghosts
965
 
        search = graph._make_breadth_first_searcher(['head'])
966
 
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
967
 
        self.assertEqual(set(['present']), search.next())
968
 
        self.assertEqual((set(['child']), set(['ghost'])),
969
 
            search.next_with_ghosts())
970
 
        self.assertRaises(StopIteration, search.next)
971
 
        # start with next
972
 
        search = graph._make_breadth_first_searcher(['head'])
973
 
        self.assertEqual(set(['head']), search.next())
974
 
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
975
 
        self.assertEqual(set(['child', 'ghost']),
976
 
            search.next())
977
 
        self.assertRaises(StopIteration, search.next_with_ghosts)
978
 
 
979
 
    def test_breadth_first_change_search(self):
980
 
        # Changing the search should work with both next and next_with_ghosts.
981
 
        graph = self.make_graph({
982
 
            'head':['present'],
983
 
            'present':['stopped'],
984
 
            'other':['other_2'],
985
 
            'other_2':[],
986
 
            })
987
 
        search = graph._make_breadth_first_searcher(['head'])
988
 
        self.assertEqual((set(['head']), set()), search.next_with_ghosts())
989
 
        self.assertEqual((set(['present']), set()), search.next_with_ghosts())
990
 
        self.assertEqual(set(['present']),
991
 
            search.stop_searching_any(['present']))
992
 
        self.assertEqual((set(['other']), set(['other_ghost'])),
993
 
            search.start_searching(['other', 'other_ghost']))
994
 
        self.assertEqual((set(['other_2']), set()), search.next_with_ghosts())
995
 
        self.assertRaises(StopIteration, search.next_with_ghosts)
996
 
        # next includes them
997
 
        search = graph._make_breadth_first_searcher(['head'])
998
 
        self.assertEqual(set(['head']), search.next())
999
 
        self.assertEqual(set(['present']), search.next())
1000
 
        self.assertEqual(set(['present']),
1001
 
            search.stop_searching_any(['present']))
1002
 
        search.start_searching(['other', 'other_ghost'])
1003
 
        self.assertEqual(set(['other_2']), search.next())
1004
 
        self.assertRaises(StopIteration, search.next)
1005
 
 
1006
 
    def assertSeenAndResult(self, instructions, search, next):
1007
 
        """Check the results of .seen and get_result() for a seach.
1008
 
 
1009
 
        :param instructions: A list of tuples:
1010
 
            (seen, recipe, included_keys, starts, stops).
1011
 
            seen, recipe and included_keys are results to check on the search
1012
 
            and the searches get_result(). starts and stops are parameters to
1013
 
            pass to start_searching and stop_searching_any during each
1014
 
            iteration, if they are not None.
1015
 
        :param search: The search to use.
1016
 
        :param next: A callable to advance the search.
1017
 
        """
1018
 
        for seen, recipe, included_keys, starts, stops in instructions:
1019
 
            # Adjust for recipe contract changes that don't vary for all the
1020
 
            # current tests.
1021
 
            recipe = ('search',) + recipe
1022
 
            next()
1023
 
            if starts is not None:
1024
 
                search.start_searching(starts)
1025
 
            if stops is not None:
1026
 
                search.stop_searching_any(stops)
1027
 
            result = search.get_result()
1028
 
            self.assertEqual(recipe, result.get_recipe())
1029
 
            self.assertEqual(set(included_keys), result.get_keys())
1030
 
            self.assertEqual(seen, search.seen)
1031
 
 
1032
 
    def test_breadth_first_get_result_excludes_current_pending(self):
1033
 
        graph = self.make_graph({
1034
 
            'head':['child'],
1035
 
            'child':[NULL_REVISION],
1036
 
            NULL_REVISION:[],
1037
 
            })
1038
 
        search = graph._make_breadth_first_searcher(['head'])
1039
 
        # At the start, nothing has been seen, to its all excluded:
1040
 
        result = search.get_result()
1041
 
        self.assertEqual(('search', set(['head']), set(['head']), 0),
1042
 
            result.get_recipe())
1043
 
        self.assertEqual(set(), result.get_keys())
1044
 
        self.assertEqual(set(), search.seen)
1045
 
        # using next:
1046
 
        expected = [
1047
 
            (set(['head']), (set(['head']), set(['child']), 1),
1048
 
             ['head'], None, None),
1049
 
            (set(['head', 'child']), (set(['head']), set([NULL_REVISION]), 2),
1050
 
             ['head', 'child'], None, None),
1051
 
            (set(['head', 'child', NULL_REVISION]), (set(['head']), set(), 3),
1052
 
             ['head', 'child', NULL_REVISION], None, None),
1053
 
            ]
1054
 
        self.assertSeenAndResult(expected, search, search.next)
1055
 
        # using next_with_ghosts:
1056
 
        search = graph._make_breadth_first_searcher(['head'])
1057
 
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
1058
 
 
1059
 
    def test_breadth_first_get_result_starts_stops(self):
1060
 
        graph = self.make_graph({
1061
 
            'head':['child'],
1062
 
            'child':[NULL_REVISION],
1063
 
            'otherhead':['otherchild'],
1064
 
            'otherchild':['excluded'],
1065
 
            'excluded':[NULL_REVISION],
1066
 
            NULL_REVISION:[]
1067
 
            })
1068
 
        search = graph._make_breadth_first_searcher([])
1069
 
        # Starting with nothing and adding a search works:
1070
 
        search.start_searching(['head'])
1071
 
        # head has been seen:
1072
 
        result = search.get_result()
1073
 
        self.assertEqual(('search', set(['head']), set(['child']), 1),
1074
 
            result.get_recipe())
1075
 
        self.assertEqual(set(['head']), result.get_keys())
1076
 
        self.assertEqual(set(['head']), search.seen)
1077
 
        # using next:
1078
 
        expected = [
1079
 
            # stop at child, and start a new search at otherhead:
1080
 
            # - otherhead counts as seen immediately when start_searching is
1081
 
            # called.
1082
 
            (set(['head', 'child', 'otherhead']),
1083
 
             (set(['head', 'otherhead']), set(['child', 'otherchild']), 2),
1084
 
             ['head', 'otherhead'], ['otherhead'], ['child']),
1085
 
            (set(['head', 'child', 'otherhead', 'otherchild']),
1086
 
             (set(['head', 'otherhead']), set(['child', 'excluded']), 3),
1087
 
             ['head', 'otherhead', 'otherchild'], None, None),
1088
 
            # stop searching excluded now
1089
 
            (set(['head', 'child', 'otherhead', 'otherchild', 'excluded']),
1090
 
             (set(['head', 'otherhead']), set(['child', 'excluded']), 3),
1091
 
             ['head', 'otherhead', 'otherchild'], None, ['excluded']),
1092
 
            ]
1093
 
        self.assertSeenAndResult(expected, search, search.next)
1094
 
        # using next_with_ghosts:
1095
 
        search = graph._make_breadth_first_searcher([])
1096
 
        search.start_searching(['head'])
1097
 
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
1098
 
 
1099
 
    def test_breadth_first_stop_searching_not_queried(self):
1100
 
        # A client should be able to say 'stop node X' even if X has not been
1101
 
        # returned to the client.
1102
 
        graph = self.make_graph({
1103
 
            'head':['child', 'ghost1'],
1104
 
            'child':[NULL_REVISION],
1105
 
            NULL_REVISION:[],
1106
 
            })
1107
 
        search = graph._make_breadth_first_searcher(['head'])
1108
 
        expected = [
1109
 
            # NULL_REVISION and ghost1 have not been returned
1110
 
            (set(['head']),
1111
 
             (set(['head']), set(['child', NULL_REVISION, 'ghost1']), 1),
1112
 
             ['head'], None, [NULL_REVISION, 'ghost1']),
1113
 
            # ghost1 has been returned, NULL_REVISION is to be returned in the
1114
 
            # next iteration.
1115
 
            (set(['head', 'child', 'ghost1']),
1116
 
             (set(['head']), set(['ghost1', NULL_REVISION]), 2),
1117
 
             ['head', 'child'], None, [NULL_REVISION, 'ghost1']),
1118
 
            ]
1119
 
        self.assertSeenAndResult(expected, search, search.next)
1120
 
        # using next_with_ghosts:
1121
 
        search = graph._make_breadth_first_searcher(['head'])
1122
 
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
1123
 
 
1124
 
    def test_breadth_first_stop_searching_late(self):
1125
 
        # A client should be able to say 'stop node X' and have it excluded
1126
 
        # from the result even if X was seen in an older iteration of the
1127
 
        # search.
1128
 
        graph = self.make_graph({
1129
 
            'head':['middle'],
1130
 
            'middle':['child'],
1131
 
            'child':[NULL_REVISION],
1132
 
            NULL_REVISION:[],
1133
 
            })
1134
 
        search = graph._make_breadth_first_searcher(['head'])
1135
 
        expected = [
1136
 
            (set(['head']), (set(['head']), set(['middle']), 1),
1137
 
             ['head'], None, None),
1138
 
            (set(['head', 'middle']), (set(['head']), set(['child']), 2),
1139
 
             ['head', 'middle'], None, None),
1140
 
            # 'middle' came from the previous iteration, but we don't stop
1141
 
            # searching it until *after* advancing the searcher.
1142
 
            (set(['head', 'middle', 'child']),
1143
 
             (set(['head']), set(['middle', 'child']), 1),
1144
 
             ['head'], None, ['middle', 'child']),
1145
 
            ]
1146
 
        self.assertSeenAndResult(expected, search, search.next)
1147
 
        # using next_with_ghosts:
1148
 
        search = graph._make_breadth_first_searcher(['head'])
1149
 
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
1150
 
 
1151
 
    def test_breadth_first_get_result_ghosts_are_excluded(self):
1152
 
        graph = self.make_graph({
1153
 
            'head':['child', 'ghost'],
1154
 
            'child':[NULL_REVISION],
1155
 
            NULL_REVISION:[],
1156
 
            })
1157
 
        search = graph._make_breadth_first_searcher(['head'])
1158
 
        # using next:
1159
 
        expected = [
1160
 
            (set(['head']),
1161
 
             (set(['head']), set(['ghost', 'child']), 1),
1162
 
             ['head'], None, None),
1163
 
            (set(['head', 'child', 'ghost']),
1164
 
             (set(['head']), set([NULL_REVISION, 'ghost']), 2),
1165
 
             ['head', 'child'], None, None),
1166
 
            ]
1167
 
        self.assertSeenAndResult(expected, search, search.next)
1168
 
        # using next_with_ghosts:
1169
 
        search = graph._make_breadth_first_searcher(['head'])
1170
 
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
1171
 
 
1172
 
    def test_breadth_first_get_result_starting_a_ghost_ghost_is_excluded(self):
1173
 
        graph = self.make_graph({
1174
 
            'head':['child'],
1175
 
            'child':[NULL_REVISION],
1176
 
            NULL_REVISION:[],
1177
 
            })
1178
 
        search = graph._make_breadth_first_searcher(['head'])
1179
 
        # using next:
1180
 
        expected = [
1181
 
            (set(['head', 'ghost']),
1182
 
             (set(['head', 'ghost']), set(['child', 'ghost']), 1),
1183
 
             ['head'], ['ghost'], None),
1184
 
            (set(['head', 'child', 'ghost']),
1185
 
             (set(['head', 'ghost']), set([NULL_REVISION, 'ghost']), 2),
1186
 
             ['head', 'child'], None, None),
1187
 
            ]
1188
 
        self.assertSeenAndResult(expected, search, search.next)
1189
 
        # using next_with_ghosts:
1190
 
        search = graph._make_breadth_first_searcher(['head'])
1191
 
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
1192
 
 
1193
 
    def test_breadth_first_revision_count_includes_NULL_REVISION(self):
1194
 
        graph = self.make_graph({
1195
 
            'head':[NULL_REVISION],
1196
 
            NULL_REVISION:[],
1197
 
            })
1198
 
        search = graph._make_breadth_first_searcher(['head'])
1199
 
        # using next:
1200
 
        expected = [
1201
 
            (set(['head']),
1202
 
             (set(['head']), set([NULL_REVISION]), 1),
1203
 
             ['head'], None, None),
1204
 
            (set(['head', NULL_REVISION]),
1205
 
             (set(['head']), set([]), 2),
1206
 
             ['head', NULL_REVISION], None, None),
1207
 
            ]
1208
 
        self.assertSeenAndResult(expected, search, search.next)
1209
 
        # using next_with_ghosts:
1210
 
        search = graph._make_breadth_first_searcher(['head'])
1211
 
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
1212
 
 
1213
 
    def test_breadth_first_search_get_result_after_StopIteration(self):
1214
 
        # StopIteration should not invalid anything..
1215
 
        graph = self.make_graph({
1216
 
            'head':[NULL_REVISION],
1217
 
            NULL_REVISION:[],
1218
 
            })
1219
 
        search = graph._make_breadth_first_searcher(['head'])
1220
 
        # using next:
1221
 
        expected = [
1222
 
            (set(['head']),
1223
 
             (set(['head']), set([NULL_REVISION]), 1),
1224
 
             ['head'], None, None),
1225
 
            (set(['head', 'ghost', NULL_REVISION]),
1226
 
             (set(['head', 'ghost']), set(['ghost']), 2),
1227
 
             ['head', NULL_REVISION], ['ghost'], None),
1228
 
            ]
1229
 
        self.assertSeenAndResult(expected, search, search.next)
1230
 
        self.assertRaises(StopIteration, search.next)
1231
 
        self.assertEqual(set(['head', 'ghost', NULL_REVISION]), search.seen)
1232
 
        result = search.get_result()
1233
 
        self.assertEqual(('search', set(['ghost', 'head']), set(['ghost']), 2),
1234
 
            result.get_recipe())
1235
 
        self.assertEqual(set(['head', NULL_REVISION]), result.get_keys())
1236
 
        # using next_with_ghosts:
1237
 
        search = graph._make_breadth_first_searcher(['head'])
1238
 
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
1239
 
        self.assertRaises(StopIteration, search.next)
1240
 
        self.assertEqual(set(['head', 'ghost', NULL_REVISION]), search.seen)
1241
 
        result = search.get_result()
1242
 
        self.assertEqual(('search', set(['ghost', 'head']), set(['ghost']), 2),
1243
 
            result.get_recipe())
1244
 
        self.assertEqual(set(['head', NULL_REVISION]), result.get_keys())
1245
 
 
1246
 
 
1247
 
class TestFindUniqueAncestors(TestGraphBase):
1248
 
 
1249
 
    def assertFindUniqueAncestors(self, graph, expected, node, common):
1250
 
        actual = graph.find_unique_ancestors(node, common)
1251
 
        self.assertEqual(expected, sorted(actual))
1252
 
 
1253
 
    def test_empty_set(self):
1254
 
        graph = self.make_graph(ancestry_1)
1255
 
        self.assertFindUniqueAncestors(graph, [], 'rev1', ['rev1'])
1256
 
        self.assertFindUniqueAncestors(graph, [], 'rev2b', ['rev2b'])
1257
 
        self.assertFindUniqueAncestors(graph, [], 'rev3', ['rev1', 'rev3'])
1258
 
 
1259
 
    def test_single_node(self):
1260
 
        graph = self.make_graph(ancestry_1)
1261
 
        self.assertFindUniqueAncestors(graph, ['rev2a'], 'rev2a', ['rev1'])
1262
 
        self.assertFindUniqueAncestors(graph, ['rev2b'], 'rev2b', ['rev1'])
1263
 
        self.assertFindUniqueAncestors(graph, ['rev3'], 'rev3', ['rev2a'])
1264
 
 
1265
 
    def test_minimal_ancestry(self):
1266
 
        graph = self.make_breaking_graph(extended_history_shortcut,
1267
 
                                         [NULL_REVISION, 'a', 'b'])
1268
 
        self.assertFindUniqueAncestors(graph, ['e'], 'e', ['d'])
1269
 
 
1270
 
        graph = self.make_breaking_graph(extended_history_shortcut,
1271
 
                                         ['b'])
1272
 
        self.assertFindUniqueAncestors(graph, ['f'], 'f', ['a', 'd'])
1273
 
 
1274
 
        graph = self.make_breaking_graph(complex_shortcut,
1275
 
                                         ['a', 'b'])
1276
 
        self.assertFindUniqueAncestors(graph, ['h'], 'h', ['i'])
1277
 
        self.assertFindUniqueAncestors(graph, ['e', 'g', 'i'], 'i', ['h'])
1278
 
        self.assertFindUniqueAncestors(graph, ['h'], 'h', ['g'])
1279
 
        self.assertFindUniqueAncestors(graph, ['h'], 'h', ['j'])
1280
 
 
1281
 
    def test_in_ancestry(self):
1282
 
        graph = self.make_graph(ancestry_1)
1283
 
        self.assertFindUniqueAncestors(graph, [], 'rev1', ['rev3'])
1284
 
        self.assertFindUniqueAncestors(graph, [], 'rev2b', ['rev4'])
1285
 
 
1286
 
    def test_multiple_revisions(self):
1287
 
        graph = self.make_graph(ancestry_1)
1288
 
        self.assertFindUniqueAncestors(graph,
1289
 
            ['rev4'], 'rev4', ['rev3', 'rev2b'])
1290
 
        self.assertFindUniqueAncestors(graph,
1291
 
            ['rev2a', 'rev3', 'rev4'], 'rev4', ['rev2b'])
1292
 
 
1293
 
    def test_complex_shortcut(self):
1294
 
        graph = self.make_graph(complex_shortcut)
1295
 
        self.assertFindUniqueAncestors(graph,
1296
 
            ['h', 'n'], 'n', ['m'])
1297
 
        self.assertFindUniqueAncestors(graph,
1298
 
            ['e', 'i', 'm'], 'm', ['n'])
1299
 
 
1300
 
    def test_complex_shortcut2(self):
1301
 
        graph = self.make_graph(complex_shortcut2)
1302
 
        self.assertFindUniqueAncestors(graph,
1303
 
            ['j', 'u'], 'u', ['t'])
1304
 
        self.assertFindUniqueAncestors(graph,
1305
 
            ['t'], 't', ['u'])
1306
 
 
1307
 
    def test_multiple_interesting_unique(self):
1308
 
        graph = self.make_graph(multiple_interesting_unique)
1309
 
        self.assertFindUniqueAncestors(graph,
1310
 
            ['j', 'y'], 'y', ['z'])
1311
 
        self.assertFindUniqueAncestors(graph,
1312
 
            ['p', 'z'], 'z', ['y'])
1313
 
 
1314
 
    def test_racing_shortcuts(self):
1315
 
        graph = self.make_graph(racing_shortcuts)
1316
 
        self.assertFindUniqueAncestors(graph,
1317
 
            ['p', 'q', 'z'], 'z', ['y'])
1318
 
        self.assertFindUniqueAncestors(graph,
1319
 
            ['h', 'i', 'j', 'y'], 'j', ['z'])
1320
 
 
1321
 
 
1322
 
class TestGraphFindDistanceToNull(TestGraphBase):
1323
 
    """Test an api that should be able to compute a revno"""
1324
 
 
1325
 
    def assertFindDistance(self, revno, graph, target_id, known_ids):
1326
 
        """Assert the output of Graph.find_distance_to_null()"""
1327
 
        actual = graph.find_distance_to_null(target_id, known_ids)
1328
 
        self.assertEqual(revno, actual)
1329
 
 
1330
 
    def test_nothing_known(self):
1331
 
        graph = self.make_graph(ancestry_1)
1332
 
        self.assertFindDistance(0, graph, NULL_REVISION, [])
1333
 
        self.assertFindDistance(1, graph, 'rev1', [])
1334
 
        self.assertFindDistance(2, graph, 'rev2a', [])
1335
 
        self.assertFindDistance(2, graph, 'rev2b', [])
1336
 
        self.assertFindDistance(3, graph, 'rev3', [])
1337
 
        self.assertFindDistance(4, graph, 'rev4', [])
1338
 
 
1339
 
    def test_rev_is_ghost(self):
1340
 
        graph = self.make_graph(ancestry_1)
1341
 
        e = self.assertRaises(errors.GhostRevisionsHaveNoRevno,
1342
 
                              graph.find_distance_to_null, 'rev_missing', [])
1343
 
        self.assertEqual('rev_missing', e.revision_id)
1344
 
        self.assertEqual('rev_missing', e.ghost_revision_id)
1345
 
 
1346
 
    def test_ancestor_is_ghost(self):
1347
 
        graph = self.make_graph({'rev':['parent']})
1348
 
        e = self.assertRaises(errors.GhostRevisionsHaveNoRevno,
1349
 
                              graph.find_distance_to_null, 'rev', [])
1350
 
        self.assertEqual('rev', e.revision_id)
1351
 
        self.assertEqual('parent', e.ghost_revision_id)
1352
 
 
1353
 
    def test_known_in_ancestry(self):
1354
 
        graph = self.make_graph(ancestry_1)
1355
 
        self.assertFindDistance(2, graph, 'rev2a', [('rev1', 1)])
1356
 
        self.assertFindDistance(3, graph, 'rev3', [('rev2a', 2)])
1357
 
 
1358
 
    def test_known_in_ancestry_limits(self):
1359
 
        graph = self.make_breaking_graph(ancestry_1, ['rev1'])
1360
 
        self.assertFindDistance(4, graph, 'rev4', [('rev3', 3)])
1361
 
 
1362
 
    def test_target_is_ancestor(self):
1363
 
        graph = self.make_graph(ancestry_1)
1364
 
        self.assertFindDistance(2, graph, 'rev2a', [('rev3', 3)])
1365
 
 
1366
 
    def test_target_is_ancestor_limits(self):
1367
 
        """We shouldn't search all history if we run into ourselves"""
1368
 
        graph = self.make_breaking_graph(ancestry_1, ['rev1'])
1369
 
        self.assertFindDistance(3, graph, 'rev3', [('rev4', 4)])
1370
 
 
1371
 
    def test_target_parallel_to_known_limits(self):
1372
 
        # Even though the known revision isn't part of the other ancestry, they
1373
 
        # eventually converge
1374
 
        graph = self.make_breaking_graph(with_tail, ['a'])
1375
 
        self.assertFindDistance(6, graph, 'f', [('g', 6)])
1376
 
        self.assertFindDistance(7, graph, 'h', [('g', 6)])
1377
 
        self.assertFindDistance(8, graph, 'i', [('g', 6)])
1378
 
        self.assertFindDistance(6, graph, 'g', [('i', 8)])
1379
 
 
1380
 
 
1381
 
class TestFindMergeOrder(TestGraphBase):
1382
 
 
1383
 
    def assertMergeOrder(self, expected, graph, tip, base_revisions):
1384
 
        self.assertEqual(expected, graph.find_merge_order(tip, base_revisions))
1385
 
 
1386
 
    def test_parents(self):
1387
 
        graph = self.make_graph(ancestry_1)
1388
 
        self.assertMergeOrder(['rev3', 'rev2b'], graph, 'rev4',
1389
 
                                                        ['rev3', 'rev2b'])
1390
 
        self.assertMergeOrder(['rev3', 'rev2b'], graph, 'rev4',
1391
 
                                                        ['rev2b', 'rev3'])
1392
 
 
1393
 
    def test_ancestors(self):
1394
 
        graph = self.make_graph(ancestry_1)
1395
 
        self.assertMergeOrder(['rev1', 'rev2b'], graph, 'rev4',
1396
 
                                                        ['rev1', 'rev2b'])
1397
 
        self.assertMergeOrder(['rev1', 'rev2b'], graph, 'rev4',
1398
 
                                                        ['rev2b', 'rev1'])
1399
 
 
1400
 
    def test_shortcut_one_ancestor(self):
1401
 
        # When we have enough info, we can stop searching
1402
 
        graph = self.make_breaking_graph(ancestry_1, ['rev3', 'rev2b', 'rev4'])
1403
 
        # Single ancestors shortcut right away
1404
 
        self.assertMergeOrder(['rev3'], graph, 'rev4', ['rev3'])
1405
 
 
1406
 
    def test_shortcut_after_one_ancestor(self):
1407
 
        graph = self.make_breaking_graph(ancestry_1, ['rev2a', 'rev2b'])
1408
 
        self.assertMergeOrder(['rev3', 'rev1'], graph, 'rev4', ['rev1', 'rev3'])
1409
 
 
1410
 
 
1411
 
class TestFindDescendants(TestGraphBase):
1412
 
 
1413
 
    def test_find_descendants_rev1_rev3(self):
1414
 
        graph = self.make_graph(ancestry_1)
1415
 
        descendants = graph.find_descendants('rev1', 'rev3')
1416
 
        self.assertEqual(set(['rev1', 'rev2a', 'rev3']), descendants)
1417
 
 
1418
 
    def test_find_descendants_rev1_rev4(self):
1419
 
        graph = self.make_graph(ancestry_1)
1420
 
        descendants = graph.find_descendants('rev1', 'rev4')
1421
 
        self.assertEqual(set(['rev1', 'rev2a', 'rev2b', 'rev3', 'rev4']),
1422
 
                         descendants)
1423
 
 
1424
 
    def test_find_descendants_rev2a_rev4(self):
1425
 
        graph = self.make_graph(ancestry_1)
1426
 
        descendants = graph.find_descendants('rev2a', 'rev4')
1427
 
        self.assertEqual(set(['rev2a', 'rev3', 'rev4']), descendants)
1428
 
 
1429
 
class TestFindLefthandMerger(TestGraphBase):
1430
 
 
1431
 
    def check_merger(self, result, ancestry, merged, tip):
1432
 
        graph = self.make_graph(ancestry)
1433
 
        self.assertEqual(result, graph.find_lefthand_merger(merged, tip))
1434
 
 
1435
 
    def test_find_lefthand_merger_rev2b(self):
1436
 
        self.check_merger('rev4', ancestry_1, 'rev2b', 'rev4')
1437
 
 
1438
 
    def test_find_lefthand_merger_rev2a(self):
1439
 
        self.check_merger('rev2a', ancestry_1, 'rev2a', 'rev4')
1440
 
 
1441
 
    def test_find_lefthand_merger_rev4(self):
1442
 
        self.check_merger(None, ancestry_1, 'rev4', 'rev2a')
1443
 
 
1444
 
    def test_find_lefthand_merger_f(self):
1445
 
        self.check_merger('i', complex_shortcut, 'f', 'm')
1446
 
 
1447
 
    def test_find_lefthand_merger_g(self):
1448
 
        self.check_merger('i', complex_shortcut, 'g', 'm')
1449
 
 
1450
 
    def test_find_lefthand_merger_h(self):
1451
 
        self.check_merger('n', complex_shortcut, 'h', 'n')
1452
 
 
1453
 
 
1454
 
class TestGetChildMap(TestGraphBase):
1455
 
 
1456
 
    def test_get_child_map(self):
1457
 
        graph = self.make_graph(ancestry_1)
1458
 
        child_map = graph.get_child_map(['rev4', 'rev3', 'rev2a', 'rev2b'])
1459
 
        self.assertEqual({'rev1': ['rev2a', 'rev2b'],
1460
 
                          'rev2a': ['rev3'],
1461
 
                          'rev2b': ['rev4'],
1462
 
                          'rev3': ['rev4']},
1463
 
                          child_map)
1464
 
 
1465
 
 
1466
 
class TestCachingParentsProvider(tests.TestCase):
1467
 
    """These tests run with:
1468
 
 
1469
 
    self.inst_pp, a recording parents provider with a graph of a->b, and b is a
1470
 
    ghost.
1471
 
    self.caching_pp, a CachingParentsProvider layered on inst_pp.
1472
 
    """
1473
 
 
1474
 
    def setUp(self):
1475
 
        super(TestCachingParentsProvider, self).setUp()
1476
 
        dict_pp = _mod_graph.DictParentsProvider({'a':('b',)})
1477
 
        self.inst_pp = InstrumentedParentsProvider(dict_pp)
1478
 
        self.caching_pp = _mod_graph.CachingParentsProvider(self.inst_pp)
1479
 
 
1480
 
    def test_get_parent_map(self):
1481
 
        """Requesting the same revision should be returned from cache"""
1482
 
        self.assertEqual({}, self.caching_pp._cache)
1483
 
        self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
1484
 
        self.assertEqual(['a'], self.inst_pp.calls)
1485
 
        self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
1486
 
        # No new call, as it should have been returned from the cache
1487
 
        self.assertEqual(['a'], self.inst_pp.calls)
1488
 
        self.assertEqual({'a':('b',)}, self.caching_pp._cache)
1489
 
 
1490
 
    def test_get_parent_map_not_present(self):
1491
 
        """The cache should also track when a revision doesn't exist"""
1492
 
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
1493
 
        self.assertEqual(['b'], self.inst_pp.calls)
1494
 
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
1495
 
        # No new calls
1496
 
        self.assertEqual(['b'], self.inst_pp.calls)
1497
 
 
1498
 
    def test_get_parent_map_mixed(self):
1499
 
        """Anything that can be returned from cache, should be"""
1500
 
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
1501
 
        self.assertEqual(['b'], self.inst_pp.calls)
1502
 
        self.assertEqual({'a':('b',)},
1503
 
                         self.caching_pp.get_parent_map(['a', 'b']))
1504
 
        self.assertEqual(['b', 'a'], self.inst_pp.calls)
1505
 
 
1506
 
    def test_get_parent_map_repeated(self):
1507
 
        """Asking for the same parent 2x will only forward 1 request."""
1508
 
        self.assertEqual({'a':('b',)},
1509
 
                         self.caching_pp.get_parent_map(['b', 'a', 'b']))
1510
 
        # Use sorted because we don't care about the order, just that each is
1511
 
        # only present 1 time.
1512
 
        self.assertEqual(['a', 'b'], sorted(self.inst_pp.calls))
1513
 
 
1514
 
    def test_note_missing_key(self):
1515
 
        """After noting that a key is missing it is cached."""
1516
 
        self.caching_pp.note_missing_key('b')
1517
 
        self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
1518
 
        self.assertEqual([], self.inst_pp.calls)
1519
 
        self.assertEqual(set(['b']), self.caching_pp.missing_keys)
1520
 
 
1521
 
 
1522
 
class TestCachingParentsProviderExtras(tests.TestCaseWithTransport):
1523
 
    """Test the behaviour when parents are provided that were not requested."""
1524
 
 
1525
 
    def setUp(self):
1526
 
        super(TestCachingParentsProviderExtras, self).setUp()
1527
 
        class ExtraParentsProvider(object):
1528
 
 
1529
 
            def get_parent_map(self, keys):
1530
 
                return {'rev1': [], 'rev2': ['rev1',]}
1531
 
 
1532
 
        self.inst_pp = InstrumentedParentsProvider(ExtraParentsProvider())
1533
 
        self.caching_pp = _mod_graph.CachingParentsProvider(
1534
 
            get_parent_map=self.inst_pp.get_parent_map)
1535
 
 
1536
 
    def test_uncached(self):
1537
 
        self.caching_pp.disable_cache()
1538
 
        self.assertEqual({'rev1': []},
1539
 
                         self.caching_pp.get_parent_map(['rev1']))
1540
 
        self.assertEqual(['rev1'], self.inst_pp.calls)
1541
 
        self.assertIs(None, self.caching_pp._cache)
1542
 
 
1543
 
    def test_cache_initially_empty(self):
1544
 
        self.assertEqual({}, self.caching_pp._cache)
1545
 
 
1546
 
    def test_cached(self):
1547
 
        self.assertEqual({'rev1': []},
1548
 
                         self.caching_pp.get_parent_map(['rev1']))
1549
 
        self.assertEqual(['rev1'], self.inst_pp.calls)
1550
 
        self.assertEqual({'rev1': [], 'rev2': ['rev1']},
1551
 
                         self.caching_pp._cache)
1552
 
        self.assertEqual({'rev1': []},
1553
 
                          self.caching_pp.get_parent_map(['rev1']))
1554
 
        self.assertEqual(['rev1'], self.inst_pp.calls)
1555
 
 
1556
 
    def test_disable_cache_clears_cache(self):
1557
 
        # Put something in the cache
1558
 
        self.caching_pp.get_parent_map(['rev1'])
1559
 
        self.assertEqual(2, len(self.caching_pp._cache))
1560
 
        self.caching_pp.disable_cache()
1561
 
        self.assertIs(None, self.caching_pp._cache)
1562
 
 
1563
 
    def test_enable_cache_raises(self):
1564
 
        e = self.assertRaises(AssertionError, self.caching_pp.enable_cache)
1565
 
        self.assertEqual('Cache enabled when already enabled.', str(e))
1566
 
 
1567
 
    def test_cache_misses(self):
1568
 
        self.caching_pp.get_parent_map(['rev3'])
1569
 
        self.caching_pp.get_parent_map(['rev3'])
1570
 
        self.assertEqual(['rev3'], self.inst_pp.calls)
1571
 
 
1572
 
    def test_no_cache_misses(self):
1573
 
        self.caching_pp.disable_cache()
1574
 
        self.caching_pp.enable_cache(cache_misses=False)
1575
 
        self.caching_pp.get_parent_map(['rev3'])
1576
 
        self.caching_pp.get_parent_map(['rev3'])
1577
 
        self.assertEqual(['rev3', 'rev3'], self.inst_pp.calls)
1578
 
 
1579
 
    def test_cache_extras(self):
1580
 
        self.assertEqual({}, self.caching_pp.get_parent_map(['rev3']))
1581
 
        self.assertEqual({'rev2': ['rev1']},
1582
 
                         self.caching_pp.get_parent_map(['rev2']))
1583
 
        self.assertEqual(['rev3'], self.inst_pp.calls)
1584
 
 
1585
 
 
1586
 
class TestCollapseLinearRegions(tests.TestCase):
1587
 
 
1588
 
    def assertCollapsed(self, collapsed, original):
1589
 
        self.assertEqual(collapsed,
1590
 
                         _mod_graph.collapse_linear_regions(original))
1591
 
 
1592
 
    def test_collapse_nothing(self):
1593
 
        d = {1:[2, 3], 2:[], 3:[]}
1594
 
        self.assertCollapsed(d, d)
1595
 
        d = {1:[2], 2:[3, 4], 3:[5], 4:[5], 5:[]}
1596
 
        self.assertCollapsed(d, d)
1597
 
 
1598
 
    def test_collapse_chain(self):
1599
 
        # Any time we have a linear chain, we should be able to collapse
1600
 
        d = {1:[2], 2:[3], 3:[4], 4:[5], 5:[]}
1601
 
        self.assertCollapsed({1:[5], 5:[]}, d)
1602
 
        d = {5:[4], 4:[3], 3:[2], 2:[1], 1:[]}
1603
 
        self.assertCollapsed({5:[1], 1:[]}, d)
1604
 
        d = {5:[3], 3:[4], 4:[1], 1:[2], 2:[]}
1605
 
        self.assertCollapsed({5:[2], 2:[]}, d)
1606
 
 
1607
 
    def test_collapse_with_multiple_children(self):
1608
 
        #    7
1609
 
        #    |
1610
 
        #    6
1611
 
        #   / \
1612
 
        #  4   5
1613
 
        #  |   |
1614
 
        #  2   3
1615
 
        #   \ /
1616
 
        #    1
1617
 
        #
1618
 
        # 4 and 5 cannot be removed because 6 has 2 children
1619
 
        # 2 and 3 cannot be removed because 1 has 2 parents
1620
 
        d = {1:[2, 3], 2:[4], 4:[6], 3:[5], 5:[6], 6:[7], 7:[]}
1621
 
        self.assertCollapsed(d, d)
1622
 
 
1623
 
 
1624
 
class TestGraphThunkIdsToKeys(tests.TestCase):
1625
 
 
1626
 
    def test_heads(self):
1627
 
        # A
1628
 
        # |\
1629
 
        # B C
1630
 
        # |/
1631
 
        # D
1632
 
        d = {('D',): [('B',), ('C',)], ('C',):[('A',)],
1633
 
             ('B',): [('A',)], ('A',): []}
1634
 
        g = _mod_graph.Graph(_mod_graph.DictParentsProvider(d))
1635
 
        graph_thunk = _mod_graph.GraphThunkIdsToKeys(g)
1636
 
        self.assertEqual(['D'], sorted(graph_thunk.heads(['D', 'A'])))
1637
 
        self.assertEqual(['D'], sorted(graph_thunk.heads(['D', 'B'])))
1638
 
        self.assertEqual(['D'], sorted(graph_thunk.heads(['D', 'C'])))
1639
 
        self.assertEqual(['B', 'C'], sorted(graph_thunk.heads(['B', 'C'])))
1640
 
 
1641
 
    def test_add_node(self):
1642
 
        d = {('C',):[('A',)], ('B',): [('A',)], ('A',): []}
1643
 
        g = _mod_graph.KnownGraph(d)
1644
 
        graph_thunk = _mod_graph.GraphThunkIdsToKeys(g)
1645
 
        graph_thunk.add_node("D", ["A", "C"])
1646
 
        self.assertEqual(['B', 'D'],
1647
 
            sorted(graph_thunk.heads(['D', 'B', 'A'])))
1648
 
 
1649
 
    def test_merge_sort(self):
1650
 
        d = {('C',):[('A',)], ('B',): [('A',)], ('A',): []}
1651
 
        g = _mod_graph.KnownGraph(d)
1652
 
        graph_thunk = _mod_graph.GraphThunkIdsToKeys(g)
1653
 
        graph_thunk.add_node("D", ["A", "C"])
1654
 
        self.assertEqual([('C', 0, (2,), False), ('A', 0, (1,), True)],
1655
 
            [(n.key, n.merge_depth, n.revno, n.end_of_merge)
1656
 
                 for n in graph_thunk.merge_sort('C')])
1657
 
 
1658
 
 
1659
 
class TestPendingAncestryResultGetKeys(TestCaseWithMemoryTransport):
1660
 
    """Tests for bzrlib.graph.PendingAncestryResult."""
1661
 
 
1662
 
    def test_get_keys(self):
1663
 
        builder = self.make_branch_builder('b')
1664
 
        builder.start_series()
1665
 
        builder.build_snapshot('rev-1', None, [
1666
 
            ('add', ('', 'root-id', 'directory', ''))])
1667
 
        builder.build_snapshot('rev-2', ['rev-1'], [])
1668
 
        builder.finish_series()
1669
 
        repo = builder.get_branch().repository
1670
 
        repo.lock_read()
1671
 
        self.addCleanup(repo.unlock)
1672
 
        result = _mod_graph.PendingAncestryResult(['rev-2'], repo)
1673
 
        self.assertEqual(set(['rev-1', 'rev-2']), set(result.get_keys()))
1674
 
 
1675
 
    def test_get_keys_excludes_ghosts(self):
1676
 
        builder = self.make_branch_builder('b')
1677
 
        builder.start_series()
1678
 
        builder.build_snapshot('rev-1', None, [
1679
 
            ('add', ('', 'root-id', 'directory', ''))])
1680
 
        builder.build_snapshot('rev-2', ['rev-1', 'ghost'], [])
1681
 
        builder.finish_series()
1682
 
        repo = builder.get_branch().repository
1683
 
        repo.lock_read()
1684
 
        self.addCleanup(repo.unlock)
1685
 
        result = _mod_graph.PendingAncestryResult(['rev-2'], repo)
1686
 
        self.assertEqual(sorted(['rev-1', 'rev-2']), sorted(result.get_keys()))
1687
 
 
1688
 
    def test_get_keys_excludes_null(self):
1689
 
        # Make a 'graph' with an iter_ancestry that returns NULL_REVISION
1690
 
        # somewhere other than the last element, which can happen in real
1691
 
        # ancestries.
1692
 
        class StubGraph(object):
1693
 
            def iter_ancestry(self, keys):
1694
 
                return [(NULL_REVISION, ()), ('foo', (NULL_REVISION,))]
1695
 
        result = _mod_graph.PendingAncestryResult(['rev-3'], None)
1696
 
        result_keys = result._get_keys(StubGraph())
1697
 
        # Only the non-null keys from the ancestry appear.
1698
 
        self.assertEqual(set(['foo']), set(result_keys))
1699
 
 
1700
 
 
1701
 
class TestPendingAncestryResultRefine(TestGraphBase):
1702
 
 
1703
 
    def test_refine(self):
1704
 
        # Used when pulling from a stacked repository, so test some revisions
1705
 
        # being satisfied from the stacking branch.
1706
 
        g = self.make_graph(
1707
 
            {"tip":["mid"], "mid":["base"], "tag":["base"],
1708
 
             "base":[NULL_REVISION], NULL_REVISION:[]})
1709
 
        result = _mod_graph.PendingAncestryResult(['tip', 'tag'], None)
1710
 
        result = result.refine(set(['tip']), set(['mid']))
1711
 
        self.assertEqual(set(['mid', 'tag']), result.heads)
1712
 
        result = result.refine(set(['mid', 'tag', 'base']),
1713
 
            set([NULL_REVISION]))
1714
 
        self.assertEqual(set([NULL_REVISION]), result.heads)
1715
 
        self.assertTrue(result.is_empty())
1716
 
 
1717
 
 
1718
 
class TestSearchResultRefine(TestGraphBase):
1719
 
 
1720
 
    def test_refine(self):
1721
 
        # Used when pulling from a stacked repository, so test some revisions
1722
 
        # being satisfied from the stacking branch.
1723
 
        g = self.make_graph(
1724
 
            {"tip":["mid"], "mid":["base"], "tag":["base"],
1725
 
             "base":[NULL_REVISION], NULL_REVISION:[]})
1726
 
        result = _mod_graph.SearchResult(set(['tip', 'tag']),
1727
 
            set([NULL_REVISION]), 4, set(['tip', 'mid', 'tag', 'base']))
1728
 
        result = result.refine(set(['tip']), set(['mid']))
1729
 
        recipe = result.get_recipe()
1730
 
        # We should be starting from tag (original head) and mid (seen ref)
1731
 
        self.assertEqual(set(['mid', 'tag']), recipe[1])
1732
 
        # We should be stopping at NULL (original stop) and tip (seen head)
1733
 
        self.assertEqual(set([NULL_REVISION, 'tip']), recipe[2])
1734
 
        self.assertEqual(3, recipe[3])
1735
 
        result = result.refine(set(['mid', 'tag', 'base']),
1736
 
            set([NULL_REVISION]))
1737
 
        recipe = result.get_recipe()
1738
 
        # We should be starting from nothing (NULL was known as a cut point)
1739
 
        self.assertEqual(set([]), recipe[1])
1740
 
        # We should be stopping at NULL (original stop) and tip (seen head) and
1741
 
        # tag (seen head) and mid(seen mid-point head). We could come back and
1742
 
        # define this as not including mid, for minimal results, but it is
1743
 
        # still 'correct' to include mid, and simpler/easier.
1744
 
        self.assertEqual(set([NULL_REVISION, 'tip', 'tag', 'mid']), recipe[2])
1745
 
        self.assertEqual(0, recipe[3])
1746
 
        self.assertTrue(result.is_empty())