~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_graph.py

  • Committer: Ian Clatworthy
  • Date: 2008-03-27 07:51:10 UTC
  • mto: (3311.1.1 ianc-integration)
  • mto: This revision was merged to the branch mainline in revision 3312.
  • Revision ID: ian.clatworthy@canonical.com-20080327075110-afgd7x03ybju06ez
Reduce evangelism in the User Guide

Show diffs side-by-side

added added

removed removed

Lines of Context:
165
165
# Complex shortcut
166
166
# This has a failure mode in that a shortcut will find some nodes in common,
167
167
# but the common searcher won't have time to find that one branch is actually
168
 
# in common. The extra nodes at the beginning are because we want to avoid
 
168
# in common. The extra nodes at the top are because we want to avoid
169
169
# walking off the graph. Specifically, node G should be considered common, but
170
170
# is likely to be seen by M long before the common searcher finds it.
171
171
#
181
181
#     |\
182
182
#     e f
183
183
#     | |\
184
 
#     | g h
185
 
#     |/| |
186
 
#     i j |
 
184
#     i | h
 
185
#     |\| |
 
186
#     | g |
 
187
#     | | |
 
188
#     | j |
187
189
#     | | |
188
190
#     | k |
189
191
#     | | |
190
192
#     | l |
191
193
#     |/|/
192
194
#     m n
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']}
197
 
 
198
 
# NULL_REVISION
199
 
#     |
200
 
#     a
201
 
#     |
202
 
#     b
203
 
#     |
204
 
#     c
205
 
#     |
206
 
#     d
207
 
#     |\
208
 
#     e |
209
 
#     | |
210
 
#     f |
211
 
#     | |
212
 
#     g h
213
 
#     | |\
214
 
#     i | j
215
 
#     |\| |
216
 
#     | k |
217
 
#     | | |
218
 
#     | l |
219
 
#     | | |
220
 
#     | m |
221
 
#     | | |
222
 
#     | n |
223
 
#     | | |
224
 
#     | o |
225
 
#     | | |
226
 
#     | p |
227
 
#     | | |
228
 
#     | q |
229
 
#     | | |
230
 
#     | r |
231
 
#     | | |
232
 
#     | s |
233
 
#     | | |
234
 
#     |/|/
235
 
#     t u
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'],
241
202
                    }
242
203
 
243
 
# Graph where different walkers will race to find the common and uncommon
244
 
# nodes.
245
 
#
246
 
# NULL_REVISION
247
 
#     |
248
 
#     a
249
 
#     |
250
 
#     b
251
 
#     |
252
 
#     c
253
 
#     |
254
 
#     d
255
 
#     |\
256
 
#     e k
257
 
#     | |
258
 
#     f-+-p
259
 
#     | | |
260
 
#     | l |
261
 
#     | | |
262
 
#     | m |
263
 
#     | |\|
264
 
#     g n q
265
 
#     |\| |
266
 
#     h o |
267
 
#     |/| |
268
 
#     i r |
269
 
#     | | |
270
 
#     | s |
271
 
#     | | |
272
 
#     | t |
273
 
#     | | |
274
 
#     | u |
275
 
#     | | |
276
 
#     | v |
277
 
#     | | |
278
 
#     | w |
279
 
#     | | |
280
 
#     | x |
281
 
#     | |\|
282
 
#     | y z
283
 
#     |/
284
 
#     j
285
 
#
286
 
# x is found to be common right away, but is the start of a long series of
287
 
# common commits.
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.
293
 
 
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']}
299
 
 
300
 
 
301
 
# A graph with multiple nodes unique to one side.
302
 
#
303
 
# NULL_REVISION
304
 
#     |
305
 
#     a
306
 
#     |
307
 
#     b
308
 
#     |
309
 
#     c
310
 
#     |
311
 
#     d
312
 
#     |\
313
 
#     e f
314
 
#     |\ \
315
 
#     g h i
316
 
#     |\ \ \
317
 
#     j k l m
318
 
#     | |/ x|
319
 
#     | n o p
320
 
#     | |/  |
321
 
#     | q   |
322
 
#     | |   |
323
 
#     | r   |
324
 
#     | |   |
325
 
#     | s   |
326
 
#     | |   |
327
 
#     | t   |
328
 
#     | |   |
329
 
#     | u   |
330
 
#     | |   |
331
 
#     | v   |
332
 
#     | |   |
333
 
#     | w   |
334
 
#     | |   |
335
 
#     | x   |
336
 
#     |/ \ /
337
 
#     y   z
338
 
#
339
 
 
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']}
346
 
 
347
 
 
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
396
252
with_ghost = {'a': ['b'], 'c': ['b', 'd'], 'b':['e'], 'd':['e', 'g'],
397
253
              'e': ['f'], 'f':[NULL_REVISION], NULL_REVISION:()}
398
254
 
399
 
# A graph that shows we can shortcut finding revnos when reaching them from the
400
 
# side.
401
 
#  NULL_REVISION
402
 
#       |
403
 
#       a
404
 
#       |
405
 
#       b
406
 
#       |
407
 
#       c
408
 
#       |
409
 
#       d
410
 
#       |
411
 
#       e
412
 
#      / \
413
 
#     f   g
414
 
#     |
415
 
#     h
416
 
#     |
417
 
#     i
418
 
 
419
 
with_tail = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'], 'd':['c'], 'e':['d'],
420
 
             'f':['e'], 'g':['e'], 'h':['f'], 'i':['h']}
421
255
 
422
256
 
423
257
class InstrumentedParentsProvider(object):
431
265
        return self._real_parents_provider.get_parent_map(nodes)
432
266
 
433
267
 
434
 
class TestGraphBase(tests.TestCase):
435
 
 
436
 
    def make_graph(self, ancestors):
437
 
        return _mod_graph.Graph(_mod_graph.DictParentsProvider(ancestors))
438
 
 
439
 
    def make_breaking_graph(self, ancestors, break_on):
440
 
        """Make a Graph that raises an exception if we hit a node."""
441
 
        g = self.make_graph(ancestors)
442
 
        orig_parent_map = g.get_parent_map
443
 
        def get_parent_map(keys):
444
 
            bad_keys = set(keys).intersection(break_on)
445
 
            if bad_keys:
446
 
                self.fail('key(s) %s was accessed' % (sorted(bad_keys),))
447
 
            return orig_parent_map(keys)
448
 
        g.get_parent_map = get_parent_map
449
 
        return g
450
 
 
451
 
 
452
268
class TestGraph(TestCaseWithMemoryTransport):
453
269
 
454
270
    def make_graph(self, ancestors):
541
357
                         graph.find_unique_lca('rev2a', 'rev2b',
542
358
                         count_steps=True))
543
359
 
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))
548
 
 
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']))
553
 
 
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']))
558
 
 
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']))
563
 
 
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']))
568
 
 
569
360
    def test_unique_lca_criss_cross(self):
570
361
        """Ensure we don't pick non-unique lcas in a criss-cross"""
571
362
        graph = self.make_graph(criss_cross)
636
427
 
637
428
    def test_graph_difference_extended_history(self):
638
429
        graph = self.make_graph(extended_history_shortcut)
 
430
        self.expectFailure('find_difference cannot handle shortcuts',
 
431
            self.assertEqual, (set(['e']), set(['f'])),
 
432
                graph.find_difference('e', 'f'))
639
433
        self.assertEqual((set(['e']), set(['f'])),
640
434
                         graph.find_difference('e', 'f'))
641
435
        self.assertEqual((set(['f']), set(['e'])),
648
442
 
649
443
    def test_graph_difference_complex_shortcut(self):
650
444
        graph = self.make_graph(complex_shortcut)
651
 
        self.assertEqual((set(['m', 'i', 'e']), set(['n', 'h'])),
 
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'])),
652
449
                         graph.find_difference('m', 'n'))
653
450
 
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'))
658
 
 
659
451
    def test_graph_difference_shortcut_extra_root(self):
660
452
        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'))
661
456
        self.assertEqual((set(['e']), set(['f', 'g'])),
662
457
                         graph.find_difference('e', 'f'))
663
458
 
1082
877
        search = graph._make_breadth_first_searcher(['head'])
1083
878
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
1084
879
 
1085
 
    def test_breadth_first_stop_searching_late(self):
1086
 
        # A client should be able to say 'stop node X' and have it excluded
1087
 
        # from the result even if X was seen in an older iteration of the
1088
 
        # search.
1089
 
        graph = self.make_graph({
1090
 
            'head':['middle'],
1091
 
            'middle':['child'],
1092
 
            'child':[NULL_REVISION],
1093
 
            NULL_REVISION:[],
1094
 
            })
1095
 
        search = graph._make_breadth_first_searcher(['head'])
1096
 
        expected = [
1097
 
            (set(['head']), (set(['head']), set(['middle']), 1),
1098
 
             ['head'], None, None),
1099
 
            (set(['head', 'middle']), (set(['head']), set(['child']), 2),
1100
 
             ['head', 'middle'], None, None),
1101
 
            # 'middle' came from the previous iteration, but we don't stop
1102
 
            # searching it until *after* advancing the searcher.
1103
 
            (set(['head', 'middle', 'child']),
1104
 
             (set(['head']), set(['middle', 'child']), 1),
1105
 
             ['head'], None, ['middle', 'child']),
1106
 
            ]
1107
 
        self.assertSeenAndResult(expected, search, search.next)
1108
 
        # using next_with_ghosts:
1109
 
        search = graph._make_breadth_first_searcher(['head'])
1110
 
        self.assertSeenAndResult(expected, search, search.next_with_ghosts)
1111
 
 
1112
880
    def test_breadth_first_get_result_ghosts_are_excluded(self):
1113
881
        graph = self.make_graph({
1114
882
            'head':['child', 'ghost'],
1205
973
        self.assertEqual(set(['head', NULL_REVISION]), result.get_keys())
1206
974
 
1207
975
 
1208
 
class TestFindUniqueAncestors(TestGraphBase):
1209
 
 
1210
 
    def assertFindUniqueAncestors(self, graph, expected, node, common):
1211
 
        actual = graph.find_unique_ancestors(node, common)
1212
 
        self.assertEqual(expected, sorted(actual))
1213
 
 
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'])
1219
 
 
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'])
1225
 
 
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'])
1230
 
 
1231
 
        graph = self.make_breaking_graph(extended_history_shortcut,
1232
 
                                         ['b'])
1233
 
        self.assertFindUniqueAncestors(graph, ['f'], 'f', ['a', 'd'])
1234
 
 
1235
 
        graph = self.make_breaking_graph(complex_shortcut,
1236
 
                                         ['a', 'b'])
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'])
1241
 
 
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'])
1246
 
 
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'])
1253
 
 
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'])
1260
 
 
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,
1266
 
            ['t'], 't', ['u'])
1267
 
 
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'])
1274
 
 
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'])
1281
 
 
1282
 
 
1283
 
class TestGraphFindDistanceToNull(TestGraphBase):
1284
 
    """Test an api that should be able to compute a revno"""
1285
 
 
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)
1290
 
 
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', [])
1299
 
 
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)
1306
 
 
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)
1313
 
 
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)])
1318
 
 
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)])
1322
 
 
1323
 
    def test_target_is_ancestor(self):
1324
 
        graph = self.make_graph(ancestry_1)
1325
 
        self.assertFindDistance(2, graph, 'rev2a', [('rev3', 3)])
1326
 
 
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)])
1331
 
 
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)])
1340
 
 
1341
 
 
1342
 
class TestFindMergeOrder(TestGraphBase):
1343
 
 
1344
 
    def assertMergeOrder(self, expected, graph, tip, base_revisions):
1345
 
        self.assertEqual(expected, graph.find_merge_order(tip, base_revisions))
1346
 
 
1347
 
    def test_parents(self):
1348
 
        graph = self.make_graph(ancestry_1)
1349
 
        self.assertMergeOrder(['rev3', 'rev2b'], graph, 'rev4',
1350
 
                                                        ['rev3', 'rev2b'])
1351
 
        self.assertMergeOrder(['rev3', 'rev2b'], graph, 'rev4',
1352
 
                                                        ['rev2b', 'rev3'])
1353
 
 
1354
 
    def test_ancestors(self):
1355
 
        graph = self.make_graph(ancestry_1)
1356
 
        self.assertMergeOrder(['rev1', 'rev2b'], graph, 'rev4',
1357
 
                                                        ['rev1', 'rev2b'])
1358
 
        self.assertMergeOrder(['rev1', 'rev2b'], graph, 'rev4',
1359
 
                                                        ['rev2b', 'rev1'])
1360
 
 
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'])
1366
 
 
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'])
1370
 
 
1371
 
 
1372
976
class TestCachingParentsProvider(tests.TestCase):
1373
977
 
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))
1414
 
 
1415
 
 
1416
 
class TestCachingParentsProviderExtras(tests.TestCaseWithTransport):
1417
 
    """Test the behaviour when parents are provided that were not requested."""
1418
 
 
1419
 
    def setUp(self):
1420
 
        super(TestCachingParentsProviderExtras, self).setUp()
1421
 
        class ExtraParentsProvider(object):
1422
 
 
1423
 
            def get_parent_map(self, keys):
1424
 
                return {'rev1': [], 'rev2': ['rev1',]}
1425
 
 
1426
 
        self.inst_pp = InstrumentedParentsProvider(ExtraParentsProvider())
1427
 
        self.caching_pp = _mod_graph.CachingParentsProvider(
1428
 
            get_parent_map=self.inst_pp.get_parent_map)
1429
 
 
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)
1436
 
 
1437
 
    def test_cache_initially_empty(self):
1438
 
        self.assertEqual({}, self.caching_pp._cache)
1439
 
 
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)
1449
 
 
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)
1456
 
 
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)
1461
 
 
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)
1467
 
 
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)
1473
 
 
1474
 
 
1475
 
class TestCollapseLinearRegions(tests.TestCase):
1476
 
 
1477
 
    def assertCollapsed(self, collapsed, original):
1478
 
        self.assertEqual(collapsed,
1479
 
                         _mod_graph.collapse_linear_regions(original))
1480
 
 
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)
1486
 
 
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)
1495
 
 
1496
 
    def test_collapse_with_multiple_children(self):
1497
 
        #    7
1498
 
        #    |
1499
 
        #    6
1500
 
        #   / \
1501
 
        #  4   5
1502
 
        #  |   |
1503
 
        #  2   3
1504
 
        #   \ /
1505
 
        #    1
1506
 
        #
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)