Merge pull request 'fix(ci): simplify Flutter test execution' (#9) from fix/simplify-ci-tests into main
Reviewed-on: #9
This commit was merged in pull request #9.
This commit is contained in:
+3
-207
@@ -31,217 +31,13 @@ jobs:
|
|||||||
run: flutter analyze --no-fatal-infos --no-fatal-warnings
|
run: flutter analyze --no-fatal-infos --no-fatal-warnings
|
||||||
|
|
||||||
- name: Ejecutar tests criticos
|
- name: Ejecutar tests criticos
|
||||||
timeout-minutes: 4
|
timeout-minutes: 15
|
||||||
run: |
|
run: |
|
||||||
python3 - <<'PY'
|
flutter test test/servicios/servicio_programacion_alarmas_test.dart test/estado/estado_alarmas_test.dart --concurrency=1 --timeout=60s
|
||||||
import os
|
|
||||||
import signal
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import time
|
|
||||||
|
|
||||||
test_paths = [
|
|
||||||
'test/servicios/servicio_programacion_alarmas_test.dart',
|
|
||||||
'test/estado/estado_alarmas_test.dart',
|
|
||||||
]
|
|
||||||
use_process_group = hasattr(os, 'killpg') and os.name != 'nt'
|
|
||||||
workspace = os.getcwd()
|
|
||||||
process_patterns = (
|
|
||||||
'flutter_tester',
|
|
||||||
'flutter_tools.snapshot',
|
|
||||||
'frontend_server',
|
|
||||||
'dartvm',
|
|
||||||
'dartaotruntime',
|
|
||||||
)
|
|
||||||
|
|
||||||
def kill_process_group(pid):
|
|
||||||
if not use_process_group:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
os.killpg(pid, signal.SIGTERM)
|
|
||||||
time.sleep(2)
|
|
||||||
os.killpg(pid, signal.SIGKILL)
|
|
||||||
except ProcessLookupError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def cleanup_flutter_processes():
|
|
||||||
ps = subprocess.run(
|
|
||||||
['ps', '-axww', '-o', 'pid=', '-o', 'command='],
|
|
||||||
text=True,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
check=False,
|
|
||||||
)
|
|
||||||
current_pid = os.getpid()
|
|
||||||
killed = []
|
|
||||||
for raw in ps.stdout.splitlines():
|
|
||||||
line = raw.strip()
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
pid_text, _, command = line.partition(' ')
|
|
||||||
try:
|
|
||||||
pid = int(pid_text)
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
if pid == current_pid:
|
|
||||||
continue
|
|
||||||
if workspace not in command and not any(path in command for path in test_paths):
|
|
||||||
continue
|
|
||||||
if not any(pattern in command for pattern in process_patterns + tuple(test_paths)):
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
os.kill(pid, signal.SIGTERM)
|
|
||||||
killed.append(pid)
|
|
||||||
except ProcessLookupError:
|
|
||||||
pass
|
|
||||||
if killed:
|
|
||||||
print('Procesos Flutter de test terminados: ' + ', '.join(map(str, killed)), flush=True)
|
|
||||||
time.sleep(1)
|
|
||||||
for pid in killed:
|
|
||||||
try:
|
|
||||||
os.kill(pid, signal.SIGKILL)
|
|
||||||
except ProcessLookupError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def run_test_file(path):
|
|
||||||
cmd = [
|
|
||||||
'flutter',
|
|
||||||
'test',
|
|
||||||
'--no-pub',
|
|
||||||
'--concurrency=1',
|
|
||||||
'--timeout=30s',
|
|
||||||
'--reporter=expanded',
|
|
||||||
path,
|
|
||||||
]
|
|
||||||
print('$ ' + ' '.join(cmd), 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,
|
|
||||||
)
|
|
||||||
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:
|
|
||||||
code = run_test_file(path)
|
|
||||||
if code != 0:
|
|
||||||
sys.exit(code)
|
|
||||||
sys.exit(0)
|
|
||||||
PY
|
|
||||||
|
|
||||||
- name: Limpiar procesos Flutter de tests
|
- name: Limpiar procesos Flutter de tests
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: pkill -f 'flutter_tester|flutter_tools.snapshot|dartaotruntime' 2>/dev/null || true
|
||||||
python3 - <<'PY'
|
|
||||||
import os
|
|
||||||
import signal
|
|
||||||
import subprocess
|
|
||||||
import time
|
|
||||||
|
|
||||||
workspace = os.getcwd()
|
|
||||||
test_paths = (
|
|
||||||
'test/estado/estado_alarmas_test.dart',
|
|
||||||
'test/servicios/servicio_programacion_alarmas_test.dart',
|
|
||||||
)
|
|
||||||
patterns = (
|
|
||||||
'flutter_tester',
|
|
||||||
'flutter_tools.snapshot',
|
|
||||||
'frontend_server',
|
|
||||||
'dartvm',
|
|
||||||
'dartaotruntime',
|
|
||||||
) + test_paths
|
|
||||||
current_pid = os.getpid()
|
|
||||||
ps = subprocess.run(
|
|
||||||
['ps', '-axww', '-o', 'pid=', '-o', 'command='],
|
|
||||||
text=True,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
check=False,
|
|
||||||
)
|
|
||||||
killed = []
|
|
||||||
for raw in ps.stdout.splitlines():
|
|
||||||
line = raw.strip()
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
pid_text, _, command = line.partition(' ')
|
|
||||||
try:
|
|
||||||
pid = int(pid_text)
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
if pid == current_pid:
|
|
||||||
continue
|
|
||||||
if workspace not in command and not any(path in command for path in test_paths):
|
|
||||||
continue
|
|
||||||
if not any(pattern in command for pattern in patterns):
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
os.kill(pid, signal.SIGTERM)
|
|
||||||
killed.append(pid)
|
|
||||||
except ProcessLookupError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if killed:
|
|
||||||
print('Procesos Flutter de test terminados: ' + ', '.join(map(str, killed)))
|
|
||||||
time.sleep(1)
|
|
||||||
for pid in killed:
|
|
||||||
try:
|
|
||||||
os.kill(pid, signal.SIGKILL)
|
|
||||||
except ProcessLookupError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
print('No quedaron procesos Flutter de test vivos')
|
|
||||||
PY
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build APK + AAB release
|
name: Build APK + AAB release
|
||||||
|
|||||||
Reference in New Issue
Block a user