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)
158
class TestKnownGraphHeads(TestCaseWithKnownGraph):
160
do_cache = None # Set by load_tests
162
def test_heads_null(self):
163
graph = self.make_known_graph(test_graph.ancestry_1)
164
self.assertEqual(set(['null:']), graph.heads(['null:']))
165
self.assertEqual(set(['rev1']), graph.heads(['null:', 'rev1']))
166
self.assertEqual(set(['rev1']), graph.heads(['rev1', 'null:']))
167
self.assertEqual(set(['rev1']), graph.heads(set(['rev1', 'null:'])))
168
self.assertEqual(set(['rev1']), graph.heads(('rev1', 'null:')))
170
def test_heads_one(self):
171
# A single node will always be a head
172
graph = self.make_known_graph(test_graph.ancestry_1)
173
self.assertEqual(set(['null:']), graph.heads(['null:']))
174
self.assertEqual(set(['rev1']), graph.heads(['rev1']))
175
self.assertEqual(set(['rev2a']), graph.heads(['rev2a']))
176
self.assertEqual(set(['rev2b']), graph.heads(['rev2b']))
177
self.assertEqual(set(['rev3']), graph.heads(['rev3']))
178
self.assertEqual(set(['rev4']), graph.heads(['rev4']))
180
def test_heads_single(self):
181
graph = self.make_known_graph(test_graph.ancestry_1)
182
self.assertEqual(set(['rev4']), graph.heads(['null:', 'rev4']))
183
self.assertEqual(set(['rev2a']), graph.heads(['rev1', 'rev2a']))
184
self.assertEqual(set(['rev2b']), graph.heads(['rev1', 'rev2b']))
185
self.assertEqual(set(['rev3']), graph.heads(['rev1', 'rev3']))
186
self.assertEqual(set(['rev3']), graph.heads(['rev3', 'rev2a']))
187
self.assertEqual(set(['rev4']), graph.heads(['rev1', 'rev4']))
188
self.assertEqual(set(['rev4']), graph.heads(['rev2a', 'rev4']))
189
self.assertEqual(set(['rev4']), graph.heads(['rev2b', 'rev4']))
190
self.assertEqual(set(['rev4']), graph.heads(['rev3', 'rev4']))
192
def test_heads_two_heads(self):
193
graph = self.make_known_graph(test_graph.ancestry_1)
194
self.assertEqual(set(['rev2a', 'rev2b']),
195
graph.heads(['rev2a', 'rev2b']))
196
self.assertEqual(set(['rev3', 'rev2b']),
197
graph.heads(['rev3', 'rev2b']))
199
def test_heads_criss_cross(self):
200
graph = self.make_known_graph(test_graph.criss_cross)
201
self.assertEqual(set(['rev2a']),
202
graph.heads(['rev2a', 'rev1']))
203
self.assertEqual(set(['rev2b']),
204
graph.heads(['rev2b', 'rev1']))
205
self.assertEqual(set(['rev3a']),
206
graph.heads(['rev3a', 'rev1']))
207
self.assertEqual(set(['rev3b']),
208
graph.heads(['rev3b', 'rev1']))
209
self.assertEqual(set(['rev2a', 'rev2b']),
210
graph.heads(['rev2a', 'rev2b']))
211
self.assertEqual(set(['rev3a']),
212
graph.heads(['rev3a', 'rev2a']))
213
self.assertEqual(set(['rev3a']),
214
graph.heads(['rev3a', 'rev2b']))
215
self.assertEqual(set(['rev3a']),
216
graph.heads(['rev3a', 'rev2a', 'rev2b']))
217
self.assertEqual(set(['rev3b']),
218
graph.heads(['rev3b', 'rev2a']))
219
self.assertEqual(set(['rev3b']),
220
graph.heads(['rev3b', 'rev2b']))
221
self.assertEqual(set(['rev3b']),
222
graph.heads(['rev3b', 'rev2a', 'rev2b']))
223
self.assertEqual(set(['rev3a', 'rev3b']),
224
graph.heads(['rev3a', 'rev3b']))
225
self.assertEqual(set(['rev3a', 'rev3b']),
226
graph.heads(['rev3a', 'rev3b', 'rev2a', 'rev2b']))
228
def test_heads_shortcut(self):
229
graph = self.make_known_graph(test_graph.history_shortcut)
230
self.assertEqual(set(['rev2a', 'rev2b', 'rev2c']),
231
graph.heads(['rev2a', 'rev2b', 'rev2c']))
232
self.assertEqual(set(['rev3a', 'rev3b']),
233
graph.heads(['rev3a', 'rev3b']))
234
self.assertEqual(set(['rev3a', 'rev3b']),
235
graph.heads(['rev2a', 'rev3a', 'rev3b']))
236
self.assertEqual(set(['rev2a', 'rev3b']),
237
graph.heads(['rev2a', 'rev3b']))
238
self.assertEqual(set(['rev2c', 'rev3a']),
239
graph.heads(['rev2c', 'rev3a']))
241
def test_heads_linear(self):
242
graph = self.make_known_graph(test_graph.racing_shortcuts)
243
self.assertEqual(set(['w']), graph.heads(['w', 's']))
244
self.assertEqual(set(['z']), graph.heads(['w', 's', 'z']))
245
self.assertEqual(set(['w', 'q']), graph.heads(['w', 's', 'q']))
246
self.assertEqual(set(['z']), graph.heads(['s', 'z']))
248
def test_heads_alt_merge(self):
249
graph = self.make_known_graph(alt_merge)
250
self.assertEqual(set(['c']), graph.heads(['a', 'c']))
252
def test_heads_with_ghost(self):
253
graph = self.make_known_graph(test_graph.with_ghost)
254
self.assertEqual(set(['e', 'g']), graph.heads(['e', 'g']))
255
self.assertEqual(set(['a', 'c']), graph.heads(['a', 'c']))
256
self.assertEqual(set(['a', 'g']), graph.heads(['a', 'g']))
257
self.assertEqual(set(['f', 'g']), graph.heads(['f', 'g']))
258
self.assertEqual(set(['c']), graph.heads(['c', 'g']))
259
self.assertEqual(set(['c']), graph.heads(['c', 'b', 'd', 'g']))
260
self.assertEqual(set(['a', 'c']), graph.heads(['a', 'c', 'e', 'g']))
261
self.assertEqual(set(['a', 'c']), graph.heads(['a', 'c', 'f']))
264
class TestKnownGraphTopoSort(TestCaseWithKnownGraph):
266
def assertTopoSortOrder(self, ancestry):
267
"""Check topo_sort and iter_topo_order is genuinely topological order.
269
For every child in the graph, check if it comes after all of it's
272
graph = self.make_known_graph(ancestry)
273
sort_result = graph.topo_sort()
274
# We should have an entry in sort_result for every entry present in the
276
self.assertEqual(len(ancestry), len(sort_result))
277
node_idx = dict((node, idx) for idx, node in enumerate(sort_result))
278
for node in sort_result:
279
parents = ancestry[node]
280
for parent in parents:
281
if parent not in ancestry:
284
if node_idx[node] <= node_idx[parent]:
285
self.fail("parent %s must come before child %s:\n%s"
286
% (parent, node, sort_result))
288
def test_topo_sort_empty(self):
289
"""TopoSort empty list"""
290
self.assertTopoSortOrder({})
292
def test_topo_sort_easy(self):
293
"""TopoSort list with one node"""
294
self.assertTopoSortOrder({0: []})
296
def test_topo_sort_cycle(self):
297
"""TopoSort traps graph with cycles"""
298
g = self.make_known_graph({0: [1],
300
self.assertRaises(errors.GraphCycleError, g.topo_sort)
302
def test_topo_sort_cycle_2(self):
303
"""TopoSort traps graph with longer cycle"""
304
g = self.make_known_graph({0: [1],
307
self.assertRaises(errors.GraphCycleError, g.topo_sort)
309
def test_topo_sort_cycle_with_tail(self):
310
"""TopoSort traps graph with longer cycle"""
311
g = self.make_known_graph({0: [1],
316
self.assertRaises(errors.GraphCycleError, g.topo_sort)
318
def test_topo_sort_1(self):
319
"""TopoSort simple nontrivial graph"""
320
self.assertTopoSortOrder({0: [3],
326
def test_topo_sort_partial(self):
327
"""Topological sort with partial ordering.
329
Multiple correct orderings are possible, so test for
330
correctness, not for exact match on the resulting list.
332
self.assertTopoSortOrder({0: [],
342
def test_topo_sort_ghost_parent(self):
343
"""Sort nodes, but don't include some parents in the output"""
344
self.assertTopoSortOrder({0: [1],
348
class TestKnownGraphMergeSort(TestCaseWithKnownGraph):
350
def assertSortAndIterate(self, ancestry, branch_tip, result_list):
351
"""Check that merge based sorting and iter_topo_order on graph works."""
352
graph = self.make_known_graph(ancestry)
353
value = graph.merge_sort(branch_tip)
354
value = [(n.key, n.merge_depth, n.revno, n.end_of_merge)
356
if result_list != value:
357
self.assertEqualDiff(pprint.pformat(result_list),
358
pprint.pformat(value))
360
def test_merge_sort_empty(self):
361
# sorting of an emptygraph does not error
362
self.assertSortAndIterate({}, None, [])
363
self.assertSortAndIterate({}, NULL_REVISION, [])
364
self.assertSortAndIterate({}, (NULL_REVISION,), [])
366
def test_merge_sort_not_empty_no_tip(self):
367
# merge sorting of a branch starting with None should result
368
# in an empty list: no revisions are dragged in.
369
self.assertSortAndIterate({0: []}, None, [])
370
self.assertSortAndIterate({0: []}, NULL_REVISION, [])
371
self.assertSortAndIterate({0: []}, (NULL_REVISION,), [])
373
def test_merge_sort_one_revision(self):
374
# sorting with one revision as the tip returns the correct fields:
375
# sequence - 0, revision id, merge depth - 0, end_of_merge
376
self.assertSortAndIterate({'id': []},
378
[('id', 0, (1,), True)])
380
def test_sequence_numbers_increase_no_merges(self):
381
# emit a few revisions with no merges to check the sequence
382
# numbering works in trivial cases
383
self.assertSortAndIterate(
388
[('C', 0, (3,), False),
389
('B', 0, (2,), False),
390
('A', 0, (1,), True),
394
def test_sequence_numbers_increase_with_merges(self):
395
# test that sequence numbers increase across merges
396
self.assertSortAndIterate(
401
[('C', 0, (2,), False),
402
('B', 1, (1,1,1), True),
403
('A', 0, (1,), True),
407
def test_merge_sort_race(self):
423
self.assertSortAndIterate(graph, 'F',
424
[('F', 0, (3,), False),
425
('D', 1, (2,2,1), False),
426
('C', 2, (2,1,1), True),
427
('B', 0, (2,), False),
428
('A', 0, (1,), True),
446
self.assertSortAndIterate(graph, 'F',
447
[('F', 0, (3,), False),
448
('D', 1, (2,1,2), False),
449
('C', 2, (2,2,1), True),
450
('X', 1, (2,1,1), True),
451
('B', 0, (2,), False),
452
('A', 0, (1,), True),
455
def test_merge_depth_with_nested_merges(self):
456
# the merge depth marker should reflect the depth of the revision
457
# in terms of merges out from the mainline
458
# revid, depth, parents:
467
self.assertSortAndIterate(
478
[('A', 0, (3,), False),
479
('B', 1, (1,3,2), False),
480
('C', 1, (1,3,1), True),
481
('D', 0, (2,), False),
482
('E', 1, (1,1,2), False),
483
('F', 2, (1,2,1), True),
484
('G', 1, (1,1,1), True),
485
('H', 0, (1,), True),
489
def test_dotted_revnos_with_simple_merges(self):
494
# D E F 3, 1.1.2, 1.2.1
496
# G H I 4, 1.2.2, 1.3.1
501
self.assertSortAndIterate(
516
[('L', 0, (6,), False),
517
('K', 1, (1,3,2), False),
518
('I', 1, (1,3,1), True),
519
('J', 0, (5,), False),
520
('H', 1, (1,2,2), False),
521
('F', 1, (1,2,1), True),
522
('G', 0, (4,), False),
523
('E', 1, (1,1,2), False),
524
('C', 1, (1,1,1), True),
525
('D', 0, (3,), False),
526
('B', 0, (2,), False),
527
('A', 0, (1,), True),
530
# Adding a shortcut from the first revision should not change any of
531
# the existing numbers
532
self.assertSortAndIterate(
549
[('N', 0, (7,), False),
550
('M', 1, (1,4,1), True),
551
('L', 0, (6,), False),
552
('K', 1, (1,3,2), False),
553
('I', 1, (1,3,1), True),
554
('J', 0, (5,), False),
555
('H', 1, (1,2,2), False),
556
('F', 1, (1,2,1), True),
557
('G', 0, (4,), False),
558
('E', 1, (1,1,2), False),
559
('C', 1, (1,1,1), True),
560
('D', 0, (3,), False),
561
('B', 0, (2,), False),
562
('A', 0, (1,), True),
566
def test_end_of_merge_not_last_revision_in_branch(self):
567
# within a branch only the last revision gets an
568
# end of merge marker.
569
self.assertSortAndIterate(
574
[('A', 0, (2,), False),
579
def test_end_of_merge_multiple_revisions_merged_at_once(self):
580
# when multiple branches are merged at once, both of their
581
# branch-endpoints should be listed as end-of-merge.
582
# Also, the order of the multiple merges should be
583
# left-right shown top to bottom.
584
# * means end of merge
593
self.assertSortAndIterate(
594
{'A': ['H', 'B', 'E'],
604
[('A', 0, (2,), False),
605
('B', 1, (1,3,2), False),
606
('C', 2, (1,4,1), True),
607
('D', 1, (1,3,1), True),
608
('E', 1, (1,1,2), False),
609
('F', 2, (1,2,1), True),
610
('G', 1, (1,1,1), True),
611
('H', 0, (1,), True),
615
def test_parallel_root_sequence_numbers_increase_with_merges(self):
616
"""When there are parallel roots, check their revnos."""
617
self.assertSortAndIterate(
622
[('C', 0, (2,), False),
623
('B', 1, (0,1,1), True),
624
('A', 0, (1,), True),
628
def test_revnos_are_globally_assigned(self):
629
"""revnos are assigned according to the revision they derive from."""
630
# in this test we setup a number of branches that all derive from
631
# the first revision, and then merge them one at a time, which
632
# should give the revisions as they merge numbers still deriving from
633
# the revision were based on.
634
# merge 3: J: ['G', 'I']
638
# merge 2: G: ['D', 'F']
642
# merge 1: D: ['A', 'C']
647
self.assertSortAndIterate(
660
[('J', 0, (4,), False),
661
('I', 1, (1,3,2), False),
662
('H', 1, (1,3,1), True),
663
('G', 0, (3,), False),
664
('F', 1, (1,2,2), False),
665
('E', 1, (1,2,1), True),
666
('D', 0, (2,), False),
667
('C', 1, (1,1,2), False),
668
('B', 1, (1,1,1), True),
669
('A', 0, (1,), True),
673
def test_roots_and_sub_branches_versus_ghosts(self):
674
"""Extra roots and their mini branches use the same numbering.
676
All of them use the 0-node numbering.
689
self.assertSortAndIterate(
710
[('R', 0, (6,), False),
711
('Q', 1, (0,4,5), False),
712
('P', 2, (0,6,1), True),
713
('O', 1, (0,4,4), False),
714
('N', 1, (0,4,3), False),
715
('M', 2, (0,5,1), True),
716
('L', 1, (0,4,2), False),
717
('K', 1, (0,4,1), True),
718
('J', 0, (5,), False),
719
('I', 1, (0,3,1), True),
720
('H', 0, (4,), False),
721
('G', 1, (0,1,3), False),
722
('F', 2, (0,2,1), True),
723
('E', 1, (0,1,2), False),
724
('D', 1, (0,1,1), True),
725
('C', 0, (3,), False),
726
('B', 0, (2,), False),
727
('A', 0, (1,), True),
731
def test_ghost(self):
732
# merge_sort should be able to ignore ghosts
738
self.assertSortAndIterate(
744
[('C', 0, (3,), False),
745
('B', 0, (2,), False),
746
('A', 0, (1,), True),
749
def test_lefthand_ghost(self):
755
self.assertSortAndIterate(
759
[('B', 0, (2,), False),
760
('A', 0, (1,), True),
763
def test_graph_cycle(self):
764
# merge_sort should fail with a simple error when a graph cycle is
776
self.assertRaises(errors.GraphCycleError,
777
self.assertSortAndIterate,
788
class TestKnownGraphStableReverseTopoSort(TestCaseWithKnownGraph):
789
"""Test the sort order returned by gc_sort."""
791
def assertSorted(self, expected, parent_map):
792
graph = self.make_known_graph(parent_map)
793
value = graph.gc_sort()
794
if expected != value:
795
self.assertEqualDiff(pprint.pformat(expected),
796
pprint.pformat(value))
798
def test_empty(self):
799
self.assertSorted([], {})
801
def test_single(self):
802
self.assertSorted(['a'], {'a':()})
803
self.assertSorted([('a',)], {('a',):()})
804
self.assertSorted([('F', 'a')], {('F', 'a'):()})
806
def test_linear(self):
807
self.assertSorted(['c', 'b', 'a'], {'a':(), 'b':('a',), 'c':('b',)})
808
self.assertSorted([('c',), ('b',), ('a',)],
809
{('a',):(), ('b',): (('a',),), ('c',): (('b',),)})
810
self.assertSorted([('F', 'c'), ('F', 'b'), ('F', 'a')],
811
{('F', 'a'):(), ('F', 'b'): (('F', 'a'),),
812
('F', 'c'): (('F', 'b'),)})
814
def test_mixed_ancestries(self):
815
# Each prefix should be sorted separately
816
self.assertSorted([('F', 'c'), ('F', 'b'), ('F', 'a'),
817
('G', 'c'), ('G', 'b'), ('G', 'a'),
818
('Q', 'c'), ('Q', 'b'), ('Q', 'a'),
820
{('F', 'a'):(), ('F', 'b'): (('F', 'a'),),
821
('F', 'c'): (('F', 'b'),),
822
('G', 'a'):(), ('G', 'b'): (('G', 'a'),),
823
('G', 'c'): (('G', 'b'),),
824
('Q', 'a'):(), ('Q', 'b'): (('Q', 'a'),),
825
('Q', 'c'): (('Q', 'b'),),
828
def test_stable_sorting(self):
829
# the sort order should be stable even when extra nodes are added
830
self.assertSorted(['b', 'c', 'a'],
831
{'a':(), 'b':('a',), 'c':('a',)})
832
self.assertSorted(['b', 'c', 'd', 'a'],
833
{'a':(), 'b':('a',), 'c':('a',), 'd':('a',)})
834
self.assertSorted(['b', 'c', 'd', 'a'],
835
{'a':(), 'b':('a',), 'c':('a',), 'd':('a',)})
836
self.assertSorted(['Z', 'b', 'c', 'd', 'a'],
837
{'a':(), 'b':('a',), 'c':('a',), 'd':('a',),
839
self.assertSorted(['e', 'b', 'c', 'f', 'Z', 'd', 'a'],
840
{'a':(), 'b':('a',), 'c':('a',), 'd':('a',),
846
def test_skip_ghost(self):
847
self.assertSorted(['b', 'c', 'a'],
848
{'a':(), 'b':('a', 'ghost'), 'c':('a',)})
850
def test_skip_mainline_ghost(self):
851
self.assertSorted(['b', 'c', 'a'],
852
{'a':(), 'b':('ghost', 'a'), 'c':('a',)})