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'],
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'],
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']}
204
348
# Shortcut with extra root
205
349
# We have a long history shortcut, and an extra root, which is why we can't
206
350
# stop searchers based on seeing NULL_REVISION
357
541
graph.find_unique_lca('rev2a', 'rev2b',
358
542
count_steps=True))
544
def assertRemoveDescendants(self, expected, graph, revisions):
545
parents = graph.get_parent_map(revisions)
546
self.assertEqual(expected,
547
graph._remove_simple_descendants(revisions, parents))
549
def test__remove_simple_descendants(self):
550
graph = self.make_graph(ancestry_1)
551
self.assertRemoveDescendants(set(['rev1']), graph,
552
set(['rev1', 'rev2a', 'rev2b', 'rev3', 'rev4']))
554
def test__remove_simple_descendants_disjoint(self):
555
graph = self.make_graph(ancestry_1)
556
self.assertRemoveDescendants(set(['rev1', 'rev3']), graph,
557
set(['rev1', 'rev3']))
559
def test__remove_simple_descendants_chain(self):
560
graph = self.make_graph(ancestry_1)
561
self.assertRemoveDescendants(set(['rev1']), graph,
562
set(['rev1', 'rev2a', 'rev3']))
564
def test__remove_simple_descendants_siblings(self):
565
graph = self.make_graph(ancestry_1)
566
self.assertRemoveDescendants(set(['rev2a', 'rev2b']), graph,
567
set(['rev2a', 'rev2b', 'rev3']))
360
569
def test_unique_lca_criss_cross(self):
361
570
"""Ensure we don't pick non-unique lcas in a criss-cross"""
362
571
graph = self.make_graph(criss_cross)
443
649
def test_graph_difference_complex_shortcut(self):
444
650
graph = self.make_graph(complex_shortcut)
445
self.expectFailure('find_difference cannot handle shortcuts',
446
self.assertEqual, (set(['m']), set(['h', 'n'])),
447
graph.find_difference('m', 'n'))
448
self.assertEqual((set(['m']), set(['h', 'n'])),
651
self.assertEqual((set(['m', 'i', 'e']), set(['n', 'h'])),
449
652
graph.find_difference('m', 'n'))
654
def test_graph_difference_complex_shortcut2(self):
655
graph = self.make_graph(complex_shortcut2)
656
self.assertEqual((set(['t']), set(['j', 'u'])),
657
graph.find_difference('t', 'u'))
451
659
def test_graph_difference_shortcut_extra_root(self):
452
660
graph = self.make_graph(shortcut_extra_root)
453
self.expectFailure('find_difference cannot handle shortcuts',
454
self.assertEqual, (set(['e']), set(['f', 'g'])),
455
graph.find_difference('e', 'f'))
456
661
self.assertEqual((set(['e']), set(['f', 'g'])),
457
662
graph.find_difference('e', 'f'))
973
1178
self.assertEqual(set(['head', NULL_REVISION]), result.get_keys())
1181
class TestFindUniqueAncestors(TestGraphBase):
1183
def assertFindUniqueAncestors(self, graph, expected, node, common):
1184
actual = graph.find_unique_ancestors(node, common)
1185
self.assertEqual(expected, sorted(actual))
1187
def test_empty_set(self):
1188
graph = self.make_graph(ancestry_1)
1189
self.assertFindUniqueAncestors(graph, [], 'rev1', ['rev1'])
1190
self.assertFindUniqueAncestors(graph, [], 'rev2b', ['rev2b'])
1191
self.assertFindUniqueAncestors(graph, [], 'rev3', ['rev1', 'rev3'])
1193
def test_single_node(self):
1194
graph = self.make_graph(ancestry_1)
1195
self.assertFindUniqueAncestors(graph, ['rev2a'], 'rev2a', ['rev1'])
1196
self.assertFindUniqueAncestors(graph, ['rev2b'], 'rev2b', ['rev1'])
1197
self.assertFindUniqueAncestors(graph, ['rev3'], 'rev3', ['rev2a'])
1199
def test_minimal_ancestry(self):
1200
graph = self.make_breaking_graph(extended_history_shortcut,
1201
[NULL_REVISION, 'a', 'b'])
1202
self.assertFindUniqueAncestors(graph, ['e'], 'e', ['d'])
1204
graph = self.make_breaking_graph(extended_history_shortcut,
1206
self.assertFindUniqueAncestors(graph, ['f'], 'f', ['a', 'd'])
1208
graph = self.make_breaking_graph(complex_shortcut,
1210
self.assertFindUniqueAncestors(graph, ['h'], 'h', ['i'])
1211
self.assertFindUniqueAncestors(graph, ['e', 'g', 'i'], 'i', ['h'])
1212
self.assertFindUniqueAncestors(graph, ['h'], 'h', ['g'])
1213
self.assertFindUniqueAncestors(graph, ['h'], 'h', ['j'])
1215
def test_in_ancestry(self):
1216
graph = self.make_graph(ancestry_1)
1217
self.assertFindUniqueAncestors(graph, [], 'rev1', ['rev3'])
1218
self.assertFindUniqueAncestors(graph, [], 'rev2b', ['rev4'])
1220
def test_multiple_revisions(self):
1221
graph = self.make_graph(ancestry_1)
1222
self.assertFindUniqueAncestors(graph,
1223
['rev4'], 'rev4', ['rev3', 'rev2b'])
1224
self.assertFindUniqueAncestors(graph,
1225
['rev2a', 'rev3', 'rev4'], 'rev4', ['rev2b'])
1227
def test_complex_shortcut(self):
1228
graph = self.make_graph(complex_shortcut)
1229
self.assertFindUniqueAncestors(graph,
1230
['h', 'n'], 'n', ['m'])
1231
self.assertFindUniqueAncestors(graph,
1232
['e', 'i', 'm'], 'm', ['n'])
1234
def test_complex_shortcut2(self):
1235
graph = self.make_graph(complex_shortcut2)
1236
self.assertFindUniqueAncestors(graph,
1237
['j', 'u'], 'u', ['t'])
1238
self.assertFindUniqueAncestors(graph,
1241
def test_multiple_interesting_unique(self):
1242
graph = self.make_graph(multiple_interesting_unique)
1243
self.assertFindUniqueAncestors(graph,
1244
['j', 'y'], 'y', ['z'])
1245
self.assertFindUniqueAncestors(graph,
1246
['p', 'z'], 'z', ['y'])
1248
def test_racing_shortcuts(self):
1249
graph = self.make_graph(racing_shortcuts)
1250
self.assertFindUniqueAncestors(graph,
1251
['p', 'q', 'z'], 'z', ['y'])
1252
self.assertFindUniqueAncestors(graph,
1253
['h', 'i', 'j', 'y'], 'j', ['z'])
1256
class TestGraphFindDistanceToNull(TestGraphBase):
1257
"""Test an api that should be able to compute a revno"""
1259
def assertFindDistance(self, revno, graph, target_id, known_ids):
1260
"""Assert the output of Graph.find_distance_to_null()"""
1261
actual = graph.find_distance_to_null(target_id, known_ids)
1262
self.assertEqual(revno, actual)
1264
def test_nothing_known(self):
1265
graph = self.make_graph(ancestry_1)
1266
self.assertFindDistance(0, graph, NULL_REVISION, [])
1267
self.assertFindDistance(1, graph, 'rev1', [])
1268
self.assertFindDistance(2, graph, 'rev2a', [])
1269
self.assertFindDistance(2, graph, 'rev2b', [])
1270
self.assertFindDistance(3, graph, 'rev3', [])
1271
self.assertFindDistance(4, graph, 'rev4', [])
1273
def test_rev_is_ghost(self):
1274
graph = self.make_graph(ancestry_1)
1275
e = self.assertRaises(errors.GhostRevisionsHaveNoRevno,
1276
graph.find_distance_to_null, 'rev_missing', [])
1277
self.assertEqual('rev_missing', e.revision_id)
1278
self.assertEqual('rev_missing', e.ghost_revision_id)
1280
def test_ancestor_is_ghost(self):
1281
graph = self.make_graph({'rev':['parent']})
1282
e = self.assertRaises(errors.GhostRevisionsHaveNoRevno,
1283
graph.find_distance_to_null, 'rev', [])
1284
self.assertEqual('rev', e.revision_id)
1285
self.assertEqual('parent', e.ghost_revision_id)
1287
def test_known_in_ancestry(self):
1288
graph = self.make_graph(ancestry_1)
1289
self.assertFindDistance(2, graph, 'rev2a', [('rev1', 1)])
1290
self.assertFindDistance(3, graph, 'rev3', [('rev2a', 2)])
1292
def test_known_in_ancestry_limits(self):
1293
graph = self.make_breaking_graph(ancestry_1, ['rev1'])
1294
self.assertFindDistance(4, graph, 'rev4', [('rev3', 3)])
1296
def test_target_is_ancestor(self):
1297
graph = self.make_graph(ancestry_1)
1298
self.assertFindDistance(2, graph, 'rev2a', [('rev3', 3)])
1300
def test_target_is_ancestor_limits(self):
1301
"""We shouldn't search all history if we run into ourselves"""
1302
graph = self.make_breaking_graph(ancestry_1, ['rev1'])
1303
self.assertFindDistance(3, graph, 'rev3', [('rev4', 4)])
1305
def test_target_parallel_to_known_limits(self):
1306
# Even though the known revision isn't part of the other ancestry, they
1307
# eventually converge
1308
graph = self.make_breaking_graph(with_tail, ['a'])
1309
self.assertFindDistance(6, graph, 'f', [('g', 6)])
1310
self.assertFindDistance(7, graph, 'h', [('g', 6)])
1311
self.assertFindDistance(8, graph, 'i', [('g', 6)])
1312
self.assertFindDistance(6, graph, 'g', [('i', 8)])
976
1315
class TestCachingParentsProvider(tests.TestCase):
978
1317
def setUp(self):