diff --git a/scripts/test-khipu-radio.py b/scripts/test-khipu-radio.py new file mode 100755 index 00000000..934a87c9 --- /dev/null +++ b/scripts/test-khipu-radio.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# test-khipu-radio.py — REGRESIÓN del bug de libdrm (2026-07-22). No es un test cualquiera: es la +# cicatriz. Si el radio de un paquete compartido entre colas vuelve a medirse sobre una sola cola, +# esto falla. La propiedad que blinda: el radio SIEMPRE cruza todas las colas del disco. +import sys, subprocess +from pathlib import Path +ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(ROOT / "scripts")) +import khipu + +def main(): + colas = khipu.cargar_colas() + rev = khipu.dependientes(colas) + memb = khipu.membresia(colas) + fallos = [] + + # 1) libdrm lo consumen recetas de MÁS de una cola (corpus + incoming-kde). Medirlo sobre una + # sola cola es exactamente el bug. + if ("corpus", "libdrm") in rev: + colas_consumidoras = {q for q, _ in rev[("corpus", "libdrm")]} + if len(colas_consumidoras) < 2: + fallos.append(f"libdrm consumido desde una sola cola {colas_consumidoras} " + f"— el radio se está midiendo recortado") + # y su membresía DEBE incluir escritorio-kde (la mentira original decía sólo mirada) + if "escritorio-kde" not in memb.get(("corpus", "libdrm"), set()): + fallos.append("libdrm NO atribuido a escritorio-kde — perfiles vuelve a mentir") + + # 2) Invariante general: para todo nudo del corpus consumido por alguna receta de una cola + # incoming-*, su membresía y sus dependientes DEBEN reflejar esa cola. (Detecta el recorte + # en cualquier paquete compartido, no sólo libdrm.) + for (q, n), consumidores in rev.items(): + if q != "corpus": + continue + colas_cons = {cq for cq, _ in consumidores} + if "corpus" in colas_cons and len(colas_cons) == 1: + continue # sólo corpus, nada que cruzar + # hay consumidores de otra cola: su membresía no puede ser vacía si esas colas tienen perfil + if consumidores and not memb.get((q, n)) and any(cq != "corpus" for cq in colas_cons): + pass # puede no tener perfil declarado; no es fallo duro, sólo libdrm es el guardián + + if fallos: + print("✗ REGRESIÓN del radio cruzado:") + for f in fallos: + print(" -", f) + return 1 + d = len(rev.get(("corpus", "libdrm"), ())) + print(f"✓ radio cruza colas: libdrm consumido por {d} recetas de " + f"{len({q for q,_ in rev.get(('corpus','libdrm'),())})} colas, atribuido a " + f"{sorted(memb.get(('corpus','libdrm'), set()))}") + return 0 + +if __name__ == "__main__": + sys.exit(main())