1
# Copyright (C) 2009, 2010 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
29
from bzrlib.tests.scenarios import load_tests_apply_scenarios
32
def caching_scenarios():
34
('python', {'module': _known_graph_py, 'do_cache': True}),
36
if compiled_known_graph_feature.available():
37
scenarios.append(('C', {'module': compiled_known_graph_feature.module,
42
def non_caching_scenarios():
44
('python-nocache', {'module': _known_graph_py, 'do_cache': False}),
46
if compiled_known_graph_feature.available():
48
('C-nocache', {'module': compiled_known_graph_feature.module,
53
load_tests = load_tests_apply_scenarios
56
compiled_known_graph_feature = tests.ModuleAvailableFeature(
57
'bzrlib._known_graph_pyx')
67
alt_merge = {'a': [], 'b': ['a'], 'c': ['b'], 'd': ['a', 'c']}
70
class TestCaseWithKnownGraph(tests.TestCase):
72
scenarios = caching_scenarios()
73
module = None # Set by load_tests
75
def make_known_graph(self, ancestry):
76
return self.module.KnownGraph(ancestry, do_cache=self.do_cache)
79
class TestKnownGraph(TestCaseWithKnownGraph):
81
def assertGDFO(self, graph, rev, gdfo):
82
node = graph._nodes[rev]
83
self.assertEqual(gdfo, node.gdfo)
85
def test_children_ancestry1(self):
86
graph = self.make_known_graph(test_graph.ancestry_1)
87
self.assertEqual(['rev1'], graph.get_child_keys(NULL_REVISION))
88
self.assertEqual(['rev2a', 'rev2b'],
89
sorted(graph.get_child_keys('rev1')))
90
self.assertEqual(['rev3'], graph.get_child_keys('rev2a'))
91
self.assertEqual(['rev4'], graph.get_child_keys('rev3'))
92
self.assertEqual(['rev4'], graph.get_child_keys('rev2b'))
93
self.assertRaises(KeyError, graph.get_child_keys, 'not_in_graph')
95
def test_parent_ancestry1(self):
96
graph = self.make_known_graph(test_graph.ancestry_1)
97
self.assertEqual([NULL_REVISION], graph.get_parent_keys('rev1'))
98
self.assertEqual(['rev1'], graph.get_parent_keys('rev2a'))
99
self.assertEqual(['rev1'], graph.get_parent_keys('rev2b'))
100
self.assertEqual(['rev2a'], graph.get_parent_keys('rev3'))
101
self.assertEqual(['rev2b', 'rev3'],
102
sorted(graph.get_parent_keys('rev4')))
103
self.assertRaises(KeyError, graph.get_child_keys, 'not_in_graph')
105
def test_parent_with_ghost(self):
106
graph = self.make_known_graph(test_graph.with_ghost)
107
self.assertEqual(None, graph.get_parent_keys('g'))
109
def test_gdfo_ancestry_1(self):
110
graph = self.make_known_graph(test_graph.ancestry_1)
111
self.assertGDFO(graph, 'rev1', 2)
112
self.assertGDFO(graph, 'rev2b', 3)
113
self.assertGDFO(graph, 'rev2a', 3)
114
self.assertGDFO(graph, 'rev3', 4)
115
self.assertGDFO(graph, 'rev4', 5)
117
def test_gdfo_feature_branch(self):
118
graph = self.make_known_graph(test_graph.feature_branch)
119
self.assertGDFO(graph, 'rev1', 2)
120
self.assertGDFO(graph, 'rev2b', 3)
121
self.assertGDFO(graph, 'rev3b', 4)
123
def test_gdfo_extended_history_shortcut(self):
124
graph = self.make_known_graph(test_graph.extended_history_shortcut)
125
self.assertGDFO(graph, 'a', 2)
126
self.assertGDFO(graph, 'b', 3)
127
self.assertGDFO(graph, 'c', 4)
128
self.assertGDFO(graph, 'd', 5)
129
self.assertGDFO(graph, 'e', 6)
130
self.assertGDFO(graph, 'f', 6)
132
def test_gdfo_with_ghost(self):
133
graph = self.make_known_graph(test_graph.with_ghost)
134
self.assertGDFO(graph, 'f', 2)
135
self.assertGDFO(graph, 'e', 3)
136
self.assertGDFO(graph, 'g', 1)
137
self.assertGDFO(graph, 'b', 4)
138
self.assertGDFO(graph, 'd', 4)
139
self.assertGDFO(graph, 'a', 5)
140
self.assertGDFO(graph, 'c', 5)
142
def test_add_existing_node(self):
143
graph = self.make_known_graph(test_graph.ancestry_1)
144
# Add a node that already exists with identical content
146
self.assertGDFO(graph, 'rev4', 5)
147
graph.add_node('rev4', ['rev3', 'rev2b'])
148
self.assertGDFO(graph, 'rev4', 5)
149
# This also works if we use a tuple rather than a list
150
graph.add_node('rev4', ('rev3', 'rev2b'))
152
def test_add_existing_node_mismatched_parents(self):
153
graph = self.make_known_graph(test_graph.ancestry_1)
154
self.assertRaises(ValueError, graph.add_node, 'rev4',
157
def test_add_node_with_ghost_parent(self):
158
graph = self.make_known_graph(test_graph.ancestry_1)
159
graph.add_node('rev5', ['rev2b', 'revGhost'])
160
self.assertGDFO(graph, 'rev5', 4)
161
self.assertGDFO(graph, 'revGhost', 1)
163
def test_add_new_root(self):
164
graph = self.make_known_graph(test_graph.ancestry_1)
165
graph.add_node('rev5', [])
166
self.assertGDFO(graph, 'rev5', 1)
168
def test_add_with_all_ghost_parents(self):
169
graph = self.make_known_graph(test_graph.ancestry_1)
170
graph.add_node('rev5', ['ghost'])
171
self.assertGDFO(graph, 'rev5', 2)
172
self.assertGDFO(graph, 'ghost', 1)
174
def test_gdfo_after_add_node(self):
175
graph = self.make_known_graph(test_graph.ancestry_1)
176
self.assertEqual([], graph.get_child_keys('rev4'))
177
graph.add_node('rev5', ['rev4'])
178
self.assertEqual(['rev4'], graph.get_parent_keys('rev5'))
179
self.assertEqual(['rev5'], graph.get_child_keys('rev4'))
180
self.assertEqual([], graph.get_child_keys('rev5'))
181
self.assertGDFO(graph, 'rev5', 6)
182
graph.add_node('rev6', ['rev2b'])
183
graph.add_node('rev7', ['rev6'])
184
graph.add_node('rev8', ['rev7', 'rev5'])
185
self.assertGDFO(graph, 'rev5', 6)
186
self.assertGDFO(graph, 'rev6', 4)
187
self.assertGDFO(graph, 'rev7', 5)
188
self.assertGDFO(graph, 'rev8', 7)
190
def test_fill_in_ghost(self):
191
graph = self.make_known_graph(test_graph.with_ghost)
192
# Add in a couple nodes and then fill in the 'ghost' so that it should
193
# cause renumbering of children nodes
194
graph.add_node('x', [])
195
graph.add_node('y', ['x'])
196
graph.add_node('z', ['y'])
197
graph.add_node('g', ['z'])
198
self.assertGDFO(graph, 'f', 2)
199
self.assertGDFO(graph, 'e', 3)
200
self.assertGDFO(graph, 'x', 1)
201
self.assertGDFO(graph, 'y', 2)
202
self.assertGDFO(graph, 'z', 3)
203
self.assertGDFO(graph, 'g', 4)
204
self.assertGDFO(graph, 'b', 4)
205
self.assertGDFO(graph, 'd', 5)
206
self.assertGDFO(graph, 'a', 5)
207
self.assertGDFO(graph, 'c', 6)
210
class TestKnownGraphHeads(TestCaseWithKnownGraph):
212
scenarios = caching_scenarios() + non_caching_scenarios()
213
do_cache = None # Set by load_tests
215
def test_heads_null(self):
216
graph = self.make_known_graph(test_graph.ancestry_1)
217
self.assertEqual(set(['null:']), graph.heads(['null:']))
218
self.assertEqual(set(['rev1']), graph.heads(['null:', 'rev1']))
219
self.assertEqual(set(['rev1']), graph.heads(['rev1', 'null:']))
220
self.assertEqual(set(['rev1']), graph.heads(set(['rev1', 'null:'])))
221
self.assertEqual(set(['rev1']), graph.heads(('rev1', 'null:')))
223
def test_heads_one(self):
224
# A single node will always be a head
225
graph = self.make_known_graph(test_graph.ancestry_1)
226
self.assertEqual(set(['null:']), graph.heads(['null:']))
227
self.assertEqual(set(['rev1']), graph.heads(['rev1']))
228
self.assertEqual(set(['rev2a']), graph.heads(['rev2a']))
229
self.assertEqual(set(['rev2b']), graph.heads(['rev2b']))
230
self.assertEqual(set(['rev3']), graph.heads(['rev3']))
231
self.assertEqual(set(['rev4']), graph.heads(['rev4']))
233
def test_heads_single(self):
234
graph = self.make_known_graph(test_graph.ancestry_1)
235
self.assertEqual(set(['rev4']), graph.heads(['null:', 'rev4']))
236
self.assertEqual(set(['rev2a']), graph.heads(['rev1', 'rev2a']))
237
self.assertEqual(set(['rev2b']), graph.heads(['rev1', 'rev2b']))
238
self.assertEqual(set(['rev3']), graph.heads(['rev1', 'rev3']))
239
self.assertEqual(set(['rev3']), graph.heads(['rev3', 'rev2a']))
240
self.assertEqual(set(['rev4']), graph.heads(['rev1', 'rev4']))
241
self.assertEqual(set(['rev4']), graph.heads(['rev2a', 'rev4']))
242
self.assertEqual(set(['rev4']), graph.heads(['rev2b', 'rev4']))
243
self.assertEqual(set(['rev4']), graph.heads(['rev3', 'rev4']))
245
def test_heads_two_heads(self):
246
graph = self.make_known_graph(test_graph.ancestry_1)
247
self.assertEqual(set(['rev2a', 'rev2b']),
248
graph.heads(['rev2a', 'rev2b']))
249
self.assertEqual(set(['rev3', 'rev2b']),
250
graph.heads(['rev3', 'rev2b']))
252
def test_heads_criss_cross(self):
253
graph = self.make_known_graph(test_graph.criss_cross)
254
self.assertEqual(set(['rev2a']),
255
graph.heads(['rev2a', 'rev1']))
256
self.assertEqual(set(['rev2b']),
257
graph.heads(['rev2b', 'rev1']))
258
self.assertEqual(set(['rev3a']),
259
graph.heads(['rev3a', 'rev1']))
260
self.assertEqual(set(['rev3b']),
261
graph.heads(['rev3b', 'rev1']))
262
self.assertEqual(set(['rev2a', 'rev2b']),
263
graph.heads(['rev2a', 'rev2b']))
264
self.assertEqual(set(['rev3a']),
265
graph.heads(['rev3a', 'rev2a']))
266
self.assertEqual(set(['rev3a']),
267
graph.heads(['rev3a', 'rev2b']))
268
self.assertEqual(set(['rev3a']),
269
graph.heads(['rev3a', 'rev2a', 'rev2b']))
270
self.assertEqual(set(['rev3b']),
271
graph.heads(['rev3b', 'rev2a']))
272
self.assertEqual(set(['rev3b']),
273
graph.heads(['rev3b', 'rev2b']))
274
self.assertEqual(set(['rev3b']),
275
graph.heads(['rev3b', 'rev2a', 'rev2b']))
276
self.assertEqual(set(['rev3a', 'rev3b']),
277
graph.heads(['rev3a', 'rev3b']))
278
self.assertEqual(set(['rev3a', 'rev3b']),
279
graph.heads(['rev3a', 'rev3b', 'rev2a', 'rev2b']))
281
def test_heads_shortcut(self):
282
graph = self.make_known_graph(test_graph.history_shortcut)
283
self.assertEqual(set(['rev2a', 'rev2b', 'rev2c']),
284
graph.heads(['rev2a', 'rev2b', 'rev2c']))
285
self.assertEqual(set(['rev3a', 'rev3b']),
286
graph.heads(['rev3a', 'rev3b']))
287
self.assertEqual(set(['rev3a', 'rev3b']),
288
graph.heads(['rev2a', 'rev3a', 'rev3b']))
289
self.assertEqual(set(['rev2a', 'rev3b']),
290
graph.heads(['rev2a', 'rev3b']))
291
self.assertEqual(set(['rev2c', 'rev3a']),
292
graph.heads(['rev2c', 'rev3a']))
294
def test_heads_linear(self):
295
graph = self.make_known_graph(test_graph.racing_shortcuts)
296
self.assertEqual(set(['w']), graph.heads(['w', 's']))
297
self.assertEqual(set(['z']), graph.heads(['w', 's', 'z']))
298
self.assertEqual(set(['w', 'q']), graph.heads(['w', 's', 'q']))
299
self.assertEqual(set(['z']), graph.heads(['s', 'z']))
301
def test_heads_alt_merge(self):
302
graph = self.make_known_graph(alt_merge)
303
self.assertEqual(set(['c']), graph.heads(['a', 'c']))
305
def test_heads_with_ghost(self):
306
graph = self.make_known_graph(test_graph.with_ghost)
307
self.assertEqual(set(['e', 'g']), graph.heads(['e', 'g']))
308
self.assertEqual(set(['a', 'c']), graph.heads(['a', 'c']))
309
self.assertEqual(set(['a', 'g']), graph.heads(['a', 'g']))
310
self.assertEqual(set(['f', 'g']), graph.heads(['f', 'g']))
311
self.assertEqual(set(['c']), graph.heads(['c', 'g']))
312
self.assertEqual(set(['c']), graph.heads(['c', 'b', 'd', 'g']))
313
self.assertEqual(set(['a', 'c']), graph.heads(['a', 'c', 'e', 'g']))
314
self.assertEqual(set(['a', 'c']), graph.heads(['a', 'c', 'f']))
316
def test_filling_in_ghosts_resets_head_cache(self):
317
graph = self.make_known_graph(test_graph.with_ghost)
318
self.assertEqual(set(['e', 'g']), graph.heads(['e', 'g']))
319
# 'g' is filled in, and decends from 'e', so the heads result is now
321
graph.add_node('g', ['e'])
322
self.assertEqual(set(['g']), graph.heads(['e', 'g']))
325
class TestKnownGraphTopoSort(TestCaseWithKnownGraph):
327
def assertTopoSortOrder(self, ancestry):
328
"""Check topo_sort and iter_topo_order is genuinely topological order.
330
For every child in the graph, check if it comes after all of it's
333
graph = self.make_known_graph(ancestry)
334
sort_result = graph.topo_sort()
335
# We should have an entry in sort_result for every entry present in the
337
self.assertEqual(len(ancestry), len(sort_result))
338
node_idx = dict((node, idx) for idx, node in enumerate(sort_result))
339
for node in sort_result:
340
parents = ancestry[node]
341
for parent in parents:
342
if parent not in ancestry:
345
if node_idx[node] <= node_idx[parent]:
346
self.fail("parent %s must come before child %s:\n%s"
347
% (parent, node, sort_result))
349
def test_topo_sort_empty(self):
350
"""TopoSort empty list"""
351
self.assertTopoSortOrder({})
353
def test_topo_sort_easy(self):
354
"""TopoSort list with one node"""
355
self.assertTopoSortOrder({0: []})
357
def test_topo_sort_cycle(self):
358
"""TopoSort traps graph with cycles"""
359
g = self.make_known_graph({0: [1],
361
self.assertRaises(errors.GraphCycleError, g.topo_sort)
363
def test_topo_sort_cycle_2(self):
364
"""TopoSort traps graph with longer cycle"""
365
g = self.make_known_graph({0: [1],
368
self.assertRaises(errors.GraphCycleError, g.topo_sort)
370
def test_topo_sort_cycle_with_tail(self):
371
"""TopoSort traps graph with longer cycle"""
372
g = self.make_known_graph({0: [1],
377
self.assertRaises(errors.GraphCycleError, g.topo_sort)
379
def test_topo_sort_1(self):
380
"""TopoSort simple nontrivial graph"""
381
self.assertTopoSortOrder({0: [3],
387
def test_topo_sort_partial(self):
388
"""Topological sort with partial ordering.
390
Multiple correct orderings are possible, so test for
391
correctness, not for exact match on the resulting list.
393
self.assertTopoSortOrder({0: [],
403
def test_topo_sort_ghost_parent(self):
404
"""Sort nodes, but don't include some parents in the output"""
405
self.assertTopoSortOrder({0: [1],
409
class TestKnownGraphMergeSort(TestCaseWithKnownGraph):
411
def assertSortAndIterate(self, ancestry, branch_tip, result_list):
412
"""Check that merge based sorting and iter_topo_order on graph works."""
413
graph = self.make_known_graph(ancestry)
414
value = graph.merge_sort(branch_tip)
415
value = [(n.key, n.merge_depth, n.revno, n.end_of_merge)
417
if result_list != value:
418
self.assertEqualDiff(pprint.pformat(result_list),
419
pprint.pformat(value))
421
def test_merge_sort_empty(self):
422
# sorting of an emptygraph does not error
423
self.assertSortAndIterate({}, None, [])
424
self.assertSortAndIterate({}, NULL_REVISION, [])
425
self.assertSortAndIterate({}, (NULL_REVISION,), [])
427
def test_merge_sort_not_empty_no_tip(self):
428
# merge sorting of a branch starting with None should result
429
# in an empty list: no revisions are dragged in.
430
self.assertSortAndIterate({0: []}, None, [])
431
self.assertSortAndIterate({0: []}, NULL_REVISION, [])
432
self.assertSortAndIterate({0: []}, (NULL_REVISION,), [])
434
def test_merge_sort_one_revision(self):
435
# sorting with one revision as the tip returns the correct fields:
436
# sequence - 0, revision id, merge depth - 0, end_of_merge
437
self.assertSortAndIterate({'id': []},
439
[('id', 0, (1,), True)])
441
def test_sequence_numbers_increase_no_merges(self):
442
# emit a few revisions with no merges to check the sequence
443
# numbering works in trivial cases
444
self.assertSortAndIterate(
449
[('C', 0, (3,), False),
450
('B', 0, (2,), False),
451
('A', 0, (1,), True),
455
def test_sequence_numbers_increase_with_merges(self):
456
# test that sequence numbers increase across merges
457
self.assertSortAndIterate(
462
[('C', 0, (2,), False),
463
('B', 1, (1,1,1), True),
464
('A', 0, (1,), True),
468
def test_merge_sort_race(self):
484
self.assertSortAndIterate(graph, 'F',
485
[('F', 0, (3,), False),
486
('D', 1, (2,2,1), False),
487
('C', 2, (2,1,1), True),
488
('B', 0, (2,), False),
489
('A', 0, (1,), True),
507
self.assertSortAndIterate(graph, 'F',
508
[('F', 0, (3,), False),
509
('D', 1, (2,1,2), False),
510
('C', 2, (2,2,1), True),
511
('X', 1, (2,1,1), True),
512
('B', 0, (2,), False),
513
('A', 0, (1,), True),
516
def test_merge_depth_with_nested_merges(self):
517
# the merge depth marker should reflect the depth of the revision
518
# in terms of merges out from the mainline
519
# revid, depth, parents:
528
self.assertSortAndIterate(
539
[('A', 0, (3,), False),
540
('B', 1, (1,3,2), False),
541
('C', 1, (1,3,1), True),
542
('D', 0, (2,), False),
543
('E', 1, (1,1,2), False),
544
('F', 2, (1,2,1), True),
545
('G', 1, (1,1,1), True),
546
('H', 0, (1,), True),
550
def test_dotted_revnos_with_simple_merges(self):
555
# D E F 3, 1.1.2, 1.2.1
557
# G H I 4, 1.2.2, 1.3.1
562
self.assertSortAndIterate(
577
[('L', 0, (6,), False),
578
('K', 1, (1,3,2), False),
579
('I', 1, (1,3,1), True),
580
('J', 0, (5,), False),
581
('H', 1, (1,2,2), False),
582
('F', 1, (1,2,1), True),
583
('G', 0, (4,), False),
584
('E', 1, (1,1,2), False),
585
('C', 1, (1,1,1), True),
586
('D', 0, (3,), False),
587
('B', 0, (2,), False),
588
('A', 0, (1,), True),
591
# Adding a shortcut from the first revision should not change any of
592
# the existing numbers
593
self.assertSortAndIterate(
610
[('N', 0, (7,), False),
611
('M', 1, (1,4,1), True),
612
('L', 0, (6,), False),
613
('K', 1, (1,3,2), False),
614
('I', 1, (1,3,1), True),
615
('J', 0, (5,), False),
616
('H', 1, (1,2,2), False),
617
('F', 1, (1,2,1), True),
618
('G', 0, (4,), False),
619
('E', 1, (1,1,2), False),
620
('C', 1, (1,1,1), True),
621
('D', 0, (3,), False),
622
('B', 0, (2,), False),
623
('A', 0, (1,), True),
627
def test_end_of_merge_not_last_revision_in_branch(self):
628
# within a branch only the last revision gets an
629
# end of merge marker.
630
self.assertSortAndIterate(
635
[('A', 0, (2,), False),
640
def test_end_of_merge_multiple_revisions_merged_at_once(self):
641
# when multiple branches are merged at once, both of their
642
# branch-endpoints should be listed as end-of-merge.
643
# Also, the order of the multiple merges should be
644
# left-right shown top to bottom.
645
# * means end of merge
654
self.assertSortAndIterate(
655
{'A': ['H', 'B', 'E'],
665
[('A', 0, (2,), False),
666
('B', 1, (1,3,2), False),
667
('C', 2, (1,4,1), True),
668
('D', 1, (1,3,1), True),
669
('E', 1, (1,1,2), False),
670
('F', 2, (1,2,1), True),
671
('G', 1, (1,1,1), True),
672
('H', 0, (1,), True),
676
def test_parallel_root_sequence_numbers_increase_with_merges(self):
677
"""When there are parallel roots, check their revnos."""
678
self.assertSortAndIterate(
683
[('C', 0, (2,), False),
684
('B', 1, (0,1,1), True),
685
('A', 0, (1,), True),
689
def test_revnos_are_globally_assigned(self):
690
"""revnos are assigned according to the revision they derive from."""
691
# in this test we setup a number of branches that all derive from
692
# the first revision, and then merge them one at a time, which
693
# should give the revisions as they merge numbers still deriving from
694
# the revision were based on.
695
# merge 3: J: ['G', 'I']
699
# merge 2: G: ['D', 'F']
703
# merge 1: D: ['A', 'C']
708
self.assertSortAndIterate(
721
[('J', 0, (4,), False),
722
('I', 1, (1,3,2), False),
723
('H', 1, (1,3,1), True),
724
('G', 0, (3,), False),
725
('F', 1, (1,2,2), False),
726
('E', 1, (1,2,1), True),
727
('D', 0, (2,), False),
728
('C', 1, (1,1,2), False),
729
('B', 1, (1,1,1), True),
730
('A', 0, (1,), True),
734
def test_roots_and_sub_branches_versus_ghosts(self):
735
"""Extra roots and their mini branches use the same numbering.
737
All of them use the 0-node numbering.
750
self.assertSortAndIterate(
771
[('R', 0, (6,), False),
772
('Q', 1, (0,4,5), False),
773
('P', 2, (0,6,1), True),
774
('O', 1, (0,4,4), False),
775
('N', 1, (0,4,3), False),
776
('M', 2, (0,5,1), True),
777
('L', 1, (0,4,2), False),
778
('K', 1, (0,4,1), True),
779
('J', 0, (5,), False),
780
('I', 1, (0,3,1), True),
781
('H', 0, (4,), False),
782
('G', 1, (0,1,3), False),
783
('F', 2, (0,2,1), True),
784
('E', 1, (0,1,2), False),
785
('D', 1, (0,1,1), True),
786
('C', 0, (3,), False),
787
('B', 0, (2,), False),
788
('A', 0, (1,), True),
792
def test_ghost(self):
793
# merge_sort should be able to ignore ghosts
799
self.assertSortAndIterate(
805
[('C', 0, (3,), False),
806
('B', 0, (2,), False),
807
('A', 0, (1,), True),
810
def test_lefthand_ghost(self):
816
self.assertSortAndIterate(
820
[('B', 0, (2,), False),
821
('A', 0, (1,), True),
824
def test_graph_cycle(self):
825
# merge_sort should fail with a simple error when a graph cycle is
837
self.assertRaises(errors.GraphCycleError,
838
self.assertSortAndIterate,
849
class TestKnownGraphStableReverseTopoSort(TestCaseWithKnownGraph):
850
"""Test the sort order returned by gc_sort."""
852
def assertSorted(self, expected, parent_map):
853
graph = self.make_known_graph(parent_map)
854
value = graph.gc_sort()
855
if expected != value:
856
self.assertEqualDiff(pprint.pformat(expected),
857
pprint.pformat(value))
859
def test_empty(self):
860
self.assertSorted([], {})
862
def test_single(self):
863
self.assertSorted(['a'], {'a':()})
864
self.assertSorted([('a',)], {('a',):()})
865
self.assertSorted([('F', 'a')], {('F', 'a'):()})
867
def test_linear(self):
868
self.assertSorted(['c', 'b', 'a'], {'a':(), 'b':('a',), 'c':('b',)})
869
self.assertSorted([('c',), ('b',), ('a',)],
870
{('a',):(), ('b',): (('a',),), ('c',): (('b',),)})
871
self.assertSorted([('F', 'c'), ('F', 'b'), ('F', 'a')],
872
{('F', 'a'):(), ('F', 'b'): (('F', 'a'),),
873
('F', 'c'): (('F', 'b'),)})
875
def test_mixed_ancestries(self):
876
# Each prefix should be sorted separately
877
self.assertSorted([('F', 'c'), ('F', 'b'), ('F', 'a'),
878
('G', 'c'), ('G', 'b'), ('G', 'a'),
879
('Q', 'c'), ('Q', 'b'), ('Q', 'a'),
881
{('F', 'a'):(), ('F', 'b'): (('F', 'a'),),
882
('F', 'c'): (('F', 'b'),),
883
('G', 'a'):(), ('G', 'b'): (('G', 'a'),),
884
('G', 'c'): (('G', 'b'),),
885
('Q', 'a'):(), ('Q', 'b'): (('Q', 'a'),),
886
('Q', 'c'): (('Q', 'b'),),
889
def test_stable_sorting(self):
890
# the sort order should be stable even when extra nodes are added
891
self.assertSorted(['b', 'c', 'a'],
892
{'a':(), 'b':('a',), 'c':('a',)})
893
self.assertSorted(['b', 'c', 'd', 'a'],
894
{'a':(), 'b':('a',), 'c':('a',), 'd':('a',)})
895
self.assertSorted(['b', 'c', 'd', 'a'],
896
{'a':(), 'b':('a',), 'c':('a',), 'd':('a',)})
897
self.assertSorted(['Z', 'b', 'c', 'd', 'a'],
898
{'a':(), 'b':('a',), 'c':('a',), 'd':('a',),
900
self.assertSorted(['e', 'b', 'c', 'f', 'Z', 'd', 'a'],
901
{'a':(), 'b':('a',), 'c':('a',), 'd':('a',),
907
def test_skip_ghost(self):
908
self.assertSorted(['b', 'c', 'a'],
909
{'a':(), 'b':('a', 'ghost'), 'c':('a',)})
911
def test_skip_mainline_ghost(self):
912
self.assertSorted(['b', 'c', 'a'],
913
{'a':(), 'b':('ghost', 'a'), 'c':('a',)})