Skip to content

Commit 9a450eb

Browse files
authored
parallel autests: print failure output (#12877)
The parallel runner captured full autest output per worker but only displayed failed test names in the summary. This made CI failures hard to debug compared to the sequential runner. Extract and print the per-test failure detail block (sub-step pass/fail, reasons, file paths) so the same diagnostic information is available in parallel mode.
1 parent 948586f commit 9a450eb

1 file changed

Lines changed: 62 additions & 0 deletions

File tree

tests/autest-parallel.py.in

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,54 @@ def parse_autest_output(output: str) -> dict:
319319
return result
320320

321321

322+
def extract_failure_output(output: str, failed_tests: List[str]) -> Dict[str, str]:
323+
"""
324+
Extract the detailed autest output section for each failed test.
325+
326+
Autest emits a block per test starting with a line like
327+
``Test: <name>: Failed`` and continuing with indented detail lines until
328+
the next top-level ``Test:`` line or the end of meaningful output.
329+
330+
Args:
331+
output: Raw autest output from a worker
332+
failed_tests: List of test names that failed
333+
334+
Returns:
335+
Dictionary mapping each failed test name to its detail block
336+
"""
337+
if not failed_tests:
338+
return {}
339+
340+
clean = strip_ansi(output)
341+
lines = clean.split('\n')
342+
343+
# Identify line ranges for each top-level "Test: <name>:" block.
344+
test_block_re = re.compile(r'^Test:\s+(\S+):\s+(Passed|Failed|Skipped)', re.IGNORECASE)
345+
block_starts: List[Tuple[int, str]] = []
346+
for i, line in enumerate(lines):
347+
m = test_block_re.match(line.strip())
348+
if m:
349+
block_starts.append((i, m.group(1)))
350+
351+
failed_set = set(failed_tests)
352+
details: Dict[str, str] = {}
353+
354+
for idx, (start_line, name) in enumerate(block_starts):
355+
if name not in failed_set:
356+
continue
357+
# The block extends until the next top-level Test: line or end of
358+
# output.
359+
if idx + 1 < len(block_starts):
360+
end_line = block_starts[idx + 1][0]
361+
else:
362+
end_line = len(lines)
363+
block = '\n'.join(lines[start_line:end_line]).rstrip()
364+
if block:
365+
details[name] = block
366+
367+
return details
368+
369+
322370
def run_single_test(test: str, script_dir: Path, sandbox: Path, ats_bin: str, build_root: str, extra_args: List[str],
323371
env: dict) -> Tuple[str, float, str, str]:
324372
"""
@@ -610,6 +658,20 @@ def print_summary(results: List[TestResult], total_duration: float, expected_tim
610658
for test in sorted(all_failed_tests):
611659
print(f" - {test}")
612660

661+
# Print detailed failure output extracted from each worker's autest
662+
# output so CI logs contain actionable diagnostics.
663+
print("-" * 70)
664+
print("FAILED TEST OUTPUT:")
665+
print("-" * 70)
666+
for r in results:
667+
if not r.failed_tests or not r.output:
668+
continue
669+
details = extract_failure_output(r.output, r.failed_tests)
670+
for test_name in sorted(details):
671+
print(f"\n--- {test_name} ---")
672+
print(details[test_name])
673+
print("-" * 70)
674+
613675
# Check for timing discrepancies
614676
if expected_timings and actual_timings:
615677
timing_warnings = []

0 commit comments

Comments
 (0)