547
374
graph = self.make_graph({'c': ['b', 'd'], 'd': ['e'], 'b': ['a'],
548
375
'a': [NULL_REVISION], 'e': [NULL_REVISION]})
549
self.assertEqual(set(['c']), graph.heads(['a', 'c', 'e']))
551
def test_heads_null(self):
552
graph = self.make_graph(ancestry_1)
553
self.assertEqual(set(['null:']), graph.heads(['null:']))
554
self.assertEqual(set(['rev1']), graph.heads(['null:', 'rev1']))
555
self.assertEqual(set(['rev1']), graph.heads(['rev1', 'null:']))
556
self.assertEqual(set(['rev1']), graph.heads(set(['rev1', 'null:'])))
557
self.assertEqual(set(['rev1']), graph.heads(('rev1', 'null:')))
559
def test_heads_one(self):
560
# A single node will always be a head
561
graph = self.make_graph(ancestry_1)
562
self.assertEqual(set(['null:']), graph.heads(['null:']))
563
self.assertEqual(set(['rev1']), graph.heads(['rev1']))
564
self.assertEqual(set(['rev2a']), graph.heads(['rev2a']))
565
self.assertEqual(set(['rev2b']), graph.heads(['rev2b']))
566
self.assertEqual(set(['rev3']), graph.heads(['rev3']))
567
self.assertEqual(set(['rev4']), graph.heads(['rev4']))
569
def test_heads_single(self):
570
graph = self.make_graph(ancestry_1)
571
self.assertEqual(set(['rev4']), graph.heads(['null:', 'rev4']))
572
self.assertEqual(set(['rev2a']), graph.heads(['rev1', 'rev2a']))
573
self.assertEqual(set(['rev2b']), graph.heads(['rev1', 'rev2b']))
574
self.assertEqual(set(['rev3']), graph.heads(['rev1', 'rev3']))
575
self.assertEqual(set(['rev4']), graph.heads(['rev1', 'rev4']))
576
self.assertEqual(set(['rev4']), graph.heads(['rev2a', 'rev4']))
577
self.assertEqual(set(['rev4']), graph.heads(['rev2b', 'rev4']))
578
self.assertEqual(set(['rev4']), graph.heads(['rev3', 'rev4']))
580
def test_heads_two_heads(self):
581
graph = self.make_graph(ancestry_1)
582
self.assertEqual(set(['rev2a', 'rev2b']),
583
graph.heads(['rev2a', 'rev2b']))
584
self.assertEqual(set(['rev3', 'rev2b']),
585
graph.heads(['rev3', 'rev2b']))
587
def test_heads_criss_cross(self):
588
graph = self.make_graph(criss_cross)
589
self.assertEqual(set(['rev2a']),
590
graph.heads(['rev2a', 'rev1']))
591
self.assertEqual(set(['rev2b']),
592
graph.heads(['rev2b', 'rev1']))
593
self.assertEqual(set(['rev3a']),
594
graph.heads(['rev3a', 'rev1']))
595
self.assertEqual(set(['rev3b']),
596
graph.heads(['rev3b', 'rev1']))
597
self.assertEqual(set(['rev2a', 'rev2b']),
598
graph.heads(['rev2a', 'rev2b']))
599
self.assertEqual(set(['rev3a']),
600
graph.heads(['rev3a', 'rev2a']))
601
self.assertEqual(set(['rev3a']),
602
graph.heads(['rev3a', 'rev2b']))
603
self.assertEqual(set(['rev3a']),
604
graph.heads(['rev3a', 'rev2a', 'rev2b']))
605
self.assertEqual(set(['rev3b']),
606
graph.heads(['rev3b', 'rev2a']))
607
self.assertEqual(set(['rev3b']),
608
graph.heads(['rev3b', 'rev2b']))
609
self.assertEqual(set(['rev3b']),
610
graph.heads(['rev3b', 'rev2a', 'rev2b']))
611
self.assertEqual(set(['rev3a', 'rev3b']),
612
graph.heads(['rev3a', 'rev3b']))
613
self.assertEqual(set(['rev3a', 'rev3b']),
614
graph.heads(['rev3a', 'rev3b', 'rev2a', 'rev2b']))
616
def test_heads_shortcut(self):
617
graph = self.make_graph(history_shortcut)
619
self.assertEqual(set(['rev2a', 'rev2b', 'rev2c']),
620
graph.heads(['rev2a', 'rev2b', 'rev2c']))
621
self.assertEqual(set(['rev3a', 'rev3b']),
622
graph.heads(['rev3a', 'rev3b']))
623
self.assertEqual(set(['rev3a', 'rev3b']),
624
graph.heads(['rev2a', 'rev3a', 'rev3b']))
625
self.assertEqual(set(['rev2a', 'rev3b']),
626
graph.heads(['rev2a', 'rev3b']))
627
self.assertEqual(set(['rev2c', 'rev3a']),
628
graph.heads(['rev2c', 'rev3a']))
630
def _run_heads_break_deeper(self, graph_dict, search):
631
"""Run heads on a graph-as-a-dict.
633
If the search asks for the parents of 'deeper' the test will fail.
637
def get_parent_map(keys):
641
self.fail('key deeper was accessed')
642
result[key] = graph_dict[key]
645
an_obj.get_parent_map = get_parent_map
646
graph = _mod_graph.Graph(an_obj)
647
return graph.heads(search)
649
def test_heads_limits_search(self):
650
# test that a heads query does not search all of history
656
self.assertEqual(set(['left', 'right']),
657
self._run_heads_break_deeper(graph_dict, ['left', 'right']))
659
def test_heads_limits_search_assymetric(self):
660
# test that a heads query does not search all of history
663
'midleft':['common'],
665
'common':['aftercommon'],
666
'aftercommon':['deeper'],
668
self.assertEqual(set(['left', 'right']),
669
self._run_heads_break_deeper(graph_dict, ['left', 'right']))
671
def test_heads_limits_search_common_search_must_continue(self):
672
# test that common nodes are still queried, preventing
673
# all-the-way-to-origin behaviour in the following graph:
675
'h1':['shortcut', 'common1'],
677
'shortcut':['common2'],
678
'common1':['common2'],
679
'common2':['deeper'],
681
self.assertEqual(set(['h1', 'h2']),
682
self._run_heads_break_deeper(graph_dict, ['h1', 'h2']))
684
def test_breadth_first_search_start_ghosts(self):
685
graph = self.make_graph({})
686
# with_ghosts reports the ghosts
687
search = graph._make_breadth_first_searcher(['a-ghost'])
688
self.assertEqual((set(), set(['a-ghost'])), search.next_with_ghosts())
689
self.assertRaises(StopIteration, search.next_with_ghosts)
691
search = graph._make_breadth_first_searcher(['a-ghost'])
692
self.assertEqual(set(['a-ghost']), search.next())
693
self.assertRaises(StopIteration, search.next)
695
def test_breadth_first_search_deep_ghosts(self):
696
graph = self.make_graph({
698
'present':['child', 'ghost'],
701
# with_ghosts reports the ghosts
702
search = graph._make_breadth_first_searcher(['head'])
703
self.assertEqual((set(['head']), set()), search.next_with_ghosts())
704
self.assertEqual((set(['present']), set()), search.next_with_ghosts())
705
self.assertEqual((set(['child']), set(['ghost'])),
706
search.next_with_ghosts())
707
self.assertRaises(StopIteration, search.next_with_ghosts)
709
search = graph._make_breadth_first_searcher(['head'])
710
self.assertEqual(set(['head']), search.next())
711
self.assertEqual(set(['present']), search.next())
712
self.assertEqual(set(['child', 'ghost']),
714
self.assertRaises(StopIteration, search.next)
716
def test_breadth_first_search_change_next_to_next_with_ghosts(self):
717
# To make the API robust, we allow calling both next() and
718
# next_with_ghosts() on the same searcher.
719
graph = self.make_graph({
721
'present':['child', 'ghost'],
724
# start with next_with_ghosts
725
search = graph._make_breadth_first_searcher(['head'])
726
self.assertEqual((set(['head']), set()), search.next_with_ghosts())
727
self.assertEqual(set(['present']), search.next())
728
self.assertEqual((set(['child']), set(['ghost'])),
729
search.next_with_ghosts())
730
self.assertRaises(StopIteration, search.next)
732
search = graph._make_breadth_first_searcher(['head'])
733
self.assertEqual(set(['head']), search.next())
734
self.assertEqual((set(['present']), set()), search.next_with_ghosts())
735
self.assertEqual(set(['child', 'ghost']),
737
self.assertRaises(StopIteration, search.next_with_ghosts)
739
def test_breadth_first_change_search(self):
740
# Changing the search should work with both next and next_with_ghosts.
741
graph = self.make_graph({
743
'present':['stopped'],
747
search = graph._make_breadth_first_searcher(['head'])
748
self.assertEqual((set(['head']), set()), search.next_with_ghosts())
749
self.assertEqual((set(['present']), set()), search.next_with_ghosts())
750
self.assertEqual(set(['present']),
751
search.stop_searching_any(['present']))
752
self.assertEqual((set(['other']), set(['other_ghost'])),
753
search.start_searching(['other', 'other_ghost']))
754
self.assertEqual((set(['other_2']), set()), search.next_with_ghosts())
755
self.assertRaises(StopIteration, search.next_with_ghosts)
757
search = graph._make_breadth_first_searcher(['head'])
758
self.assertEqual(set(['head']), search.next())
759
self.assertEqual(set(['present']), search.next())
760
self.assertEqual(set(['present']),
761
search.stop_searching_any(['present']))
762
search.start_searching(['other', 'other_ghost'])
763
self.assertEqual(set(['other_2']), search.next())
764
self.assertRaises(StopIteration, search.next)
766
def assertSeenAndResult(self, instructions, search, next):
767
"""Check the results of .seen and get_result() for a seach.
769
:param instructions: A list of tuples:
770
(seen, recipe, included_keys, starts, stops).
771
seen, recipe and included_keys are results to check on the search
772
and the searches get_result(). starts and stops are parameters to
773
pass to start_searching and stop_searching_any during each
774
iteration, if they are not None.
775
:param search: The search to use.
776
:param next: A callable to advance the search.
778
for seen, recipe, included_keys, starts, stops in instructions:
780
if starts is not None:
781
search.start_searching(starts)
782
if stops is not None:
783
search.stop_searching_any(stops)
784
result = search.get_result()
785
self.assertEqual(recipe, result.get_recipe())
786
self.assertEqual(set(included_keys), result.get_keys())
787
self.assertEqual(seen, search.seen)
789
def test_breadth_first_get_result_excludes_current_pending(self):
790
graph = self.make_graph({
792
'child':[NULL_REVISION],
795
search = graph._make_breadth_first_searcher(['head'])
796
# At the start, nothing has been seen, to its all excluded:
797
result = search.get_result()
798
self.assertEqual((set(['head']), set(['head']), 0),
800
self.assertEqual(set(), result.get_keys())
801
self.assertEqual(set(), search.seen)
804
(set(['head']), (set(['head']), set(['child']), 1),
805
['head'], None, None),
806
(set(['head', 'child']), (set(['head']), set([NULL_REVISION]), 2),
807
['head', 'child'], None, None),
808
(set(['head', 'child', NULL_REVISION]), (set(['head']), set(), 3),
809
['head', 'child', NULL_REVISION], None, None),
811
self.assertSeenAndResult(expected, search, search.next)
812
# using next_with_ghosts:
813
search = graph._make_breadth_first_searcher(['head'])
814
self.assertSeenAndResult(expected, search, search.next_with_ghosts)
816
def test_breadth_first_get_result_starts_stops(self):
817
graph = self.make_graph({
819
'child':[NULL_REVISION],
820
'otherhead':['otherchild'],
821
'otherchild':['excluded'],
822
'excluded':[NULL_REVISION],
825
search = graph._make_breadth_first_searcher([])
826
# Starting with nothing and adding a search works:
827
search.start_searching(['head'])
828
# head has been seen:
829
result = search.get_result()
830
self.assertEqual((set(['head']), set(['child']), 1),
832
self.assertEqual(set(['head']), result.get_keys())
833
self.assertEqual(set(['head']), search.seen)
836
# stop at child, and start a new search at otherhead:
837
# - otherhead counts as seen immediately when start_searching is
839
(set(['head', 'child', 'otherhead']),
840
(set(['head', 'otherhead']), set(['child', 'otherchild']), 2),
841
['head', 'otherhead'], ['otherhead'], ['child']),
842
(set(['head', 'child', 'otherhead', 'otherchild']),
843
(set(['head', 'otherhead']), set(['child', 'excluded']), 3),
844
['head', 'otherhead', 'otherchild'], None, None),
845
# stop searching excluded now
846
(set(['head', 'child', 'otherhead', 'otherchild', 'excluded']),
847
(set(['head', 'otherhead']), set(['child', 'excluded']), 3),
848
['head', 'otherhead', 'otherchild'], None, ['excluded']),
850
self.assertSeenAndResult(expected, search, search.next)
851
# using next_with_ghosts:
852
search = graph._make_breadth_first_searcher([])
853
search.start_searching(['head'])
854
self.assertSeenAndResult(expected, search, search.next_with_ghosts)
856
def test_breadth_first_stop_searching_not_queried(self):
857
# A client should be able to say 'stop node X' even if X has not been
858
# returned to the client.
859
graph = self.make_graph({
860
'head':['child', 'ghost1'],
861
'child':[NULL_REVISION],
864
search = graph._make_breadth_first_searcher(['head'])
866
# NULL_REVISION and ghost1 have not been returned
867
(set(['head']), (set(['head']), set(['child', 'ghost1']), 1),
868
['head'], None, [NULL_REVISION, 'ghost1']),
869
# ghost1 has been returned, NULL_REVISION is to be returned in the
871
(set(['head', 'child', 'ghost1']),
872
(set(['head']), set(['ghost1', NULL_REVISION]), 2),
873
['head', 'child'], None, [NULL_REVISION, 'ghost1']),
875
self.assertSeenAndResult(expected, search, search.next)
876
# using next_with_ghosts:
877
search = graph._make_breadth_first_searcher(['head'])
878
self.assertSeenAndResult(expected, search, search.next_with_ghosts)
880
def test_breadth_first_get_result_ghosts_are_excluded(self):
881
graph = self.make_graph({
882
'head':['child', 'ghost'],
883
'child':[NULL_REVISION],
886
search = graph._make_breadth_first_searcher(['head'])
890
(set(['head']), set(['ghost', 'child']), 1),
891
['head'], None, None),
892
(set(['head', 'child', 'ghost']),
893
(set(['head']), set([NULL_REVISION, 'ghost']), 2),
894
['head', 'child'], None, None),
896
self.assertSeenAndResult(expected, search, search.next)
897
# using next_with_ghosts:
898
search = graph._make_breadth_first_searcher(['head'])
899
self.assertSeenAndResult(expected, search, search.next_with_ghosts)
901
def test_breadth_first_get_result_starting_a_ghost_ghost_is_excluded(self):
902
graph = self.make_graph({
904
'child':[NULL_REVISION],
907
search = graph._make_breadth_first_searcher(['head'])
910
(set(['head', 'ghost']),
911
(set(['head', 'ghost']), set(['child', 'ghost']), 1),
912
['head'], ['ghost'], None),
913
(set(['head', 'child', 'ghost']),
914
(set(['head', 'ghost']), set([NULL_REVISION, 'ghost']), 2),
915
['head', 'child'], None, None),
917
self.assertSeenAndResult(expected, search, search.next)
918
# using next_with_ghosts:
919
search = graph._make_breadth_first_searcher(['head'])
920
self.assertSeenAndResult(expected, search, search.next_with_ghosts)
922
def test_breadth_first_revision_count_includes_NULL_REVISION(self):
923
graph = self.make_graph({
924
'head':[NULL_REVISION],
927
search = graph._make_breadth_first_searcher(['head'])
931
(set(['head']), set([NULL_REVISION]), 1),
932
['head'], None, None),
933
(set(['head', NULL_REVISION]),
934
(set(['head']), set([]), 2),
935
['head', NULL_REVISION], None, None),
937
self.assertSeenAndResult(expected, search, search.next)
938
# using next_with_ghosts:
939
search = graph._make_breadth_first_searcher(['head'])
940
self.assertSeenAndResult(expected, search, search.next_with_ghosts)
942
def test_breadth_first_search_get_result_after_StopIteration(self):
943
# StopIteration should not invalid anything..
944
graph = self.make_graph({
945
'head':[NULL_REVISION],
948
search = graph._make_breadth_first_searcher(['head'])
952
(set(['head']), set([NULL_REVISION]), 1),
953
['head'], None, None),
954
(set(['head', 'ghost', NULL_REVISION]),
955
(set(['head', 'ghost']), set(['ghost']), 2),
956
['head', NULL_REVISION], ['ghost'], None),
958
self.assertSeenAndResult(expected, search, search.next)
959
self.assertRaises(StopIteration, search.next)
960
self.assertEqual(set(['head', 'ghost', NULL_REVISION]), search.seen)
961
result = search.get_result()
962
self.assertEqual((set(['ghost', 'head']), set(['ghost']), 2),
964
self.assertEqual(set(['head', NULL_REVISION]), result.get_keys())
965
# using next_with_ghosts:
966
search = graph._make_breadth_first_searcher(['head'])
967
self.assertSeenAndResult(expected, search, search.next_with_ghosts)
968
self.assertRaises(StopIteration, search.next)
969
self.assertEqual(set(['head', 'ghost', NULL_REVISION]), search.seen)
970
result = search.get_result()
971
self.assertEqual((set(['ghost', 'head']), set(['ghost']), 2),
973
self.assertEqual(set(['head', NULL_REVISION]), result.get_keys())
976
class TestCachingParentsProvider(tests.TestCase):
979
super(TestCachingParentsProvider, self).setUp()
980
dict_pp = _mod_graph.DictParentsProvider({'a':('b',)})
981
self.inst_pp = InstrumentedParentsProvider(dict_pp)
982
self.caching_pp = _mod_graph.CachingParentsProvider(self.inst_pp)
984
def test_get_parent_map(self):
985
"""Requesting the same revision should be returned from cache"""
986
self.assertEqual({}, self.caching_pp._cache)
987
self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
988
self.assertEqual(['a'], self.inst_pp.calls)
989
self.assertEqual({'a':('b',)}, self.caching_pp.get_parent_map(['a']))
990
# No new call, as it should have been returned from the cache
991
self.assertEqual(['a'], self.inst_pp.calls)
992
self.assertEqual({'a':('b',)}, self.caching_pp._cache)
994
def test_get_parent_map_not_present(self):
995
"""The cache should also track when a revision doesn't exist"""
996
self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
997
self.assertEqual(['b'], self.inst_pp.calls)
998
self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
1000
self.assertEqual(['b'], self.inst_pp.calls)
1001
self.assertEqual({'b':None}, self.caching_pp._cache)
1003
def test_get_parent_map_mixed(self):
1004
"""Anything that can be returned from cache, should be"""
1005
self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
1006
self.assertEqual(['b'], self.inst_pp.calls)
1007
self.assertEqual({'a':('b',)},
1008
self.caching_pp.get_parent_map(['a', 'b']))
1009
self.assertEqual(['b', 'a'], self.inst_pp.calls)
1011
def test_get_parent_map_repeated(self):
1012
"""Asking for the same parent 2x will only forward 1 request."""
1013
self.assertEqual({'a':('b',)},
1014
self.caching_pp.get_parent_map(['b', 'a', 'b']))
1015
# Use sorted because we don't care about the order, just that each is
1016
# only present 1 time.
1017
self.assertEqual(['a', 'b'], sorted(self.inst_pp.calls))
376
self.assertEqual(['c'], graph._filter_candidate_lca(['a', 'c', 'e']))