| #!/usr/bin/env python3 | 
 | # Copyright 2021 The Chromium Authors | 
 | # Use of this source code is governed by a BSD-style license that can be | 
 | # found in the LICENSE file. | 
 | """Graph analysis functions for the testing framework. | 
 | """ | 
 |  | 
 | import logging | 
 | from typing import List, Optional, Tuple | 
 |  | 
 | from models import ActionCoverage | 
 | from models import ActionType | 
 | from models import Action | 
 | from models import ActionNode | 
 | from models import CoverageTest | 
 | from models import TestPlatform | 
 |  | 
 |  | 
 | def build_action_node_graph(root_node: ActionNode, | 
 |                             tests: List[CoverageTest]) -> None: | 
 |     """ | 
 |     Builds a graph of ActionNodes on the given `root_node`. The graph is | 
 |     generated by iterating the `tests`, starting at the `root_node` for each | 
 |     test, and adding ActionNodes to the graph for each state-change action | 
 |     (following the graph for every state-change action). State-check actions are | 
 |     added to the previous state-change action ActionNode. | 
 |     """ | 
 |     assert root_node is not None | 
 |     assert isinstance(root_node, ActionNode) | 
 |     assert isinstance(tests, list) | 
 |     for test in tests: | 
 |         assert isinstance(test, CoverageTest) | 
 |         parent = root_node | 
 |         for action in test.actions: | 
 |             assert isinstance(action, Action) | 
 |             assert action.type is not ActionType.PARAMETERIZED | 
 |             if action.is_state_check(): | 
 |                 parent.add_state_check_action(action) | 
 |             else: | 
 |                 node = None | 
 |                 if action.name in parent.children: | 
 |                     node = parent.children[action.name] | 
 |                 else: | 
 |                     node = ActionNode(action) | 
 |                     parent.add_child(node) | 
 |                 parent = node | 
 |  | 
 |  | 
 | def generage_graphviz_dot_file(root_node: ActionNode, | 
 |                                platform: Optional[TestPlatform]) -> str: | 
 |     def get_all_nodes_and_assign_graph_ids(root: ActionNode | 
 |                                            ) -> List[ActionNode]: | 
 |         current_graph_id = 0 | 
 |  | 
 |         def get_all_nodes_helper(node: ActionNode, nodes: List[ActionNode]): | 
 |             nonlocal current_graph_id | 
 |             if node.graph_id is not None: | 
 |                 return | 
 |             node.graph_id = current_graph_id | 
 |             current_graph_id += 1 | 
 |             nodes.append(node) | 
 |             if not node.children: | 
 |                 return | 
 |             for child in node.children.values(): | 
 |                 get_all_nodes_helper(child, nodes) | 
 |  | 
 |         # Skip the root node, as it is only there for the algorithm to work. | 
 |         all_nodes = [] | 
 |         for child in root.children.values(): | 
 |             get_all_nodes_helper(child, all_nodes) | 
 |         return all_nodes | 
 |  | 
 |     def print_graph(node: ActionNode) -> List[str]: | 
 |         assert node.graph_id is not None, node.action.name | 
 |         if not node.children: | 
 |             return [] | 
 |         lines = [] | 
 |         for child in node.children.values(): | 
 |             assert child.graph_id is not None, child.action.name | 
 |             edge_str = str(node.graph_id) + " -> " + str(child.graph_id) | 
 |             lines.append(edge_str) | 
 |             lines.extend(print_graph(child)) | 
 |         return lines | 
 |  | 
 |     lines = [] | 
 |     lines.append("strict digraph {") | 
 |     nodes = get_all_nodes_and_assign_graph_ids(root_node) | 
 |     for node in nodes: | 
 |         color_str = ("seagreen" if platform is None | 
 |                      or platform in node.action.full_coverage_platforms else | 
 |                      "sandybrown") | 
 |         label_str = node.get_graphviz_label() | 
 |         lines.append(f"{node.graph_id}[label={label_str} color={color_str}]") | 
 |     # Skip the root node, as it is only there for the algorithm to work. | 
 |     for child in root_node.children.values(): | 
 |         lines.extend(print_graph(child)) | 
 |     lines.append("}") | 
 |     return "\n".join(lines) | 
 |  | 
 |  | 
 | # Removes any nodes and actions from the graph that are not supported by the | 
 | # given platform. | 
 | def trim_graph_to_platform_actions(root_node: ActionNode, | 
 |                                    platform: TestPlatform) -> None: | 
 |     """ | 
 |     Removes any nodes and actions from the graph that are not supported by the | 
 |     given platform. | 
 |     """ | 
 |     new_children = {} | 
 |     for child in root_node.children.values(): | 
 |         if child.action.supported_for_platform(platform): | 
 |             new_children[child.action.name] = child | 
 |     root_node.children = new_children | 
 |     new_state_check_actions = {} | 
 |     for action in root_node.state_check_actions.values(): | 
 |         if action.supported_for_platform(platform): | 
 |             new_state_check_actions[action.name] = action | 
 |     root_node.state_check_actions = new_state_check_actions | 
 |     for child in root_node.children.values(): | 
 |         trim_graph_to_platform_actions(child, platform) | 
 |  | 
 |  | 
 | def generate_framework_tests(root_node: ActionNode, | 
 |                              platform: TestPlatform) -> List[CoverageTest]: | 
 |     assert isinstance(root_node, ActionNode) | 
 |  | 
 |     def GetAllPaths(node: ActionNode) -> List[List[ActionNode]]: | 
 |         assert node is not None | 
 |         assert isinstance(node, ActionNode) | 
 |         paths = [] | 
 |         for child in node.children.values(): | 
 |             for path in GetAllPaths(child): | 
 |                 assert path is not None | 
 |                 assert isinstance(path, list) | 
 |                 assert bool(path) | 
 |                 paths.append([node] + path) | 
 |         if len(paths) == 0: | 
 |             paths = [[node]] | 
 |         return paths | 
 |  | 
 |     all_paths = GetAllPaths(root_node) | 
 |     result = [] | 
 |     for path in all_paths: | 
 |         all_actions_in_path = [] | 
 |         for node in path[1:]:  # Skip the root node | 
 |             all_actions_in_path.append(node.action) | 
 |             all_actions_in_path.extend(node.state_check_actions.values()) | 
 |         result.append(CoverageTest(all_actions_in_path, {platform})) | 
 |     return result | 
 |  | 
 |  | 
 | def generate_coverage_file_and_percents( | 
 |         required_coverage_tests: List[CoverageTest], | 
 |         tested_graph_root: ActionNode, | 
 |         platform: TestPlatform) -> Tuple[str, float, float]: | 
 |     lines = [] | 
 |     total_actions = 0.0 | 
 |     actions_fully_covered = 0.0 | 
 |     actions_partially_covered = 0.0 | 
 |     for coverage_test in required_coverage_tests: | 
 |         action_strings = [] | 
 |         last_action_node = tested_graph_root | 
 |         for action in coverage_test.actions: | 
 |             total_actions += 1 | 
 |             coverage = None | 
 |             if last_action_node is not None: | 
 |                 if action.name in last_action_node.children: | 
 |                     coverage = action.get_coverage_for_platform(platform) | 
 |                 if (action.is_state_check() | 
 |                         and last_action_node.has_state_check_action(action)): | 
 |                     coverage = action.get_coverage_for_platform(platform) | 
 |             if coverage is None: | 
 |                 last_action_node = None | 
 |                 action_strings.append(action.name + '🌑') | 
 |                 continue | 
 |             elif (coverage == ActionCoverage.FULL): | 
 |                 actions_fully_covered += 1 | 
 |                 action_strings.append(action.name + '🌕') | 
 |             elif (coverage == ActionCoverage.PARTIAL): | 
 |                 action_strings.append(action.name + '🌓') | 
 |                 actions_partially_covered += 1 | 
 |             # Only proceed if the action was in the children. If not, then it | 
 |             # was in the stateless action list and we stay at the same node. | 
 |             if action.name in last_action_node.children: | 
 |                 last_action_node = last_action_node.children[action.name] | 
 |         lines.append(action_strings) | 
 |  | 
 |     full_coverage = actions_fully_covered / total_actions | 
 |     partial_coverage = ((actions_fully_covered + actions_partially_covered) / | 
 |                         total_actions) | 
 |     logging.info(f"Coverage for {platform}:") | 
 |     logging.info(f"Full coverage: {full_coverage:.0%}, " | 
 |                  f", with partial coverage: {partial_coverage:.0%}") | 
 |     return ("\n".join(["\t".join(line) | 
 |                        for line in lines]), full_coverage, partial_coverage) |