1
# Copyright (C) 2009 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for the python and pyrex extensions of KnownGraph"""
27
from bzrlib.tests import test_graph
28
from bzrlib.revision import NULL_REVISION
31
def load_tests(standard_tests, module, loader):
32
"""Parameterize tests for all versions of groupcompress."""
34
('python', {'module': _known_graph_py, 'do_cache': True}),
37
('python-nocache', {'module': _known_graph_py, 'do_cache': False}),
39
suite = loader.suiteClass()
40
if CompiledKnownGraphFeature.available():
41
from bzrlib import _known_graph_pyx
42
scenarios.append(('C', {'module': _known_graph_pyx, 'do_cache': True}))
43
caching_scenarios.append(('C-nocache',
44
{'module': _known_graph_pyx, 'do_cache': False}))
46
# the compiled module isn't available, so we add a failing test
47
class FailWithoutFeature(tests.TestCase):
49
self.requireFeature(CompiledKnownGraphFeature)
50
suite.addTest(loader.loadTestsFromTestCase(FailWithoutFeature))
51
# TestKnownGraphHeads needs to be permutated with and without caching.
52
# All other TestKnownGraph tests only need to be tested across module
53
heads_suite, other_suite = tests.split_suite_by_condition(
54
standard_tests, tests.condition_isinstance(TestKnownGraphHeads))
55
suite = tests.multiply_tests(other_suite, scenarios, suite)
56
suite = tests.multiply_tests(heads_suite, scenarios + caching_scenarios,
61
class _CompiledKnownGraphFeature(tests.Feature):
65
import bzrlib._known_graph_pyx
70
def feature_name(self):
71
return 'bzrlib._known_graph_pyx'
73
CompiledKnownGraphFeature = _CompiledKnownGraphFeature()
83
alt_merge = {'a': [], 'b': ['a'], 'c': ['b'], 'd': ['a', 'c']}
86
class TestCaseWithKnownGraph(tests.TestCase):
88
module = None # Set by load_tests
90
def make_known_graph(self, ancestry):
91
return self.module.KnownGraph(ancestry, do_cache=self.do_cache)
94
class TestKnownGraph(TestCaseWithKnownGraph):
96
def assertGDFO(self, graph, rev, gdfo):
97
node = graph._nodes[rev]
98
self.assertEqual(gdfo, node.gdfo)
100
def test_children_ancestry1(self):
101
graph = self.make_known_graph(test_graph.ancestry_1)
102
self.assertEqual(['rev1'], graph.get_child_keys(NULL_REVISION))
103
self.assertEqual(['rev2a', 'rev2b'],
104
sorted(graph.get_child_keys('rev1')))
105
self.assertEqual(['rev3'], graph.get_child_keys('rev2a'))
106
self.assertEqual(['rev4'], graph.get_child_keys('rev3'))
107
self.assertEqual(['rev4'], graph.get_child_keys('rev2b'))
108
self.assertRaises(KeyError, graph.get_child_keys, 'not_in_graph')
110
def test_parent_ancestry1(self):
111
graph = self.make_known_graph(test_graph.ancestry_1)
112
self.assertEqual([NULL_REVISION], graph.get_parent_keys('rev1'))
113
self.assertEqual(['rev1'], graph.get_parent_keys('rev2a'))
114
self.assertEqual(['rev1'], graph.get_parent_keys('rev2b'))
115
self.assertEqual(['rev2a'], graph.get_parent_keys('rev3'))
116
self.assertEqual(['rev2b', 'rev3'],
117
sorted(graph.get_parent_keys('rev4')))
118
self.assertRaises(KeyError, graph.get_child_keys, 'not_in_graph')
120
def test_parent_with_ghost(self):
121
graph = self.make_known_graph(test_graph.with_ghost)
122
self.assertEqual(None, graph.get_parent_keys('g'))
124
def test_gdfo_ancestry_1(self):
125
graph = self.make_known_graph(test_graph.ancestry_1)
126
self.assertGDFO(graph, 'rev1', 2)
127
self.assertGDFO(graph, 'rev2b', 3)
128
self.assertGDFO(graph, 'rev2a', 3)
129
self.assertGDFO(graph, 'rev3', 4)
130
self.assertGDFO(graph, 'rev4', 5)
132
def test_gdfo_feature_branch(self):
133
graph = self.make_known_graph(test_graph.feature_branch)
134
self.assertGDFO(graph, 'rev1', 2)
135
self.assertGDFO(graph, 'rev2b', 3)
136
self.assertGDFO(graph, 'rev3b', 4)
138
def test_gdfo_extended_history_shortcut(self):
139
graph = self.make_known_graph(test_graph.extended_history_shortcut)
140
self.assertGDFO(graph, 'a', 2)
141
self.assertGDFO(graph, 'b', 3)
142
self.assertGDFO(graph, 'c', 4)
143
self.assertGDFO(graph, 'd', 5)
144
self.assertGDFO(graph, 'e', 6)
145
self.assertGDFO(graph, 'f', 6)
147
def test_gdfo_with_ghost(self):
148
graph = self.make_known_graph(test_graph.with_ghost)
149
self.assertGDFO(graph, 'f', 2)
150
self.assertGDFO(graph, 'e', 3)
151
self.assertGDFO(graph, 'g', 1)
152
self.assertGDFO(graph, 'b', 4)
153
self.assertGDFO(graph, 'd', 4)
154
self.assertGDFO(graph, 'a', 5)
155
self.assertGDFO(graph, 'c', 5)
157
def test_add_existing_node(self):
158
graph = self.make_known_graph(test_graph.ancestry_1)
159
# Add a node that already exists with identical content
161
self.assertGDFO(graph, 'rev4', 5)
162
graph.add_node('rev4', ['rev3', 'rev2b'])
163
self.assertGDFO(graph, 'rev4', 5)
164
# This also works if we use a tuple rather than a list
165
graph.add_node('rev4', ('rev3', 'rev2b'))
167
def test_add_existing_node_mismatched_parents(self):
168
graph = self.make_known_graph(test_graph.ancestry_1)
169
self.assertRaises(ValueError, graph.add_node, 'rev4',
172
def test_add_node_with_ghost_parent(self):
173
graph = self.make_known_graph(test_graph.ancestry_1)
174
graph.add_node('rev5', ['rev2b', 'revGhost'])
175
self.assertGDFO(graph, 'rev5', 4)
176
self.assertGDFO(graph, 'revGhost', 1)
178
def test_add_new_root(self):
179
graph = self.make_known_graph(test_graph.ancestry_1)
180
graph.add_node('rev5', [])
181
self.assertGDFO(graph, 'rev5', 1)
183
def test_add_with_all_ghost_parents(self):
184
graph = self.make_known_graph(test_graph.ancestry_1)
185
graph.add_node('rev5', ['ghost'])
186
self.assertGDFO(graph, 'rev5', 2)
187
self.assertGDFO(graph, 'ghost', 1)
189
def test_gdfo_after_add_node(self):
190
graph = self.make_known_graph(test_graph.ancestry_1)
191
self.assertEqual([], graph.get_child_keys('rev4'))
192
graph.add_node('rev5', ['rev4'])
193
self.assertEqual(['rev4'], graph.get_parent_keys('rev5'))
194
self.assertEqual(['rev5'], graph.get_child_keys('rev4'))
195
self.assertEqual([], graph.get_child_keys('rev5'))
196
self.assertGDFO(graph, 'rev5', 6)
197
graph.add_node('rev6', ['rev2b'])
198
graph.add_node('rev7', ['rev6'])
199
graph.add_node('rev8', ['rev7', 'rev5'])
200
self.assertGDFO(graph, 'rev5', 6)
201
self.assertGDFO(graph, 'rev6', 4)
202
self.assertGDFO(graph, 'rev7', 5)
203
self.assertGDFO(graph, 'rev8', 7)
205
def test_fill_in_ghost(self):
206
graph = self.make_known_graph(test_graph.with_ghost)
207
# Add in a couple nodes and then fill in the 'ghost' so that it should
208
# cause renumbering of children nodes
209
graph.add_node('x', [])
210
graph.add_node('y', ['x'])
211
graph.add_node('z', ['y'])
212
graph.add_node('g', ['z'])
213
self.assertGDFO(graph, 'f', 2)
214
self.assertGDFO(graph, 'e', 3)
215
self.assertGDFO(graph, 'x', 1)
216
self.assertGDFO(graph, 'y', 2)
217
self.assertGDFO(graph, 'z', 3)
218
self.assertGDFO(graph, 'g', 4)
219
self.assertGDFO(graph, 'b', 4)
220
self.assertGDFO(graph, 'd', 5)
221
self.assertGDFO(graph, 'a', 5)
222
self.assertGDFO(graph, 'c', 6)
225
class TestKnownGraphHeads(TestCaseWithKnownGraph):
227
do_cache = None # Set by load_tests
229
def test_heads_null(self):
230
graph = self.make_known_graph(test_graph.ancestry_1)
231
self.assertEqual(set(['null:']), graph.heads(['null:']))
232
self.assertEqual(set(['rev1']), graph.heads(['null:', 'rev1']))
233
self.assertEqual(set(['rev1']), graph.heads(['rev1', 'null:']))
234
self.assertEqual(set(['rev1']), graph.heads(set(['rev1', 'null:'])))
235
self.assertEqual(set(['rev1']), graph.heads(('rev1', 'null:')))
237
def test_heads_one(self):
238
# A single node will always be a head
239
graph = self.make_known_graph(test_graph.ancestry_1)
240
self.assertEqual(set(['null:']), graph.heads(['null:']))
241
self.assertEqual(set(['rev1']), graph.heads(['rev1']))
242
self.assertEqual(set(['rev2a']), graph.heads(['rev2a']))
243
self.assertEqual(set(['rev2b']), graph.heads(['rev2b']))
244
self.assertEqual(set(['rev3']), graph.heads(['rev3']))
245
self.assertEqual(set(['rev4']), graph.heads(['rev4']))
247
def test_heads_single(self):
248
graph = self.make_known_graph(test_graph.ancestry_1)
249
self.assertEqual(set(['rev4']), graph.heads(['null:', 'rev4']))
250
self.assertEqual(set(['rev2a']), graph.heads(['rev1', 'rev2a']))
251
self.assertEqual(set(['rev2b']), graph.heads(['rev1', 'rev2b']))
252
self.assertEqual(set(['rev3']), graph.heads(['rev1', 'rev3']))
253
self.assertEqual(set(['rev3']), graph.heads(['rev3', 'rev2a']))
254
self.assertEqual(set(['rev4']), graph.heads(['rev1', 'rev4']))
255
self.assertEqual(set(['rev4']), graph.heads(['rev2a', 'rev4']))
256
self.assertEqual(set(['rev4']), graph.heads(['rev2b', 'rev4']))
257
self.assertEqual(set(['rev4']), graph.heads(['rev3', 'rev4']))
259
def test_heads_two_heads(self):
260
graph = self.make_known_graph(test_graph.ancestry_1)
261
self.assertEqual(set(['rev2a', 'rev2b']),
262
graph.heads(['rev2a', 'rev2b']))
263
self.assertEqual(set(['rev3', 'rev2b']),
264
graph.heads(['rev3', 'rev2b']))
266
def test_heads_criss_cross(self):
267
graph = self.make_known_graph(test_graph.criss_cross)
268
self.assertEqual(set(['rev2a']),
269
graph.heads(['rev2a', 'rev1']))
270
self.assertEqual(set(['rev2b']),
271
graph.heads(['rev2b', 'rev1']))
272
self.assertEqual(set(['rev3a']),
273
graph.heads(['rev3a', 'rev1']))
274
self.assertEqual(set(['rev3b']),
275
graph.heads(['rev3b', 'rev1']))
276
self.assertEqual(set(['rev2a', 'rev2b']),
277
graph.heads(['rev2a', 'rev2b']))
278
self.assertEqual(set(['rev3a']),
279
graph.heads(['rev3a', 'rev2a']))
280
self.assertEqual(set(['rev3a']),
281
graph.heads(['rev3a', 'rev2b']))
282
self.assertEqual(set(['rev3a']),
283
graph.heads(['rev3a', 'rev2a', 'rev2b']))
284
self.assertEqual(set(['rev3b']),
285
graph.heads(['rev3b', 'rev2a']))
286
self.assertEqual(set(['rev3b']),
287
graph.heads(['rev3b', 'rev2b']))
288
self.assertEqual(set(['rev3b']),
289
graph.heads(['rev3b', 'rev2a', 'rev2b']))
290
self.assertEqual(set(['rev3a', 'rev3b']),
291
graph.heads(['rev3a', 'rev3b']))
292
self.assertEqual(set(['rev3a', 'rev3b']),
293
graph.heads(['rev3a', 'rev3b', 'rev2a', 'rev2b']))
295
def test_heads_shortcut(self):
296
graph = self.make_known_graph(test_graph.history_shortcut)
297
self.assertEqual(set(['rev2a', 'rev2b', 'rev2c']),
298
graph.heads(['rev2a', 'rev2b', 'rev2c']))
299
self.assertEqual(set(['rev3a', 'rev3b']),
300
graph.heads(['rev3a', 'rev3b']))
301
self.assertEqual(set(['rev3a', 'rev3b']),
302
graph.heads(['rev2a', 'rev3a', 'rev3b']))
303
self.assertEqual(set(['rev2a', 'rev3b']),
304
graph.heads(['rev2a', 'rev3b']))
305
self.assertEqual(set(['rev2c', 'rev3a']),
306
graph.heads(['rev2c', 'rev3a']))
308
def test_heads_linear(self):
309
graph = self.make_known_graph(test_graph.racing_shortcuts)
310
self.assertEqual(set(['w']), graph.heads(['w', 's']))
311
self.assertEqual(set(['z']), graph.heads(['w', 's', 'z']))
312
self.assertEqual(set(['w', 'q']), graph.heads(['w', 's', 'q']))
313
self.assertEqual(set(['z']), graph.heads(['s', 'z']))
315
def test_heads_alt_merge(self):
316
graph = self.make_known_graph(alt_merge)
317
self.assertEqual(set(['c']), graph.heads(['a', 'c']))
319
def test_heads_with_ghost(self):
320
graph = self.make_known_graph(test_graph.with_ghost)
321
self.assertEqual(set(['e', 'g']), graph.heads(['e', 'g']))
322
self.assertEqual(set(['a', 'c']), graph.heads(['a', 'c']))
323
self.assertEqual(set(['a', 'g']), graph.heads(['a', 'g']))
324
self.assertEqual(set(['f', 'g']), graph.heads(['f', 'g']))
325
self.assertEqual(set(['c']), graph.heads(['c', 'g']))
326
self.assertEqual(set(['c']), graph.heads(['c', 'b', 'd', 'g']))
327
self.assertEqual(set(['a', 'c']), graph.heads(['a', 'c', 'e', 'g']))
328
self.assertEqual(set(['a', 'c']), graph.heads(['a', 'c', 'f']))
330
def test_filling_in_ghosts_resets_head_cache(self):
331
graph = self.make_known_graph(test_graph.with_ghost)
332
self.assertEqual(set(['e', 'g']), graph.heads(['e', 'g']))
333
# 'g' is filled in, and decends from 'e', so the heads result is now
335
graph.add_node('g', ['e'])
336
self.assertEqual(set(['g']), graph.heads(['e', 'g']))
339
class TestKnownGraphTopoSort(TestCaseWithKnownGraph):
341
def assertTopoSortOrder(self, ancestry):
342
"""Check topo_sort and iter_topo_order is genuinely topological order.
344
For every child in the graph, check if it comes after all of it's
347
graph = self.make_known_graph(ancestry)
348
sort_result = graph.topo_sort()
349
# We should have an entry in sort_result for every entry present in the
351
self.assertEqual(len(ancestry), len(sort_result))
352
node_idx = dict((node, idx) for idx, node in enumerate(sort_result))
353
for node in sort_result:
354
parents = ancestry[node]
355
for parent in parents:
356
if parent not in ancestry:
359
if node_idx[node] <= node_idx[parent]:
360
self.fail("parent %s must come before child %s:\n%s"
361
% (parent, node, sort_result))
363
def test_topo_sort_empty(self):
364
"""TopoSort empty list"""
365
self.assertTopoSortOrder({})
367
def test_topo_sort_easy(self):
368
"""TopoSort list with one node"""
369
self.assertTopoSortOrder({0: []})
371
def test_topo_sort_cycle(self):
372
"""TopoSort traps graph with cycles"""
373
g = self.make_known_graph({0: [1],
375
self.assertRaises(errors.GraphCycleError, g.topo_sort)
377
def test_topo_sort_cycle_2(self):
378
"""TopoSort traps graph with longer cycle"""
379
g = self.make_known_graph({0: [1],
382
self.assertRaises(errors.GraphCycleError, g.topo_sort)
384
def test_topo_sort_cycle_with_tail(self):
385
"""TopoSort traps graph with longer cycle"""
386
g = self.make_known_graph({0: [1],
391
self.assertRaises(errors.GraphCycleError, g.topo_sort)
393
def test_topo_sort_1(self):
394
"""TopoSort simple nontrivial graph"""
395
self.assertTopoSortOrder({0: [3],
401
def test_topo_sort_partial(self):
402
"""Topological sort with partial ordering.
404
Multiple correct orderings are possible, so test for
405
correctness, not for exact match on the resulting list.
407
self.assertTopoSortOrder({0: [],
417
def test_topo_sort_ghost_parent(self):
418
"""Sort nodes, but don't include some parents in the output"""
419
self.assertTopoSortOrder({0: [1],
423
class TestKnownGraphMergeSort(TestCaseWithKnownGraph):
425
def assertSortAndIterate(self, ancestry, branch_tip, result_list):
426
"""Check that merge based sorting and iter_topo_order on graph works."""
427
graph = self.make_known_graph(ancestry)
428
value = graph.merge_sort(branch_tip)
429
value = [(n.key, n.merge_depth, n.revno, n.end_of_merge)
431
if result_list != value:
432
self.assertEqualDiff(pprint.pformat(result_list),
433
pprint.pformat(value))
435
def test_merge_sort_empty(self):
436
# sorting of an emptygraph does not error
437
self.assertSortAndIterate({}, None, [])
438
self.assertSortAndIterate({}, NULL_REVISION, [])
439
self.assertSortAndIterate({}, (NULL_REVISION,), [])
441
def test_merge_sort_not_empty_no_tip(self):
442
# merge sorting of a branch starting with None should result
443
# in an empty list: no revisions are dragged in.
444
self.assertSortAndIterate({0: []}, None, [])
445
self.assertSortAndIterate({0: []}, NULL_REVISION, [])
446
self.assertSortAndIterate({0: []}, (NULL_REVISION,), [])
448
def test_merge_sort_one_revision(self):
449
# sorting with one revision as the tip returns the correct fields:
450
# sequence - 0, revision id, merge depth - 0, end_of_merge
451
self.assertSortAndIterate({'id': []},
453
[('id', 0, (1,), True)])
455
def test_sequence_numbers_increase_no_merges(self):
456
# emit a few revisions with no merges to check the sequence
457
# numbering works in trivial cases
458
self.assertSortAndIterate(
463
[('C', 0, (3,), False),
464
('B', 0, (2,), False),
465
('A', 0, (1,), True),
469
def test_sequence_numbers_increase_with_merges(self):
470
# test that sequence numbers increase across merges
471
self.assertSortAndIterate(
476
[('C', 0, (2,), False),
477
('B', 1, (1,1,1), True),
478
('A', 0, (1,), True),
482
def test_merge_sort_race(self):
498
self.assertSortAndIterate(graph, 'F',
499
[('F', 0, (3,), False),
500
('D', 1, (2,2,1), False),
501
('C', 2, (2,1,1), True),
502
('B', 0, (2,), False),
503
('A', 0, (1,), True),
521
self.assertSortAndIterate(graph, 'F',
522
[('F', 0, (3,), False),
523
('D', 1, (2,1,2), False),
524
('C', 2, (2,2,1), True),
525
('X', 1, (2,1,1), True),
526
('B', 0, (2,), False),
527
('A', 0, (1,), True),
530
def test_merge_depth_with_nested_merges(self):
531
# the merge depth marker should reflect the depth of the revision
532
# in terms of merges out from the mainline
533
# revid, depth, parents:
542
self.assertSortAndIterate(
553
[('A', 0, (3,), False),
554
('B', 1, (1,3,2), False),
555
('C', 1, (1,3,1), True),
556
('D', 0, (2,), False),
557
('E', 1, (1,1,2), False),
558
('F', 2, (1,2,1), True),
559
('G', 1, (1,1,1), True),
560
('H', 0, (1,), True),
564
def test_dotted_revnos_with_simple_merges(self):
569
# D E F 3, 1.1.2, 1.2.1
571
# G H I 4, 1.2.2, 1.3.1
576
self.assertSortAndIterate(
591
[('L', 0, (6,), False),
592
('K', 1, (1,3,2), False),
593
('I', 1, (1,3,1), True),
594
('J', 0, (5,), False),
595
('H', 1, (1,2,2), False),
596
('F', 1, (1,2,1), True),
597
('G', 0, (4,), False),
598
('E', 1, (1,1,2), False),
599
('C', 1, (1,1,1), True),
600
('D', 0, (3,), False),
601
('B', 0, (2,), False),
602
('A', 0, (1,), True),
605
# Adding a shortcut from the first revision should not change any of
606
# the existing numbers
607
self.assertSortAndIterate(
624
[('N', 0, (7,), False),
625
('M', 1, (1,4,1), True),
626
('L', 0, (6,), False),
627
('K', 1, (1,3,2), False),
628
('I', 1, (1,3,1), True),
629
('J', 0, (5,), False),
630
('H', 1, (1,2,2), False),
631
('F', 1, (1,2,1), True),
632
('G', 0, (4,), False),
633
('E', 1, (1,1,2), False),
634
('C', 1, (1,1,1), True),
635
('D', 0, (3,), False),
636
('B', 0, (2,), False),
637
('A', 0, (1,), True),
641
def test_end_of_merge_not_last_revision_in_branch(self):
642
# within a branch only the last revision gets an
643
# end of merge marker.
644
self.assertSortAndIterate(
649
[('A', 0, (2,), False),
654
def test_end_of_merge_multiple_revisions_merged_at_once(self):
655
# when multiple branches are merged at once, both of their
656
# branch-endpoints should be listed as end-of-merge.
657
# Also, the order of the multiple merges should be
658
# left-right shown top to bottom.
659
# * means end of merge
668
self.assertSortAndIterate(
669
{'A': ['H', 'B', 'E'],
679
[('A', 0, (2,), False),
680
('B', 1, (1,3,2), False),
681
('C', 2, (1,4,1), True),
682
('D', 1, (1,3,1), True),
683
('E', 1, (1,1,2), False),
684
('F', 2, (1,2,1), True),
685
('G', 1, (1,1,1), True),
686
('H', 0, (1,), True),
690
def test_parallel_root_sequence_numbers_increase_with_merges(self):
691
"""When there are parallel roots, check their revnos."""
692
self.assertSortAndIterate(
697
[('C', 0, (2,), False),
698
('B', 1, (0,1,1), True),
699
('A', 0, (1,), True),
703
def test_revnos_are_globally_assigned(self):
704
"""revnos are assigned according to the revision they derive from."""
705
# in this test we setup a number of branches that all derive from
706
# the first revision, and then merge them one at a time, which
707
# should give the revisions as they merge numbers still deriving from
708
# the revision were based on.
709
# merge 3: J: ['G', 'I']
713
# merge 2: G: ['D', 'F']
717
# merge 1: D: ['A', 'C']
722
self.assertSortAndIterate(
735
[('J', 0, (4,), False),
736
('I', 1, (1,3,2), False),
737
('H', 1, (1,3,1), True),
738
('G', 0, (3,), False),
739
('F', 1, (1,2,2), False),
740
('E', 1, (1,2,1), True),
741
('D', 0, (2,), False),
742
('C', 1, (1,1,2), False),
743
('B', 1, (1,1,1), True),
744
('A', 0, (1,), True),
748
def test_roots_and_sub_branches_versus_ghosts(self):
749
"""Extra roots and their mini branches use the same numbering.
751
All of them use the 0-node numbering.
764
self.assertSortAndIterate(
785
[('R', 0, (6,), False),
786
('Q', 1, (0,4,5), False),
787
('P', 2, (0,6,1), True),
788
('O', 1, (0,4,4), False),
789
('N', 1, (0,4,3), False),
790
('M', 2, (0,5,1), True),
791
('L', 1, (0,4,2), False),
792
('K', 1, (0,4,1), True),
793
('J', 0, (5,), False),
794
('I', 1, (0,3,1), True),
795
('H', 0, (4,), False),
796
('G', 1, (0,1,3), False),
797
('F', 2, (0,2,1), True),
798
('E', 1, (0,1,2), False),
799
('D', 1, (0,1,1), True),
800
('C', 0, (3,), False),
801
('B', 0, (2,), False),
802
('A', 0, (1,), True),
806
def test_ghost(self):
807
# merge_sort should be able to ignore ghosts
813
self.assertSortAndIterate(
819
[('C', 0, (3,), False),
820
('B', 0, (2,), False),
821
('A', 0, (1,), True),
824
def test_lefthand_ghost(self):
830
self.assertSortAndIterate(
834
[('B', 0, (2,), False),
835
('A', 0, (1,), True),
838
def test_graph_cycle(self):
839
# merge_sort should fail with a simple error when a graph cycle is
851
self.assertRaises(errors.GraphCycleError,
852
self.assertSortAndIterate,
863
class TestKnownGraphStableReverseTopoSort(TestCaseWithKnownGraph):
864
"""Test the sort order returned by gc_sort."""
866
def assertSorted(self, expected, parent_map):
867
graph = self.make_known_graph(parent_map)
868
value = graph.gc_sort()
869
if expected != value:
870
self.assertEqualDiff(pprint.pformat(expected),
871
pprint.pformat(value))
873
def test_empty(self):
874
self.assertSorted([], {})
876
def test_single(self):
877
self.assertSorted(['a'], {'a':()})
878
self.assertSorted([('a',)], {('a',):()})
879
self.assertSorted([('F', 'a')], {('F', 'a'):()})
881
def test_linear(self):
882
self.assertSorted(['c', 'b', 'a'], {'a':(), 'b':('a',), 'c':('b',)})
883
self.assertSorted([('c',), ('b',), ('a',)],
884
{('a',):(), ('b',): (('a',),), ('c',): (('b',),)})
885
self.assertSorted([('F', 'c'), ('F', 'b'), ('F', 'a')],
886
{('F', 'a'):(), ('F', 'b'): (('F', 'a'),),
887
('F', 'c'): (('F', 'b'),)})
889
def test_mixed_ancestries(self):
890
# Each prefix should be sorted separately
891
self.assertSorted([('F', 'c'), ('F', 'b'), ('F', 'a'),
892
('G', 'c'), ('G', 'b'), ('G', 'a'),
893
('Q', 'c'), ('Q', 'b'), ('Q', 'a'),
895
{('F', 'a'):(), ('F', 'b'): (('F', 'a'),),
896
('F', 'c'): (('F', 'b'),),
897
('G', 'a'):(), ('G', 'b'): (('G', 'a'),),
898
('G', 'c'): (('G', 'b'),),
899
('Q', 'a'):(), ('Q', 'b'): (('Q', 'a'),),
900
('Q', 'c'): (('Q', 'b'),),
903
def test_stable_sorting(self):
904
# the sort order should be stable even when extra nodes are added
905
self.assertSorted(['b', 'c', 'a'],
906
{'a':(), 'b':('a',), 'c':('a',)})
907
self.assertSorted(['b', 'c', 'd', 'a'],
908
{'a':(), 'b':('a',), 'c':('a',), 'd':('a',)})
909
self.assertSorted(['b', 'c', 'd', 'a'],
910
{'a':(), 'b':('a',), 'c':('a',), 'd':('a',)})
911
self.assertSorted(['Z', 'b', 'c', 'd', 'a'],
912
{'a':(), 'b':('a',), 'c':('a',), 'd':('a',),
914
self.assertSorted(['e', 'b', 'c', 'f', 'Z', 'd', 'a'],
915
{'a':(), 'b':('a',), 'c':('a',), 'd':('a',),
921
def test_skip_ghost(self):
922
self.assertSorted(['b', 'c', 'a'],
923
{'a':(), 'b':('a', 'ghost'), 'c':('a',)})
925
def test_skip_mainline_ghost(self):
926
self.assertSorted(['b', 'c', 'a'],
927
{'a':(), 'b':('ghost', 'a'), 'c':('a',)})