~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_graph.py

  • Committer: Vincent Ladeuil
  • Date: 2007-06-20 14:25:06 UTC
  • mfrom: (2540 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2646.
  • Revision ID: v.ladeuil+lp@free.fr-20070620142506-txsb1v8538kpsafw
merge bzr.dev @ 2540

Show diffs side-by-side

added added

removed removed

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