~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_graph.py

  • Committer: Jelmer Vernooij
  • Date: 2011-02-26 15:39:49 UTC
  • mto: (5691.1.1 bzr.dev)
  • mto: This revision was merged to the branch mainline in revision 5692.
  • Revision ID: jelmer@samba.org-20110226153949-o0fk909b30g7z570
Fix the use of "bzr tags" in branches with ghosts in their mainline /and/ tags on revisions not in the branch ancestry.

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