/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 bzrlib/tests/test__known_graph.py

  • Committer: Martin Pool
  • Date: 2009-09-14 01:48:28 UTC
  • mfrom: (4685 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4688.
  • Revision ID: mbp@sourcefrog.net-20090914014828-ydr9rlkdfq2sv57z
Merge news

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
"""Tests for the python and pyrex extensions of KnownGraph"""
18
18
 
 
19
import pprint
 
20
 
19
21
from bzrlib import (
20
22
    errors,
21
23
    graph as _mod_graph,
30
32
    """Parameterize tests for all versions of groupcompress."""
31
33
    scenarios = [
32
34
        ('python', {'module': _known_graph_py, 'do_cache': True}),
 
35
    ]
 
36
    caching_scenarios = [
33
37
        ('python-nocache', {'module': _known_graph_py, 'do_cache': False}),
34
38
    ]
35
39
    suite = loader.suiteClass()
36
40
    if CompiledKnownGraphFeature.available():
37
41
        from bzrlib import _known_graph_pyx
38
42
        scenarios.append(('C', {'module': _known_graph_pyx, 'do_cache': True}))
39
 
        scenarios.append(('C-nocache',
 
43
        caching_scenarios.append(('C-nocache',
40
44
                          {'module': _known_graph_pyx, 'do_cache': False}))
41
45
    else:
42
46
        # the compiled module isn't available, so we add a failing test
44
48
            def test_fail(self):
45
49
                self.requireFeature(CompiledKnownGraphFeature)
46
50
        suite.addTest(loader.loadTestsFromTestCase(FailWithoutFeature))
47
 
    result = tests.multiply_tests(standard_tests, scenarios, suite)
48
 
    return result
 
51
    # TestKnownGraphHeads needs to be permutated with and without caching.
 
52
    # All other TestKnownGraph tests only need to be tested across module
 
53
    heads_suite, other_suite = tests.split_suite_by_condition(
 
54
        standard_tests, tests.condition_isinstance(TestKnownGraphHeads))
 
55
    suite = tests.multiply_tests(other_suite, scenarios, suite)
 
56
    suite = tests.multiply_tests(heads_suite, scenarios + caching_scenarios,
 
57
                                 suite)
 
58
    return suite
49
59
 
50
60
 
51
61
class _CompiledKnownGraphFeature(tests.Feature):
73
83
alt_merge = {'a': [], 'b': ['a'], 'c': ['b'], 'd': ['a', 'c']}
74
84
 
75
85
 
76
 
class TestKnownGraph(tests.TestCase):
 
86
class TestCaseWithKnownGraph(tests.TestCase):
77
87
 
78
88
    module = None # Set by load_tests
79
 
    do_cache = None # Set by load_tests
80
89
 
81
90
    def make_known_graph(self, ancestry):
82
91
        return self.module.KnownGraph(ancestry, do_cache=self.do_cache)
83
92
 
 
93
 
 
94
class TestKnownGraph(TestCaseWithKnownGraph):
 
95
 
84
96
    def assertGDFO(self, graph, rev, gdfo):
85
97
        node = graph._nodes[rev]
86
98
        self.assertEqual(gdfo, node.gdfo)
87
99
 
88
100
    def test_children_ancestry1(self):
89
101
        graph = self.make_known_graph(test_graph.ancestry_1)
90
 
        self.assertEqual(['rev1'], graph._nodes[NULL_REVISION].child_keys)
 
102
        self.assertEqual(['rev1'], graph.get_child_keys(NULL_REVISION))
91
103
        self.assertEqual(['rev2a', 'rev2b'],
92
 
                         sorted(graph._nodes['rev1'].child_keys))
93
 
        self.assertEqual(['rev3'], sorted(graph._nodes['rev2a'].child_keys))
94
 
        self.assertEqual(['rev4'], sorted(graph._nodes['rev3'].child_keys))
95
 
        self.assertEqual(['rev4'], sorted(graph._nodes['rev2b'].child_keys))
 
104
                         sorted(graph.get_child_keys('rev1')))
 
105
        self.assertEqual(['rev3'], graph.get_child_keys('rev2a'))
 
106
        self.assertEqual(['rev4'], graph.get_child_keys('rev3'))
 
107
        self.assertEqual(['rev4'], graph.get_child_keys('rev2b'))
 
108
        self.assertRaises(KeyError, graph.get_child_keys, 'not_in_graph')
 
109
 
 
110
    def test_parent_ancestry1(self):
 
111
        graph = self.make_known_graph(test_graph.ancestry_1)
 
112
        self.assertEqual([NULL_REVISION], graph.get_parent_keys('rev1'))
 
113
        self.assertEqual(['rev1'], graph.get_parent_keys('rev2a'))
 
114
        self.assertEqual(['rev1'], graph.get_parent_keys('rev2b'))
 
115
        self.assertEqual(['rev2a'], graph.get_parent_keys('rev3'))
 
116
        self.assertEqual(['rev2b', 'rev3'],
 
117
                         sorted(graph.get_parent_keys('rev4')))
 
118
        self.assertRaises(KeyError, graph.get_child_keys, 'not_in_graph')
 
119
    
 
120
    def test_parent_with_ghost(self):
 
121
        graph = self.make_known_graph(test_graph.with_ghost)
 
122
        self.assertEqual(None, graph.get_parent_keys('g'))
96
123
 
97
124
    def test_gdfo_ancestry_1(self):
98
125
        graph = self.make_known_graph(test_graph.ancestry_1)
127
154
        self.assertGDFO(graph, 'a', 5)
128
155
        self.assertGDFO(graph, 'c', 5)
129
156
 
 
157
 
 
158
class TestKnownGraphHeads(TestCaseWithKnownGraph):
 
159
 
 
160
    do_cache = None # Set by load_tests
 
161
 
130
162
    def test_heads_null(self):
131
163
        graph = self.make_known_graph(test_graph.ancestry_1)
132
164
        self.assertEqual(set(['null:']), graph.heads(['null:']))
227
259
        self.assertEqual(set(['c']), graph.heads(['c', 'b', 'd', 'g']))
228
260
        self.assertEqual(set(['a', 'c']), graph.heads(['a', 'c', 'e', 'g']))
229
261
        self.assertEqual(set(['a', 'c']), graph.heads(['a', 'c', 'f']))
 
262
 
 
263
 
 
264
class TestKnownGraphTopoSort(TestCaseWithKnownGraph):
 
265
 
 
266
    def assertTopoSortOrder(self, ancestry):
 
267
        """Check topo_sort and iter_topo_order is genuinely topological order.
 
268
 
 
269
        For every child in the graph, check if it comes after all of it's
 
270
        parents.
 
271
        """
 
272
        graph = self.make_known_graph(ancestry)
 
273
        sort_result = graph.topo_sort()
 
274
        # We should have an entry in sort_result for every entry present in the
 
275
        # graph.
 
276
        self.assertEqual(len(ancestry), len(sort_result))
 
277
        node_idx = dict((node, idx) for idx, node in enumerate(sort_result))
 
278
        for node in sort_result:
 
279
            parents = ancestry[node]
 
280
            for parent in parents:
 
281
                if parent not in ancestry:
 
282
                    # ghost
 
283
                    continue
 
284
                if node_idx[node] <= node_idx[parent]:
 
285
                    self.fail("parent %s must come before child %s:\n%s"
 
286
                              % (parent, node, sort_result))
 
287
 
 
288
    def test_topo_sort_empty(self):
 
289
        """TopoSort empty list"""
 
290
        self.assertTopoSortOrder({})
 
291
 
 
292
    def test_topo_sort_easy(self):
 
293
        """TopoSort list with one node"""
 
294
        self.assertTopoSortOrder({0: []})
 
295
 
 
296
    def test_topo_sort_cycle(self):
 
297
        """TopoSort traps graph with cycles"""
 
298
        g = self.make_known_graph({0: [1],
 
299
                                  1: [0]})
 
300
        self.assertRaises(errors.GraphCycleError, g.topo_sort)
 
301
 
 
302
    def test_topo_sort_cycle_2(self):
 
303
        """TopoSort traps graph with longer cycle"""
 
304
        g = self.make_known_graph({0: [1],
 
305
                                   1: [2],
 
306
                                   2: [0]})
 
307
        self.assertRaises(errors.GraphCycleError, g.topo_sort)
 
308
 
 
309
    def test_topo_sort_cycle_with_tail(self):
 
310
        """TopoSort traps graph with longer cycle"""
 
311
        g = self.make_known_graph({0: [1],
 
312
                                   1: [2],
 
313
                                   2: [3, 4],
 
314
                                   3: [0],
 
315
                                   4: []})
 
316
        self.assertRaises(errors.GraphCycleError, g.topo_sort)
 
317
 
 
318
    def test_topo_sort_1(self):
 
319
        """TopoSort simple nontrivial graph"""
 
320
        self.assertTopoSortOrder({0: [3],
 
321
                                  1: [4],
 
322
                                  2: [1, 4],
 
323
                                  3: [],
 
324
                                  4: [0, 3]})
 
325
 
 
326
    def test_topo_sort_partial(self):
 
327
        """Topological sort with partial ordering.
 
328
 
 
329
        Multiple correct orderings are possible, so test for
 
330
        correctness, not for exact match on the resulting list.
 
331
        """
 
332
        self.assertTopoSortOrder({0: [],
 
333
                                  1: [0],
 
334
                                  2: [0],
 
335
                                  3: [0],
 
336
                                  4: [1, 2, 3],
 
337
                                  5: [1, 2],
 
338
                                  6: [1, 2],
 
339
                                  7: [2, 3],
 
340
                                  8: [0, 1, 4, 5, 6]})
 
341
 
 
342
    def test_topo_sort_ghost_parent(self):
 
343
        """Sort nodes, but don't include some parents in the output"""
 
344
        self.assertTopoSortOrder({0: [1],
 
345
                                  1: [2]})
 
346
 
 
347
 
 
348
class TestKnownGraphMergeSort(TestCaseWithKnownGraph):
 
349
 
 
350
    def assertSortAndIterate(self, ancestry, branch_tip, result_list):
 
351
        """Check that merge based sorting and iter_topo_order on graph works."""
 
352
        graph = self.make_known_graph(ancestry)
 
353
        value = graph.merge_sort(branch_tip)
 
354
        value = [(n.key, n.merge_depth, n.revno, n.end_of_merge)
 
355
                 for n in value]
 
356
        if result_list != value:
 
357
            self.assertEqualDiff(pprint.pformat(result_list),
 
358
                                 pprint.pformat(value))
 
359
 
 
360
    def test_merge_sort_empty(self):
 
361
        # sorting of an emptygraph does not error
 
362
        self.assertSortAndIterate({}, None, [])
 
363
        self.assertSortAndIterate({}, NULL_REVISION, [])
 
364
        self.assertSortAndIterate({}, (NULL_REVISION,), [])
 
365
 
 
366
    def test_merge_sort_not_empty_no_tip(self):
 
367
        # merge sorting of a branch starting with None should result
 
368
        # in an empty list: no revisions are dragged in.
 
369
        self.assertSortAndIterate({0: []}, None, [])
 
370
        self.assertSortAndIterate({0: []}, NULL_REVISION, [])
 
371
        self.assertSortAndIterate({0: []}, (NULL_REVISION,), [])
 
372
 
 
373
    def test_merge_sort_one_revision(self):
 
374
        # sorting with one revision as the tip returns the correct fields:
 
375
        # sequence - 0, revision id, merge depth - 0, end_of_merge
 
376
        self.assertSortAndIterate({'id': []},
 
377
                                  'id',
 
378
                                  [('id', 0, (1,), True)])
 
379
 
 
380
    def test_sequence_numbers_increase_no_merges(self):
 
381
        # emit a few revisions with no merges to check the sequence
 
382
        # numbering works in trivial cases
 
383
        self.assertSortAndIterate(
 
384
            {'A': [],
 
385
             'B': ['A'],
 
386
             'C': ['B']},
 
387
            'C',
 
388
            [('C', 0, (3,), False),
 
389
             ('B', 0, (2,), False),
 
390
             ('A', 0, (1,), True),
 
391
             ],
 
392
            )
 
393
 
 
394
    def test_sequence_numbers_increase_with_merges(self):
 
395
        # test that sequence numbers increase across merges
 
396
        self.assertSortAndIterate(
 
397
            {'A': [],
 
398
             'B': ['A'],
 
399
             'C': ['A', 'B']},
 
400
            'C',
 
401
            [('C', 0, (2,), False),
 
402
             ('B', 1, (1,1,1), True),
 
403
             ('A', 0, (1,), True),
 
404
             ],
 
405
            )
 
406
 
 
407
    def test_merge_sort_race(self):
 
408
        # A
 
409
        # |
 
410
        # B-.
 
411
        # |\ \
 
412
        # | | C
 
413
        # | |/
 
414
        # | D
 
415
        # |/
 
416
        # F
 
417
        graph = {'A': [],
 
418
                 'B': ['A'],
 
419
                 'C': ['B'],
 
420
                 'D': ['B', 'C'],
 
421
                 'F': ['B', 'D'],
 
422
                 }
 
423
        self.assertSortAndIterate(graph, 'F',
 
424
            [('F', 0, (3,), False),
 
425
             ('D', 1, (2,2,1), False),
 
426
             ('C', 2, (2,1,1), True),
 
427
             ('B', 0, (2,), False),
 
428
             ('A', 0, (1,), True),
 
429
             ])
 
430
        # A
 
431
        # |
 
432
        # B-.
 
433
        # |\ \
 
434
        # | X C
 
435
        # | |/
 
436
        # | D
 
437
        # |/
 
438
        # F
 
439
        graph = {'A': [],
 
440
                 'B': ['A'],
 
441
                 'C': ['B'],
 
442
                 'X': ['B'],
 
443
                 'D': ['X', 'C'],
 
444
                 'F': ['B', 'D'],
 
445
                 }
 
446
        self.assertSortAndIterate(graph, 'F',
 
447
            [('F', 0, (3,), False),
 
448
             ('D', 1, (2,1,2), False),
 
449
             ('C', 2, (2,2,1), True),
 
450
             ('X', 1, (2,1,1), True),
 
451
             ('B', 0, (2,), False),
 
452
             ('A', 0, (1,), True),
 
453
             ])
 
454
 
 
455
    def test_merge_depth_with_nested_merges(self):
 
456
        # the merge depth marker should reflect the depth of the revision
 
457
        # in terms of merges out from the mainline
 
458
        # revid, depth, parents:
 
459
        #  A 0   [D, B]
 
460
        #  B  1  [C, F]
 
461
        #  C  1  [H]
 
462
        #  D 0   [H, E]
 
463
        #  E  1  [G, F]
 
464
        #  F   2 [G]
 
465
        #  G  1  [H]
 
466
        #  H 0
 
467
        self.assertSortAndIterate(
 
468
            {'A': ['D', 'B'],
 
469
             'B': ['C', 'F'],
 
470
             'C': ['H'],
 
471
             'D': ['H', 'E'],
 
472
             'E': ['G', 'F'],
 
473
             'F': ['G'],
 
474
             'G': ['H'],
 
475
             'H': []
 
476
             },
 
477
            'A',
 
478
            [('A', 0, (3,),  False),
 
479
             ('B', 1, (1,3,2), False),
 
480
             ('C', 1, (1,3,1), True),
 
481
             ('D', 0, (2,), False),
 
482
             ('E', 1, (1,1,2), False),
 
483
             ('F', 2, (1,2,1), True),
 
484
             ('G', 1, (1,1,1), True),
 
485
             ('H', 0, (1,), True),
 
486
             ],
 
487
            )
 
488
 
 
489
    def test_dotted_revnos_with_simple_merges(self):
 
490
        # A         1
 
491
        # |\
 
492
        # B C       2, 1.1.1
 
493
        # | |\
 
494
        # D E F     3, 1.1.2, 1.2.1
 
495
        # |/ /|
 
496
        # G H I     4, 1.2.2, 1.3.1
 
497
        # |/ /
 
498
        # J K       5, 1.3.2
 
499
        # |/
 
500
        # L         6
 
501
        self.assertSortAndIterate(
 
502
            {'A': [],
 
503
             'B': ['A'],
 
504
             'C': ['A'],
 
505
             'D': ['B'],
 
506
             'E': ['C'],
 
507
             'F': ['C'],
 
508
             'G': ['D', 'E'],
 
509
             'H': ['F'],
 
510
             'I': ['F'],
 
511
             'J': ['G', 'H'],
 
512
             'K': ['I'],
 
513
             'L': ['J', 'K'],
 
514
            },
 
515
            'L',
 
516
            [('L', 0, (6,), False),
 
517
             ('K', 1, (1,3,2), False),
 
518
             ('I', 1, (1,3,1), True),
 
519
             ('J', 0, (5,), False),
 
520
             ('H', 1, (1,2,2), False),
 
521
             ('F', 1, (1,2,1), True),
 
522
             ('G', 0, (4,), False),
 
523
             ('E', 1, (1,1,2), False),
 
524
             ('C', 1, (1,1,1), True),
 
525
             ('D', 0, (3,), False),
 
526
             ('B', 0, (2,), False),
 
527
             ('A', 0, (1,),  True),
 
528
             ],
 
529
            )
 
530
        # Adding a shortcut from the first revision should not change any of
 
531
        # the existing numbers
 
532
        self.assertSortAndIterate(
 
533
            {'A': [],
 
534
             'B': ['A'],
 
535
             'C': ['A'],
 
536
             'D': ['B'],
 
537
             'E': ['C'],
 
538
             'F': ['C'],
 
539
             'G': ['D', 'E'],
 
540
             'H': ['F'],
 
541
             'I': ['F'],
 
542
             'J': ['G', 'H'],
 
543
             'K': ['I'],
 
544
             'L': ['J', 'K'],
 
545
             'M': ['A'],
 
546
             'N': ['L', 'M'],
 
547
            },
 
548
            'N',
 
549
            [('N', 0, (7,), False),
 
550
             ('M', 1, (1,4,1), True),
 
551
             ('L', 0, (6,), False),
 
552
             ('K', 1, (1,3,2), False),
 
553
             ('I', 1, (1,3,1), True),
 
554
             ('J', 0, (5,), False),
 
555
             ('H', 1, (1,2,2), False),
 
556
             ('F', 1, (1,2,1), True),
 
557
             ('G', 0, (4,), False),
 
558
             ('E', 1, (1,1,2), False),
 
559
             ('C', 1, (1,1,1), True),
 
560
             ('D', 0, (3,), False),
 
561
             ('B', 0, (2,), False),
 
562
             ('A', 0, (1,),  True),
 
563
             ],
 
564
            )
 
565
 
 
566
    def test_end_of_merge_not_last_revision_in_branch(self):
 
567
        # within a branch only the last revision gets an
 
568
        # end of merge marker.
 
569
        self.assertSortAndIterate(
 
570
            {'A': ['B'],
 
571
             'B': [],
 
572
             },
 
573
            'A',
 
574
            [('A', 0, (2,), False),
 
575
             ('B', 0, (1,), True)
 
576
             ],
 
577
            )
 
578
 
 
579
    def test_end_of_merge_multiple_revisions_merged_at_once(self):
 
580
        # when multiple branches are merged at once, both of their
 
581
        # branch-endpoints should be listed as end-of-merge.
 
582
        # Also, the order of the multiple merges should be
 
583
        # left-right shown top to bottom.
 
584
        # * means end of merge
 
585
        # A 0    [H, B, E]
 
586
        # B  1   [D, C]
 
587
        # C   2  [D]       *
 
588
        # D  1   [H]       *
 
589
        # E  1   [G, F]
 
590
        # F   2  [G]       *
 
591
        # G  1   [H]       *
 
592
        # H 0    []        *
 
593
        self.assertSortAndIterate(
 
594
            {'A': ['H', 'B', 'E'],
 
595
             'B': ['D', 'C'],
 
596
             'C': ['D'],
 
597
             'D': ['H'],
 
598
             'E': ['G', 'F'],
 
599
             'F': ['G'],
 
600
             'G': ['H'],
 
601
             'H': [],
 
602
             },
 
603
            'A',
 
604
            [('A', 0, (2,), False),
 
605
             ('B', 1, (1,3,2), False),
 
606
             ('C', 2, (1,4,1), True),
 
607
             ('D', 1, (1,3,1), True),
 
608
             ('E', 1, (1,1,2), False),
 
609
             ('F', 2, (1,2,1), True),
 
610
             ('G', 1, (1,1,1), True),
 
611
             ('H', 0, (1,), True),
 
612
             ],
 
613
            )
 
614
 
 
615
    def test_parallel_root_sequence_numbers_increase_with_merges(self):
 
616
        """When there are parallel roots, check their revnos."""
 
617
        self.assertSortAndIterate(
 
618
            {'A': [],
 
619
             'B': [],
 
620
             'C': ['A', 'B']},
 
621
            'C',
 
622
            [('C', 0, (2,), False),
 
623
             ('B', 1, (0,1,1), True),
 
624
             ('A', 0, (1,), True),
 
625
             ],
 
626
            )
 
627
 
 
628
    def test_revnos_are_globally_assigned(self):
 
629
        """revnos are assigned according to the revision they derive from."""
 
630
        # in this test we setup a number of branches that all derive from
 
631
        # the first revision, and then merge them one at a time, which
 
632
        # should give the revisions as they merge numbers still deriving from
 
633
        # the revision were based on.
 
634
        # merge 3: J: ['G', 'I']
 
635
        # branch 3:
 
636
        #  I: ['H']
 
637
        #  H: ['A']
 
638
        # merge 2: G: ['D', 'F']
 
639
        # branch 2:
 
640
        #  F: ['E']
 
641
        #  E: ['A']
 
642
        # merge 1: D: ['A', 'C']
 
643
        # branch 1:
 
644
        #  C: ['B']
 
645
        #  B: ['A']
 
646
        # root: A: []
 
647
        self.assertSortAndIterate(
 
648
            {'J': ['G', 'I'],
 
649
             'I': ['H',],
 
650
             'H': ['A'],
 
651
             'G': ['D', 'F'],
 
652
             'F': ['E'],
 
653
             'E': ['A'],
 
654
             'D': ['A', 'C'],
 
655
             'C': ['B'],
 
656
             'B': ['A'],
 
657
             'A': [],
 
658
             },
 
659
            'J',
 
660
            [('J', 0, (4,), False),
 
661
             ('I', 1, (1,3,2), False),
 
662
             ('H', 1, (1,3,1), True),
 
663
             ('G', 0, (3,), False),
 
664
             ('F', 1, (1,2,2), False),
 
665
             ('E', 1, (1,2,1), True),
 
666
             ('D', 0, (2,), False),
 
667
             ('C', 1, (1,1,2), False),
 
668
             ('B', 1, (1,1,1), True),
 
669
             ('A', 0, (1,), True),
 
670
             ],
 
671
            )
 
672
 
 
673
    def test_roots_and_sub_branches_versus_ghosts(self):
 
674
        """Extra roots and their mini branches use the same numbering.
 
675
 
 
676
        All of them use the 0-node numbering.
 
677
        """
 
678
        #       A D   K
 
679
        #       | |\  |\
 
680
        #       B E F L M
 
681
        #       | |/  |/
 
682
        #       C G   N
 
683
        #       |/    |\
 
684
        #       H I   O P
 
685
        #       |/    |/
 
686
        #       J     Q
 
687
        #       |.---'
 
688
        #       R
 
689
        self.assertSortAndIterate(
 
690
            {'A': [],
 
691
             'B': ['A'],
 
692
             'C': ['B'],
 
693
             'D': [],
 
694
             'E': ['D'],
 
695
             'F': ['D'],
 
696
             'G': ['E', 'F'],
 
697
             'H': ['C', 'G'],
 
698
             'I': [],
 
699
             'J': ['H', 'I'],
 
700
             'K': [],
 
701
             'L': ['K'],
 
702
             'M': ['K'],
 
703
             'N': ['L', 'M'],
 
704
             'O': ['N'],
 
705
             'P': ['N'],
 
706
             'Q': ['O', 'P'],
 
707
             'R': ['J', 'Q'],
 
708
            },
 
709
            'R',
 
710
            [('R', 0, (6,), False),
 
711
             ('Q', 1, (0,4,5), False),
 
712
             ('P', 2, (0,6,1), True),
 
713
             ('O', 1, (0,4,4), False),
 
714
             ('N', 1, (0,4,3), False),
 
715
             ('M', 2, (0,5,1), True),
 
716
             ('L', 1, (0,4,2), False),
 
717
             ('K', 1, (0,4,1), True),
 
718
             ('J', 0, (5,), False),
 
719
             ('I', 1, (0,3,1), True),
 
720
             ('H', 0, (4,), False),
 
721
             ('G', 1, (0,1,3), False),
 
722
             ('F', 2, (0,2,1), True),
 
723
             ('E', 1, (0,1,2), False),
 
724
             ('D', 1, (0,1,1), True),
 
725
             ('C', 0, (3,), False),
 
726
             ('B', 0, (2,), False),
 
727
             ('A', 0, (1,), True),
 
728
             ],
 
729
            )
 
730
 
 
731
    def test_ghost(self):
 
732
        # merge_sort should be able to ignore ghosts
 
733
        # A
 
734
        # |
 
735
        # B ghost
 
736
        # |/
 
737
        # C
 
738
        self.assertSortAndIterate(
 
739
            {'A': [],
 
740
             'B': ['A'],
 
741
             'C': ['B', 'ghost'],
 
742
            },
 
743
            'C',
 
744
            [('C', 0, (3,), False),
 
745
             ('B', 0, (2,), False),
 
746
             ('A', 0, (1,), True),
 
747
            ])
 
748
 
 
749
    def test_lefthand_ghost(self):
 
750
        # ghost
 
751
        #  |
 
752
        #  A
 
753
        #  |
 
754
        #  B
 
755
        self.assertSortAndIterate(
 
756
            {'A': ['ghost'],
 
757
             'B': ['A'],
 
758
            }, 'B',
 
759
            [('B', 0, (2,), False),
 
760
             ('A', 0, (1,), True),
 
761
            ])
 
762
 
 
763
    def test_graph_cycle(self):
 
764
        # merge_sort should fail with a simple error when a graph cycle is
 
765
        # encountered.
 
766
        #
 
767
        # A
 
768
        # |,-.
 
769
        # B  |
 
770
        # |  |
 
771
        # C  ^
 
772
        # |  |
 
773
        # D  |
 
774
        # |'-'
 
775
        # E
 
776
        self.assertRaises(errors.GraphCycleError,
 
777
            self.assertSortAndIterate,
 
778
                {'A': [],
 
779
                 'B': ['D'],
 
780
                 'C': ['B'],
 
781
                 'D': ['C'],
 
782
                 'E': ['D'],
 
783
                },
 
784
                'E',
 
785
                [])
 
786
 
 
787
 
 
788
class TestKnownGraphStableReverseTopoSort(TestCaseWithKnownGraph):
 
789
    """Test the sort order returned by gc_sort."""
 
790
 
 
791
    def assertSorted(self, expected, parent_map):
 
792
        graph = self.make_known_graph(parent_map)
 
793
        value = graph.gc_sort()
 
794
        if expected != value:
 
795
            self.assertEqualDiff(pprint.pformat(expected),
 
796
                                 pprint.pformat(value))
 
797
 
 
798
    def test_empty(self):
 
799
        self.assertSorted([], {})
 
800
 
 
801
    def test_single(self):
 
802
        self.assertSorted(['a'], {'a':()})
 
803
        self.assertSorted([('a',)], {('a',):()})
 
804
        self.assertSorted([('F', 'a')], {('F', 'a'):()})
 
805
 
 
806
    def test_linear(self):
 
807
        self.assertSorted(['c', 'b', 'a'], {'a':(), 'b':('a',), 'c':('b',)})
 
808
        self.assertSorted([('c',), ('b',), ('a',)],
 
809
                          {('a',):(), ('b',): (('a',),), ('c',): (('b',),)})
 
810
        self.assertSorted([('F', 'c'), ('F', 'b'), ('F', 'a')],
 
811
                          {('F', 'a'):(), ('F', 'b'): (('F', 'a'),),
 
812
                           ('F', 'c'): (('F', 'b'),)})
 
813
 
 
814
    def test_mixed_ancestries(self):
 
815
        # Each prefix should be sorted separately
 
816
        self.assertSorted([('F', 'c'), ('F', 'b'), ('F', 'a'),
 
817
                           ('G', 'c'), ('G', 'b'), ('G', 'a'),
 
818
                           ('Q', 'c'), ('Q', 'b'), ('Q', 'a'),
 
819
                          ],
 
820
                          {('F', 'a'):(), ('F', 'b'): (('F', 'a'),),
 
821
                           ('F', 'c'): (('F', 'b'),),
 
822
                           ('G', 'a'):(), ('G', 'b'): (('G', 'a'),),
 
823
                           ('G', 'c'): (('G', 'b'),),
 
824
                           ('Q', 'a'):(), ('Q', 'b'): (('Q', 'a'),),
 
825
                           ('Q', 'c'): (('Q', 'b'),),
 
826
                          })
 
827
 
 
828
    def test_stable_sorting(self):
 
829
        # the sort order should be stable even when extra nodes are added
 
830
        self.assertSorted(['b', 'c', 'a'],
 
831
                          {'a':(), 'b':('a',), 'c':('a',)})
 
832
        self.assertSorted(['b', 'c', 'd', 'a'],
 
833
                          {'a':(), 'b':('a',), 'c':('a',), 'd':('a',)})
 
834
        self.assertSorted(['b', 'c', 'd', 'a'],
 
835
                          {'a':(), 'b':('a',), 'c':('a',), 'd':('a',)})
 
836
        self.assertSorted(['Z', 'b', 'c', 'd', 'a'],
 
837
                          {'a':(), 'b':('a',), 'c':('a',), 'd':('a',),
 
838
                           'Z':('a',)})
 
839
        self.assertSorted(['e', 'b', 'c', 'f', 'Z', 'd', 'a'],
 
840
                          {'a':(), 'b':('a',), 'c':('a',), 'd':('a',),
 
841
                           'Z':('a',),
 
842
                           'e':('b', 'c', 'd'),
 
843
                           'f':('d', 'Z'),
 
844
                           })
 
845
 
 
846
    def test_skip_ghost(self):
 
847
        self.assertSorted(['b', 'c', 'a'],
 
848
                          {'a':(), 'b':('a', 'ghost'), 'c':('a',)})
 
849
 
 
850
    def test_skip_mainline_ghost(self):
 
851
        self.assertSorted(['b', 'c', 'a'],
 
852
                          {'a':(), 'b':('ghost', 'a'), 'c':('a',)})