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…
⎇ 9b5861d815cfb08418773920067400bd11f9aa66
scripts/test-atuq-chrome.py
Ñawpa willay
Pi imata llamkarqan
ch'uya
atuq: dos cosas que el README daba por ciertas eran falsas, y las dos se midieron
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())