116
117
# rev2a rev2b rev2c
119
120
history_shortcut = {'rev1': [NULL_REVISION], 'rev2a': ['rev1'],
120
121
'rev2b': ['rev1'], 'rev2c': ['rev1'],
121
122
'rev3a': ['rev2a', 'rev2b'], 'rev3b': ['rev2b', 'rev2c']}
124
# Extended history shortcut
136
extended_history_shortcut = {'a': [NULL_REVISION],
145
# Both sides will see 'A' first, even though it is actually a decendent of a
146
# different common revision.
160
double_shortcut = {'a':[NULL_REVISION], 'b':['a'], 'c':['b'],
161
'd':['c'], 'e':['c'], 'f':['a', 'd'],
165
# This has a failure mode in that a shortcut will find some nodes in common,
166
# but the common searcher won't have time to find that one branch is actually
167
# in common. The extra nodes at the top are because we want to avoid
168
# walking off the graph. Specifically, node G should be considered common, but
169
# is likely to be seen by M long before the common searcher finds it.
194
complex_shortcut = {'d':[NULL_REVISION],
195
'x':['d'], 'y':['x'],
196
'e':['y'], 'f':['d'], 'g':['f', 'i'], 'h':['f'],
197
'i':['e'], 'j':['g'], 'k':['j'],
198
'l':['k'], 'm':['i', 's'], 'n':['s', 'h'],
199
'o':['l'], 'p':['o'], 'q':['p'],
200
'r':['q'], 's':['r'],
203
# Shortcut with extra root
204
# We have a long history shortcut, and an extra root, which is why we can't
205
# stop searchers based on seeing NULL_REVISION
217
shortcut_extra_root = {'a': [NULL_REVISION],
222
'f': ['a', 'd', 'g'],
223
'g': [NULL_REVISION],
144
247
self.calls.extend(nodes)
145
248
return self._real_parents_provider.get_parents(nodes)
250
def get_parent_map(self, nodes):
251
self.calls.extend(nodes)
252
return self._real_parents_provider.get_parent_map(nodes)
148
255
class TestGraph(TestCaseWithMemoryTransport):
150
257
def make_graph(self, ancestors):
258
# XXX: This seems valid, is there a reason to actually create a
259
# repository and put things in it?
260
return _mod_graph.Graph(_mod_graph.DictParentsProvider(ancestors))
151
261
tree = self.prepare_memory_tree('.')
152
262
self.build_ancestry(tree, ancestors)
153
263
self.addCleanup(tree.unlock)
261
371
self.assertEqual(NULL_REVISION,
262
372
graph.find_unique_lca('rev4a', 'rev1b'))
374
def test_lca_double_shortcut(self):
375
graph = self.make_graph(double_shortcut)
376
self.assertEqual('c', graph.find_unique_lca('f', 'g'))
264
378
def test_common_ancestor_two_repos(self):
265
379
"""Ensure we do unique_lca using data from two repos"""
266
380
mainline_tree = self.prepare_memory_tree('mainline')
289
403
self.assertEqual((set(['rev4', 'rev3', 'rev2a']), set()),
290
404
graph.find_difference('rev4', 'rev2b'))
406
def test_graph_difference_separate_ancestry(self):
407
graph = self.make_graph(ancestry_2)
408
self.assertEqual((set(['rev1a']), set(['rev1b'])),
409
graph.find_difference('rev1a', 'rev1b'))
410
self.assertEqual((set(['rev1a', 'rev2a', 'rev3a', 'rev4a']),
412
graph.find_difference('rev4a', 'rev1b'))
292
414
def test_graph_difference_criss_cross(self):
293
415
graph = self.make_graph(criss_cross)
294
416
self.assertEqual((set(['rev3a']), set(['rev3b'])),
296
418
self.assertEqual((set([]), set(['rev3b', 'rev2b'])),
297
419
graph.find_difference('rev2a', 'rev3b'))
421
def test_graph_difference_extended_history(self):
422
graph = self.make_graph(extended_history_shortcut)
423
self.expectFailure('find_difference cannot handle shortcuts',
424
self.assertEqual, (set(['e']), set(['f'])),
425
graph.find_difference('e', 'f'))
426
self.assertEqual((set(['e']), set(['f'])),
427
graph.find_difference('e', 'f'))
428
self.assertEqual((set(['f']), set(['e'])),
429
graph.find_difference('f', 'e'))
431
def test_graph_difference_double_shortcut(self):
432
graph = self.make_graph(double_shortcut)
433
self.assertEqual((set(['d', 'f']), set(['e', 'g'])),
434
graph.find_difference('f', 'g'))
436
def test_graph_difference_complex_shortcut(self):
437
graph = self.make_graph(complex_shortcut)
438
self.expectFailure('find_difference cannot handle shortcuts',
439
self.assertEqual, (set(['m']), set(['h', 'n'])),
440
graph.find_difference('m', 'n'))
441
self.assertEqual((set(['m']), set(['h', 'n'])),
442
graph.find_difference('m', 'n'))
444
def test_graph_difference_shortcut_extra_root(self):
445
graph = self.make_graph(shortcut_extra_root)
446
self.expectFailure('find_difference cannot handle shortcuts',
447
self.assertEqual, (set(['e']), set(['f', 'g'])),
448
graph.find_difference('e', 'f'))
449
self.assertEqual((set(['e']), set(['f', 'g'])),
450
graph.find_difference('e', 'f'))
299
452
def test_stacked_parents_provider(self):
301
454
parents1 = _mod_graph.DictParentsProvider({'rev2': ['rev3']})
463
616
self.fail('key deeper was accessed')
464
617
result.append(graph_dict[key])
619
def get_parent_map(keys):
623
self.fail('key deeper was accessed')
624
result[key] = graph_dict[key]
467
627
an_obj.get_parents = get_parents
628
an_obj.get_parent_map = get_parent_map
468
629
graph = _mod_graph.Graph(an_obj)
469
630
return graph.heads(search)
503
664
self.assertEqual(set(['h1', 'h2']),
504
665
self._run_heads_break_deeper(graph_dict, ['h1', 'h2']))
668
class TestCachingParentsProvider(tests.TestCase):
671
super(TestCachingParentsProvider, self).setUp()
672
dict_pp = _mod_graph.DictParentsProvider({'a':('b',)})
673
self.inst_pp = InstrumentedParentsProvider(dict_pp)
674
self.caching_pp = _mod_graph.CachingParentsProvider(self.inst_pp)
676
def test_get_parents(self):
677
"""Requesting the same revision should be returned from cache"""
678
self.assertEqual({}, self.caching_pp._cache)
679
self.assertEqual([('b',)], self.caching_pp.get_parents(['a']))
680
self.assertEqual(['a'], self.inst_pp.calls)
681
self.assertEqual([('b',)], self.caching_pp.get_parents(['a']))
682
# No new call, as it should have been returned from the cache
683
self.assertEqual(['a'], self.inst_pp.calls)
684
self.assertEqual({'a':('b',)}, self.caching_pp._cache)
686
def test_get_parents_not_present(self):
687
"""The cache should also track when a revision doesn't exist"""
688
self.assertEqual([None], self.caching_pp.get_parents(['b']))
689
self.assertEqual(['b'], self.inst_pp.calls)
690
self.assertEqual([None], self.caching_pp.get_parents(['b']))
692
self.assertEqual(['b'], self.inst_pp.calls)
693
self.assertEqual({'b':None}, self.caching_pp._cache)
695
def test_get_parents_mixed(self):
696
"""Anything that can be returned from cache, should be"""
697
self.assertEqual([None], self.caching_pp.get_parents(['b']))
698
self.assertEqual(['b'], self.inst_pp.calls)
699
self.assertEqual([('b',), None],
700
self.caching_pp.get_parents(['a', 'b']))
701
self.assertEqual(['b', 'a'], self.inst_pp.calls)
703
def test_get_parents_repeated(self):
704
"""Asking for the same parent 2x will only forward 1 request."""
705
self.assertEqual([None, ('b',), None],
706
self.caching_pp.get_parents(['b', 'a', 'b']))
707
# Use sorted because we don't care about the order, just that each is
708
# only present 1 time.
709
self.assertEqual(['a', 'b'], sorted(self.inst_pp.calls))