xref: /aosp_15_r20/external/cronet/testing/flake_suppressor_common/result_output.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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