17db96d56Sopenharmony_ciimport graphlib 27db96d56Sopenharmony_ciimport os 37db96d56Sopenharmony_ciimport unittest 47db96d56Sopenharmony_ci 57db96d56Sopenharmony_cifrom test.support.script_helper import assert_python_ok 67db96d56Sopenharmony_ci 77db96d56Sopenharmony_ciclass TestTopologicalSort(unittest.TestCase): 87db96d56Sopenharmony_ci def _test_graph(self, graph, expected): 97db96d56Sopenharmony_ci def static_order_with_groups(ts): 107db96d56Sopenharmony_ci ts.prepare() 117db96d56Sopenharmony_ci while ts.is_active(): 127db96d56Sopenharmony_ci nodes = ts.get_ready() 137db96d56Sopenharmony_ci for node in nodes: 147db96d56Sopenharmony_ci ts.done(node) 157db96d56Sopenharmony_ci yield tuple(sorted(nodes)) 167db96d56Sopenharmony_ci 177db96d56Sopenharmony_ci ts = graphlib.TopologicalSorter(graph) 187db96d56Sopenharmony_ci self.assertEqual(list(static_order_with_groups(ts)), list(expected)) 197db96d56Sopenharmony_ci 207db96d56Sopenharmony_ci ts = graphlib.TopologicalSorter(graph) 217db96d56Sopenharmony_ci # need to be a bit careful comparing the result of ts.static_order and 227db96d56Sopenharmony_ci # expected, because the order within a group is dependent on set 237db96d56Sopenharmony_ci # iteration order 247db96d56Sopenharmony_ci it = iter(ts.static_order()) 257db96d56Sopenharmony_ci for group in expected: 267db96d56Sopenharmony_ci tsgroup = {next(it) for element in group} 277db96d56Sopenharmony_ci self.assertEqual(set(group), tsgroup) 287db96d56Sopenharmony_ci 297db96d56Sopenharmony_ci def _assert_cycle(self, graph, cycle): 307db96d56Sopenharmony_ci ts = graphlib.TopologicalSorter() 317db96d56Sopenharmony_ci for node, dependson in graph.items(): 327db96d56Sopenharmony_ci ts.add(node, *dependson) 337db96d56Sopenharmony_ci try: 347db96d56Sopenharmony_ci ts.prepare() 357db96d56Sopenharmony_ci except graphlib.CycleError as e: 367db96d56Sopenharmony_ci _, seq = e.args 377db96d56Sopenharmony_ci self.assertIn(" ".join(map(str, cycle)), " ".join(map(str, seq * 2))) 387db96d56Sopenharmony_ci else: 397db96d56Sopenharmony_ci raise 407db96d56Sopenharmony_ci 417db96d56Sopenharmony_ci def test_simple_cases(self): 427db96d56Sopenharmony_ci self._test_graph( 437db96d56Sopenharmony_ci {2: {11}, 9: {11, 8}, 10: {11, 3}, 11: {7, 5}, 8: {7, 3}}, 447db96d56Sopenharmony_ci [(3, 5, 7), (8, 11), (2, 9, 10)], 457db96d56Sopenharmony_ci ) 467db96d56Sopenharmony_ci 477db96d56Sopenharmony_ci self._test_graph({1: {}}, [(1,)]) 487db96d56Sopenharmony_ci 497db96d56Sopenharmony_ci self._test_graph( 507db96d56Sopenharmony_ci {x: {x + 1} for x in range(10)}, [(x,) for x in range(10, -1, -1)] 517db96d56Sopenharmony_ci ) 527db96d56Sopenharmony_ci 537db96d56Sopenharmony_ci self._test_graph( 547db96d56Sopenharmony_ci {2: {3}, 3: {4}, 4: {5}, 5: {1}, 11: {12}, 12: {13}, 13: {14}, 14: {15}}, 557db96d56Sopenharmony_ci [(1, 15), (5, 14), (4, 13), (3, 12), (2, 11)], 567db96d56Sopenharmony_ci ) 577db96d56Sopenharmony_ci 587db96d56Sopenharmony_ci self._test_graph( 597db96d56Sopenharmony_ci { 607db96d56Sopenharmony_ci 0: [1, 2], 617db96d56Sopenharmony_ci 1: [3], 627db96d56Sopenharmony_ci 2: [5, 6], 637db96d56Sopenharmony_ci 3: [4], 647db96d56Sopenharmony_ci 4: [9], 657db96d56Sopenharmony_ci 5: [3], 667db96d56Sopenharmony_ci 6: [7], 677db96d56Sopenharmony_ci 7: [8], 687db96d56Sopenharmony_ci 8: [4], 697db96d56Sopenharmony_ci 9: [], 707db96d56Sopenharmony_ci }, 717db96d56Sopenharmony_ci [(9,), (4,), (3, 8), (1, 5, 7), (6,), (2,), (0,)], 727db96d56Sopenharmony_ci ) 737db96d56Sopenharmony_ci 747db96d56Sopenharmony_ci self._test_graph({0: [1, 2], 1: [], 2: [3], 3: []}, [(1, 3), (2,), (0,)]) 757db96d56Sopenharmony_ci 767db96d56Sopenharmony_ci self._test_graph( 777db96d56Sopenharmony_ci {0: [1, 2], 1: [], 2: [3], 3: [], 4: [5], 5: [6], 6: []}, 787db96d56Sopenharmony_ci [(1, 3, 6), (2, 5), (0, 4)], 797db96d56Sopenharmony_ci ) 807db96d56Sopenharmony_ci 817db96d56Sopenharmony_ci def test_no_dependencies(self): 827db96d56Sopenharmony_ci self._test_graph({1: {2}, 3: {4}, 5: {6}}, [(2, 4, 6), (1, 3, 5)]) 837db96d56Sopenharmony_ci 847db96d56Sopenharmony_ci self._test_graph({1: set(), 3: set(), 5: set()}, [(1, 3, 5)]) 857db96d56Sopenharmony_ci 867db96d56Sopenharmony_ci def test_the_node_multiple_times(self): 877db96d56Sopenharmony_ci # Test same node multiple times in dependencies 887db96d56Sopenharmony_ci self._test_graph({1: {2}, 3: {4}, 0: [2, 4, 4, 4, 4, 4]}, [(2, 4), (0, 1, 3)]) 897db96d56Sopenharmony_ci 907db96d56Sopenharmony_ci # Test adding the same dependency multiple times 917db96d56Sopenharmony_ci ts = graphlib.TopologicalSorter() 927db96d56Sopenharmony_ci ts.add(1, 2) 937db96d56Sopenharmony_ci ts.add(1, 2) 947db96d56Sopenharmony_ci ts.add(1, 2) 957db96d56Sopenharmony_ci self.assertEqual([*ts.static_order()], [2, 1]) 967db96d56Sopenharmony_ci 977db96d56Sopenharmony_ci def test_graph_with_iterables(self): 987db96d56Sopenharmony_ci dependson = (2 * x + 1 for x in range(5)) 997db96d56Sopenharmony_ci ts = graphlib.TopologicalSorter({0: dependson}) 1007db96d56Sopenharmony_ci self.assertEqual(list(ts.static_order()), [1, 3, 5, 7, 9, 0]) 1017db96d56Sopenharmony_ci 1027db96d56Sopenharmony_ci def test_add_dependencies_for_same_node_incrementally(self): 1037db96d56Sopenharmony_ci # Test same node multiple times 1047db96d56Sopenharmony_ci ts = graphlib.TopologicalSorter() 1057db96d56Sopenharmony_ci ts.add(1, 2) 1067db96d56Sopenharmony_ci ts.add(1, 3) 1077db96d56Sopenharmony_ci ts.add(1, 4) 1087db96d56Sopenharmony_ci ts.add(1, 5) 1097db96d56Sopenharmony_ci 1107db96d56Sopenharmony_ci ts2 = graphlib.TopologicalSorter({1: {2, 3, 4, 5}}) 1117db96d56Sopenharmony_ci self.assertEqual([*ts.static_order()], [*ts2.static_order()]) 1127db96d56Sopenharmony_ci 1137db96d56Sopenharmony_ci def test_empty(self): 1147db96d56Sopenharmony_ci self._test_graph({}, []) 1157db96d56Sopenharmony_ci 1167db96d56Sopenharmony_ci def test_cycle(self): 1177db96d56Sopenharmony_ci # Self cycle 1187db96d56Sopenharmony_ci self._assert_cycle({1: {1}}, [1, 1]) 1197db96d56Sopenharmony_ci # Simple cycle 1207db96d56Sopenharmony_ci self._assert_cycle({1: {2}, 2: {1}}, [1, 2, 1]) 1217db96d56Sopenharmony_ci # Indirect cycle 1227db96d56Sopenharmony_ci self._assert_cycle({1: {2}, 2: {3}, 3: {1}}, [1, 3, 2, 1]) 1237db96d56Sopenharmony_ci # not all elements involved in a cycle 1247db96d56Sopenharmony_ci self._assert_cycle({1: {2}, 2: {3}, 3: {1}, 5: {4}, 4: {6}}, [1, 3, 2, 1]) 1257db96d56Sopenharmony_ci # Multiple cycles 1267db96d56Sopenharmony_ci self._assert_cycle({1: {2}, 2: {1}, 3: {4}, 4: {5}, 6: {7}, 7: {6}}, [1, 2, 1]) 1277db96d56Sopenharmony_ci # Cycle in the middle of the graph 1287db96d56Sopenharmony_ci self._assert_cycle({1: {2}, 2: {3}, 3: {2, 4}, 4: {5}}, [3, 2]) 1297db96d56Sopenharmony_ci 1307db96d56Sopenharmony_ci def test_calls_before_prepare(self): 1317db96d56Sopenharmony_ci ts = graphlib.TopologicalSorter() 1327db96d56Sopenharmony_ci 1337db96d56Sopenharmony_ci with self.assertRaisesRegex(ValueError, r"prepare\(\) must be called first"): 1347db96d56Sopenharmony_ci ts.get_ready() 1357db96d56Sopenharmony_ci with self.assertRaisesRegex(ValueError, r"prepare\(\) must be called first"): 1367db96d56Sopenharmony_ci ts.done(3) 1377db96d56Sopenharmony_ci with self.assertRaisesRegex(ValueError, r"prepare\(\) must be called first"): 1387db96d56Sopenharmony_ci ts.is_active() 1397db96d56Sopenharmony_ci 1407db96d56Sopenharmony_ci def test_prepare_multiple_times(self): 1417db96d56Sopenharmony_ci ts = graphlib.TopologicalSorter() 1427db96d56Sopenharmony_ci ts.prepare() 1437db96d56Sopenharmony_ci with self.assertRaisesRegex(ValueError, r"cannot prepare\(\) more than once"): 1447db96d56Sopenharmony_ci ts.prepare() 1457db96d56Sopenharmony_ci 1467db96d56Sopenharmony_ci def test_invalid_nodes_in_done(self): 1477db96d56Sopenharmony_ci ts = graphlib.TopologicalSorter() 1487db96d56Sopenharmony_ci ts.add(1, 2, 3, 4) 1497db96d56Sopenharmony_ci ts.add(2, 3, 4) 1507db96d56Sopenharmony_ci ts.prepare() 1517db96d56Sopenharmony_ci ts.get_ready() 1527db96d56Sopenharmony_ci 1537db96d56Sopenharmony_ci with self.assertRaisesRegex(ValueError, "node 2 was not passed out"): 1547db96d56Sopenharmony_ci ts.done(2) 1557db96d56Sopenharmony_ci with self.assertRaisesRegex(ValueError, r"node 24 was not added using add\(\)"): 1567db96d56Sopenharmony_ci ts.done(24) 1577db96d56Sopenharmony_ci 1587db96d56Sopenharmony_ci def test_done(self): 1597db96d56Sopenharmony_ci ts = graphlib.TopologicalSorter() 1607db96d56Sopenharmony_ci ts.add(1, 2, 3, 4) 1617db96d56Sopenharmony_ci ts.add(2, 3) 1627db96d56Sopenharmony_ci ts.prepare() 1637db96d56Sopenharmony_ci 1647db96d56Sopenharmony_ci self.assertEqual(ts.get_ready(), (3, 4)) 1657db96d56Sopenharmony_ci # If we don't mark anything as done, get_ready() returns nothing 1667db96d56Sopenharmony_ci self.assertEqual(ts.get_ready(), ()) 1677db96d56Sopenharmony_ci ts.done(3) 1687db96d56Sopenharmony_ci # Now 2 becomes available as 3 is done 1697db96d56Sopenharmony_ci self.assertEqual(ts.get_ready(), (2,)) 1707db96d56Sopenharmony_ci self.assertEqual(ts.get_ready(), ()) 1717db96d56Sopenharmony_ci ts.done(4) 1727db96d56Sopenharmony_ci ts.done(2) 1737db96d56Sopenharmony_ci # Only 1 is missing 1747db96d56Sopenharmony_ci self.assertEqual(ts.get_ready(), (1,)) 1757db96d56Sopenharmony_ci self.assertEqual(ts.get_ready(), ()) 1767db96d56Sopenharmony_ci ts.done(1) 1777db96d56Sopenharmony_ci self.assertEqual(ts.get_ready(), ()) 1787db96d56Sopenharmony_ci self.assertFalse(ts.is_active()) 1797db96d56Sopenharmony_ci 1807db96d56Sopenharmony_ci def test_is_active(self): 1817db96d56Sopenharmony_ci ts = graphlib.TopologicalSorter() 1827db96d56Sopenharmony_ci ts.add(1, 2) 1837db96d56Sopenharmony_ci ts.prepare() 1847db96d56Sopenharmony_ci 1857db96d56Sopenharmony_ci self.assertTrue(ts.is_active()) 1867db96d56Sopenharmony_ci self.assertEqual(ts.get_ready(), (2,)) 1877db96d56Sopenharmony_ci self.assertTrue(ts.is_active()) 1887db96d56Sopenharmony_ci ts.done(2) 1897db96d56Sopenharmony_ci self.assertTrue(ts.is_active()) 1907db96d56Sopenharmony_ci self.assertEqual(ts.get_ready(), (1,)) 1917db96d56Sopenharmony_ci self.assertTrue(ts.is_active()) 1927db96d56Sopenharmony_ci ts.done(1) 1937db96d56Sopenharmony_ci self.assertFalse(ts.is_active()) 1947db96d56Sopenharmony_ci 1957db96d56Sopenharmony_ci def test_not_hashable_nodes(self): 1967db96d56Sopenharmony_ci ts = graphlib.TopologicalSorter() 1977db96d56Sopenharmony_ci self.assertRaises(TypeError, ts.add, dict(), 1) 1987db96d56Sopenharmony_ci self.assertRaises(TypeError, ts.add, 1, dict()) 1997db96d56Sopenharmony_ci self.assertRaises(TypeError, ts.add, dict(), dict()) 2007db96d56Sopenharmony_ci 2017db96d56Sopenharmony_ci def test_order_of_insertion_does_not_matter_between_groups(self): 2027db96d56Sopenharmony_ci def get_groups(ts): 2037db96d56Sopenharmony_ci ts.prepare() 2047db96d56Sopenharmony_ci while ts.is_active(): 2057db96d56Sopenharmony_ci nodes = ts.get_ready() 2067db96d56Sopenharmony_ci ts.done(*nodes) 2077db96d56Sopenharmony_ci yield set(nodes) 2087db96d56Sopenharmony_ci 2097db96d56Sopenharmony_ci ts = graphlib.TopologicalSorter() 2107db96d56Sopenharmony_ci ts.add(3, 2, 1) 2117db96d56Sopenharmony_ci ts.add(1, 0) 2127db96d56Sopenharmony_ci ts.add(4, 5) 2137db96d56Sopenharmony_ci ts.add(6, 7) 2147db96d56Sopenharmony_ci ts.add(4, 7) 2157db96d56Sopenharmony_ci 2167db96d56Sopenharmony_ci ts2 = graphlib.TopologicalSorter() 2177db96d56Sopenharmony_ci ts2.add(1, 0) 2187db96d56Sopenharmony_ci ts2.add(3, 2, 1) 2197db96d56Sopenharmony_ci ts2.add(4, 7) 2207db96d56Sopenharmony_ci ts2.add(6, 7) 2217db96d56Sopenharmony_ci ts2.add(4, 5) 2227db96d56Sopenharmony_ci 2237db96d56Sopenharmony_ci self.assertEqual(list(get_groups(ts)), list(get_groups(ts2))) 2247db96d56Sopenharmony_ci 2257db96d56Sopenharmony_ci def test_static_order_does_not_change_with_the_hash_seed(self): 2267db96d56Sopenharmony_ci def check_order_with_hash_seed(seed): 2277db96d56Sopenharmony_ci code = """if 1: 2287db96d56Sopenharmony_ci import graphlib 2297db96d56Sopenharmony_ci ts = graphlib.TopologicalSorter() 2307db96d56Sopenharmony_ci ts.add('blech', 'bluch', 'hola') 2317db96d56Sopenharmony_ci ts.add('abcd', 'blech', 'bluch', 'a', 'b') 2327db96d56Sopenharmony_ci ts.add('a', 'a string', 'something', 'b') 2337db96d56Sopenharmony_ci ts.add('bluch', 'hola', 'abcde', 'a', 'b') 2347db96d56Sopenharmony_ci print(list(ts.static_order())) 2357db96d56Sopenharmony_ci """ 2367db96d56Sopenharmony_ci env = os.environ.copy() 2377db96d56Sopenharmony_ci # signal to assert_python not to do a copy 2387db96d56Sopenharmony_ci # of os.environ on its own 2397db96d56Sopenharmony_ci env["__cleanenv"] = True 2407db96d56Sopenharmony_ci env["PYTHONHASHSEED"] = str(seed) 2417db96d56Sopenharmony_ci out = assert_python_ok("-c", code, **env) 2427db96d56Sopenharmony_ci return out 2437db96d56Sopenharmony_ci 2447db96d56Sopenharmony_ci run1 = check_order_with_hash_seed(1234) 2457db96d56Sopenharmony_ci run2 = check_order_with_hash_seed(31415) 2467db96d56Sopenharmony_ci 2477db96d56Sopenharmony_ci self.assertNotEqual(run1, "") 2487db96d56Sopenharmony_ci self.assertNotEqual(run2, "") 2497db96d56Sopenharmony_ci self.assertEqual(run1, run2) 2507db96d56Sopenharmony_ci 2517db96d56Sopenharmony_ciif __name__ == "__main__": 2527db96d56Sopenharmony_ci unittest.main() 253