/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/tests/test_graph.py

  • Committer: Jelmer Vernooij
  • Date: 2020-05-24 00:39:50 UTC
  • mto: This revision was merged to the branch mainline in revision 7504.
  • Revision ID: jelmer@jelmer.uk-20200524003950-bbc545r76vc5yajg
Add github action.

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