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+
68from junitparser import JUnitXml , Failure
79
10+
11+ # This data structure should match the definition in llvm-zorg in
12+ # premerge/advisor/advisor_lib.py
13+ class FailureExplanation (TypedDict ):
14+ name : str
15+ explained : bool
16+ reason : str | None
17+
18+
819SEE_BUILD_FILE_STR = "Download the build's log file to see the details."
920UNRELATED_FAILURES_STR = (
1021 "If these failures are unrelated to your changes (for example "
@@ -82,16 +93,29 @@ def find_failure_in_ninja_logs(ninja_logs: list[list[str]]) -> list[tuple[str, s
8293 return failures
8394
8495
85- def _format_ninja_failures (ninja_failures : list [tuple [str , str ]]) -> list [str ]:
86- """Formats ninja failures into summary views for the report."""
96+ def _format_failures (
97+ failures : list [tuple [str , str ]], failure_explanations : dict [str , FailureExplanation ]
98+ ) -> list [str ]:
99+ """Formats failures into summary views for the report."""
87100 output = []
88- for build_failure in ninja_failures :
101+ for build_failure in failures :
89102 failed_action , failure_message = build_failure
103+ failure_explanation = None
104+ if failed_action in failure_explanations :
105+ failure_explanation = failure_explanations [failed_action ]
106+ output .append ("<details>" )
107+ if failure_explanation :
108+ output .extend (
109+ [
110+ f"<summary>{ failed_action } (Likely Already Failing)</summary>" "" ,
111+ failure_explanation ["reason" ],
112+ "" ,
113+ ]
114+ )
115+ else :
116+ output .extend ([f"<summary>{ failed_action } </summary>" , "" ])
90117 output .extend (
91118 [
92- "<details>" ,
93- f"<summary>{ failed_action } </summary>" ,
94- "" ,
95119 "```" ,
96120 failure_message ,
97121 "```" ,
@@ -132,12 +156,19 @@ def generate_report(
132156 ninja_logs : list [list [str ]],
133157 size_limit = 1024 * 1024 ,
134158 list_failures = True ,
159+ failure_explanations_list : list [FailureExplanation ] = [],
135160):
136161 failures = get_failures (junit_objects )
137162 tests_run = 0
138163 tests_skipped = 0
139164 tests_failed = 0
140165
166+ failure_explanations : dict [str , FailureExplanation ] = {}
167+ for failure_explanation in failure_explanations_list :
168+ if not failure_explanation ["explained" ]:
169+ continue
170+ failure_explanations [failure_explanation ["name" ]] = failure_explanation
171+
141172 for results in junit_objects :
142173 for testsuite in results :
143174 tests_run += testsuite .tests
@@ -176,7 +207,7 @@ def generate_report(
176207 "" ,
177208 ]
178209 )
179- report .extend (_format_ninja_failures (ninja_failures ))
210+ report .extend (_format_failures (ninja_failures , failure_explanations ))
180211 report .extend (
181212 [
182213 "" ,
@@ -212,18 +243,7 @@ def plural(num_tests):
212243
213244 for testsuite_name , failures in failures .items ():
214245 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- )
246+ report .extend (_format_failures (failures , failure_explanations ))
227247 elif return_code != 0 :
228248 # No tests failed but the build was in a failed state. Bring this to the user's
229249 # attention.
@@ -248,7 +268,7 @@ def plural(num_tests):
248268 "" ,
249269 ]
250270 )
251- report .extend (_format_ninja_failures (ninja_failures ))
271+ report .extend (_format_failures (ninja_failures , failure_explanations ))
252272
253273 if failures or return_code != 0 :
254274 report .extend (["" , UNRELATED_FAILURES_STR ])
0 commit comments