Repository: oppia/oppia

Issue:
Backend test flake: Incomplete coverage of create_topological_sort_of_all_services
Example: https://github.com/oppia/oppia/actions/runs/3521701249/jobs/5903838607#step:8:1749

Log:

```
INCOMPLETE COVERAGE (99.0%): scripts.create_topological_sort_of_all_services_test
Name                                                 Stmts   Miss Branch BrPart  Cover   Missing
------------------------------------------------------------------------------------------------
scripts/create_topological_sort_of_all_services.py      72      0     40      1    99%   139->138
------------------------------------------------------------------------------------------------
TOTAL                                                   72      0     40      1    99%
```

The topological sort script produces a different order each time, so I suspect that this non-determinism is at the root of this flake. From the code it doesn't look like we actually take advantage of any of the set's special properties, so we could just swap it out for a list:

https://github.com/oppia/oppia/blob/09adf9fd37f1c93564515fc736fb9c657b645dda/scripts/create_topological_sort_of_all_services.py#L73
Backend test flake: Incomplete coverage of create_topological_sort_of_all_services
Example: https://github.com/oppia/oppia/actions/runs/3521701249/jobs/5903838607#step:8:1749

Log:

```
INCOMPLETE COVERAGE (99.0%): scripts.create_topological_sort_of_all_services_test
Name                                                 Stmts   Miss Branch BrPart  Cover   Missing
------------------------------------------------------------------------------------------------
scripts/create_topological_sort_of_all_services.py      72      0     40      1    99%   139->138
------------------------------------------------------------------------------------------------
TOTAL                                                   72      0     40      1    99%
```

The topological sort script produces a different order each time, so I suspect that this non-determinism is at the root of this flake. From the code it doesn't look like we actually take advantage of any of the set's special properties, so we could just swap it out for a list:

https://github.com/oppia/oppia/blob/09adf9fd37f1c93564515fc736fb9c657b645dda/scripts/create_topological_sort_of_all_services.py#L73


Discussion:

Resolving patch:
diff --git a/scripts/create_topological_sort_of_all_services.py b/scripts/create_topological_sort_of_all_services.py
--- a/scripts/create_topological_sort_of_all_services.py
+++ b/scripts/create_topological_sort_of_all_services.py
@@ -26,7 +26,7 @@
 from core import utils
 
 import esprima
-from typing import Dict, List, Set, Tuple
+from typing import Dict, List, Tuple
 
 
 DIRECTORY_NAMES = ['core/templates', 'extensions']
@@ -56,21 +56,21 @@ def dfs(
     topo_sort_stack.append(node)
 
 
-def make_graph() -> Tuple[Dict[str, List[str]], Set[str]]:
+def make_graph() -> Tuple[Dict[str, List[str]], List[str]]:
     """Creates an adjaceny list considering services as node and dependencies
     as edges.
 
     Returns:
-        tuple(dict, set(str)). Adjancency list of the graph formed with
-        services as nodes and dependencies as edges, set of all the services.
+        tuple(dict, list(str)). Adjancency list of the graph formed with
+        services as nodes and dependencies as edges, list of all the services.
     """
     adj_list = collections.defaultdict(list)
-    nodes_set = set()
+    nodes_list = []
     for dirname in DIRECTORY_NAMES:
         for root, _, filenames in os.walk(dirname):
             for filename in filenames:
                 if filename.endswith(SERVICE_FILES_SUFFICES):
-                    nodes_set.add(filename)
+                    nodes_list.append(filename)
                     filepath = os.path.join(root, filename)
                     with utils.open_file(filepath, 'r') as f:
                         file_lines = f.readlines()
@@ -124,18 +124,18 @@ def make_graph() -> Tuple[Dict[str, List[str]], Set[str]]:
                                 dep_name = os.path.basename(dep_path)
                                 adj_list[dep_name].append(filename)
 
-    return (adj_list, nodes_set)
+    return (adj_list, nodes_list)
 
 
 def main() -> None:
     """Prints the topological order of the services based on the
     dependencies.
     """
-    adj_list, nodes_set = make_graph()
+    adj_list, nodes_list = make_graph()
     visit_stack: List[str] = []
     topo_sort_stack: List[str] = []
 
-    for unchecked_node in nodes_set:
+    for unchecked_node in nodes_list:
         if unchecked_node not in visit_stack:
             dfs(unchecked_node, topo_sort_stack, adj_list, visit_stack)
 


Test patch:
diff --git a/scripts/create_topological_sort_of_all_services_test.py b/scripts/create_topological_sort_of_all_services_test.py
--- a/scripts/create_topological_sort_of_all_services_test.py
+++ b/scripts/create_topological_sort_of_all_services_test.py
@@ -49,7 +49,7 @@ def test_make_graph(self) -> None:
         with self.swap(
             create_topological_sort_of_all_services, 'DIRECTORY_NAMES',
             MOCK_DIRECTORY_NAMES):
-            adj_list, node_set = (
+            adj_list, node_list = (
                 create_topological_sort_of_all_services.make_graph())
 
             expected_adj_list = {
@@ -71,7 +71,7 @@ def test_make_graph(self) -> None:
                 self.assertEqual(
                     sorted(adj_list[key]), sorted(expected_adj_list[key]))
 
-            self.assertEqual(node_set, expected_node_set)
+            self.assertEqual(set(node_list), expected_node_set)
 
     def test_complete_process(self) -> None:
         actual_output = []