33# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
44"""Library to parse JUnit XML files and return a markdown report."""
55
6+ from typing import TypedDict
7+ import platform
8+
69from junitparser import JUnitXml , Failure
710
11+
12+ # This data structure should match the definition in llvm-zorg in
13+ # premerge/advisor/advisor_lib.py
14+ class FailureExplanation (TypedDict ):
15+ name : str
16+ explained : bool
17+ reason : str | None
18+
19+
820SEE_BUILD_FILE_STR = "Download the build's log file to see the details."
921UNRELATED_FAILURES_STR = (
1022 "If these failures are unrelated to your changes (for example "
@@ -82,16 +94,29 @@ def find_failure_in_ninja_logs(ninja_logs: list[list[str]]) -> list[tuple[str, s
8294 return failures
8395
8496
85- def _format_ninja_failures (ninja_failures : list [tuple [str , str ]]) -> list [str ]:
86- """Formats ninja failures into summary views for the report."""
97+ def _format_failures (
98+ failures : list [tuple [str , str ]], failure_explanations : dict [str , FailureExplanation ]
99+ ) -> list [str ]:
100+ """Formats failures into summary views for the report."""
87101 output = []
88- for build_failure in ninja_failures :
102+ for build_failure in failures :
89103 failed_action , failure_message = build_failure
104+ failure_explanation = None
105+ if failed_action in failure_explanations :
106+ failure_explanation = failure_explanations [failed_action ]
107+ output .append ("<details>" )
108+ if failure_explanation :
109+ output .extend (
110+ [
111+ f"<summary>{ failed_action } (Likely Already Failing)</summary>" "" ,
112+ failure_explanation ["reason" ],
113+ "" ,
114+ ]
115+ )
116+ else :
117+ output .extend ([f"<summary>{ failed_action } </summary>" , "" ])
90118 output .extend (
91119 [
92- "<details>" ,
93- f"<summary>{ failed_action } </summary>" ,
94- "" ,
95120 "```" ,
96121 failure_message ,
97122 "```" ,
@@ -132,12 +157,19 @@ def generate_report(
132157 ninja_logs : list [list [str ]],
133158 size_limit = 1024 * 1024 ,
134159 list_failures = True ,
160+ failure_explanations_list : list [FailureExplanation ] = [],
135161):
136162 failures = get_failures (junit_objects )
137163 tests_run = 0
138164 tests_skipped = 0
139165 tests_failed = 0
140166
167+ failure_explanations : dict [str , FailureExplanation ] = {}
168+ for failure_explanation in failure_explanations_list :
169+ if not failure_explanation ["explained" ]:
170+ continue
171+ failure_explanations [failure_explanation ["name" ]] = failure_explanation
172+
141173 for results in junit_objects :
142174 for testsuite in results :
143175 tests_run += testsuite .tests
@@ -176,7 +208,7 @@ def generate_report(
176208 "" ,
177209 ]
178210 )
179- report .extend (_format_ninja_failures (ninja_failures ))
211+ report .extend (_format_failures (ninja_failures , failure_explanations ))
180212 report .extend (
181213 [
182214 "" ,
@@ -212,18 +244,7 @@ def plural(num_tests):
212244
213245 for testsuite_name , failures in failures .items ():
214246 report .extend (["" , f"### { testsuite_name } " ])
215- for name , output in failures :
216- report .extend (
217- [
218- "<details>" ,
219- f"<summary>{ name } </summary>" ,
220- "" ,
221- "```" ,
222- output ,
223- "```" ,
224- "</details>" ,
225- ]
226- )
247+ report .extend (_format_failures (failures , failure_explanations ))
227248 elif return_code != 0 :
228249 # No tests failed but the build was in a failed state. Bring this to the user's
229250 # attention.
@@ -248,7 +269,7 @@ def plural(num_tests):
248269 "" ,
249270 ]
250271 )
251- report .extend (_format_ninja_failures (ninja_failures ))
272+ report .extend (_format_failures (ninja_failures , failure_explanations ))
252273
253274 if failures or return_code != 0 :
254275 report .extend (["" , UNRELATED_FAILURES_STR ])
@@ -285,3 +306,13 @@ def load_info_from_files(build_log_files):
285306def generate_report_from_files (title , return_code , build_log_files ):
286307 junit_objects , ninja_logs = load_info_from_files (build_log_files )
287308 return generate_report (title , return_code , junit_objects , ninja_logs )
309+
310+
311+ def compute_platform_title () -> str :
312+ logo = ":window:" if platform .system () == "Windows" else ":penguin:"
313+ # On Linux the machine value is x86_64 on Windows it is AMD64.
314+ if platform .machine () == "x86_64" or platform .machine () == "AMD64" :
315+ arch = "x64"
316+ else :
317+ arch = platform .machine ()
318+ return f"{ logo } { platform .system ()} { arch } Test Results"
0 commit comments