@@ -170,72 +170,104 @@ def process_first_table(lines: List[str], start_idx: int) -> Tuple[List[str], in
170170 return content , i
171171
172172
173+ SECTION_HEADERS = {
174+ '[ERROR] Failures:' : 'failures' ,
175+ '[ERROR] Errors:' : 'errors' ,
176+ '[WARNING] Flakes:' : 'flakes' ,
177+ }
178+
179+
173180# TODO: Yetus should support this natively, but docker integration with job summaries doesn't seem
174181# to work out of the box.
175- def extract_failed_tests_from_unit_files (output_dir : Path ) -> List [Tuple [str , List [str ]]]:
182+ def extract_test_results_from_unit_files (
183+ output_dir : Path ,
184+ ) -> Tuple [List [Tuple [str , List [str ]]], List [Tuple [str , List [str ]]]]:
176185 """
177- Extract failed test names from patch-unit-*.txt files.
186+ Extract failed and flaky test names from patch-unit-*.txt files.
178187
179- Parses Maven surefire output to find lines like:
180- [ERROR] org.apache.hadoop.hbase.types.TestPBCell.testRoundTrip
188+ Parses Maven surefire summary sections:
189+ [ERROR] Failures: - assertion failures
190+ [ERROR] Errors: - exceptions thrown during test execution
191+ [WARNING] Flakes: - tests that failed on some runs but passed on retry
181192
182193 Returns:
183- List of (module_name, [failed_test_names]) tuples
194+ Tuple of (failed_tests, flaky_tests) where each is
195+ List of (module_name, [test_names]) tuples
184196 """
185- results = []
197+ all_failed = []
198+ all_flaky = []
186199
187200 for unit_file in output_dir .glob ('patch-unit-*.txt' ):
188201 module_name = unit_file .stem .replace ('patch-unit-' , '' )
189202 failed_tests = set ()
203+ flaky_tests = set ()
190204
191205 with open (unit_file , 'r' ) as f :
192- in_failures_section = False
206+ current_section = None
193207 for line in f :
194208 stripped = line .strip ()
195209
196- if stripped == '[ERROR] Failures:' :
197- in_failures_section = True
210+ if stripped in SECTION_HEADERS :
211+ current_section = SECTION_HEADERS [ stripped ]
198212 continue
199213
200- if in_failures_section :
201- if stripped .startswith ('[ERROR]' ) and not stripped .startswith ('[ERROR] Run' ):
202- test_name = stripped .replace ('[ERROR] ' , '' ).strip ()
214+ if stripped .startswith ('[ERROR] Tests run:' ):
215+ current_section = None
216+ continue
217+
218+ if current_section is None :
219+ continue
220+
221+ if re .match (r'\[(ERROR|INFO)]\s+Run \d+:' , stripped ):
222+ continue
223+ if stripped .startswith ('[INFO]' ) or not stripped :
224+ continue
225+
226+ if current_section in ('failures' , 'errors' ):
227+ if stripped .startswith ('[ERROR]' ):
228+ test_name = stripped [len ('[ERROR]' ):].strip ()
203229 if test_name and '.' in test_name :
204230 failed_tests .add (test_name )
205- elif stripped .startswith ('[INFO]' ) or not stripped :
206- in_failures_section = False
231+ elif current_section == 'flakes' :
232+ if stripped .startswith ('[WARNING]' ):
233+ test_name = stripped [len ('[WARNING]' ):].strip ()
234+ if test_name and '.' in test_name :
235+ flaky_tests .add (test_name )
207236
208237 if failed_tests :
209- results .append ((module_name , sorted (failed_tests )))
238+ all_failed .append ((module_name , sorted (failed_tests )))
239+ if flaky_tests :
240+ all_flaky .append ((module_name , sorted (flaky_tests )))
210241
211- return results
242+ return all_failed , all_flaky
212243
213244
214- def format_failed_tests_section (failed_tests : List [Tuple [str , List [str ]]]) -> List [str ]:
215- """
216- Format failed tests into markdown.
217-
218- Args:
219- failed_tests: List of (module_name, [test_names]) tuples
220-
221- Returns:
222- List of markdown lines
223- """
224- if not failed_tests :
245+ def _format_test_table (
246+ heading : str , tests : List [Tuple [str , List [str ]]], column_name : str
247+ ) -> List [str ]:
248+ if not tests :
225249 return []
226250
227251 content = []
228- content .append ('\n ## ❌ Failed Tests \n \n ' )
229- content .append ('| Module | Failed Tests |\n ' )
252+ content .append (f '\n ## { heading } \n \n ' )
253+ content .append (f '| Module | { column_name } |\n ' )
230254 content .append ('|--------|-------------|\n ' )
231255
232- for module_name , tests in failed_tests :
233- tests_str = ', ' . join ( tests )
234- content .append (f'| { module_name } | { tests_str } |\n ' )
256+ for module_name , names in tests :
257+ for name in names :
258+ content .append (f'| { module_name } | { name } |\n ' )
235259
236260 return content
237261
238262
263+ def format_failed_tests_section (failed_tests : List [Tuple [str , List [str ]]]) -> List [str ]:
264+ return _format_test_table ('❌ Failed Tests' , failed_tests , 'Failed Tests' )
265+
266+
267+ def format_flaky_tests_section (flaky_tests : List [Tuple [str , List [str ]]]) -> List [str ]:
268+ return _format_test_table ('⚠️ Flaky Tests (passed on retry)' , flaky_tests , 'Flaky Tests' )
269+
270+
239271def process_second_table (lines : List [str ], start_idx : int ) -> Tuple [List [str ], int ]:
240272 """
241273 Process the second table (Subsystem, Report/Notes).
@@ -306,8 +338,9 @@ def convert_console_to_markdown(input_file: str, output_file: Optional[str] = No
306338
307339 # Extract and add failed tests from patch-unit-*.txt files
308340 if not added_failed_tests :
309- failed_tests = extract_failed_tests_from_unit_files (output_dir )
341+ failed_tests , flaky_tests = extract_test_results_from_unit_files (output_dir )
310342 content .extend (format_failed_tests_section (failed_tests ))
343+ content .extend (format_flaky_tests_section (flaky_tests ))
311344 added_failed_tests = True
312345 continue
313346
0 commit comments