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']))
264
class TestKnownGraphTopoSort(TestCaseWithKnownGraph):
266
def assertTopoSortOrder(self, ancestry):
267
"""Check topo_sort and iter_topo_order is genuinely topological order.
269
For every child in the graph, check if it comes after all of it's
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
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:
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))
288
def test_topo_sort_empty(self):
289
"""TopoSort empty list"""
290
self.assertTopoSortOrder({})
292
def test_topo_sort_easy(self):
293
"""TopoSort list with one node"""
294
self.assertTopoSortOrder({0: []})
296
def test_topo_sort_cycle(self):
297
"""TopoSort traps graph with cycles"""
298
g = self.make_known_graph({0: [1],
300
self.assertRaises(errors.GraphCycleError, g.topo_sort)
302
def test_topo_sort_cycle_2(self):
303
"""TopoSort traps graph with longer cycle"""
304
g = self.make_known_graph({0: [1],
307
self.assertRaises(errors.GraphCycleError, g.topo_sort)
309
def test_topo_sort_cycle_with_tail(self):
310
"""TopoSort traps graph with longer cycle"""
311
g = self.make_known_graph({0: [1],
316
self.assertRaises(errors.GraphCycleError, g.topo_sort)
318
def test_topo_sort_1(self):
319
"""TopoSort simple nontrivial graph"""
320
self.assertTopoSortOrder({0: [3],
326
def test_topo_sort_partial(self):
327
"""Topological sort with partial ordering.
329
Multiple correct orderings are possible, so test for
330
correctness, not for exact match on the resulting list.
332
self.assertTopoSortOrder({0: [],
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],
348
class TestKnownGraphMergeSort(TestCaseWithKnownGraph):
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)
356
if result_list != value:
357
self.assertEqualDiff(pprint.pformat(result_list),
358
pprint.pformat(value))
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,), [])
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,), [])
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': []},
378
[('id', 0, (1,), True)])
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(
388
[('C', 0, (3,), False),
389
('B', 0, (2,), False),
390
('A', 0, (1,), True),
394
def test_sequence_numbers_increase_with_merges(self):
395
# test that sequence numbers increase across merges
396
self.assertSortAndIterate(
401
[('C', 0, (2,), False),
402
('B', 1, (1,1,1), True),
403
('A', 0, (1,), True),
407
def test_merge_sort_race(self):
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),
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),
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:
467
self.assertSortAndIterate(
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),
489
def test_dotted_revnos_with_simple_merges(self):
494
# D E F 3, 1.1.2, 1.2.1
496
# G H I 4, 1.2.2, 1.3.1
501
self.assertSortAndIterate(
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),
530
# Adding a shortcut from the first revision should not change any of
531
# the existing numbers
532
self.assertSortAndIterate(
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),
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(
574
[('A', 0, (2,), False),
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
593
self.assertSortAndIterate(
594
{'A': ['H', 'B', 'E'],
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),
615
def test_parallel_root_sequence_numbers_increase_with_merges(self):
616
"""When there are parallel roots, check their revnos."""
617
self.assertSortAndIterate(
622
[('C', 0, (2,), False),
623
('B', 1, (0,1,1), True),
624
('A', 0, (1,), True),
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']
638
# merge 2: G: ['D', 'F']
642
# merge 1: D: ['A', 'C']
647
self.assertSortAndIterate(
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),
673
def test_roots_and_sub_branches_versus_ghosts(self):
674
"""Extra roots and their mini branches use the same numbering.
676
All of them use the 0-node numbering.
689
self.assertSortAndIterate(
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),
731
def test_ghost(self):
732
# merge_sort should be able to ignore ghosts
738
self.assertSortAndIterate(
744
[('C', 0, (3,), False),
745
('B', 0, (2,), False),
746
('A', 0, (1,), True),
749
def test_lefthand_ghost(self):
755
self.assertSortAndIterate(
759
[('B', 0, (2,), False),
760
('A', 0, (1,), True),
763
def test_graph_cycle(self):
764
# merge_sort should fail with a simple error when a graph cycle is
776
self.assertRaises(errors.GraphCycleError,
777
self.assertSortAndIterate,
788
class TestKnownGraphStableReverseTopoSort(TestCaseWithKnownGraph):
789
"""Test the sort order returned by gc_sort."""
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))
798
def test_empty(self):
799
self.assertSorted([], {})
801
def test_single(self):
802
self.assertSorted(['a'], {'a':()})
803
self.assertSorted([('a',)], {('a',):()})
804
self.assertSorted([('F', 'a')], {('F', 'a'):()})
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'),)})
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'),
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'),),
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',),
839
self.assertSorted(['e', 'b', 'c', 'f', 'Z', 'd', 'a'],
840
{'a':(), 'b':('a',), 'c':('a',), 'd':('a',),
846
def test_skip_ghost(self):
847
self.assertSorted(['b', 'c', 'a'],
848
{'a':(), 'b':('a', 'ghost'), 'c':('a',)})
850
def test_skip_mainline_ghost(self):
851
self.assertSorted(['b', 'c', 'a'],
852
{'a':(), 'b':('ghost', 'a'), 'c':('a',)})