~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_graph.py

  • Committer: Martin Pool
  • Date: 2005-09-15 08:37:41 UTC
  • Revision ID: mbp@sourcefrog.net-20050915083741-70d7550b97c7b580
- some updates for fetch/update function

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