#!/usr/bin/env bash # La puerta: decide si un cambio automatico se queda o se revierte. # # Es la pieza central del pipeline nocturno. Todo lo que el sistema se cambia a # si mismo pasa por aqui, y lo que no la pasa se deshace ENTERO. Sin esto, # dejar algo modificando codigo sin nadie delante es una forma elaborada de # romper un proyecto que funcionaba. # # Que comprueba, en orden de lo mas barato a lo mas caro (para fallar pronto): # 1. que el Python compile # 2. que el catalogo sea JSON valido y el generador lo reproduzca # 3. que el emparejador no haya perdido ordenes (08) # 4. que las acciones sigan respondiendo (repaso completo) # 5. la suite entera # # puerta.sh rapida solo 1-3, para iterar # puerta.sh completa todo, antes de commitear set -uo pipefail RAIZ=$(cd -P "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd) MODO=${1:-completa} cd "$RAIZ" fallo() { echo "PUERTA: $1" >&2; exit 1; } # --- 1. compila el Python ------------------------------------------------- python3 -m compileall -q newelle/src config voz/*.py >/dev/null 2>&1 \ || fallo "hay Python que no compila" # --- 2. el catalogo es valido y reproducible ------------------------------ python3 -c " import json,sys d=json.load(open('newelle/data/acciones/acciones.json')) acc=d['acciones'] assert len(acc)>100, f'solo {len(acc)} acciones' ids=[a['id'] for a in acc] assert len(ids)==len(set(ids)), 'ids repetidos' for a in acc: assert a.get('frases'), f\"{a['id']} sin frases\" assert a.get('acuse'), f\"{a['id']} sin acuse\" assert 'señor' in a['acuse'].lower(), f\"{a['id']} no trata de señor\" if a.get('captura') and a.get('comando'): assert '{argumento' in a['comando'], f\"{a['id']} captura y no usa el argumento\" " 2>/dev/null || fallo "el catalogo esta mal formado" # El generador es la fuente: si no reproduce el catalogo, alguien edito el # producto y el proximo que regenere pierde el trabajo. cp newelle/data/acciones/acciones.json /tmp/jarvis-puerta-antes.json (cd newelle/data/acciones && python3 _generar.py >/dev/null 2>&1) if ! diff -q /tmp/jarvis-puerta-antes.json newelle/data/acciones/acciones.json >/dev/null 2>&1; then cp /tmp/jarvis-puerta-antes.json newelle/data/acciones/acciones.json fallo "el generador no reproduce el catalogo (se edito el producto)" fi # --- 3. el emparejador no ha perdido ordenes ------------------------------ python3 - <<'FIN' || fallo "el emparejador ha perdido ordenes que antes acertaba" import importlib.util, sys spec = importlib.util.spec_from_file_location("acc", "newelle/src/utility/acciones.py") m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m) cat = m.Catalogo(["newelle/data/acciones/acciones.json"]) # El minimo innegociable: si alguna de estas deja de funcionar, el cambio se # revierte pase lo que pase. Son las que el usuario usa de verdad. IMPRESCINDIBLES = [ ("cuanto espacio queda", "disco_libre"), ("cuanta memoria", "memoria"), ("que temperatura hay", "temperatura"), ("abre una terminal", "terminal_abrir"), ("apaga la pantalla", "apagar_pantalla"), ("captura de pantalla", "captura_pantalla"), ("abre una ventana privada", "ff_firefox_privado"), ("pon la ventana a la izquierda", "ventana_izquierda"), ("necesito que pongas la pelicula de iron man", "pelicula_abrir"), ("podrias abrir una terminal", "terminal_abrir"), ] malas = [] for frase, esperado in IMPRESCINDIBLES: a, _ = cat.busca(frase) if a is None or a.id != esperado: malas.append(f"{frase!r}→{a.id if a else 'ninguna'} (deberia {esperado})") # Y la conversacion no puede engancharse a una accion CHARLA = ["que tal estas", "necesito un abrazo", "quiero contarte una cosa", "cuentame un chiste", "que piensas de la vida", "necesito pensar"] coladas = [c for c in CHARLA if cat.busca(c)[0] is not None] if malas or coladas: for x in malas: print(" perdida:", x, file=sys.stderr) for c in coladas: print(" charla colada:", c, file=sys.stderr) sys.exit(1) FIN [ "$MODO" = rapida ] && { echo "PUERTA: rapida OK"; exit 0; } # --- 4. las acciones responden ------------------------------------------- SALIDA=$(timeout 900 python3 config/repasar_acciones.py 2>&1 | tail -3) echo "$SALIDA" | grep -q "de .* responden bien" || fallo "el repaso de acciones no termino" ROTAS=$(echo "$SALIDA" | grep -oE "fallan: .*" || true) [ -n "$ROTAS" ] && fallo "hay acciones rotas → $ROTAS" # --- 5. la suite ---------------------------------------------------------- # Se salta la 07, que abre la app y se queda colgada sin nadie delante. SUITE=$(cd pruebas && for t in [0-9][0-9]_*.sh; do case "$t" in 05_*|07_*) continue;; esac timeout 400 "./$t" 2>&1 | tail -1 done) FALLOS=$(echo "$SUITE" | grep -oE "[0-9]+ fallan" | awk '{s+=$1} END{print s+0}') [ "${FALLOS:-0}" -gt 0 ] && fallo "la suite tiene $FALLOS comprobaciones en rojo" echo "PUERTA: completa OK"