~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_tsort.py

  • Committer: Aaron Bentley
  • Date: 2008-10-16 21:27:10 UTC
  • mfrom: (0.15.26 unshelve)
  • mto: (0.16.74 shelf-ui)
  • mto: This revision was merged to the branch mainline in revision 3820.
  • Revision ID: aaron@aaronbentley.com-20081016212710-h9av3nhk76dvmsv5
Merge with unshelve

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005 Canonical Ltd
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
 
 
18
"""Tests for topological sort."""
 
19
 
 
20
 
 
21
from bzrlib.tests import TestCase
 
22
from bzrlib.tsort import topo_sort, TopoSorter, MergeSorter, merge_sort
 
23
from bzrlib.errors import GraphCycleError
 
24
from bzrlib.revision import NULL_REVISION
 
25
 
 
26
 
 
27
class TopoSortTests(TestCase):
 
28
 
 
29
    def assertSortAndIterate(self, graph, result_list):
 
30
        """Check that sorting and iter_topo_order on graph works."""
 
31
        self.assertEquals(result_list, topo_sort(graph))
 
32
        self.assertEqual(result_list,
 
33
                         list(TopoSorter(graph).iter_topo_order()))
 
34
 
 
35
    def assertSortAndIterateRaise(self, exception_type, graph):
 
36
        """Try both iterating and topo_sorting graph and expect an exception."""
 
37
        self.assertRaises(exception_type, topo_sort, graph)
 
38
        self.assertRaises(exception_type,
 
39
                          list,
 
40
                          TopoSorter(graph).iter_topo_order())
 
41
 
 
42
    def test_tsort_empty(self):
 
43
        """TopoSort empty list"""
 
44
        self.assertSortAndIterate([], [])
 
45
 
 
46
    def test_tsort_easy(self):
 
47
        """TopoSort list with one node"""
 
48
        self.assertSortAndIterate({0: []}.items(), [0])
 
49
 
 
50
    def test_tsort_cycle(self):
 
51
        """TopoSort traps graph with cycles"""
 
52
        self.assertSortAndIterateRaise(GraphCycleError,
 
53
                                       {0: [1], 
 
54
                                        1: [0]}.items())
 
55
 
 
56
    def test_tsort_cycle_2(self):
 
57
        """TopoSort traps graph with longer cycle"""
 
58
        self.assertSortAndIterateRaise(GraphCycleError,
 
59
                                       {0: [1], 
 
60
                                        1: [2], 
 
61
                                        2: [0]}.items())
 
62
                 
 
63
    def test_tsort_1(self):
 
64
        """TopoSort simple nontrivial graph"""
 
65
        self.assertSortAndIterate({0: [3], 
 
66
                                   1: [4],
 
67
                                   2: [1, 4],
 
68
                                   3: [], 
 
69
                                   4: [0, 3]}.items(),
 
70
                                  [3, 0, 4, 1, 2])
 
71
 
 
72
    def test_tsort_partial(self):
 
73
        """Topological sort with partial ordering.
 
74
 
 
75
        If the graph does not give an order between two nodes, they are 
 
76
        returned in lexicographical order.
 
77
        """
 
78
        self.assertSortAndIterate(([(0, []),
 
79
                                   (1, [0]),
 
80
                                   (2, [0]),
 
81
                                   (3, [0]),
 
82
                                   (4, [1, 2, 3]),
 
83
                                   (5, [1, 2]),
 
84
                                   (6, [1, 2]),
 
85
                                   (7, [2, 3]),
 
86
                                   (8, [0, 1, 4, 5, 6])]),
 
87
                                  [0, 1, 2, 3, 4, 5, 6, 7, 8])
 
88
 
 
89
    def test_tsort_unincluded_parent(self):
 
90
        """Sort nodes, but don't include some parents in the output"""
 
91
        self.assertSortAndIterate([(0, [1]),
 
92
                                   (1, [2])],
 
93
                                   [1, 0])
 
94
 
 
95
 
 
96
class MergeSortTests(TestCase):
 
97
 
 
98
    def assertSortAndIterate(self, graph, branch_tip, result_list,
 
99
            generate_revno, mainline_revisions=None):
 
100
        """Check that merge based sorting and iter_topo_order on graph works."""
 
101
        value = merge_sort(graph, branch_tip,
 
102
                           mainline_revisions=mainline_revisions,
 
103
                           generate_revno=generate_revno)
 
104
        if result_list != value:
 
105
            import pprint
 
106
            self.assertEqualDiff(pprint.pformat(result_list),
 
107
                                 pprint.pformat(value))
 
108
        self.assertEquals(result_list,
 
109
            merge_sort(graph, branch_tip, mainline_revisions=mainline_revisions,
 
110
                generate_revno=generate_revno))
 
111
        self.assertEqual(result_list,
 
112
            list(MergeSorter(
 
113
                graph,
 
114
                branch_tip,
 
115
                mainline_revisions=mainline_revisions,
 
116
                generate_revno=generate_revno,
 
117
                ).iter_topo_order()))
 
118
 
 
119
    def test_merge_sort_empty(self):
 
120
        # sorting of an emptygraph does not error
 
121
        self.assertSortAndIterate({}, None, [], False)
 
122
        self.assertSortAndIterate({}, None, [], True)
 
123
        self.assertSortAndIterate({}, NULL_REVISION, [], False)
 
124
        self.assertSortAndIterate({}, NULL_REVISION, [], True)
 
125
 
 
126
    def test_merge_sort_not_empty_no_tip(self):
 
127
        # merge sorting of a branch starting with None should result
 
128
        # in an empty list: no revisions are dragged in.
 
129
        self.assertSortAndIterate({0: []}.items(), None, [], False)
 
130
        self.assertSortAndIterate({0: []}.items(), None, [], True)
 
131
 
 
132
    def test_merge_sort_one_revision(self):
 
133
        # sorting with one revision as the tip returns the correct fields:
 
134
        # sequence - 0, revision id, merge depth - 0, end_of_merge
 
135
        self.assertSortAndIterate({'id': []}.items(),
 
136
                                  'id',
 
137
                                  [(0, 'id', 0, True)],
 
138
                                  False)
 
139
        self.assertSortAndIterate({'id': []}.items(),
 
140
                                  'id',
 
141
                                  [(0, 'id', 0, (1,), True)],
 
142
                                  True)
 
143
    
 
144
    def test_sequence_numbers_increase_no_merges(self):
 
145
        # emit a few revisions with no merges to check the sequence
 
146
        # numbering works in trivial cases
 
147
        self.assertSortAndIterate(
 
148
            {'A': [],
 
149
             'B': ['A'],
 
150
             'C': ['B']}.items(),
 
151
            'C',
 
152
            [(0, 'C', 0, False),
 
153
             (1, 'B', 0, False),
 
154
             (2, 'A', 0, True),
 
155
             ],
 
156
            False
 
157
            )
 
158
        self.assertSortAndIterate(
 
159
            {'A': [],
 
160
             'B': ['A'],
 
161
             'C': ['B']}.items(),
 
162
            'C',
 
163
            [(0, 'C', 0, (3,), False),
 
164
             (1, 'B', 0, (2,), False),
 
165
             (2, 'A', 0, (1,), True),
 
166
             ],
 
167
            True
 
168
            )
 
169
 
 
170
    def test_sequence_numbers_increase_with_merges(self):
 
171
        # test that sequence numbers increase across merges
 
172
        self.assertSortAndIterate(
 
173
            {'A': [],
 
174
             'B': ['A'],
 
175
             'C': ['A', 'B']}.items(),
 
176
            'C',
 
177
            [(0, 'C', 0, False),
 
178
             (1, 'B', 1, True),
 
179
             (2, 'A', 0, True),
 
180
             ],
 
181
            False
 
182
            )
 
183
        self.assertSortAndIterate(
 
184
            {'A': [],
 
185
             'B': ['A'],
 
186
             'C': ['A', 'B']}.items(),
 
187
            'C',
 
188
            [(0, 'C', 0, (2,), False),
 
189
             (1, 'B', 1, (1,1,1), True),
 
190
             (2, 'A', 0, (1,), True),
 
191
             ],
 
192
            True
 
193
            )
 
194
 
 
195
    def test_merge_sort_race(self):
 
196
        # A
 
197
        # |
 
198
        # B-.
 
199
        # |\ \
 
200
        # | | C
 
201
        # | |/
 
202
        # | D
 
203
        # |/
 
204
        # F
 
205
        graph = {'A': [],
 
206
                 'B': ['A'],
 
207
                 'C': ['B'],
 
208
                 'D': ['B', 'C'],
 
209
                 'F': ['B', 'D'],
 
210
                 }
 
211
        self.assertSortAndIterate(graph, 'F',
 
212
            [(0, 'F', 0, (3,), False),
 
213
             (1, 'D', 1, (2,2,1), False),
 
214
             (2, 'C', 1, (2,1,1), True), # XXX: Shouldn't it be merge_depth=2?
 
215
             (3, 'B', 0, (2,), False),
 
216
             (4, 'A', 0, (1,), True),
 
217
             ], True)
 
218
        # A
 
219
        # |
 
220
        # B-.
 
221
        # |\ \
 
222
        # | X C
 
223
        # | |/
 
224
        # | D
 
225
        # |/
 
226
        # F
 
227
        graph = {'A': [],
 
228
                 'B': ['A'],
 
229
                 'C': ['B'],
 
230
                 'X': ['B'],
 
231
                 'D': ['X', 'C'],
 
232
                 'F': ['B', 'D'],
 
233
                 }
 
234
        self.assertSortAndIterate(graph, 'F',
 
235
            [(0, 'F', 0, (3,), False),
 
236
             (1, 'D', 1, (2,1,2), False),
 
237
             (2, 'C', 2, (2,2,1), True),
 
238
             (3, 'X', 1, (2,1,1), True),
 
239
             (4, 'B', 0, (2,), False),
 
240
             (5, 'A', 0, (1,), True),
 
241
             ], True)
 
242
 
 
243
    def test_merge_depth_with_nested_merges(self):
 
244
        # the merge depth marker should reflect the depth of the revision
 
245
        # in terms of merges out from the mainline
 
246
        # revid, depth, parents:
 
247
        #  A 0   [D, B]   
 
248
        #  B  1  [C, F]   
 
249
        #  C  1  [H] 
 
250
        #  D 0   [H, E]
 
251
        #  E  1  [G, F]
 
252
        #  F   2 [G]
 
253
        #  G  1  [H]
 
254
        #  H 0
 
255
        self.assertSortAndIterate(
 
256
            {'A': ['D', 'B'],
 
257
             'B': ['C', 'F'],
 
258
             'C': ['H'],
 
259
             'D': ['H', 'E'],
 
260
             'E': ['G', 'F'],
 
261
             'F': ['G'],
 
262
             'G': ['H'],
 
263
             'H': []
 
264
             }.items(),
 
265
            'A',
 
266
            [(0, 'A', 0, False),
 
267
             (1, 'B', 1, False),
 
268
             (2, 'C', 1, True),
 
269
             (3, 'D', 0, False),
 
270
             (4, 'E', 1, False),
 
271
             (5, 'F', 2, True),
 
272
             (6, 'G', 1, True),
 
273
             (7, 'H', 0, True),
 
274
             ],
 
275
            False
 
276
            )
 
277
        self.assertSortAndIterate(
 
278
            {'A': ['D', 'B'],
 
279
             'B': ['C', 'F'],
 
280
             'C': ['H'],
 
281
             'D': ['H', 'E'],
 
282
             'E': ['G', 'F'],
 
283
             'F': ['G'],
 
284
             'G': ['H'],
 
285
             'H': []
 
286
             }.items(),
 
287
            'A',
 
288
            [(0, 'A', 0, (3,),  False),
 
289
             (1, 'B', 1, (1,3,2), False),
 
290
             (2, 'C', 1, (1,3,1), True),
 
291
             (3, 'D', 0, (2,), False),
 
292
             (4, 'E', 1, (1,1,2), False),
 
293
             (5, 'F', 2, (1,2,1), True),
 
294
             (6, 'G', 1, (1,1,1), True),
 
295
             (7, 'H', 0, (1,), True),
 
296
             ],
 
297
            True
 
298
            )
 
299
 
 
300
    def test_dotted_revnos_with_simple_merges(self):
 
301
        # A         1
 
302
        # |\
 
303
        # B C       2, 1.1.1
 
304
        # | |\
 
305
        # D E F     3, 1.1.2, 1.2.1
 
306
        # |/ /|
 
307
        # G H I     4, 1.2.2, 1.3.1
 
308
        # |/ /
 
309
        # J K       5, 1.3.2
 
310
        # |/
 
311
        # L         6
 
312
        self.assertSortAndIterate(
 
313
            {'A': [],
 
314
             'B': ['A'],
 
315
             'C': ['A'],
 
316
             'D': ['B'],
 
317
             'E': ['C'],
 
318
             'F': ['C'],
 
319
             'G': ['D', 'E'],
 
320
             'H': ['F'],
 
321
             'I': ['F'],
 
322
             'J': ['G', 'H'],
 
323
             'K': ['I'],
 
324
             'L': ['J', 'K'],
 
325
            }.items(),
 
326
            'L',
 
327
            [(0, 'L', 0, (6,), False),
 
328
             (1, 'K', 1, (1,3,2), False),
 
329
             (2, 'I', 1, (1,3,1), True),
 
330
             (3, 'J', 0, (5,), False),
 
331
             (4, 'H', 1, (1,2,2), False),
 
332
             (5, 'F', 1, (1,2,1), True),
 
333
             (6, 'G', 0, (4,), False),
 
334
             (7, 'E', 1, (1,1,2), False),
 
335
             (8, 'C', 1, (1,1,1), True),
 
336
             (9, 'D', 0, (3,), False),
 
337
             (10, 'B', 0, (2,), False),
 
338
             (11, 'A', 0, (1,),  True),
 
339
             ],
 
340
            True
 
341
            )
 
342
        # Adding a shortcut from the first revision should not change any of
 
343
        # the existing numbers
 
344
        self.assertSortAndIterate(
 
345
            {'A': [],
 
346
             'B': ['A'],
 
347
             'C': ['A'],
 
348
             'D': ['B'],
 
349
             'E': ['C'],
 
350
             'F': ['C'],
 
351
             'G': ['D', 'E'],
 
352
             'H': ['F'],
 
353
             'I': ['F'],
 
354
             'J': ['G', 'H'],
 
355
             'K': ['I'],
 
356
             'L': ['J', 'K'],
 
357
             'M': ['A'],
 
358
             'N': ['L', 'M'],
 
359
            }.items(),
 
360
            'N',
 
361
            [(0, 'N', 0, (7,), False),
 
362
             (1, 'M', 1, (1,4,1), True),
 
363
             (2, 'L', 0, (6,), False),
 
364
             (3, 'K', 1, (1,3,2), False),
 
365
             (4, 'I', 1, (1,3,1), True),
 
366
             (5, 'J', 0, (5,), False),
 
367
             (6, 'H', 1, (1,2,2), False),
 
368
             (7, 'F', 1, (1,2,1), True),
 
369
             (8, 'G', 0, (4,), False),
 
370
             (9, 'E', 1, (1,1,2), False),
 
371
             (10, 'C', 1, (1,1,1), True),
 
372
             (11, 'D', 0, (3,), False),
 
373
             (12, 'B', 0, (2,), False),
 
374
             (13, 'A', 0, (1,),  True),
 
375
             ],
 
376
            True
 
377
            )
 
378
 
 
379
    def test_end_of_merge_not_last_revision_in_branch(self):
 
380
        # within a branch only the last revision gets an
 
381
        # end of merge marker.
 
382
        self.assertSortAndIterate(
 
383
            {'A': ['B'],
 
384
             'B': [],
 
385
             },
 
386
            'A',
 
387
            [(0, 'A', 0, False),
 
388
             (1, 'B', 0, True)
 
389
             ],
 
390
            False
 
391
            )
 
392
        self.assertSortAndIterate(
 
393
            {'A': ['B'],
 
394
             'B': [],
 
395
             },
 
396
            'A',
 
397
            [(0, 'A', 0, (2,), False),
 
398
             (1, 'B', 0, (1,), True)
 
399
             ],
 
400
            True
 
401
            )
 
402
 
 
403
    def test_end_of_merge_multiple_revisions_merged_at_once(self):
 
404
        # when multiple branches are merged at once, both of their
 
405
        # branch-endpoints should be listed as end-of-merge.
 
406
        # Also, the order of the multiple merges should be 
 
407
        # left-right shown top to bottom.
 
408
        # * means end of merge
 
409
        # A 0    [H, B, E] 
 
410
        # B  1   [D, C]
 
411
        # C   2  [D]       *
 
412
        # D  1   [H]       *
 
413
        # E  1   [G, F]
 
414
        # F   2  [G]       *
 
415
        # G  1   [H]       *
 
416
        # H 0    []        *
 
417
        self.assertSortAndIterate(
 
418
            {'A': ['H', 'B', 'E'],
 
419
             'B': ['D', 'C'],
 
420
             'C': ['D'],
 
421
             'D': ['H'],
 
422
             'E': ['G', 'F'],
 
423
             'F': ['G'],
 
424
             'G': ['H'],
 
425
             'H': [],
 
426
             },
 
427
            'A',
 
428
            [(0, 'A', 0, False),
 
429
             (1, 'B', 1, False),
 
430
             (2, 'C', 2, True),
 
431
             (3, 'D', 1, True),
 
432
             (4, 'E', 1, False),
 
433
             (5, 'F', 2, True),
 
434
             (6, 'G', 1, True),
 
435
             (7, 'H', 0, True),
 
436
             ],
 
437
            False
 
438
            )
 
439
        self.assertSortAndIterate(
 
440
            {'A': ['H', 'B', 'E'],
 
441
             'B': ['D', 'C'],
 
442
             'C': ['D'],
 
443
             'D': ['H'],
 
444
             'E': ['G', 'F'],
 
445
             'F': ['G'],
 
446
             'G': ['H'],
 
447
             'H': [],
 
448
             },
 
449
            'A',
 
450
            [(0, 'A', 0, (2,), False),
 
451
             (1, 'B', 1, (1,3,2), False),
 
452
             (2, 'C', 2, (1,4,1), True),
 
453
             (3, 'D', 1, (1,3,1), True),
 
454
             (4, 'E', 1, (1,1,2), False),
 
455
             (5, 'F', 2, (1,2,1), True),
 
456
             (6, 'G', 1, (1,1,1), True),
 
457
             (7, 'H', 0, (1,), True),
 
458
             ],
 
459
            True
 
460
            )
 
461
 
 
462
    def test_mainline_revs_partial(self):
 
463
        # when a mainline_revisions list is passed this must
 
464
        # override the graphs idea of mainline, and must also
 
465
        # truncate the output to the specified range, if needed.
 
466
        # so we test both at once: a mainline_revisions list that
 
467
        # disagrees with the graph about which revs are 'mainline'
 
468
        # and also truncates the output.
 
469
        # graph:
 
470
        # A 0 [E, B]
 
471
        # B 1 [D, C]
 
472
        # C 2 [D]
 
473
        # D 1 [E]
 
474
        # E 0
 
475
        # with a mainline of NONE,E,A (the inferred one) this will show the merge
 
476
        # depths above.
 
477
        # with a overriden mainline of NONE,E,D,B,A it should show:
 
478
        # A 0
 
479
        # B 0
 
480
        # C 1
 
481
        # D 0
 
482
        # E 0
 
483
        # and thus when truncated to D,B,A it should show
 
484
        # A 0
 
485
        # B 0
 
486
        # C 1 
 
487
        # because C is brought in by B in this view and D
 
488
        # is the terminating revision id
 
489
        # this should also preserve revision numbers: C should still be 2.1.1
 
490
        self.assertSortAndIterate(
 
491
            {'A': ['E', 'B'],
 
492
             'B': ['D', 'C'],
 
493
             'C': ['D'],
 
494
             'D': ['E'],
 
495
             'E': []
 
496
             },
 
497
            'A',
 
498
            [(0, 'A', 0, False),
 
499
             (1, 'B', 0, False),
 
500
             (2, 'C', 1, True),
 
501
             ],
 
502
            False,
 
503
            mainline_revisions=['D', 'B', 'A']
 
504
            )
 
505
        self.assertSortAndIterate(
 
506
            {'A': ['E', 'B'],
 
507
             'B': ['D', 'C'],
 
508
             'C': ['D'],
 
509
             'D': ['E'],
 
510
             'E': []
 
511
             },
 
512
            'A',
 
513
            [(0, 'A', 0, (4,), False),
 
514
             (1, 'B', 0, (3,), False),
 
515
             (2, 'C', 1, (2,1,1), True),
 
516
             ],
 
517
            True,
 
518
            mainline_revisions=['D', 'B', 'A']
 
519
            )
 
520
 
 
521
    def test_mainline_revs_with_none(self):
 
522
        # a simple test to ensure that a mainline_revs
 
523
        # list which goes all the way to None works
 
524
        self.assertSortAndIterate(
 
525
            {'A': [],
 
526
             },
 
527
            'A',
 
528
            [(0, 'A', 0, True),
 
529
             ],
 
530
            False,
 
531
            mainline_revisions=[None, 'A']
 
532
            )
 
533
        self.assertSortAndIterate(
 
534
            {'A': [],
 
535
             },
 
536
            'A',
 
537
            [(0, 'A', 0, (1,), True),
 
538
             ],
 
539
            True,
 
540
            mainline_revisions=[None, 'A']
 
541
            )
 
542
 
 
543
    def test_mainline_revs_with_ghost(self):
 
544
        # We have a mainline, but the end of it is actually a ghost
 
545
        # The graph that is passed to tsort has had ghosts filtered out, but
 
546
        # the mainline history has not.
 
547
        self.assertSortAndIterate(
 
548
            {'B':[],
 
549
             'C':['B']}.items(),
 
550
            'C',
 
551
            [(0, 'C', 0, (2,), False),
 
552
             (1, 'B', 0, (1,), True),
 
553
             ],
 
554
             True, mainline_revisions=['A', 'B', 'C'])
 
555
 
 
556
    def test_parallel_root_sequence_numbers_increase_with_merges(self):
 
557
        """When there are parallel roots, check their revnos."""
 
558
        self.assertSortAndIterate(
 
559
            {'A': [],
 
560
             'B': [],
 
561
             'C': ['A', 'B']}.items(),
 
562
            'C',
 
563
            [(0, 'C', 0, (2,), False),
 
564
             (1, 'B', 1, (0,1,1), True),
 
565
             (2, 'A', 0, (1,), True),
 
566
             ],
 
567
            True
 
568
            )
 
569
        
 
570
    def test_revnos_are_globally_assigned(self):
 
571
        """revnos are assigned according to the revision they derive from."""
 
572
        # in this test we setup a number of branches that all derive from 
 
573
        # the first revision, and then merge them one at a time, which 
 
574
        # should give the revisions as they merge numbers still deriving from
 
575
        # the revision were based on.
 
576
        # merge 3: J: ['G', 'I']
 
577
        # branch 3:
 
578
        #  I: ['H']
 
579
        #  H: ['A']
 
580
        # merge 2: G: ['D', 'F']
 
581
        # branch 2:
 
582
        #  F: ['E']
 
583
        #  E: ['A']
 
584
        # merge 1: D: ['A', 'C']
 
585
        # branch 1:
 
586
        #  C: ['B']
 
587
        #  B: ['A']
 
588
        # root: A: []
 
589
        self.assertSortAndIterate(
 
590
            {'J': ['G', 'I'],
 
591
             'I': ['H',],
 
592
             'H': ['A'],
 
593
             'G': ['D', 'F'],
 
594
             'F': ['E'],
 
595
             'E': ['A'],
 
596
             'D': ['A', 'C'],
 
597
             'C': ['B'],
 
598
             'B': ['A'],
 
599
             'A': [],
 
600
             }.items(),
 
601
            'J',
 
602
            [(0, 'J', 0, (4,), False),
 
603
             (1, 'I', 1, (1,3,2), False),
 
604
             (2, 'H', 1, (1,3,1), True),
 
605
             (3, 'G', 0, (3,), False),
 
606
             (4, 'F', 1, (1,2,2), False),
 
607
             (5, 'E', 1, (1,2,1), True),
 
608
             (6, 'D', 0, (2,), False),
 
609
             (7, 'C', 1, (1,1,2), False),
 
610
             (8, 'B', 1, (1,1,1), True),
 
611
             (9, 'A', 0, (1,), True),
 
612
             ],
 
613
            True
 
614
            )
 
615
 
 
616
    def test_roots_and_sub_branches_versus_ghosts(self):
 
617
        """Extra roots and their mini branches use the same numbering.
 
618
 
 
619
        All of them use the 0-node numbering.
 
620
        """
 
621
        #       A D   K
 
622
        #       | |\  |\
 
623
        #       B E F L M
 
624
        #       | |/  |/
 
625
        #       C G   N
 
626
        #       |/    |\
 
627
        #       H I   O P
 
628
        #       |/    |/
 
629
        #       J     Q
 
630
        #       |.---'
 
631
        #       R
 
632
        self.assertSortAndIterate(
 
633
            {'A': [],
 
634
             'B': ['A'],
 
635
             'C': ['B'],
 
636
             'D': [],
 
637
             'E': ['D'],
 
638
             'F': ['D'],
 
639
             'G': ['E', 'F'],
 
640
             'H': ['C', 'G'],
 
641
             'I': [],
 
642
             'J': ['H', 'I'],
 
643
             'K': [],
 
644
             'L': ['K'],
 
645
             'M': ['K'],
 
646
             'N': ['L', 'M'],
 
647
             'O': ['N'],
 
648
             'P': ['N'],
 
649
             'Q': ['O', 'P'],
 
650
             'R': ['J', 'Q'],
 
651
            }.items(),
 
652
            'R',
 
653
            [( 0, 'R', 0, (6,), False),
 
654
             ( 1, 'Q', 1, (0,4,5), False),
 
655
             ( 2, 'P', 2, (0,6,1), True),
 
656
             ( 3, 'O', 1, (0,4,4), False),
 
657
             ( 4, 'N', 1, (0,4,3), False),
 
658
             ( 5, 'M', 2, (0,5,1), True),
 
659
             ( 6, 'L', 1, (0,4,2), False),
 
660
             ( 7, 'K', 1, (0,4,1), True),
 
661
             ( 8, 'J', 0, (5,), False),
 
662
             ( 9, 'I', 1, (0,3,1), True),
 
663
             (10, 'H', 0, (4,), False),
 
664
             (11, 'G', 1, (0,1,3), False),
 
665
             (12, 'F', 2, (0,2,1), True),
 
666
             (13, 'E', 1, (0,1,2), False),
 
667
             (14, 'D', 1, (0,1,1), True),
 
668
             (15, 'C', 0, (3,), False),
 
669
             (16, 'B', 0, (2,), False),
 
670
             (17, 'A', 0, (1,), True),
 
671
             ],
 
672
            True
 
673
            )