gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1572Shell 203Rust 109Markdown 993728 Definiciones

⎇ 93c035f15f8f8aab331a99650e173b325ebf91b0 scripts/test-atuq-chrome.py Python · 232 líneas · 11.4 KB Historia Quién tocó qué crudo

atuq: dos cosas que el README daba por ciertas eran falsas, y las dos se midieron 97c35d67b · Sergio ·

1#!/usr/bin/env python3
2"""¿Corre JS NUESTRO en el chrome de `atuq`, y funciona la vista dividida? (SDD 26 §4)
3
4    python3 scripts/test-atuq-chrome.py                     # exige las dos cosas
5    python3 scripts/test-atuq-chrome.py --negative-control   # con las pestañas FIJADAS: no debe partir
6
7── LAS DOS PREGUNTAS, Y POR QUÉ VAN JUNTAS ───────────────────────────────────────────────────
8El README de atuq decía que «el chrome de verdad» —split view, dientes propios— exige entrar a
9`omni.ja` y poner contenido nuestro adentro. Las dos mitades de esa frase resultaron falsas, y esta
10sonda mide cada una:
11
121. **El gancho.** `atuq.cfg` ya corre con privilegios (de ahí sale `nsIStyleSheetService`), así que
13   desde ahí se puede observar `browser-delayed-startup-finished` y tocar el `gBrowser` de CADA
14   ventana. O sea: **el chrome se programa sin abrir el zip**, que además es lo que hay que hacer,
15   porque re-empacar `omni.ja` pelea con el orden del `jarlog` del PGO (por eso `jarlog` está
16   aparcado).
172. **La vista dividida.** Firefox 154 la trae HECHA y PRENDIDA de fábrica:
18   `browser/omni.ja` lleva `tabbrowser/tabsplitview.js` (492 líneas, el custom element),
19   `opentabs-splitview.mjs` y `split-view-footer.js`, y `defaults/preferences/firefox.js` dice
20   `pref("browser.tabs.splitView.enabled", true)`. Eso no es «lo que falta escribir»: es una
21   función que ya tenemos y que nadie había ejercitado.
22
23Lo que la sonda hace en la ventana real: abre dos pestañas, llama a `gBrowser.addTabSplitView`, y
24después pregunta al motor por `gBrowser.activeSplitView` y `gBrowser.splitViewBrowsers`. Son hechos
25del DOM del chrome, no píxeles — corre headless.
26
27── EL CONTROL NEGATIVO SALE DEL PROPIO MOTOR ─────────────────────────────────────────────────
28`addTabSplitView` tiene un `bail out` documentado: si TODAS las pestañas están fijadas, borra el
29wrapper y devuelve `null` (no se pueden agrupar). `--negative-control` fija las dos pestañas y exige
30justamente eso. Sin él, una sonda que dijera «partió» pase lo que pase se vería idéntica a una que
31funciona.
32
33REQUISITOS: el rootfs de `scripts/atuq-nested.sh` (que lo hidrata) y `atuq` sellado.
34"""
35import os
36import shutil
37import subprocess
38import sys
39import tempfile
40
41ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
42RFS = os.environ.get("RFS", "/mnt/cosecha/escritorios/atuq-rootfs")
43ALPINE = os.path.join(ROOT, ".dev-fs/alpine")
44HAMMER = os.path.join(ROOT, "target/release/takana")
45STORE = os.environ.get("HAMMER_STORE", os.path.join(ROOT, "store"))
46APPDIR = "usr/lib/atuq"
47
48# El bloque se APENDA al `atuq.cfg` del artefacto (no lo reemplaza): así se mide el fichero real de
49# atuq con la sonda encima, y no una copia que podría haber divergido. La capa de overlay pisa el
50# fichero entero, por eso hay que reescribirlo completo con el original adentro.
51SONDA = """
52// ── SONDA DE PRUEBA — NO VIAJA EN EL ARTEFACTO (scripts/test-atuq-chrome.py) ───────────────────
53try {
54  const S = (typeof Services !== "undefined")
55    ? Services
56    : ChromeUtils.importESModule("resource://gre/modules/Services.sys.mjs").Services;
57  const lineas = [];
58  const di = function (s) {
59    lineas.push(s);
60    try { dump("CHROME-TEST " + s + "\\n"); } catch (e) {}
61    // El fichero es la vía FIABLE: `dump()` desde el proceso padre depende de prefs y de cómo se
62    // compiló. Se usan las dos y el guión lee la que haya.
63    try { IOUtils.writeUTF8("/salida/chrome.txt", lineas.join("\\n") + "\\n"); } catch (e) {}
64  };
65  di("VIVO el JS de atuq.cfg corre");
66  S.obs.addObserver(function observador(win) {
67    S.obs.removeObserver(observador, "browser-delayed-startup-finished");
68    try {
69      di("VENTANA " + (win.gBrowser ? "con gBrowser" : "SIN gBrowser"));
70      di("PREF splitView.enabled=" +
71         S.prefs.getBoolPref("browser.tabs.splitView.enabled", false));
72      const principal = S.scriptSecurityManager.getSystemPrincipal();
73      const t1 = win.gBrowser.addTab("about:blank", { triggeringPrincipal: principal });
74      const t2 = win.gBrowser.addTab("about:blank", { triggeringPrincipal: principal });
75      if (FIJADAS) { win.gBrowser.pinTab(t1); win.gBrowser.pinTab(t2); }
76      const sv = win.gBrowser.addTabSplitView([t1, t2]);
77      di("WRAPPER " + (sv ? ("tabs=" + sv.tabs.length) : "null"));
78      if (sv && sv.tabs.length) { win.gBrowser.selectedTab = sv.tabs[0]; }
79      win.setTimeout(function () {
80        di("ACTIVA " + (win.gBrowser.activeSplitView ? "si" : "no"));
81        di("BROWSERS " + win.gBrowser.splitViewBrowsers.length);
82        di("FIN");
83      }, 2000);
84    } catch (e) {
85      di("EXCEPCIÓN " + e);
86    }
87  }, "browser-delayed-startup-finished");
88} catch (e) {
89  try { dump("CHROME-TEST EXCEPCIÓN-EXTERIOR " + e + "\\n"); } catch (e2) {}
90}
91"""
92
93
94def fatal(msg):
95    print(f"✗ {msg}", file=sys.stderr)
96    raise SystemExit(1)
97
98
99def artefacto():
100    """El artefacto VIGENTE de atuq, por `takana hash` y nunca por glob.
101
102    ⚠ ESCOTILLA `ATUQ_DIR`, que grita: el corpus es compartido y otro frente puede re-hashear
103    `firefox` dejando el atuq vigente sin sellar durante las horas del build. Un resultado obtenido
104    con la escotilla NO se cita como «el atuq de hoy pasa».
105    """
106    forzado = os.environ.get("ATUQ_DIR")
107    if forzado:
108        if not os.path.isdir(forzado):
109            fatal(f"ATUQ_DIR={forzado} no existe")
110        print(f"⚠ ATUQ_DIR: se mide {os.path.basename(forzado)}, que NO es necesariamente el vigente")
111        return forzado
112    r = subprocess.run([HAMMER, "--store", STORE, "hash", "recipes/atuq.toml"],
113                       cwd=ROOT, capture_output=True, text=True)
114    if r.returncode != 0:
115        fatal(f"no pude hashear atuq: {r.stderr.strip()}")
116    h = r.stdout.strip().splitlines()[-1].removeprefix("b3:")
117    d = os.path.join(STORE, f"{h}-atuq")
118    if not os.path.isdir(d):
119        fatal(f"atuq b3:{h[:8]} no está sellado — construilo antes")
120    return d
121
122
123def main():
124    negativo = "--negative-control" in sys.argv[1:]
125    if negativo:
126        print("== MODO CONTROL NEGATIVO: las dos pestañas van FIJADAS; el motor tiene que negarse")
127    if not os.path.isdir(RFS):
128        fatal(f"no está el rootfs en {RFS} — corré antes `scripts/atuq-nested.sh`, que lo hidrata")
129    atuq = artefacto()
130    print(f"artefacto: {os.path.basename(atuq)}")
131
132    tmp = tempfile.mkdtemp(prefix="atuq-chrome-")
133    salida = os.path.join(tmp, "salida")
134    capa_app = os.path.join(tmp, "capa", APPDIR)
135    os.makedirs(os.path.join(salida, "perfil"))
136    os.makedirs(capa_app)
137    try:
138        original = open(os.path.join(atuq, APPDIR, "atuq.cfg"), encoding="utf8").read()
139        with open(os.path.join(capa_app, "atuq.cfg"), "w", encoding="utf8") as fh:
140            fh.write(original)
141            fh.write(SONDA.replace("FIJADAS", "true" if negativo else "false"))
142
143        with open(os.path.join(salida, "perfil", "user.js"), "w") as fh:
144            fh.write('user_pref("browser.dom.window.dump.enabled", true);\n')
145            fh.write('user_pref("browser.shell.checkDefaultBrowser", false);\n')
146
147        guion = """
148        mkdir -p /root
149        [ -s /etc/machine-id ] || tr -d - < /proc/sys/kernel/random/uuid > /etc/machine-id
150        timeout 60 /usr/bin/atuq --profile /salida/perfil --no-remote --headless about:blank \
151            > /salida/consola.log 2>&1
152        exit 0
153        """
154        subprocess.run([
155            "bwrap",
156            "--overlay-src", ALPINE, "--overlay-src", RFS, "--overlay-src", atuq,
157            "--overlay-src", os.path.join(tmp, "capa"), "--tmp-overlay", "/",
158            "--dev-bind", "/dev", "/dev", "--proc", "/proc", "--bind", "/sys", "/sys",
159            "--tmpfs", "/tmp", "--tmpfs", "/run", "--unshare-pid",
160            "--bind", salida, "/salida",
161            "--uid", "0", "--gid", "0",
162            "--setenv", "PATH", "/usr/bin:/bin:/usr/sbin:/sbin",
163            "--setenv", "XDG_RUNTIME_DIR", "/run", "--setenv", "HOME", "/root",
164            "--setenv", "XDG_DATA_DIRS", "/usr/share", "--setenv", "MOZ_HEADLESS", "1",
165            "--setenv", "MOZ_DISABLE_CONTENT_SANDBOX", "1",
166            "--setenv", "MOZ_DISABLE_GMP_SANDBOX", "1",
167            "--setenv", "MOZ_DISABLE_RDD_SANDBOX", "1",
168            "--setenv", "MOZ_DISABLE_SOCKET_PROCESS_SANDBOX", "1",
169            "--setenv", "MOZ_DISABLE_UTILITY_SANDBOX", "1",
170            "sh", "-c", guion,
171        ], check=False)
172
173        log = open(os.path.join(salida, "consola.log"), errors="replace").read()
174        lineas = [l[len("CHROME-TEST "):] for l in log.splitlines() if l.startswith("CHROME-TEST ")]
175        fichero = os.path.join(salida, "chrome.txt")
176        if os.path.exists(fichero):
177            del_fichero = open(fichero, errors="replace").read().splitlines()
178            # El fichero es la fuente autorizada si dice MÁS que el stdout.
179            if len(del_fichero) > len(lineas):
180                lineas = del_fichero
181        for l in lineas:
182            print("   " + l)
183
184        def valor(clave):
185            for l in lineas:
186                if l.startswith(clave + " "):
187                    return l.split(" ", 1)[1]
188            return None
189
190        if not lineas:
191            destino = os.path.join(ROOT, "work", "atuq-chrome.log")
192            os.makedirs(os.path.dirname(destino), exist_ok=True)
193            open(destino, "w").write(log)
194            fatal("la sonda no dijo NADA: el JS de `atuq.cfg` no corrió, o no llegó a la ventana."
195                  f" Log en {destino}")
196        if valor("VIVO") is None:
197            fatal("no salió la línea VIVO — el bloque de la sonda no se ejecutó")
198        if "FIN" not in lineas and valor("EXCEPCIÓN"):
199            fatal(f"el JS del chrome se rompió: {valor('EXCEPCIÓN')}")
200
201        wrapper, activa, browsers = valor("WRAPPER"), valor("ACTIVA"), valor("BROWSERS")
202        if negativo:
203            if wrapper != "null":
204                fatal(f"con las dos pestañas FIJADAS el motor partió igual (WRAPPER={wrapper})"
205                      " — esta prueba no distingue nada")
206            if activa != "no":
207                fatal(f"control negativo: ACTIVA={activa}, se esperaba «no»")
208            print("✓ control negativo: con las pestañas fijadas NO hay vista dividida"
209                  " (la sonda sí lo vería)")
210            return 0
211
212        if valor("VENTANA") != "con gBrowser":
213            fatal("el observador corrió pero la ventana no traía `gBrowser`")
214        if valor("PREF") != "splitView.enabled=true":
215            fatal(f"la vista dividida viene APAGADA en este build ({valor('PREF')})"
216                  " — habría que prenderla en atuq.cfg")
217        if wrapper != "tabs=2":
218            fatal(f"`addTabSplitView` no armó la vista de dos pestañas (WRAPPER={wrapper})")
219        if activa != "si":
220            fatal(f"la vista se creó pero el motor no la reporta ACTIVA (ACTIVA={activa})")
221        if browsers != "2":
222            fatal(f"el motor no reporta dos navegadores en la vista (BROWSERS={browsers})")
223        print("✓ el JS de `atuq.cfg` llega al `gBrowser` de la ventana:"
224              " el chrome se programa SIN abrir `omni.ja`")
225        print("✓ la vista dividida del motor funciona en nuestro build:"
226              " dos pestañas, vista ACTIVA y dos navegadores")
227        return 0
228    finally:
229        shutil.rmtree(tmp, ignore_errors=True)
230
231
232sys.exit(main())