192
complex_shortcut = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'],
193
'e':['d'], 'f':['d'], 'g':['f'], 'h':['f'],
194
'i':['e', 'g'], 'j':['g'], 'k':['j'],
195
'l':['k'], 'm':['i', 'l'], 'n':['l', 'h']}
235
complex_shortcut2 = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'],
236
'e':['d'], 'f':['e'], 'g':['f'], 'h':['d'], 'i':['g'],
237
'j':['h'], 'k':['h', 'i'], 'l':['k'], 'm':['l'], 'n':['m'],
238
'o':['n'], 'p':['o'], 'q':['p'], 'r':['q'], 's':['r'],
239
't':['i', 's'], 'u':['s', 'j'],
195
complex_shortcut = {'d':[NULL_REVISION],
196
'x':['d'], 'y':['x'],
197
'e':['y'], 'f':['d'], 'g':['f', 'i'], 'h':['f'],
198
'i':['e'], 'j':['g'], 'k':['j'],
199
'l':['k'], 'm':['i', 's'], 'n':['s', 'h'],
200
'o':['l'], 'p':['o'], 'q':['p'],
201
'r':['q'], 's':['r'],
242
# Graph where different walkers will race to find the common and uncommon
285
# x is found to be common right away, but is the start of a long series of
287
# o is actually common, but the i-j shortcut makes it look like it is actually
288
# unique to j at first, you have to traverse all of x->o to find it.
289
# q,m gives the walker from j a common point to stop searching, as does p,f.
290
# k-n exists so that the second pass still has nodes that are worth searching,
291
# rather than instantly cancelling the extra walker.
293
racing_shortcuts = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'],
294
'e':['d'], 'f':['e'], 'g':['f'], 'h':['g'], 'i':['h', 'o'], 'j':['i', 'y'],
295
'k':['d'], 'l':['k'], 'm':['l'], 'n':['m'], 'o':['n', 'g'], 'p':['f'],
296
'q':['p', 'm'], 'r':['o'], 's':['r'], 't':['s'], 'u':['t'], 'v':['u'],
297
'w':['v'], 'x':['w'], 'y':['x'], 'z':['x', 'q']}
300
# A graph with multiple nodes unique to one side.
339
multiple_interesting_unique = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'],
340
'd':['c'], 'e':['d'], 'f':['d'], 'g':['e'], 'h':['e'], 'i':['f'],
341
'j':['g'], 'k':['g'], 'l':['h'], 'm':['i'], 'n':['k', 'l'],
342
'o':['m'], 'p':['m', 'l'], 'q':['n', 'o'], 'r':['q'], 's':['r'],
343
't':['s'], 'u':['t'], 'v':['u'], 'w':['v'], 'x':['w'],
344
'y':['j', 'x'], 'z':['x', 'p']}
347
204
# Shortcut with extra root
348
205
# We have a long history shortcut, and an extra root, which is why we can't
349
206
# stop searchers based on seeing NULL_REVISION
1244
933
self.assertRaises(StopIteration, search.next)
1245
934
self.assertEqual(set(['head', 'ghost', NULL_REVISION]), search.seen)
1246
935
result = search.get_result()
1247
self.assertEqual(('search', set(['ghost', 'head']), set(['ghost']), 2),
936
self.assertEqual((set(['ghost', 'head']), set(['ghost']), 2),
1248
937
result.get_recipe())
1249
938
self.assertEqual(set(['head', NULL_REVISION]), result.get_keys())
1252
class TestFindUniqueAncestors(TestGraphBase):
1254
def assertFindUniqueAncestors(self, graph, expected, node, common):
1255
actual = graph.find_unique_ancestors(node, common)
1256
self.assertEqual(expected, sorted(actual))
1258
def test_empty_set(self):
1259
graph = self.make_graph(ancestry_1)
1260
self.assertFindUniqueAncestors(graph, [], 'rev1', ['rev1'])
1261
self.assertFindUniqueAncestors(graph, [], 'rev2b', ['rev2b'])
1262
self.assertFindUniqueAncestors(graph, [], 'rev3', ['rev1', 'rev3'])
1264
def test_single_node(self):
1265
graph = self.make_graph(ancestry_1)
1266
self.assertFindUniqueAncestors(graph, ['rev2a'], 'rev2a', ['rev1'])
1267
self.assertFindUniqueAncestors(graph, ['rev2b'], 'rev2b', ['rev1'])
1268
self.assertFindUniqueAncestors(graph, ['rev3'], 'rev3', ['rev2a'])
1270
def test_minimal_ancestry(self):
1271
graph = self.make_breaking_graph(extended_history_shortcut,
1272
[NULL_REVISION, 'a', 'b'])
1273
self.assertFindUniqueAncestors(graph, ['e'], 'e', ['d'])
1275
graph = self.make_breaking_graph(extended_history_shortcut,
1277
self.assertFindUniqueAncestors(graph, ['f'], 'f', ['a', 'd'])
1279
graph = self.make_breaking_graph(complex_shortcut,
1281
self.assertFindUniqueAncestors(graph, ['h'], 'h', ['i'])
1282
self.assertFindUniqueAncestors(graph, ['e', 'g', 'i'], 'i', ['h'])
1283
self.assertFindUniqueAncestors(graph, ['h'], 'h', ['g'])
1284
self.assertFindUniqueAncestors(graph, ['h'], 'h', ['j'])
1286
def test_in_ancestry(self):
1287
graph = self.make_graph(ancestry_1)
1288
self.assertFindUniqueAncestors(graph, [], 'rev1', ['rev3'])
1289
self.assertFindUniqueAncestors(graph, [], 'rev2b', ['rev4'])
1291
def test_multiple_revisions(self):
1292
graph = self.make_graph(ancestry_1)
1293
self.assertFindUniqueAncestors(graph,
1294
['rev4'], 'rev4', ['rev3', 'rev2b'])
1295
self.assertFindUniqueAncestors(graph,
1296
['rev2a', 'rev3', 'rev4'], 'rev4', ['rev2b'])
1298
def test_complex_shortcut(self):
1299
graph = self.make_graph(complex_shortcut)
1300
self.assertFindUniqueAncestors(graph,
1301
['h', 'n'], 'n', ['m'])
1302
self.assertFindUniqueAncestors(graph,
1303
['e', 'i', 'm'], 'm', ['n'])
1305
def test_complex_shortcut2(self):
1306
graph = self.make_graph(complex_shortcut2)
1307
self.assertFindUniqueAncestors(graph,
1308
['j', 'u'], 'u', ['t'])
1309
self.assertFindUniqueAncestors(graph,
1312
def test_multiple_interesting_unique(self):
1313
graph = self.make_graph(multiple_interesting_unique)
1314
self.assertFindUniqueAncestors(graph,
1315
['j', 'y'], 'y', ['z'])
1316
self.assertFindUniqueAncestors(graph,
1317
['p', 'z'], 'z', ['y'])
1319
def test_racing_shortcuts(self):
1320
graph = self.make_graph(racing_shortcuts)
1321
self.assertFindUniqueAncestors(graph,
1322
['p', 'q', 'z'], 'z', ['y'])
1323
self.assertFindUniqueAncestors(graph,
1324
['h', 'i', 'j', 'y'], 'j', ['z'])
1327
class TestGraphFindDistanceToNull(TestGraphBase):
1328
"""Test an api that should be able to compute a revno"""
1330
def assertFindDistance(self, revno, graph, target_id, known_ids):
1331
"""Assert the output of Graph.find_distance_to_null()"""
1332
actual = graph.find_distance_to_null(target_id, known_ids)
1333
self.assertEqual(revno, actual)
1335
def test_nothing_known(self):
1336
graph = self.make_graph(ancestry_1)
1337
self.assertFindDistance(0, graph, NULL_REVISION, [])
1338
self.assertFindDistance(1, graph, 'rev1', [])
1339
self.assertFindDistance(2, graph, 'rev2a', [])
1340
self.assertFindDistance(2, graph, 'rev2b', [])
1341
self.assertFindDistance(3, graph, 'rev3', [])
1342
self.assertFindDistance(4, graph, 'rev4', [])
1344
def test_rev_is_ghost(self):
1345
graph = self.make_graph(ancestry_1)
1346
e = self.assertRaises(errors.GhostRevisionsHaveNoRevno,
1347
graph.find_distance_to_null, 'rev_missing', [])
1348
self.assertEqual('rev_missing', e.revision_id)
1349
self.assertEqual('rev_missing', e.ghost_revision_id)
1351
def test_ancestor_is_ghost(self):
1352
graph = self.make_graph({'rev':['parent']})
1353
e = self.assertRaises(errors.GhostRevisionsHaveNoRevno,
1354
graph.find_distance_to_null, 'rev', [])
1355
self.assertEqual('rev', e.revision_id)
1356
self.assertEqual('parent', e.ghost_revision_id)
1358
def test_known_in_ancestry(self):
1359
graph = self.make_graph(ancestry_1)
1360
self.assertFindDistance(2, graph, 'rev2a', [('rev1', 1)])
1361
self.assertFindDistance(3, graph, 'rev3', [('rev2a', 2)])
1363
def test_known_in_ancestry_limits(self):
1364
graph = self.make_breaking_graph(ancestry_1, ['rev1'])
1365
self.assertFindDistance(4, graph, 'rev4', [('rev3', 3)])
1367
def test_target_is_ancestor(self):
1368
graph = self.make_graph(ancestry_1)
1369
self.assertFindDistance(2, graph, 'rev2a', [('rev3', 3)])
1371
def test_target_is_ancestor_limits(self):
1372
"""We shouldn't search all history if we run into ourselves"""
1373
graph = self.make_breaking_graph(ancestry_1, ['rev1'])
1374
self.assertFindDistance(3, graph, 'rev3', [('rev4', 4)])
1376
def test_target_parallel_to_known_limits(self):
1377
# Even though the known revision isn't part of the other ancestry, they
1378
# eventually converge
1379
graph = self.make_breaking_graph(with_tail, ['a'])
1380
self.assertFindDistance(6, graph, 'f', [('g', 6)])
1381
self.assertFindDistance(7, graph, 'h', [('g', 6)])
1382
self.assertFindDistance(8, graph, 'i', [('g', 6)])
1383
self.assertFindDistance(6, graph, 'g', [('i', 8)])
1386
class TestFindMergeOrder(TestGraphBase):
1388
def assertMergeOrder(self, expected, graph, tip, base_revisions):
1389
self.assertEqual(expected, graph.find_merge_order(tip, base_revisions))
1391
def test_parents(self):
1392
graph = self.make_graph(ancestry_1)
1393
self.assertMergeOrder(['rev3', 'rev2b'], graph, 'rev4',
1395
self.assertMergeOrder(['rev3', 'rev2b'], graph, 'rev4',
1398
def test_ancestors(self):
1399
graph = self.make_graph(ancestry_1)
1400
self.assertMergeOrder(['rev1', 'rev2b'], graph, 'rev4',
1402
self.assertMergeOrder(['rev1', 'rev2b'], graph, 'rev4',
1405
def test_shortcut_one_ancestor(self):
1406
# When we have enough info, we can stop searching
1407
graph = self.make_breaking_graph(ancestry_1, ['rev3', 'rev2b', 'rev4'])
1408
# Single ancestors shortcut right away
1409
self.assertMergeOrder(['rev3'], graph, 'rev4', ['rev3'])
1411
def test_shortcut_after_one_ancestor(self):
1412
graph = self.make_breaking_graph(ancestry_1, ['rev2a', 'rev2b'])
1413
self.assertMergeOrder(['rev3', 'rev1'], graph, 'rev4', ['rev1', 'rev3'])
1416
class TestFindDescendants(TestGraphBase):
1418
def test_find_descendants_rev1_rev3(self):
1419
graph = self.make_graph(ancestry_1)
1420
descendants = graph.find_descendants('rev1', 'rev3')
1421
self.assertEqual(set(['rev1', 'rev2a', 'rev3']), descendants)
1423
def test_find_descendants_rev1_rev4(self):
1424
graph = self.make_graph(ancestry_1)
1425
descendants = graph.find_descendants('rev1', 'rev4')
1426
self.assertEqual(set(['rev1', 'rev2a', 'rev2b', 'rev3', 'rev4']),
1429
def test_find_descendants_rev2a_rev4(self):
1430
graph = self.make_graph(ancestry_1)
1431
descendants = graph.find_descendants('rev2a', 'rev4')
1432
self.assertEqual(set(['rev2a', 'rev3', 'rev4']), descendants)
1434
class TestFindLefthandMerger(TestGraphBase):
1436
def check_merger(self, result, ancestry, merged, tip):
1437
graph = self.make_graph(ancestry)
1438
self.assertEqual(result, graph.find_lefthand_merger(merged, tip))
1440
def test_find_lefthand_merger_rev2b(self):
1441
self.check_merger('rev4', ancestry_1, 'rev2b', 'rev4')
1443
def test_find_lefthand_merger_rev2a(self):
1444
self.check_merger('rev2a', ancestry_1, 'rev2a', 'rev4')
1446
def test_find_lefthand_merger_rev4(self):
1447
self.check_merger(None, ancestry_1, 'rev4', 'rev2a')
1449
def test_find_lefthand_merger_f(self):
1450
self.check_merger('i', complex_shortcut, 'f', 'm')
1452
def test_find_lefthand_merger_g(self):
1453
self.check_merger('i', complex_shortcut, 'g', 'm')
1455
def test_find_lefthand_merger_h(self):
1456
self.check_merger('n', complex_shortcut, 'h', 'n')
1459
class TestGetChildMap(TestGraphBase):
1461
def test_get_child_map(self):
1462
graph = self.make_graph(ancestry_1)
1463
child_map = graph.get_child_map(['rev4', 'rev3', 'rev2a', 'rev2b'])
1464
self.assertEqual({'rev1': ['rev2a', 'rev2b'],
1471
941
class TestCachingParentsProvider(tests.TestCase):
1472
"""These tests run with:
1474
self.inst_pp, a recording parents provider with a graph of a->b, and b is a
1476
self.caching_pp, a CachingParentsProvider layered on inst_pp.
1479
943
def setUp(self):
1480
944
super(TestCachingParentsProvider, self).setUp()
1481
dict_pp = _mod_graph.DictParentsProvider({'a': ('b',)})
945
dict_pp = _mod_graph.DictParentsProvider({'a':('b',)})
1482
946
self.inst_pp = InstrumentedParentsProvider(dict_pp)
1483
947
self.caching_pp = _mod_graph.CachingParentsProvider(self.inst_pp)
1515
980
# Use sorted because we don't care about the order, just that each is
1516
981
# only present 1 time.
1517
982
self.assertEqual(['a', 'b'], sorted(self.inst_pp.calls))
1519
def test_note_missing_key(self):
1520
"""After noting that a key is missing it is cached."""
1521
self.caching_pp.note_missing_key('b')
1522
self.assertEqual({}, self.caching_pp.get_parent_map(['b']))
1523
self.assertEqual([], self.inst_pp.calls)
1524
self.assertEqual(set(['b']), self.caching_pp.missing_keys)
1526
def test_get_cached_parent_map(self):
1527
self.assertEqual({}, self.caching_pp.get_cached_parent_map(['a']))
1528
self.assertEqual([], self.inst_pp.calls)
1529
self.assertEqual({'a': ('b',)}, self.caching_pp.get_parent_map(['a']))
1530
self.assertEqual(['a'], self.inst_pp.calls)
1531
self.assertEqual({'a': ('b',)},
1532
self.caching_pp.get_cached_parent_map(['a']))
1535
class TestCachingParentsProviderExtras(tests.TestCaseWithTransport):
1536
"""Test the behaviour when parents are provided that were not requested."""
1539
super(TestCachingParentsProviderExtras, self).setUp()
1540
class ExtraParentsProvider(object):
1542
def get_parent_map(self, keys):
1543
return {'rev1': [], 'rev2': ['rev1',]}
1545
self.inst_pp = InstrumentedParentsProvider(ExtraParentsProvider())
1546
self.caching_pp = _mod_graph.CachingParentsProvider(
1547
get_parent_map=self.inst_pp.get_parent_map)
1549
def test_uncached(self):
1550
self.caching_pp.disable_cache()
1551
self.assertEqual({'rev1': []},
1552
self.caching_pp.get_parent_map(['rev1']))
1553
self.assertEqual(['rev1'], self.inst_pp.calls)
1554
self.assertIs(None, self.caching_pp._cache)
1556
def test_cache_initially_empty(self):
1557
self.assertEqual({}, self.caching_pp._cache)
1559
def test_cached(self):
1560
self.assertEqual({'rev1': []},
1561
self.caching_pp.get_parent_map(['rev1']))
1562
self.assertEqual(['rev1'], self.inst_pp.calls)
1563
self.assertEqual({'rev1': [], 'rev2': ['rev1']},
1564
self.caching_pp._cache)
1565
self.assertEqual({'rev1': []},
1566
self.caching_pp.get_parent_map(['rev1']))
1567
self.assertEqual(['rev1'], self.inst_pp.calls)
1569
def test_disable_cache_clears_cache(self):
1570
# Put something in the cache
1571
self.caching_pp.get_parent_map(['rev1'])
1572
self.assertEqual(2, len(self.caching_pp._cache))
1573
self.caching_pp.disable_cache()
1574
self.assertIs(None, self.caching_pp._cache)
1576
def test_enable_cache_raises(self):
1577
e = self.assertRaises(AssertionError, self.caching_pp.enable_cache)
1578
self.assertEqual('Cache enabled when already enabled.', str(e))
1580
def test_cache_misses(self):
1581
self.caching_pp.get_parent_map(['rev3'])
1582
self.caching_pp.get_parent_map(['rev3'])
1583
self.assertEqual(['rev3'], self.inst_pp.calls)
1585
def test_no_cache_misses(self):
1586
self.caching_pp.disable_cache()
1587
self.caching_pp.enable_cache(cache_misses=False)
1588
self.caching_pp.get_parent_map(['rev3'])
1589
self.caching_pp.get_parent_map(['rev3'])
1590
self.assertEqual(['rev3', 'rev3'], self.inst_pp.calls)
1592
def test_cache_extras(self):
1593
self.assertEqual({}, self.caching_pp.get_parent_map(['rev3']))
1594
self.assertEqual({'rev2': ['rev1']},
1595
self.caching_pp.get_parent_map(['rev2']))
1596
self.assertEqual(['rev3'], self.inst_pp.calls)
1598
def test_extras_using_cached(self):
1599
self.assertEqual({}, self.caching_pp.get_cached_parent_map(['rev3']))
1600
self.assertEqual({}, self.caching_pp.get_parent_map(['rev3']))
1601
self.assertEqual({'rev2': ['rev1']},
1602
self.caching_pp.get_cached_parent_map(['rev2']))
1603
self.assertEqual(['rev3'], self.inst_pp.calls)
1607
class TestCollapseLinearRegions(tests.TestCase):
1609
def assertCollapsed(self, collapsed, original):
1610
self.assertEqual(collapsed,
1611
_mod_graph.collapse_linear_regions(original))
1613
def test_collapse_nothing(self):
1614
d = {1:[2, 3], 2:[], 3:[]}
1615
self.assertCollapsed(d, d)
1616
d = {1:[2], 2:[3, 4], 3:[5], 4:[5], 5:[]}
1617
self.assertCollapsed(d, d)
1619
def test_collapse_chain(self):
1620
# Any time we have a linear chain, we should be able to collapse
1621
d = {1:[2], 2:[3], 3:[4], 4:[5], 5:[]}
1622
self.assertCollapsed({1:[5], 5:[]}, d)
1623
d = {5:[4], 4:[3], 3:[2], 2:[1], 1:[]}
1624
self.assertCollapsed({5:[1], 1:[]}, d)
1625
d = {5:[3], 3:[4], 4:[1], 1:[2], 2:[]}
1626
self.assertCollapsed({5:[2], 2:[]}, d)
1628
def test_collapse_with_multiple_children(self):
1639
# 4 and 5 cannot be removed because 6 has 2 children
1640
# 2 and 3 cannot be removed because 1 has 2 parents
1641
d = {1:[2, 3], 2:[4], 4:[6], 3:[5], 5:[6], 6:[7], 7:[]}
1642
self.assertCollapsed(d, d)
1645
class TestGraphThunkIdsToKeys(tests.TestCase):
1647
def test_heads(self):
1653
d = {('D',): [('B',), ('C',)], ('C',):[('A',)],
1654
('B',): [('A',)], ('A',): []}
1655
g = _mod_graph.Graph(_mod_graph.DictParentsProvider(d))
1656
graph_thunk = _mod_graph.GraphThunkIdsToKeys(g)
1657
self.assertEqual(['D'], sorted(graph_thunk.heads(['D', 'A'])))
1658
self.assertEqual(['D'], sorted(graph_thunk.heads(['D', 'B'])))
1659
self.assertEqual(['D'], sorted(graph_thunk.heads(['D', 'C'])))
1660
self.assertEqual(['B', 'C'], sorted(graph_thunk.heads(['B', 'C'])))
1662
def test_add_node(self):
1663
d = {('C',):[('A',)], ('B',): [('A',)], ('A',): []}
1664
g = _mod_graph.KnownGraph(d)
1665
graph_thunk = _mod_graph.GraphThunkIdsToKeys(g)
1666
graph_thunk.add_node("D", ["A", "C"])
1667
self.assertEqual(['B', 'D'],
1668
sorted(graph_thunk.heads(['D', 'B', 'A'])))
1670
def test_merge_sort(self):
1671
d = {('C',):[('A',)], ('B',): [('A',)], ('A',): []}
1672
g = _mod_graph.KnownGraph(d)
1673
graph_thunk = _mod_graph.GraphThunkIdsToKeys(g)
1674
graph_thunk.add_node("D", ["A", "C"])
1675
self.assertEqual([('C', 0, (2,), False), ('A', 0, (1,), True)],
1676
[(n.key, n.merge_depth, n.revno, n.end_of_merge)
1677
for n in graph_thunk.merge_sort('C')])
1680
class TestPendingAncestryResultGetKeys(TestCaseWithMemoryTransport):
1681
"""Tests for bzrlib.graph.PendingAncestryResult."""
1683
def test_get_keys(self):
1684
builder = self.make_branch_builder('b')
1685
builder.start_series()
1686
builder.build_snapshot('rev-1', None, [
1687
('add', ('', 'root-id', 'directory', ''))])
1688
builder.build_snapshot('rev-2', ['rev-1'], [])
1689
builder.finish_series()
1690
repo = builder.get_branch().repository
1692
self.addCleanup(repo.unlock)
1693
result = _mod_graph.PendingAncestryResult(['rev-2'], repo)
1694
self.assertEqual(set(['rev-1', 'rev-2']), set(result.get_keys()))
1696
def test_get_keys_excludes_ghosts(self):
1697
builder = self.make_branch_builder('b')
1698
builder.start_series()
1699
builder.build_snapshot('rev-1', None, [
1700
('add', ('', 'root-id', 'directory', ''))])
1701
builder.build_snapshot('rev-2', ['rev-1', 'ghost'], [])
1702
builder.finish_series()
1703
repo = builder.get_branch().repository
1705
self.addCleanup(repo.unlock)
1706
result = _mod_graph.PendingAncestryResult(['rev-2'], repo)
1707
self.assertEqual(sorted(['rev-1', 'rev-2']), sorted(result.get_keys()))
1709
def test_get_keys_excludes_null(self):
1710
# Make a 'graph' with an iter_ancestry that returns NULL_REVISION
1711
# somewhere other than the last element, which can happen in real
1713
class StubGraph(object):
1714
def iter_ancestry(self, keys):
1715
return [(NULL_REVISION, ()), ('foo', (NULL_REVISION,))]
1716
result = _mod_graph.PendingAncestryResult(['rev-3'], None)
1717
result_keys = result._get_keys(StubGraph())
1718
# Only the non-null keys from the ancestry appear.
1719
self.assertEqual(set(['foo']), set(result_keys))
1722
class TestPendingAncestryResultRefine(TestGraphBase):
1724
def test_refine(self):
1725
# Used when pulling from a stacked repository, so test some revisions
1726
# being satisfied from the stacking branch.
1727
g = self.make_graph(
1728
{"tip":["mid"], "mid":["base"], "tag":["base"],
1729
"base":[NULL_REVISION], NULL_REVISION:[]})
1730
result = _mod_graph.PendingAncestryResult(['tip', 'tag'], None)
1731
result = result.refine(set(['tip']), set(['mid']))
1732
self.assertEqual(set(['mid', 'tag']), result.heads)
1733
result = result.refine(set(['mid', 'tag', 'base']),
1734
set([NULL_REVISION]))
1735
self.assertEqual(set([NULL_REVISION]), result.heads)
1736
self.assertTrue(result.is_empty())
1739
class TestSearchResultRefine(TestGraphBase):
1741
def test_refine(self):
1742
# Used when pulling from a stacked repository, so test some revisions
1743
# being satisfied from the stacking branch.
1744
g = self.make_graph(
1745
{"tip":["mid"], "mid":["base"], "tag":["base"],
1746
"base":[NULL_REVISION], NULL_REVISION:[]})
1747
result = _mod_graph.SearchResult(set(['tip', 'tag']),
1748
set([NULL_REVISION]), 4, set(['tip', 'mid', 'tag', 'base']))
1749
result = result.refine(set(['tip']), set(['mid']))
1750
recipe = result.get_recipe()
1751
# We should be starting from tag (original head) and mid (seen ref)
1752
self.assertEqual(set(['mid', 'tag']), recipe[1])
1753
# We should be stopping at NULL (original stop) and tip (seen head)
1754
self.assertEqual(set([NULL_REVISION, 'tip']), recipe[2])
1755
self.assertEqual(3, recipe[3])
1756
result = result.refine(set(['mid', 'tag', 'base']),
1757
set([NULL_REVISION]))
1758
recipe = result.get_recipe()
1759
# We should be starting from nothing (NULL was known as a cut point)
1760
self.assertEqual(set([]), recipe[1])
1761
# We should be stopping at NULL (original stop) and tip (seen head) and
1762
# tag (seen head) and mid(seen mid-point head). We could come back and
1763
# define this as not including mid, for minimal results, but it is
1764
# still 'correct' to include mid, and simpler/easier.
1765
self.assertEqual(set([NULL_REVISION, 'tip', 'tag', 'mid']), recipe[2])
1766
self.assertEqual(0, recipe[3])
1767
self.assertTrue(result.is_empty())
1770
class TestSearchResultFromParentMap(TestGraphBase):
1772
def assertSearchResult(self, start_keys, stop_keys, key_count, parent_map,
1774
(start, stop, count) = _mod_graph.search_result_from_parent_map(
1775
parent_map, missing_keys)
1776
self.assertEqual((sorted(start_keys), sorted(stop_keys), key_count),
1777
(sorted(start), sorted(stop), count))
1779
def test_no_parents(self):
1780
self.assertSearchResult([], [], 0, {})
1781
self.assertSearchResult([], [], 0, None)
1783
def test_ancestry_1(self):
1784
self.assertSearchResult(['rev4'], [NULL_REVISION], len(ancestry_1),
1787
def test_ancestry_2(self):
1788
self.assertSearchResult(['rev1b', 'rev4a'], [NULL_REVISION],
1789
len(ancestry_2), ancestry_2)
1790
self.assertSearchResult(['rev1b', 'rev4a'], [],
1791
len(ancestry_2)+1, ancestry_2,
1792
missing_keys=[NULL_REVISION])
1794
def test_partial_search(self):
1795
parent_map = dict((k,extended_history_shortcut[k])
1796
for k in ['e', 'f'])
1797
self.assertSearchResult(['e', 'f'], ['d', 'a'], 2,
1799
parent_map.update((k,extended_history_shortcut[k])
1800
for k in ['d', 'a'])
1801
self.assertSearchResult(['e', 'f'], ['c', NULL_REVISION], 4,
1803
parent_map['c'] = extended_history_shortcut['c']
1804
self.assertSearchResult(['e', 'f'], ['b'], 6,
1805
parent_map, missing_keys=[NULL_REVISION])
1806
parent_map['b'] = extended_history_shortcut['b']
1807
self.assertSearchResult(['e', 'f'], [], 7,
1808
parent_map, missing_keys=[NULL_REVISION])
1811
class TestLimitedSearchResultFromParentMap(TestGraphBase):
1813
def assertSearchResult(self, start_keys, stop_keys, key_count, parent_map,
1814
missing_keys, tip_keys, depth):
1815
(start, stop, count) = _mod_graph.limited_search_result_from_parent_map(
1816
parent_map, missing_keys, tip_keys, depth)
1817
self.assertEqual((sorted(start_keys), sorted(stop_keys), key_count),
1818
(sorted(start), sorted(stop), count))
1820
def test_empty_ancestry(self):
1821
self.assertSearchResult([], [], 0, {}, (), ['tip-rev-id'], 10)
1823
def test_ancestry_1(self):
1824
self.assertSearchResult(['rev4'], ['rev1'], 4,
1825
ancestry_1, (), ['rev1'], 10)
1826
self.assertSearchResult(['rev2a', 'rev2b'], ['rev1'], 2,
1827
ancestry_1, (), ['rev1'], 1)
1830
def test_multiple_heads(self):
1831
self.assertSearchResult(['e', 'f'], ['a'], 5,
1832
extended_history_shortcut, (), ['a'], 10)
1833
# Note that even though we only take 1 step back, we find 'f', which
1834
# means the described search will still find d and c.
1835
self.assertSearchResult(['f'], ['a'], 4,
1836
extended_history_shortcut, (), ['a'], 1)
1837
self.assertSearchResult(['f'], ['a'], 4,
1838
extended_history_shortcut, (), ['a'], 2)
1841
class TestStackedParentsProvider(tests.TestCase):
1844
super(TestStackedParentsProvider, self).setUp()
1847
def get_shared_provider(self, info, ancestry, has_cached):
1848
pp = _mod_graph.DictParentsProvider(ancestry)
1850
pp.get_cached_parent_map = pp.get_parent_map
1851
return SharedInstrumentedParentsProvider(pp, self.calls, info)
1853
def test_stacked_parents_provider(self):
1854
parents1 = _mod_graph.DictParentsProvider({'rev2': ['rev3']})
1855
parents2 = _mod_graph.DictParentsProvider({'rev1': ['rev4']})
1856
stacked = _mod_graph.StackedParentsProvider([parents1, parents2])
1857
self.assertEqual({'rev1':['rev4'], 'rev2':['rev3']},
1858
stacked.get_parent_map(['rev1', 'rev2']))
1859
self.assertEqual({'rev2':['rev3'], 'rev1':['rev4']},
1860
stacked.get_parent_map(['rev2', 'rev1']))
1861
self.assertEqual({'rev2':['rev3']},
1862
stacked.get_parent_map(['rev2', 'rev2']))
1863
self.assertEqual({'rev1':['rev4']},
1864
stacked.get_parent_map(['rev1', 'rev1']))
1866
def test_stacked_parents_provider_overlapping(self):
1867
# rev2 is availible in both providers.
1871
parents1 = _mod_graph.DictParentsProvider({'rev2': ['rev1']})
1872
parents2 = _mod_graph.DictParentsProvider({'rev2': ['rev1']})
1873
stacked = _mod_graph.StackedParentsProvider([parents1, parents2])
1874
self.assertEqual({'rev2': ['rev1']},
1875
stacked.get_parent_map(['rev2']))
1877
def test_handles_no_get_cached_parent_map(self):
1878
# this shows that we both handle when a provider doesn't implement
1879
# get_cached_parent_map
1880
pp1 = self.get_shared_provider('pp1', {'rev2': ('rev1',)},
1882
pp2 = self.get_shared_provider('pp2', {'rev2': ('rev1',)},
1884
stacked = _mod_graph.StackedParentsProvider([pp1, pp2])
1885
self.assertEqual({'rev2': ('rev1',)}, stacked.get_parent_map(['rev2']))
1886
# No call on 'pp1' because it doesn't provide get_cached_parent_map
1887
self.assertEqual([('pp2', 'cached', ['rev2'])], self.calls)
1889
def test_query_order(self):
1890
# We should call get_cached_parent_map on all providers before we call
1891
# get_parent_map. Further, we should track what entries we have found,
1892
# and not re-try them.
1893
pp1 = self.get_shared_provider('pp1', {'a': ()}, has_cached=True)
1894
pp2 = self.get_shared_provider('pp2', {'c': ('b',)}, has_cached=False)
1895
pp3 = self.get_shared_provider('pp3', {'b': ('a',)}, has_cached=True)
1896
stacked = _mod_graph.StackedParentsProvider([pp1, pp2, pp3])
1897
self.assertEqual({'a': (), 'b': ('a',), 'c': ('b',)},
1898
stacked.get_parent_map(['a', 'b', 'c', 'd']))
1899
self.assertEqual([('pp1', 'cached', ['a', 'b', 'c', 'd']),
1900
# No call to pp2, because it doesn't have cached
1901
('pp3', 'cached', ['b', 'c', 'd']),
1902
('pp1', ['c', 'd']),
1903
('pp2', ['c', 'd']),