1# Copyright 2021 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4"""Module for outputting results in a human-readable format.""" 5 6import tempfile 7from typing import Dict, IO, List, Optional, Union 8 9from flake_suppressor_common import common_typing as ct 10 11UrlListType = List[str] 12StringTagsToUrlsType = Dict[str, UrlListType] 13TestToStringTagsType = Dict[str, StringTagsToUrlsType] 14StringMapType = Dict[str, TestToStringTagsType] 15 16TestToUrlListType = Dict[str, UrlListType] 17SuiteToTestsType = Dict[str, TestToUrlListType] 18ConfigGroupedStringMapType = Dict[str, SuiteToTestsType] 19 20NodeType = Union[UrlListType, StringTagsToUrlsType, TestToStringTagsType, 21 StringMapType, TestToUrlListType, SuiteToTestsType, 22 ConfigGroupedStringMapType] 23 24 25def GenerateHtmlOutputFile(aggregated_results: ct.AggregatedResultsType, 26 outfile: Optional[IO] = None) -> None: 27 """Generates an HTML results file. 28 29 Args: 30 aggregated_results: A map containing the aggregated test results. 31 outfile: A file-like object to output to. Will create one if not provided. 32 """ 33 outfile = outfile or tempfile.NamedTemporaryFile( 34 mode='w', delete=False, suffix='.html') 35 try: 36 outfile.write('<html>\n<body>\n') 37 string_map = _ConvertAggregatedResultsToStringMap(aggregated_results) 38 _OutputMapToHtmlFile(string_map, 'Grouped By Test', outfile) 39 config_map = _ConvertFromTestGroupingToConfigGrouping(string_map) 40 _OutputMapToHtmlFile(config_map, 'Grouped By Config', outfile) 41 outfile.write('</body>\n</html>\n') 42 finally: 43 outfile.close() 44 print('HTML results: %s' % outfile.name) 45 46 47def _OutputMapToHtmlFile(string_map: StringMapType, result_header: str, 48 output_file: IO) -> None: 49 """Outputs a map to a file as a nested list. 50 51 Args: 52 string_map: The string map to output. 53 result_header: A string containing the header contents placed before the 54 nested list. 55 output_file: A file-like object to output the map to. 56 """ 57 output_file.write('<h1>%s</h1>\n' % result_header) 58 output_file.write('<ul>\n') 59 _RecursiveHtmlToFile(string_map, output_file) 60 output_file.write('</ul>\n') 61 62 63def _RecursiveHtmlToFile(node: NodeType, output_file: IO) -> None: 64 """Recursively outputs a string map to an output file as HTML. 65 66 Specifically, contents are output as an unordered list (<ul>). 67 68 Args: 69 node: The current node to output. Must be either a dict or list. 70 output_file: A file-like object to output the HTML to. 71 """ 72 if isinstance(node, dict): 73 for key, value in node.items(): 74 output_file.write('<li>%s</li>\n' % key) 75 output_file.write('<ul>\n') 76 _RecursiveHtmlToFile(value, output_file) 77 output_file.write('</ul>\n') 78 elif isinstance(node, list): 79 for element in node: 80 output_file.write('<li><a href="%s">%s</a></li>\n' % (element, element)) 81 else: 82 raise RuntimeError('Unsupported type %s' % type(node).__name__) 83 84 85def _ConvertAggregatedResultsToStringMap( 86 aggregated_results: ct.AggregatedResultsType) -> StringMapType: 87 """Converts aggregated results to a format usable by _RecursiveHtmlToFile. 88 89 Specifically, updates the string representation of the typ tags and replaces 90 the lowest level dict with the build URL list. 91 92 Args: 93 aggregated_results: A map containing the aggregated test results. 94 95 Returns: 96 A map in the format: 97 { 98 'suite': { 99 'test': { 100 'space separated typ tags': ['build', 'url', 'list'] 101 } 102 } 103 } 104 """ 105 string_map = {} 106 for suite, test_map in aggregated_results.items(): 107 for test, tag_map in test_map.items(): 108 for typ_tags, build_url_list in tag_map.items(): 109 str_typ_tags = ' '.join(typ_tags) 110 string_map.setdefault(suite, 111 {}).setdefault(test, 112 {})[str_typ_tags] = build_url_list 113 return string_map 114 115 116def _ConvertFromTestGroupingToConfigGrouping(string_map: StringMapType 117 ) -> ConfigGroupedStringMapType: 118 """Converts |string| map to be grouped by typ tags/configuration. 119 120 Args: 121 string_map: The output of _ConvertAggregatedResultsToStringMap. 122 123 Returns: 124 A map in the format: 125 { 126 'space separated typ tags': { 127 'suite': { 128 'test': ['build', 'url', 'list'] 129 } 130 } 131 } 132 """ 133 converted_map = {} 134 for suite, test_map in string_map.items(): 135 for test, tag_map in test_map.items(): 136 for typ_tags, build_urls in tag_map.items(): 137 converted_map.setdefault(typ_tags, {}).setdefault(suite, 138 {})[test] = build_urls 139 return converted_map 140