193
complex_shortcut = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'],
194
'e':['d'], 'f':['d'], 'g':['f'], 'h':['f'],
195
'i':['e', 'g'], 'j':['g'], 'k':['j'],
196
'l':['k'], 'm':['i', 'l'], 'n':['l', 'h']}
236
complex_shortcut2 = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'],
237
'e':['d'], 'f':['e'], 'g':['f'], 'h':['d'], 'i':['g'],
238
'j':['h'], 'k':['h', 'i'], 'l':['k'], 'm':['l'], 'n':['m'],
239
'o':['n'], 'p':['o'], 'q':['p'], 'r':['q'], 's':['r'],
240
'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'],
243
# Graph where different walkers will race to find the common and uncommon
286
# x is found to be common right away, but is the start of a long series of
288
# o is actually common, but the i-j shortcut makes it look like it is actually
289
# unique to j at first, you have to traverse all of x->o to find it.
290
# q,m gives the walker from j a common point to stop searching, as does p,f.
291
# k-n exists so that the second pass still has nodes that are worth searching,
292
# rather than instantly cancelling the extra walker.
294
racing_shortcuts = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'],
295
'e':['d'], 'f':['e'], 'g':['f'], 'h':['g'], 'i':['h', 'o'], 'j':['i', 'y'],
296
'k':['d'], 'l':['k'], 'm':['l'], 'n':['m'], 'o':['n', 'g'], 'p':['f'],
297
'q':['p', 'm'], 'r':['o'], 's':['r'], 't':['s'], 'u':['t'], 'v':['u'],
298
'w':['v'], 'x':['w'], 'y':['x'], 'z':['x', 'q']}
301
# A graph with multiple nodes unique to one side.
340
multiple_interesting_unique = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'],
341
'd':['c'], 'e':['d'], 'f':['d'], 'g':['e'], 'h':['e'], 'i':['f'],
342
'j':['g'], 'k':['g'], 'l':['h'], 'm':['i'], 'n':['k', 'l'],
343
'o':['m'], 'p':['m', 'l'], 'q':['n', 'o'], 'r':['q'], 's':['r'],
344
't':['s'], 'u':['t'], 'v':['u'], 'w':['v'], 'x':['w'],
345
'y':['j', 'x'], 'z':['x', 'p']}
348
204
# Shortcut with extra root
349
205
# We have a long history shortcut, and an extra root, which is why we can't
350
206
# stop searchers based on seeing NULL_REVISION
1205
973
self.assertEqual(set(['head', NULL_REVISION]), result.get_keys())
1208
class TestFindUniqueAncestors(TestGraphBase):
1210
def assertFindUniqueAncestors(self, graph, expected, node, common):
1211
actual = graph.find_unique_ancestors(node, common)
1212
self.assertEqual(expected, sorted(actual))
1214
def test_empty_set(self):
1215
graph = self.make_graph(ancestry_1)
1216
self.assertFindUniqueAncestors(graph, [], 'rev1', ['rev1'])
1217
self.assertFindUniqueAncestors(graph, [], 'rev2b', ['rev2b'])
1218
self.assertFindUniqueAncestors(graph, [], 'rev3', ['rev1', 'rev3'])
1220
def test_single_node(self):
1221
graph = self.make_graph(ancestry_1)
1222
self.assertFindUniqueAncestors(graph, ['rev2a'], 'rev2a', ['rev1'])
1223
self.assertFindUniqueAncestors(graph, ['rev2b'], 'rev2b', ['rev1'])
1224
self.assertFindUniqueAncestors(graph, ['rev3'], 'rev3', ['rev2a'])
1226
def test_minimal_ancestry(self):
1227
graph = self.make_breaking_graph(extended_history_shortcut,
1228
[NULL_REVISION, 'a', 'b'])
1229
self.assertFindUniqueAncestors(graph, ['e'], 'e', ['d'])
1231
graph = self.make_breaking_graph(extended_history_shortcut,
1233
self.assertFindUniqueAncestors(graph, ['f'], 'f', ['a', 'd'])
1235
graph = self.make_breaking_graph(complex_shortcut,
1237
self.assertFindUniqueAncestors(graph, ['h'], 'h', ['i'])
1238
self.assertFindUniqueAncestors(graph, ['e', 'g', 'i'], 'i', ['h'])
1239
self.assertFindUniqueAncestors(graph, ['h'], 'h', ['g'])
1240
self.assertFindUniqueAncestors(graph, ['h'], 'h', ['j'])
1242
def test_in_ancestry(self):
1243
graph = self.make_graph(ancestry_1)
1244
self.assertFindUniqueAncestors(graph, [], 'rev1', ['rev3'])
1245
self.assertFindUniqueAncestors(graph, [], 'rev2b', ['rev4'])
1247
def test_multiple_revisions(self):
1248
graph = self.make_graph(ancestry_1)
1249
self.assertFindUniqueAncestors(graph,
1250
['rev4'], 'rev4', ['rev3', 'rev2b'])
1251
self.assertFindUniqueAncestors(graph,
1252
['rev2a', 'rev3', 'rev4'], 'rev4', ['rev2b'])
1254
def test_complex_shortcut(self):
1255
graph = self.make_graph(complex_shortcut)
1256
self.assertFindUniqueAncestors(graph,
1257
['h', 'n'], 'n', ['m'])
1258
self.assertFindUniqueAncestors(graph,
1259
['e', 'i', 'm'], 'm', ['n'])
1261
def test_complex_shortcut2(self):
1262
graph = self.make_graph(complex_shortcut2)
1263
self.assertFindUniqueAncestors(graph,
1264
['j', 'u'], 'u', ['t'])
1265
self.assertFindUniqueAncestors(graph,
1268
def test_multiple_interesting_unique(self):
1269
graph = self.make_graph(multiple_interesting_unique)
1270
self.assertFindUniqueAncestors(graph,
1271
['j', 'y'], 'y', ['z'])
1272
self.assertFindUniqueAncestors(graph,
1273
['p', 'z'], 'z', ['y'])
1275
def test_racing_shortcuts(self):
1276
graph = self.make_graph(racing_shortcuts)
1277
self.assertFindUniqueAncestors(graph,
1278
['p', 'q', 'z'], 'z', ['y'])
1279
self.assertFindUniqueAncestors(graph,
1280
['h', 'i', 'j', 'y'], 'j', ['z'])
1283
class TestGraphFindDistanceToNull(TestGraphBase):
1284
"""Test an api that should be able to compute a revno"""
1286
def assertFindDistance(self, revno, graph, target_id, known_ids):
1287
"""Assert the output of Graph.find_distance_to_null()"""
1288
actual = graph.find_distance_to_null(target_id, known_ids)
1289
self.assertEqual(revno, actual)
1291
def test_nothing_known(self):
1292
graph = self.make_graph(ancestry_1)
1293
self.assertFindDistance(0, graph, NULL_REVISION, [])
1294
self.assertFindDistance(1, graph, 'rev1', [])
1295
self.assertFindDistance(2, graph, 'rev2a', [])
1296
self.assertFindDistance(2, graph, 'rev2b', [])
1297
self.assertFindDistance(3, graph, 'rev3', [])
1298
self.assertFindDistance(4, graph, 'rev4', [])
1300
def test_rev_is_ghost(self):
1301
graph = self.make_graph(ancestry_1)
1302
e = self.assertRaises(errors.GhostRevisionsHaveNoRevno,
1303
graph.find_distance_to_null, 'rev_missing', [])
1304
self.assertEqual('rev_missing', e.revision_id)
1305
self.assertEqual('rev_missing', e.ghost_revision_id)
1307
def test_ancestor_is_ghost(self):
1308
graph = self.make_graph({'rev':['parent']})
1309
e = self.assertRaises(errors.GhostRevisionsHaveNoRevno,
1310
graph.find_distance_to_null, 'rev', [])
1311
self.assertEqual('rev', e.revision_id)
1312
self.assertEqual('parent', e.ghost_revision_id)
1314
def test_known_in_ancestry(self):
1315
graph = self.make_graph(ancestry_1)
1316
self.assertFindDistance(2, graph, 'rev2a', [('rev1', 1)])
1317
self.assertFindDistance(3, graph, 'rev3', [('rev2a', 2)])
1319
def test_known_in_ancestry_limits(self):
1320
graph = self.make_breaking_graph(ancestry_1, ['rev1'])
1321
self.assertFindDistance(4, graph, 'rev4', [('rev3', 3)])
1323
def test_target_is_ancestor(self):
1324
graph = self.make_graph(ancestry_1)
1325
self.assertFindDistance(2, graph, 'rev2a', [('rev3', 3)])
1327
def test_target_is_ancestor_limits(self):
1328
"""We shouldn't search all history if we run into ourselves"""
1329
graph = self.make_breaking_graph(ancestry_1, ['rev1'])
1330
self.assertFindDistance(3, graph, 'rev3', [('rev4', 4)])
1332
def test_target_parallel_to_known_limits(self):
1333
# Even though the known revision isn't part of the other ancestry, they
1334
# eventually converge
1335
graph = self.make_breaking_graph(with_tail, ['a'])
1336
self.assertFindDistance(6, graph, 'f', [('g', 6)])
1337
self.assertFindDistance(7, graph, 'h', [('g', 6)])
1338
self.assertFindDistance(8, graph, 'i', [('g', 6)])
1339
self.assertFindDistance(6, graph, 'g', [('i', 8)])
1342
class TestFindMergeOrder(TestGraphBase):
1344
def assertMergeOrder(self, expected, graph, tip, base_revisions):
1345
self.assertEqual(expected, graph.find_merge_order(tip, base_revisions))
1347
def test_parents(self):
1348
graph = self.make_graph(ancestry_1)
1349
self.assertMergeOrder(['rev3', 'rev2b'], graph, 'rev4',
1351
self.assertMergeOrder(['rev3', 'rev2b'], graph, 'rev4',
1354
def test_ancestors(self):
1355
graph = self.make_graph(ancestry_1)
1356
self.assertMergeOrder(['rev1', 'rev2b'], graph, 'rev4',
1358
self.assertMergeOrder(['rev1', 'rev2b'], graph, 'rev4',
1361
def test_shortcut_one_ancestor(self):
1362
# When we have enough info, we can stop searching
1363
graph = self.make_breaking_graph(ancestry_1, ['rev3', 'rev2b', 'rev4'])
1364
# Single ancestors shortcut right away
1365
self.assertMergeOrder(['rev3'], graph, 'rev4', ['rev3'])
1367
def test_shortcut_after_one_ancestor(self):
1368
graph = self.make_breaking_graph(ancestry_1, ['rev2a', 'rev2b'])
1369
self.assertMergeOrder(['rev3', 'rev1'], graph, 'rev4', ['rev1', 'rev3'])
1372
976
class TestCachingParentsProvider(tests.TestCase):
1374
978
def setUp(self):
1411
1015
# Use sorted because we don't care about the order, just that each is
1412
1016
# only present 1 time.
1413
1017
self.assertEqual(['a', 'b'], sorted(self.inst_pp.calls))
1416
class TestCachingParentsProviderExtras(tests.TestCaseWithTransport):
1417
"""Test the behaviour when parents are provided that were not requested."""
1420
super(TestCachingParentsProviderExtras, self).setUp()
1421
class ExtraParentsProvider(object):
1423
def get_parent_map(self, keys):
1424
return {'rev1': [], 'rev2': ['rev1',]}
1426
self.inst_pp = InstrumentedParentsProvider(ExtraParentsProvider())
1427
self.caching_pp = _mod_graph.CachingParentsProvider(
1428
get_parent_map=self.inst_pp.get_parent_map)
1430
def test_uncached(self):
1431
self.caching_pp.disable_cache()
1432
self.assertEqual({'rev1': []},
1433
self.caching_pp.get_parent_map(['rev1']))
1434
self.assertEqual(['rev1'], self.inst_pp.calls)
1435
self.assertIs(None, self.caching_pp._cache)
1437
def test_cache_initially_empty(self):
1438
self.assertEqual({}, self.caching_pp._cache)
1440
def test_cached(self):
1441
self.assertEqual({'rev1': []},
1442
self.caching_pp.get_parent_map(['rev1']))
1443
self.assertEqual(['rev1'], self.inst_pp.calls)
1444
self.assertEqual({'rev1': [], 'rev2': ['rev1']},
1445
self.caching_pp._cache)
1446
self.assertEqual({'rev1': []},
1447
self.caching_pp.get_parent_map(['rev1']))
1448
self.assertEqual(['rev1'], self.inst_pp.calls)
1450
def test_disable_cache_clears_cache(self):
1451
# Put something in the cache
1452
self.caching_pp.get_parent_map(['rev1'])
1453
self.assertEqual(2, len(self.caching_pp._cache))
1454
self.caching_pp.disable_cache()
1455
self.assertIs(None, self.caching_pp._cache)
1457
def test_cache_misses(self):
1458
self.caching_pp.get_parent_map(['rev3'])
1459
self.caching_pp.get_parent_map(['rev3'])
1460
self.assertEqual(['rev3'], self.inst_pp.calls)
1462
def test_no_cache_misses(self):
1463
self.caching_pp.enable_cache(cache_misses=False)
1464
self.caching_pp.get_parent_map(['rev3'])
1465
self.caching_pp.get_parent_map(['rev3'])
1466
self.assertEqual(['rev3', 'rev3'], self.inst_pp.calls)
1468
def test_cache_extras(self):
1469
self.assertEqual({}, self.caching_pp.get_parent_map(['rev3']))
1470
self.assertEqual({'rev2': ['rev1']},
1471
self.caching_pp.get_parent_map(['rev2']))
1472
self.assertEqual(['rev3'], self.inst_pp.calls)
1475
class TestCollapseLinearRegions(tests.TestCase):
1477
def assertCollapsed(self, collapsed, original):
1478
self.assertEqual(collapsed,
1479
_mod_graph.collapse_linear_regions(original))
1481
def test_collapse_nothing(self):
1482
d = {1:[2, 3], 2:[], 3:[]}
1483
self.assertCollapsed(d, d)
1484
d = {1:[2], 2:[3, 4], 3:[5], 4:[5], 5:[]}
1485
self.assertCollapsed(d, d)
1487
def test_collapse_chain(self):
1488
# Any time we have a linear chain, we should be able to collapse
1489
d = {1:[2], 2:[3], 3:[4], 4:[5], 5:[]}
1490
self.assertCollapsed({1:[5], 5:[]}, d)
1491
d = {5:[4], 4:[3], 3:[2], 2:[1], 1:[]}
1492
self.assertCollapsed({5:[1], 1:[]}, d)
1493
d = {5:[3], 3:[4], 4:[1], 1:[2], 2:[]}
1494
self.assertCollapsed({5:[2], 2:[]}, d)
1496
def test_collapse_with_multiple_children(self):
1507
# 4 and 5 cannot be removed because 6 has 2 children
1508
# 2 and 3 cannot be removed because 1 has 2 parents
1509
d = {1:[2, 3], 2:[4], 4:[6], 3:[5], 5:[6], 6:[7], 7:[]}
1510
self.assertCollapsed(d, d)