| # 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. |
| """Module for outputting results in a human-readable format.""" |
| |
| import tempfile |
| from typing import Dict, IO, List, Optional, Union |
| |
| from flake_suppressor_common import common_typing as ct |
| |
| UrlListType = List[str] |
| StringTagsToUrlsType = Dict[str, UrlListType] |
| TestToStringTagsType = Dict[str, StringTagsToUrlsType] |
| StringMapType = Dict[str, TestToStringTagsType] |
| |
| TestToUrlListType = Dict[str, UrlListType] |
| SuiteToTestsType = Dict[str, TestToUrlListType] |
| ConfigGroupedStringMapType = Dict[str, SuiteToTestsType] |
| |
| NodeType = Union[UrlListType, StringTagsToUrlsType, TestToStringTagsType, |
| StringMapType, TestToUrlListType, SuiteToTestsType, |
| ConfigGroupedStringMapType] |
| |
| |
| def GenerateHtmlOutputFile(aggregated_results: ct.AggregatedResultsType, |
| outfile: Optional[IO] = None) -> None: |
| """Generates an HTML results file. |
| |
| Args: |
| aggregated_results: A map containing the aggregated test results. |
| outfile: A file-like object to output to. Will create one if not provided. |
| """ |
| outfile = outfile or tempfile.NamedTemporaryFile( |
| mode='w', delete=False, suffix='.html') |
| try: |
| outfile.write('<html>\n<body>\n') |
| string_map = _ConvertAggregatedResultsToStringMap(aggregated_results) |
| _OutputMapToHtmlFile(string_map, 'Grouped By Test', outfile) |
| config_map = _ConvertFromTestGroupingToConfigGrouping(string_map) |
| _OutputMapToHtmlFile(config_map, 'Grouped By Config', outfile) |
| outfile.write('</body>\n</html>\n') |
| finally: |
| outfile.close() |
| print('HTML results: %s' % outfile.name) |
| |
| |
| def _OutputMapToHtmlFile(string_map: StringMapType, result_header: str, |
| output_file: IO) -> None: |
| """Outputs a map to a file as a nested list. |
| |
| Args: |
| string_map: The string map to output. |
| result_header: A string containing the header contents placed before the |
| nested list. |
| output_file: A file-like object to output the map to. |
| """ |
| output_file.write('<h1>%s</h1>\n' % result_header) |
| output_file.write('<ul>\n') |
| _RecursiveHtmlToFile(string_map, output_file) |
| output_file.write('</ul>\n') |
| |
| |
| def _RecursiveHtmlToFile(node: NodeType, output_file: IO) -> None: |
| """Recursively outputs a string map to an output file as HTML. |
| |
| Specifically, contents are output as an unordered list (<ul>). |
| |
| Args: |
| node: The current node to output. Must be either a dict or list. |
| output_file: A file-like object to output the HTML to. |
| """ |
| if isinstance(node, dict): |
| for key, value in node.items(): |
| output_file.write('<li>%s</li>\n' % key) |
| output_file.write('<ul>\n') |
| _RecursiveHtmlToFile(value, output_file) |
| output_file.write('</ul>\n') |
| elif isinstance(node, list): |
| for element in node: |
| output_file.write('<li><a href="%s">%s</a></li>\n' % (element, element)) |
| else: |
| raise RuntimeError('Unsupported type %s' % type(node).__name__) |
| |
| |
| def _ConvertAggregatedResultsToStringMap( |
| aggregated_results: ct.AggregatedResultsType) -> StringMapType: |
| """Converts aggregated results to a format usable by _RecursiveHtmlToFile. |
| |
| Specifically, updates the string representation of the typ tags and replaces |
| the lowest level dict with the build URL list. |
| |
| Args: |
| aggregated_results: A map containing the aggregated test results. |
| |
| Returns: |
| A map in the format: |
| { |
| 'suite': { |
| 'test': { |
| 'space separated typ tags': ['build', 'url', 'list'] |
| } |
| } |
| } |
| """ |
| string_map = {} |
| for suite, test_map in aggregated_results.items(): |
| for test, tag_map in test_map.items(): |
| for typ_tags, build_url_list in tag_map.items(): |
| str_typ_tags = ' '.join(typ_tags) |
| string_map.setdefault(suite, |
| {}).setdefault(test, |
| {})[str_typ_tags] = build_url_list |
| return string_map |
| |
| |
| def _ConvertFromTestGroupingToConfigGrouping(string_map: StringMapType |
| ) -> ConfigGroupedStringMapType: |
| """Converts |string| map to be grouped by typ tags/configuration. |
| |
| Args: |
| string_map: The output of _ConvertAggregatedResultsToStringMap. |
| |
| Returns: |
| A map in the format: |
| { |
| 'space separated typ tags': { |
| 'suite': { |
| 'test': ['build', 'url', 'list'] |
| } |
| } |
| } |
| """ |
| converted_map = {} |
| for suite, test_map in string_map.items(): |
| for test, tag_map in test_map.items(): |
| for typ_tags, build_urls in tag_map.items(): |
| converted_map.setdefault(typ_tags, {}).setdefault(suite, |
| {})[test] = build_urls |
| return converted_map |