1379
1312
self.assertFindDistance(6, graph, 'g', [('i', 8)])
1382
class TestFindMergeOrder(TestGraphBase):
1384
def assertMergeOrder(self, expected, graph, tip, base_revisions):
1385
self.assertEqual(expected, graph.find_merge_order(tip, base_revisions))
1387
def test_parents(self):
1388
graph = self.make_graph(ancestry_1)
1389
self.assertMergeOrder(['rev3', 'rev2b'], graph, 'rev4',
1391
self.assertMergeOrder(['rev3', 'rev2b'], graph, 'rev4',
1394
def test_ancestors(self):
1395
graph = self.make_graph(ancestry_1)
1396
self.assertMergeOrder(['rev1', 'rev2b'], graph, 'rev4',
1398
self.assertMergeOrder(['rev1', 'rev2b'], graph, 'rev4',
1401
def test_shortcut_one_ancestor(self):
1402
# When we have enough info, we can stop searching
1403
graph = self.make_breaking_graph(ancestry_1, ['rev3', 'rev2b', 'rev4'])
1404
# Single ancestors shortcut right away
1405
self.assertMergeOrder(['rev3'], graph, 'rev4', ['rev3'])
1407
def test_shortcut_after_one_ancestor(self):
1408
graph = self.make_breaking_graph(ancestry_1, ['rev2a', 'rev2b'])
1409
self.assertMergeOrder(['rev3', 'rev1'], graph, 'rev4', ['rev1', 'rev3'])
1412
class TestFindDescendants(TestGraphBase):
1414
def test_find_descendants_rev1_rev3(self):
1415
graph = self.make_graph(ancestry_1)
1416
descendants = graph.find_descendants('rev1', 'rev3')
1417
self.assertEqual(set(['rev1', 'rev2a', 'rev3']), descendants)
1419
def test_find_descendants_rev1_rev4(self):
1420
graph = self.make_graph(ancestry_1)
1421
descendants = graph.find_descendants('rev1', 'rev4')
1422
self.assertEqual(set(['rev1', 'rev2a', 'rev2b', 'rev3', 'rev4']),
1425
def test_find_descendants_rev2a_rev4(self):
1426
graph = self.make_graph(ancestry_1)
1427
descendants = graph.find_descendants('rev2a', 'rev4')
1428
self.assertEqual(set(['rev2a', 'rev3', 'rev4']), descendants)
1430
class TestFindLefthandMerger(TestGraphBase):
1432
def check_merger(self, result, ancestry, merged, tip):
1433
graph = self.make_graph(ancestry)
1434
self.assertEqual(result, graph.find_lefthand_merger(merged, tip))
1436
def test_find_lefthand_merger_rev2b(self):
1437
self.check_merger('rev4', ancestry_1, 'rev2b', 'rev4')
1439
def test_find_lefthand_merger_rev2a(self):
1440
self.check_merger('rev2a', ancestry_1, 'rev2a', 'rev4')
1442
def test_find_lefthand_merger_rev4(self):
1443
self.check_merger(None, ancestry_1, 'rev4', 'rev2a')
1445
def test_find_lefthand_merger_f(self):
1446
self.check_merger('i', complex_shortcut, 'f', 'm')
1448
def test_find_lefthand_merger_g(self):
1449
self.check_merger('i', complex_shortcut, 'g', 'm')
1451
def test_find_lefthand_merger_h(self):
1452
self.check_merger('n', complex_shortcut, 'h', 'n')
1455
class TestGetChildMap(TestGraphBase):
1457
def test_get_child_map(self):
1458
graph = self.make_graph(ancestry_1)
1459
child_map = graph.get_child_map(['rev4', 'rev3', 'rev2a', 'rev2b'])
1460
self.assertEqual({'rev1': ['rev2a', 'rev2b'],
1467
1315
class TestCachingParentsProvider(tests.TestCase):
1468
"""These tests run with:
1470
self.inst_pp, a recording parents provider with a graph of a->b, and b is a
1472
self.caching_pp, a CachingParentsProvider layered on inst_pp.
1475
1317
def setUp(self):
1476
1318
super(TestCachingParentsProvider, self).setUp()
1511
1354
# Use sorted because we don't care about the order, just that each is
1512
1355
# only present 1 time.
1513
1356
self.assertEqual(['a', 'b'], sorted(self.inst_pp.calls))
1515
def test_note_missing_key(self):
1516
"""After noting that a key is missing it is cached."""
1517
self.caching_pp.note_missing_key('b')
1518
self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
1519
self.assertEqual([], self.inst_pp.calls)
1520
self.assertEqual(set(['b']), self.caching_pp.missing_keys)
1523
class TestCachingParentsProviderExtras(tests.TestCaseWithTransport):
1524
"""Test the behaviour when parents are provided that were not requested."""
1527
super(TestCachingParentsProviderExtras, self).setUp()
1528
class ExtraParentsProvider(object):
1530
def get_parent_map(self, keys):
1531
return {'rev1': [], 'rev2': ['rev1',]}
1533
self.inst_pp = InstrumentedParentsProvider(ExtraParentsProvider())
1534
self.caching_pp = _mod_graph.CachingParentsProvider(
1535
get_parent_map=self.inst_pp.get_parent_map)
1537
def test_uncached(self):
1538
self.caching_pp.disable_cache()
1539
self.assertEqual({'rev1': []},
1540
self.caching_pp.get_parent_map(['rev1']))
1541
self.assertEqual(['rev1'], self.inst_pp.calls)
1542
self.assertIs(None, self.caching_pp._cache)
1544
def test_cache_initially_empty(self):
1545
self.assertEqual({}, self.caching_pp._cache)
1547
def test_cached(self):
1548
self.assertEqual({'rev1': []},
1549
self.caching_pp.get_parent_map(['rev1']))
1550
self.assertEqual(['rev1'], self.inst_pp.calls)
1551
self.assertEqual({'rev1': [], 'rev2': ['rev1']},
1552
self.caching_pp._cache)
1553
self.assertEqual({'rev1': []},
1554
self.caching_pp.get_parent_map(['rev1']))
1555
self.assertEqual(['rev1'], self.inst_pp.calls)
1557
def test_disable_cache_clears_cache(self):
1558
# Put something in the cache
1559
self.caching_pp.get_parent_map(['rev1'])
1560
self.assertEqual(2, len(self.caching_pp._cache))
1561
self.caching_pp.disable_cache()
1562
self.assertIs(None, self.caching_pp._cache)
1564
def test_enable_cache_raises(self):
1565
e = self.assertRaises(AssertionError, self.caching_pp.enable_cache)
1566
self.assertEqual('Cache enabled when already enabled.', str(e))
1568
def test_cache_misses(self):
1569
self.caching_pp.get_parent_map(['rev3'])
1570
self.caching_pp.get_parent_map(['rev3'])
1571
self.assertEqual(['rev3'], self.inst_pp.calls)
1573
def test_no_cache_misses(self):
1574
self.caching_pp.disable_cache()
1575
self.caching_pp.enable_cache(cache_misses=False)
1576
self.caching_pp.get_parent_map(['rev3'])
1577
self.caching_pp.get_parent_map(['rev3'])
1578
self.assertEqual(['rev3', 'rev3'], self.inst_pp.calls)
1580
def test_cache_extras(self):
1581
self.assertEqual({}, self.caching_pp.get_parent_map(['rev3']))
1582
self.assertEqual({'rev2': ['rev1']},
1583
self.caching_pp.get_parent_map(['rev2']))
1584
self.assertEqual(['rev3'], self.inst_pp.calls)
1587
class TestCollapseLinearRegions(tests.TestCase):
1589
def assertCollapsed(self, collapsed, original):
1590
self.assertEqual(collapsed,
1591
_mod_graph.collapse_linear_regions(original))
1593
def test_collapse_nothing(self):
1594
d = {1:[2, 3], 2:[], 3:[]}
1595
self.assertCollapsed(d, d)
1596
d = {1:[2], 2:[3, 4], 3:[5], 4:[5], 5:[]}
1597
self.assertCollapsed(d, d)
1599
def test_collapse_chain(self):
1600
# Any time we have a linear chain, we should be able to collapse
1601
d = {1:[2], 2:[3], 3:[4], 4:[5], 5:[]}
1602
self.assertCollapsed({1:[5], 5:[]}, d)
1603
d = {5:[4], 4:[3], 3:[2], 2:[1], 1:[]}
1604
self.assertCollapsed({5:[1], 1:[]}, d)
1605
d = {5:[3], 3:[4], 4:[1], 1:[2], 2:[]}
1606
self.assertCollapsed({5:[2], 2:[]}, d)
1608
def test_collapse_with_multiple_children(self):
1619
# 4 and 5 cannot be removed because 6 has 2 children
1620
# 2 and 3 cannot be removed because 1 has 2 parents
1621
d = {1:[2, 3], 2:[4], 4:[6], 3:[5], 5:[6], 6:[7], 7:[]}
1622
self.assertCollapsed(d, d)
1625
class TestGraphThunkIdsToKeys(tests.TestCase):
1627
def test_heads(self):
1633
d = {('D',): [('B',), ('C',)], ('C',):[('A',)],
1634
('B',): [('A',)], ('A',): []}
1635
g = _mod_graph.Graph(_mod_graph.DictParentsProvider(d))
1636
graph_thunk = _mod_graph.GraphThunkIdsToKeys(g)
1637
self.assertEqual(['D'], sorted(graph_thunk.heads(['D', 'A'])))
1638
self.assertEqual(['D'], sorted(graph_thunk.heads(['D', 'B'])))
1639
self.assertEqual(['D'], sorted(graph_thunk.heads(['D', 'C'])))
1640
self.assertEqual(['B', 'C'], sorted(graph_thunk.heads(['B', 'C'])))
1642
def test_add_node(self):
1643
d = {('C',):[('A',)], ('B',): [('A',)], ('A',): []}
1644
g = _mod_graph.KnownGraph(d)
1645
graph_thunk = _mod_graph.GraphThunkIdsToKeys(g)
1646
graph_thunk.add_node("D", ["A", "C"])
1647
self.assertEqual(['B', 'D'],
1648
sorted(graph_thunk.heads(['D', 'B', 'A'])))
1651
class TestPendingAncestryResultGetKeys(TestCaseWithMemoryTransport):
1652
"""Tests for bzrlib.graph.PendingAncestryResult."""
1654
def test_get_keys(self):
1655
builder = self.make_branch_builder('b')
1656
builder.start_series()
1657
builder.build_snapshot('rev-1', None, [
1658
('add', ('', 'root-id', 'directory', ''))])
1659
builder.build_snapshot('rev-2', ['rev-1'], [])
1660
builder.finish_series()
1661
repo = builder.get_branch().repository
1663
self.addCleanup(repo.unlock)
1664
result = _mod_graph.PendingAncestryResult(['rev-2'], repo)
1665
self.assertEqual(set(['rev-1', 'rev-2']), set(result.get_keys()))
1667
def test_get_keys_excludes_ghosts(self):
1668
builder = self.make_branch_builder('b')
1669
builder.start_series()
1670
builder.build_snapshot('rev-1', None, [
1671
('add', ('', 'root-id', 'directory', ''))])
1672
builder.build_snapshot('rev-2', ['rev-1', 'ghost'], [])
1673
builder.finish_series()
1674
repo = builder.get_branch().repository
1676
self.addCleanup(repo.unlock)
1677
result = _mod_graph.PendingAncestryResult(['rev-2'], repo)
1678
self.assertEqual(sorted(['rev-1', 'rev-2']), sorted(result.get_keys()))
1680
def test_get_keys_excludes_null(self):
1681
# Make a 'graph' with an iter_ancestry that returns NULL_REVISION
1682
# somewhere other than the last element, which can happen in real
1684
class StubGraph(object):
1685
def iter_ancestry(self, keys):
1686
return [(NULL_REVISION, ()), ('foo', (NULL_REVISION,))]
1687
result = _mod_graph.PendingAncestryResult(['rev-3'], None)
1688
result_keys = result._get_keys(StubGraph())
1689
# Only the non-null keys from the ancestry appear.
1690
self.assertEqual(set(['foo']), set(result_keys))
1693
class TestPendingAncestryResultRefine(TestGraphBase):
1695
def test_refine(self):
1696
# Used when pulling from a stacked repository, so test some revisions
1697
# being satisfied from the stacking branch.
1698
g = self.make_graph(
1699
{"tip":["mid"], "mid":["base"], "tag":["base"],
1700
"base":[NULL_REVISION], NULL_REVISION:[]})
1701
result = _mod_graph.PendingAncestryResult(['tip', 'tag'], None)
1702
result = result.refine(set(['tip']), set(['mid']))
1703
self.assertEqual(set(['mid', 'tag']), result.heads)
1704
result = result.refine(set(['mid', 'tag', 'base']),
1705
set([NULL_REVISION]))
1706
self.assertEqual(set([NULL_REVISION]), result.heads)
1707
self.assertTrue(result.is_empty())
1710
class TestSearchResultRefine(TestGraphBase):
1712
def test_refine(self):
1713
# Used when pulling from a stacked repository, so test some revisions
1714
# being satisfied from the stacking branch.
1715
g = self.make_graph(
1716
{"tip":["mid"], "mid":["base"], "tag":["base"],
1717
"base":[NULL_REVISION], NULL_REVISION:[]})
1718
result = _mod_graph.SearchResult(set(['tip', 'tag']),
1719
set([NULL_REVISION]), 4, set(['tip', 'mid', 'tag', 'base']))
1720
result = result.refine(set(['tip']), set(['mid']))
1721
recipe = result.get_recipe()
1722
# We should be starting from tag (original head) and mid (seen ref)
1723
self.assertEqual(set(['mid', 'tag']), recipe[1])
1724
# We should be stopping at NULL (original stop) and tip (seen head)
1725
self.assertEqual(set([NULL_REVISION, 'tip']), recipe[2])
1726
self.assertEqual(3, recipe[3])
1727
result = result.refine(set(['mid', 'tag', 'base']),
1728
set([NULL_REVISION]))
1729
recipe = result.get_recipe()
1730
# We should be starting from nothing (NULL was known as a cut point)
1731
self.assertEqual(set([]), recipe[1])
1732
# We should be stopping at NULL (original stop) and tip (seen head) and
1733
# tag (seen head) and mid(seen mid-point head). We could come back and
1734
# define this as not including mid, for minimal results, but it is
1735
# still 'correct' to include mid, and simpler/easier.
1736
self.assertEqual(set([NULL_REVISION, 'tip', 'tag', 'mid']), recipe[2])
1737
self.assertEqual(0, recipe[3])
1738
self.assertTrue(result.is_empty())