1 import graphlib
2 import os
3 import unittest
4
5 from test.support.script_helper import assert_python_ok
6
7 class ESC[4;38;5;81mTestTopologicalSort(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
8 def _test_graph(self, graph, expected):
9 def static_order_with_groups(ts):
10 ts.prepare()
11 while ts.is_active():
12 nodes = ts.get_ready()
13 for node in nodes:
14 ts.done(node)
15 yield tuple(sorted(nodes))
16
17 ts = graphlib.TopologicalSorter(graph)
18 self.assertEqual(list(static_order_with_groups(ts)), list(expected))
19
20 ts = graphlib.TopologicalSorter(graph)
21 # need to be a bit careful comparing the result of ts.static_order and
22 # expected, because the order within a group is dependent on set
23 # iteration order
24 it = iter(ts.static_order())
25 for group in expected:
26 tsgroup = {next(it) for element in group}
27 self.assertEqual(set(group), tsgroup)
28
29 def _assert_cycle(self, graph, cycle):
30 ts = graphlib.TopologicalSorter()
31 for node, dependson in graph.items():
32 ts.add(node, *dependson)
33 try:
34 ts.prepare()
35 except graphlib.CycleError as e:
36 _, seq = e.args
37 self.assertIn(" ".join(map(str, cycle)), " ".join(map(str, seq * 2)))
38 else:
39 raise
40
41 def test_simple_cases(self):
42 self._test_graph(
43 {2: {11}, 9: {11, 8}, 10: {11, 3}, 11: {7, 5}, 8: {7, 3}},
44 [(3, 5, 7), (8, 11), (2, 9, 10)],
45 )
46
47 self._test_graph({1: {}}, [(1,)])
48
49 self._test_graph(
50 {x: {x + 1} for x in range(10)}, [(x,) for x in range(10, -1, -1)]
51 )
52
53 self._test_graph(
54 {2: {3}, 3: {4}, 4: {5}, 5: {1}, 11: {12}, 12: {13}, 13: {14}, 14: {15}},
55 [(1, 15), (5, 14), (4, 13), (3, 12), (2, 11)],
56 )
57
58 self._test_graph(
59 {
60 0: [1, 2],
61 1: [3],
62 2: [5, 6],
63 3: [4],
64 4: [9],
65 5: [3],
66 6: [7],
67 7: [8],
68 8: [4],
69 9: [],
70 },
71 [(9,), (4,), (3, 8), (1, 5, 7), (6,), (2,), (0,)],
72 )
73
74 self._test_graph({0: [1, 2], 1: [], 2: [3], 3: []}, [(1, 3), (2,), (0,)])
75
76 self._test_graph(
77 {0: [1, 2], 1: [], 2: [3], 3: [], 4: [5], 5: [6], 6: []},
78 [(1, 3, 6), (2, 5), (0, 4)],
79 )
80
81 def test_no_dependencies(self):
82 self._test_graph({1: {2}, 3: {4}, 5: {6}}, [(2, 4, 6), (1, 3, 5)])
83
84 self._test_graph({1: set(), 3: set(), 5: set()}, [(1, 3, 5)])
85
86 def test_the_node_multiple_times(self):
87 # Test same node multiple times in dependencies
88 self._test_graph({1: {2}, 3: {4}, 0: [2, 4, 4, 4, 4, 4]}, [(2, 4), (0, 1, 3)])
89
90 # Test adding the same dependency multiple times
91 ts = graphlib.TopologicalSorter()
92 ts.add(1, 2)
93 ts.add(1, 2)
94 ts.add(1, 2)
95 self.assertEqual([*ts.static_order()], [2, 1])
96
97 def test_graph_with_iterables(self):
98 dependson = (2 * x + 1 for x in range(5))
99 ts = graphlib.TopologicalSorter({0: dependson})
100 self.assertEqual(list(ts.static_order()), [1, 3, 5, 7, 9, 0])
101
102 def test_add_dependencies_for_same_node_incrementally(self):
103 # Test same node multiple times
104 ts = graphlib.TopologicalSorter()
105 ts.add(1, 2)
106 ts.add(1, 3)
107 ts.add(1, 4)
108 ts.add(1, 5)
109
110 ts2 = graphlib.TopologicalSorter({1: {2, 3, 4, 5}})
111 self.assertEqual([*ts.static_order()], [*ts2.static_order()])
112
113 def test_empty(self):
114 self._test_graph({}, [])
115
116 def test_cycle(self):
117 # Self cycle
118 self._assert_cycle({1: {1}}, [1, 1])
119 # Simple cycle
120 self._assert_cycle({1: {2}, 2: {1}}, [1, 2, 1])
121 # Indirect cycle
122 self._assert_cycle({1: {2}, 2: {3}, 3: {1}}, [1, 3, 2, 1])
123 # not all elements involved in a cycle
124 self._assert_cycle({1: {2}, 2: {3}, 3: {1}, 5: {4}, 4: {6}}, [1, 3, 2, 1])
125 # Multiple cycles
126 self._assert_cycle({1: {2}, 2: {1}, 3: {4}, 4: {5}, 6: {7}, 7: {6}}, [1, 2, 1])
127 # Cycle in the middle of the graph
128 self._assert_cycle({1: {2}, 2: {3}, 3: {2, 4}, 4: {5}}, [3, 2])
129
130 def test_calls_before_prepare(self):
131 ts = graphlib.TopologicalSorter()
132
133 with self.assertRaisesRegex(ValueError, r"prepare\(\) must be called first"):
134 ts.get_ready()
135 with self.assertRaisesRegex(ValueError, r"prepare\(\) must be called first"):
136 ts.done(3)
137 with self.assertRaisesRegex(ValueError, r"prepare\(\) must be called first"):
138 ts.is_active()
139
140 def test_prepare_multiple_times(self):
141 ts = graphlib.TopologicalSorter()
142 ts.prepare()
143 with self.assertRaisesRegex(ValueError, r"cannot prepare\(\) more than once"):
144 ts.prepare()
145
146 def test_invalid_nodes_in_done(self):
147 ts = graphlib.TopologicalSorter()
148 ts.add(1, 2, 3, 4)
149 ts.add(2, 3, 4)
150 ts.prepare()
151 ts.get_ready()
152
153 with self.assertRaisesRegex(ValueError, "node 2 was not passed out"):
154 ts.done(2)
155 with self.assertRaisesRegex(ValueError, r"node 24 was not added using add\(\)"):
156 ts.done(24)
157
158 def test_done(self):
159 ts = graphlib.TopologicalSorter()
160 ts.add(1, 2, 3, 4)
161 ts.add(2, 3)
162 ts.prepare()
163
164 self.assertEqual(ts.get_ready(), (3, 4))
165 # If we don't mark anything as done, get_ready() returns nothing
166 self.assertEqual(ts.get_ready(), ())
167 ts.done(3)
168 # Now 2 becomes available as 3 is done
169 self.assertEqual(ts.get_ready(), (2,))
170 self.assertEqual(ts.get_ready(), ())
171 ts.done(4)
172 ts.done(2)
173 # Only 1 is missing
174 self.assertEqual(ts.get_ready(), (1,))
175 self.assertEqual(ts.get_ready(), ())
176 ts.done(1)
177 self.assertEqual(ts.get_ready(), ())
178 self.assertFalse(ts.is_active())
179
180 def test_is_active(self):
181 ts = graphlib.TopologicalSorter()
182 ts.add(1, 2)
183 ts.prepare()
184
185 self.assertTrue(ts.is_active())
186 self.assertEqual(ts.get_ready(), (2,))
187 self.assertTrue(ts.is_active())
188 ts.done(2)
189 self.assertTrue(ts.is_active())
190 self.assertEqual(ts.get_ready(), (1,))
191 self.assertTrue(ts.is_active())
192 ts.done(1)
193 self.assertFalse(ts.is_active())
194
195 def test_not_hashable_nodes(self):
196 ts = graphlib.TopologicalSorter()
197 self.assertRaises(TypeError, ts.add, dict(), 1)
198 self.assertRaises(TypeError, ts.add, 1, dict())
199 self.assertRaises(TypeError, ts.add, dict(), dict())
200
201 def test_order_of_insertion_does_not_matter_between_groups(self):
202 def get_groups(ts):
203 ts.prepare()
204 while ts.is_active():
205 nodes = ts.get_ready()
206 ts.done(*nodes)
207 yield set(nodes)
208
209 ts = graphlib.TopologicalSorter()
210 ts.add(3, 2, 1)
211 ts.add(1, 0)
212 ts.add(4, 5)
213 ts.add(6, 7)
214 ts.add(4, 7)
215
216 ts2 = graphlib.TopologicalSorter()
217 ts2.add(1, 0)
218 ts2.add(3, 2, 1)
219 ts2.add(4, 7)
220 ts2.add(6, 7)
221 ts2.add(4, 5)
222
223 self.assertEqual(list(get_groups(ts)), list(get_groups(ts2)))
224
225 def test_static_order_does_not_change_with_the_hash_seed(self):
226 def check_order_with_hash_seed(seed):
227 code = """if 1:
228 import graphlib
229 ts = graphlib.TopologicalSorter()
230 ts.add('blech', 'bluch', 'hola')
231 ts.add('abcd', 'blech', 'bluch', 'a', 'b')
232 ts.add('a', 'a string', 'something', 'b')
233 ts.add('bluch', 'hola', 'abcde', 'a', 'b')
234 print(list(ts.static_order()))
235 """
236 env = os.environ.copy()
237 # signal to assert_python not to do a copy
238 # of os.environ on its own
239 env["__cleanenv"] = True
240 env["PYTHONHASHSEED"] = str(seed)
241 out = assert_python_ok("-c", code, **env)
242 return out
243
244 run1 = check_order_with_hash_seed(1234)
245 run2 = check_order_with_hash_seed(31415)
246
247 self.assertNotEqual(run1, "")
248 self.assertNotEqual(run2, "")
249 self.assertEqual(run1, run2)
250
251 if __name__ == "__main__":
252 unittest.main()