From 82f70e2fa334a418e74fd2dcb735c438d9206aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Bautista=20Fern=C3=A1ndez?= Date: Fri, 29 May 2026 13:55:29 +0200 Subject: [PATCH] fix(ci): poll flutter test output from file --- .gitea/workflows/build.yml | 95 +++++++++++++++++++++++--------------- 1 file changed, 59 insertions(+), 36 deletions(-) diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 592b28e..f8ad2bf 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -31,13 +31,14 @@ jobs: run: flutter analyze --no-fatal-infos --no-fatal-warnings - name: Ejecutar tests criticos + timeout-minutes: 4 run: | python3 - <<'PY' import os - import select import signal import subprocess import sys + import tempfile import time test_paths = [ @@ -113,42 +114,64 @@ jobs: path, ] print('$ ' + ' '.join(cmd), flush=True) - process = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - bufsize=1, - start_new_session=use_process_group, - ) - deadline = time.monotonic() + 120 - success_deadline = None - while True: - if process.stdout is not None and select.select([process.stdout], [], [], 1)[0]: - line = process.stdout.readline() - if line: - print(line, end='', flush=True) - if 'All tests passed!' in line: - success_deadline = time.monotonic() + 5 - - returncode = process.poll() - if returncode is not None: - cleanup_flutter_processes() - return returncode - if success_deadline is not None and time.monotonic() >= success_deadline: - print( - f'WARN: {path} informó éxito pero Flutter no cerró; terminando proceso residual', - flush=True, + output_path = None + try: + with tempfile.NamedTemporaryFile(delete=False) as tmp: + output_path = tmp.name + with open(output_path, 'w', encoding='utf-8', errors='replace') as writer: + process = subprocess.Popen( + cmd, + stdout=writer, + stderr=subprocess.STDOUT, + start_new_session=use_process_group, ) - kill_process_group(process.pid) - cleanup_flutter_processes() - return 0 - if time.monotonic() >= deadline: - print(f'ERROR: timeout ejecutando test critico {path}', file=sys.stderr, flush=True) - kill_process_group(process.pid) - cleanup_flutter_processes() - return 124 - time.sleep(1) + deadline = time.monotonic() + 90 + success_deadline = None + + def flush_new_output(): + nonlocal success_deadline + with open(output_path, 'r', encoding='utf-8', errors='replace') as reader: + reader.seek(flush_new_output.offset) + chunk = reader.read() + flush_new_output.offset = reader.tell() + if not chunk: + return + print(chunk, end='', flush=True) + if 'All tests passed!' in chunk and success_deadline is None: + success_deadline = time.monotonic() + 5 + + flush_new_output.offset = 0 + + while True: + flush_new_output() + + returncode = process.poll() + if returncode is not None: + flush_new_output() + cleanup_flutter_processes() + return returncode + if success_deadline is not None and time.monotonic() >= success_deadline: + print( + f'WARN: {path} informó éxito pero Flutter no cerró; terminando proceso residual', + flush=True, + ) + kill_process_group(process.pid) + flush_new_output() + cleanup_flutter_processes() + return 0 + if time.monotonic() >= deadline: + print(f'ERROR: timeout ejecutando test critico {path}', file=sys.stderr, flush=True) + kill_process_group(process.pid) + flush_new_output() + cleanup_flutter_processes() + return 124 + time.sleep(1) + finally: + if output_path: + try: + os.unlink(output_path) + except FileNotFoundError: + pass cleanup_flutter_processes() for path in test_paths: