ci(tools): Clip idf.py failure logs so Windows log_cli cannot stall

pytest.ini enables log_cli, so logging the full ninja stdout after a
failed first-time build is one multi-MB ERROR. That hung shard 3/6 for
hours after the hints test. Log the last 80 lines; keep the full output
on the exception and at DEBUG.
This commit is contained in:
Jakub Kocka
2026-08-28 15:19:59 +02:00
parent 02a689149c
commit 2a07ad46e7

View File

@@ -19,6 +19,55 @@ EnvDict = dict[str, str]
IdfPyFunc = typing.Callable[..., subprocess.CompletedProcess]
_LOG_ERROR_MARKERS = (
'CMake Error',
'FAILED:',
'fatal error',
'ninja: build stopped',
'HINT:',
)
def _clip_log_output(text: str | None, max_lines: int = 80, max_line_len: int = 400) -> str:
"""Last ``max_lines`` of process output for logging, plus failure lines.
pytest.ini enables ``log_cli``, so ``logging.error(full_stdout)`` after a
failed build is one record. On Windows CI that live-log can stall for hours
even when the line count is small: CMake's ``-- Component paths:`` line is
a single multi-KB (sometimes multi-MB) string.
"""
if not text:
return ''
lines = text.splitlines()
def _short(line: str) -> str:
if len(line) <= max_line_len:
return line
return line[:max_line_len] + f'... [{len(line) - max_line_len} chars omitted]'
omitted = max(0, len(lines) - max_lines)
tail_start = len(lines) - max_lines if omitted else 0
tail = lines[tail_start:]
failures: list[str] = []
for idx, line in enumerate(lines):
if idx >= tail_start:
break
if any(marker in line for marker in _LOG_ERROR_MARKERS):
failures.append(line)
if len(failures) >= 40:
break
parts: list[str] = []
if failures:
parts.append('[... failure lines ...]')
parts.extend(_short(line) for line in failures)
if omitted:
parts.append(f'[... {omitted} lines omitted ...]')
parts.extend(_short(line) for line in tail)
return '\n'.join(parts)
def normalize_output(text: str) -> str:
"""Collapse all whitespace runs to a single space.
@@ -108,8 +157,8 @@ def run_idf_py(
except subprocess.CalledProcessError as e:
logging.error('The following idf.py command has failed: {}'.format(' '.join(cmd)))
logging.error(f'Working directory: {workdir}')
logging.error(f'Stdout: {e.stdout}')
logging.error(f'Stderr: {e.stderr}')
logging.error(f'Stdout: {_clip_log_output(e.stdout)}')
logging.error(f'Stderr: {_clip_log_output(e.stderr)}')
raise
@@ -153,8 +202,8 @@ def run_cmake(
except subprocess.CalledProcessError as e:
logging.error('The following cmake command has failed: {}'.format(' '.join(cmd)))
logging.error(f'Working directory: {workdir}')
logging.error(f'Stdout: {e.stdout}')
logging.error(f'Stderr: {e.stderr}')
logging.error(f'Stdout: {_clip_log_output(e.stdout)}')
logging.error(f'Stderr: {_clip_log_output(e.stderr)}')
raise