#!/usr/bin/env python3 # test-yupana-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 yupana def main(): colas = yupana.cargar_colas() rev = yupana.dependientes(colas) memb = yupana.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 # 3) INVARIANTE (c): el detector de duplicados debe seguir viendo la fuente, no el nombre. La # identidad de un nudo es lo que BAJA (sha), no cómo se llama. Si esto se rompe (p.ej. alguien # agrupa por nombre), las colisiones de fuente —shas copiados por error— dejan de verse. recs_f = yupana._recetas_con_fuente() por_sha = {} for k, r in recs_f.items(): if r["sha"]: por_sha.setdefault(r["sha"], set()).add(k[1]) # nombres por sha colis = {s: ns for s, ns in por_sha.items() if len({yupana._base(n) for n in ns}) > 1} # familias distintas, misma fuente if not colis: fallos.append("el detector de duplicados no ve NINGUNA colisión de fuente — " "¿se está agrupando por nombre en vez de por sha? (invariante c ciego)") if fallos: print("✗ REGRESIÓN de yupana:") 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()))}") print(f"✓ detector de duplicados por fuente: {len(colis)} colisiones vistas (invariante c activo)") return 0 if __name__ == "__main__": sys.exit(main())