722
698
instrumented_graph.is_ancestor('rev2a', 'rev2b')
723
699
self.assertTrue('null:' not in instrumented_provider.calls)
725
def test_is_between(self):
726
graph = self.make_graph(ancestry_1)
727
self.assertEqual(True, graph.is_between('null:', 'null:', 'null:'))
728
self.assertEqual(True, graph.is_between('rev1', 'null:', 'rev1'))
729
self.assertEqual(True, graph.is_between('rev1', 'rev1', 'rev4'))
730
self.assertEqual(True, graph.is_between('rev4', 'rev1', 'rev4'))
731
self.assertEqual(True, graph.is_between('rev3', 'rev1', 'rev4'))
732
self.assertEqual(False, graph.is_between('rev4', 'rev1', 'rev3'))
733
self.assertEqual(False, graph.is_between('rev1', 'rev2a', 'rev4'))
734
self.assertEqual(False, graph.is_between('null:', 'rev1', 'rev4'))
736
701
def test_is_ancestor_boundary(self):
737
702
"""Ensure that we avoid searching the whole graph.
739
704
This requires searching through b as a common ancestor, so we
740
705
can identify that e is common.
1121
1082
search = graph._make_breadth_first_searcher(['head'])
1122
1083
self.assertSeenAndResult(expected, search, search.next_with_ghosts)
1124
def test_breadth_first_stop_searching_late(self):
1125
# A client should be able to say 'stop node X' and have it excluded
1126
# from the result even if X was seen in an older iteration of the
1128
graph = self.make_graph({
1131
'child':[NULL_REVISION],
1134
search = graph._make_breadth_first_searcher(['head'])
1136
(set(['head']), (set(['head']), set(['middle']), 1),
1137
['head'], None, None),
1138
(set(['head', 'middle']), (set(['head']), set(['child']), 2),
1139
['head', 'middle'], None, None),
1140
# 'middle' came from the previous iteration, but we don't stop
1141
# searching it until *after* advancing the searcher.
1142
(set(['head', 'middle', 'child']),
1143
(set(['head']), set(['middle', 'child']), 1),
1144
['head'], None, ['middle', 'child']),
1146
self.assertSeenAndResult(expected, search, search.next)
1147
# using next_with_ghosts:
1148
search = graph._make_breadth_first_searcher(['head'])
1149
self.assertSeenAndResult(expected, search, search.next_with_ghosts)
1151
1085
def test_breadth_first_get_result_ghosts_are_excluded(self):
1152
1086
graph = self.make_graph({
1153
1087
'head':['child', 'ghost'],
1408
1342
self.assertMergeOrder(['rev3', 'rev1'], graph, 'rev4', ['rev1', 'rev3'])
1411
class TestFindDescendants(TestGraphBase):
1413
def test_find_descendants_rev1_rev3(self):
1414
graph = self.make_graph(ancestry_1)
1415
descendants = graph.find_descendants('rev1', 'rev3')
1416
self.assertEqual(set(['rev1', 'rev2a', 'rev3']), descendants)
1418
def test_find_descendants_rev1_rev4(self):
1419
graph = self.make_graph(ancestry_1)
1420
descendants = graph.find_descendants('rev1', 'rev4')
1421
self.assertEqual(set(['rev1', 'rev2a', 'rev2b', 'rev3', 'rev4']),
1424
def test_find_descendants_rev2a_rev4(self):
1425
graph = self.make_graph(ancestry_1)
1426
descendants = graph.find_descendants('rev2a', 'rev4')
1427
self.assertEqual(set(['rev2a', 'rev3', 'rev4']), descendants)
1429
class TestFindLefthandMerger(TestGraphBase):
1431
def check_merger(self, result, ancestry, merged, tip):
1432
graph = self.make_graph(ancestry)
1433
self.assertEqual(result, graph.find_lefthand_merger(merged, tip))
1435
def test_find_lefthand_merger_rev2b(self):
1436
self.check_merger('rev4', ancestry_1, 'rev2b', 'rev4')
1438
def test_find_lefthand_merger_rev2a(self):
1439
self.check_merger('rev2a', ancestry_1, 'rev2a', 'rev4')
1441
def test_find_lefthand_merger_rev4(self):
1442
self.check_merger(None, ancestry_1, 'rev4', 'rev2a')
1444
def test_find_lefthand_merger_f(self):
1445
self.check_merger('i', complex_shortcut, 'f', 'm')
1447
def test_find_lefthand_merger_g(self):
1448
self.check_merger('i', complex_shortcut, 'g', 'm')
1450
def test_find_lefthand_merger_h(self):
1451
self.check_merger('n', complex_shortcut, 'h', 'n')
1454
class TestGetChildMap(TestGraphBase):
1456
def test_get_child_map(self):
1457
graph = self.make_graph(ancestry_1)
1458
child_map = graph.get_child_map(['rev4', 'rev3', 'rev2a', 'rev2b'])
1459
self.assertEqual({'rev1': ['rev2a', 'rev2b'],
1466
1345
class TestCachingParentsProvider(tests.TestCase):
1467
"""These tests run with:
1469
self.inst_pp, a recording parents provider with a graph of a->b, and b is a
1471
self.caching_pp, a CachingParentsProvider layered on inst_pp.
1474
1347
def setUp(self):
1475
1348
super(TestCachingParentsProvider, self).setUp()
1511
1385
# only present 1 time.
1512
1386
self.assertEqual(['a', 'b'], sorted(self.inst_pp.calls))
1514
def test_note_missing_key(self):
1515
"""After noting that a key is missing it is cached."""
1516
self.caching_pp.note_missing_key('b')
1517
self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
1518
self.assertEqual([], self.inst_pp.calls)
1519
self.assertEqual(set(['b']), self.caching_pp.missing_keys)
1522
class TestCachingParentsProviderExtras(tests.TestCaseWithTransport):
1523
"""Test the behaviour when parents are provided that were not requested."""
1526
super(TestCachingParentsProviderExtras, self).setUp()
1527
class ExtraParentsProvider(object):
1529
def get_parent_map(self, keys):
1530
return {'rev1': [], 'rev2': ['rev1',]}
1532
self.inst_pp = InstrumentedParentsProvider(ExtraParentsProvider())
1533
self.caching_pp = _mod_graph.CachingParentsProvider(
1534
get_parent_map=self.inst_pp.get_parent_map)
1536
def test_uncached(self):
1537
self.caching_pp.disable_cache()
1538
self.assertEqual({'rev1': []},
1539
self.caching_pp.get_parent_map(['rev1']))
1540
self.assertEqual(['rev1'], self.inst_pp.calls)
1541
self.assertIs(None, self.caching_pp._cache)
1543
def test_cache_initially_empty(self):
1544
self.assertEqual({}, self.caching_pp._cache)
1546
def test_cached(self):
1547
self.assertEqual({'rev1': []},
1548
self.caching_pp.get_parent_map(['rev1']))
1549
self.assertEqual(['rev1'], self.inst_pp.calls)
1550
self.assertEqual({'rev1': [], 'rev2': ['rev1']},
1551
self.caching_pp._cache)
1552
self.assertEqual({'rev1': []},
1553
self.caching_pp.get_parent_map(['rev1']))
1554
self.assertEqual(['rev1'], self.inst_pp.calls)
1556
def test_disable_cache_clears_cache(self):
1557
# Put something in the cache
1558
self.caching_pp.get_parent_map(['rev1'])
1559
self.assertEqual(2, len(self.caching_pp._cache))
1560
self.caching_pp.disable_cache()
1561
self.assertIs(None, self.caching_pp._cache)
1563
def test_enable_cache_raises(self):
1564
e = self.assertRaises(AssertionError, self.caching_pp.enable_cache)
1565
self.assertEqual('Cache enabled when already enabled.', str(e))
1567
def test_cache_misses(self):
1568
self.caching_pp.get_parent_map(['rev3'])
1569
self.caching_pp.get_parent_map(['rev3'])
1570
self.assertEqual(['rev3'], self.inst_pp.calls)
1572
def test_no_cache_misses(self):
1573
self.caching_pp.disable_cache()
1574
self.caching_pp.enable_cache(cache_misses=False)
1575
self.caching_pp.get_parent_map(['rev3'])
1576
self.caching_pp.get_parent_map(['rev3'])
1577
self.assertEqual(['rev3', 'rev3'], self.inst_pp.calls)
1579
def test_cache_extras(self):
1580
self.assertEqual({}, self.caching_pp.get_parent_map(['rev3']))
1581
self.assertEqual({'rev2': ['rev1']},
1582
self.caching_pp.get_parent_map(['rev2']))
1583
self.assertEqual(['rev3'], self.inst_pp.calls)
1586
1389
class TestCollapseLinearRegions(tests.TestCase):
1619
1422
# 2 and 3 cannot be removed because 1 has 2 parents
1620
1423
d = {1:[2, 3], 2:[4], 4:[6], 3:[5], 5:[6], 6:[7], 7:[]}
1621
1424
self.assertCollapsed(d, d)
1624
class TestGraphThunkIdsToKeys(tests.TestCase):
1626
def test_heads(self):
1632
d = {('D',): [('B',), ('C',)], ('C',):[('A',)],
1633
('B',): [('A',)], ('A',): []}
1634
g = _mod_graph.Graph(_mod_graph.DictParentsProvider(d))
1635
graph_thunk = _mod_graph.GraphThunkIdsToKeys(g)
1636
self.assertEqual(['D'], sorted(graph_thunk.heads(['D', 'A'])))
1637
self.assertEqual(['D'], sorted(graph_thunk.heads(['D', 'B'])))
1638
self.assertEqual(['D'], sorted(graph_thunk.heads(['D', 'C'])))
1639
self.assertEqual(['B', 'C'], sorted(graph_thunk.heads(['B', 'C'])))
1641
def test_add_node(self):
1642
d = {('C',):[('A',)], ('B',): [('A',)], ('A',): []}
1643
g = _mod_graph.KnownGraph(d)
1644
graph_thunk = _mod_graph.GraphThunkIdsToKeys(g)
1645
graph_thunk.add_node("D", ["A", "C"])
1646
self.assertEqual(['B', 'D'],
1647
sorted(graph_thunk.heads(['D', 'B', 'A'])))
1649
def test_merge_sort(self):
1650
d = {('C',):[('A',)], ('B',): [('A',)], ('A',): []}
1651
g = _mod_graph.KnownGraph(d)
1652
graph_thunk = _mod_graph.GraphThunkIdsToKeys(g)
1653
graph_thunk.add_node("D", ["A", "C"])
1654
self.assertEqual([('C', 0, (2,), False), ('A', 0, (1,), True)],
1655
[(n.key, n.merge_depth, n.revno, n.end_of_merge)
1656
for n in graph_thunk.merge_sort('C')])
1659
class TestPendingAncestryResultGetKeys(TestCaseWithMemoryTransport):
1660
"""Tests for bzrlib.graph.PendingAncestryResult."""
1662
def test_get_keys(self):
1663
builder = self.make_branch_builder('b')
1664
builder.start_series()
1665
builder.build_snapshot('rev-1', None, [
1666
('add', ('', 'root-id', 'directory', ''))])
1667
builder.build_snapshot('rev-2', ['rev-1'], [])
1668
builder.finish_series()
1669
repo = builder.get_branch().repository
1671
self.addCleanup(repo.unlock)
1672
result = _mod_graph.PendingAncestryResult(['rev-2'], repo)
1673
self.assertEqual(set(['rev-1', 'rev-2']), set(result.get_keys()))
1675
def test_get_keys_excludes_ghosts(self):
1676
builder = self.make_branch_builder('b')
1677
builder.start_series()
1678
builder.build_snapshot('rev-1', None, [
1679
('add', ('', 'root-id', 'directory', ''))])
1680
builder.build_snapshot('rev-2', ['rev-1', 'ghost'], [])
1681
builder.finish_series()
1682
repo = builder.get_branch().repository
1684
self.addCleanup(repo.unlock)
1685
result = _mod_graph.PendingAncestryResult(['rev-2'], repo)
1686
self.assertEqual(sorted(['rev-1', 'rev-2']), sorted(result.get_keys()))
1688
def test_get_keys_excludes_null(self):
1689
# Make a 'graph' with an iter_ancestry that returns NULL_REVISION
1690
# somewhere other than the last element, which can happen in real
1692
class StubGraph(object):
1693
def iter_ancestry(self, keys):
1694
return [(NULL_REVISION, ()), ('foo', (NULL_REVISION,))]
1695
result = _mod_graph.PendingAncestryResult(['rev-3'], None)
1696
result_keys = result._get_keys(StubGraph())
1697
# Only the non-null keys from the ancestry appear.
1698
self.assertEqual(set(['foo']), set(result_keys))
1701
class TestPendingAncestryResultRefine(TestGraphBase):
1703
def test_refine(self):
1704
# Used when pulling from a stacked repository, so test some revisions
1705
# being satisfied from the stacking branch.
1706
g = self.make_graph(
1707
{"tip":["mid"], "mid":["base"], "tag":["base"],
1708
"base":[NULL_REVISION], NULL_REVISION:[]})
1709
result = _mod_graph.PendingAncestryResult(['tip', 'tag'], None)
1710
result = result.refine(set(['tip']), set(['mid']))
1711
self.assertEqual(set(['mid', 'tag']), result.heads)
1712
result = result.refine(set(['mid', 'tag', 'base']),
1713
set([NULL_REVISION]))
1714
self.assertEqual(set([NULL_REVISION]), result.heads)
1715
self.assertTrue(result.is_empty())
1718
class TestSearchResultRefine(TestGraphBase):
1720
def test_refine(self):
1721
# Used when pulling from a stacked repository, so test some revisions
1722
# being satisfied from the stacking branch.
1723
g = self.make_graph(
1724
{"tip":["mid"], "mid":["base"], "tag":["base"],
1725
"base":[NULL_REVISION], NULL_REVISION:[]})
1726
result = _mod_graph.SearchResult(set(['tip', 'tag']),
1727
set([NULL_REVISION]), 4, set(['tip', 'mid', 'tag', 'base']))
1728
result = result.refine(set(['tip']), set(['mid']))
1729
recipe = result.get_recipe()
1730
# We should be starting from tag (original head) and mid (seen ref)
1731
self.assertEqual(set(['mid', 'tag']), recipe[1])
1732
# We should be stopping at NULL (original stop) and tip (seen head)
1733
self.assertEqual(set([NULL_REVISION, 'tip']), recipe[2])
1734
self.assertEqual(3, recipe[3])
1735
result = result.refine(set(['mid', 'tag', 'base']),
1736
set([NULL_REVISION]))
1737
recipe = result.get_recipe()
1738
# We should be starting from nothing (NULL was known as a cut point)
1739
self.assertEqual(set([]), recipe[1])
1740
# We should be stopping at NULL (original stop) and tip (seen head) and
1741
# tag (seen head) and mid(seen mid-point head). We could come back and
1742
# define this as not including mid, for minimal results, but it is
1743
# still 'correct' to include mid, and simpler/easier.
1744
self.assertEqual(set([NULL_REVISION, 'tip', 'tag', 'mid']), recipe[2])
1745
self.assertEqual(0, recipe[3])
1746
self.assertTrue(result.is_empty())