sh: el banco DIFERENCIAL — compara el ÁRBOL producido, no sólo stdout, y el generador saca el bug de brush sin saber que existe
El banco POSIX de 57 casos lo pasa brush 56/57 y aun así no construye una receta autotools: mide lo que a uno se le ocurrió preguntar. Este pregunta si el candidato hace LO MISMO que el control sobre el material que hay. - compara (rc, stdout, árbol de ficheros producido); stderr es aviso, no divergencia — el texto de error diverge legítimamente - el árbol es la columna que faltaba: el caso 10 diverge SÓLO ahí, que es la clase de bug del libtool mal escapado (rc=0, stdout idéntico) - toda divergencia se confirma re-corriendo control y candidato: separa caso no determinista de shell inestable de divergencia real - fuentes: fixtures de regresión, generador de tortura de comillas, las 1130 fases del corpus y configure/libtool reales en sh -n Medido, 1273 casos en 5,5 s: brush 35 duras, bash 8, y CERO en los 1156 casos de parseo. El generador saca el #1394 entero sin conocerlo. Hallazgo nuevo: brush no ejecuta nunca el trap EXIT de un subshell — y eso golpea el frente del PRODUCTO, donde el veredicto del paso 3 decía que no había evidencia en contra.
This commit is contained in:
@@ -454,3 +454,128 @@ ripgrep` en el store del worker.
|
||||
- **Las otras 21 dependientes**, cuando la granja llegue.
|
||||
- El siguiente corte natural son los `BORRAR` que no se tocaron por prudencia y los 272 que siguen:
|
||||
el número baja a 0 por el paso 2 (declarar la munición sellada) y el 5 (`grep`/`sed` POSIX).
|
||||
|
||||
---
|
||||
|
||||
# El banco DIFERENCIAL — construido y CORRIDO, 2026-09-21
|
||||
|
||||
**Instrumento:** `scripts/sh-banco-diferencial.py` · **casos de regresión:**
|
||||
`scripts/fixtures/sh-casos/` · **dónde:** el hub, contra los tres shells sellados que ya están en
|
||||
`/store` (`2a2b1280…-busybox`, `715237c7…-brush`, `87bf1a59…-bash`). No tocó la granja ni el lock.
|
||||
|
||||
## Por qué, si ya existía `sh-banco-posix.sh`
|
||||
|
||||
El veredicto del paso 3 dejó escrita la lección y no el instrumento:
|
||||
|
||||
> **brush pasa 56/57 del banco POSIX y aun así no construye una sola receta autotools.**
|
||||
|
||||
Un banco de casos escritos a mano mide lo que a uno se le ocurrió preguntar. El banco diferencial
|
||||
pregunta otra cosa, y no hace falta saber la respuesta de antemano:
|
||||
|
||||
> **¿este shell hace LO MISMO que el control, sobre el material que hay?**
|
||||
|
||||
Tres decisiones de diseño, y las tres se ganaron con el bug de brush:
|
||||
|
||||
1. **Se compara el ÁRBOL DE FICHEROS PRODUCIDO, no sólo stdout.** El `libtool` mal escapado salía
|
||||
con `rc=0` y stdout idéntico: **ningún banco de stdout puede verlo**. Cada caso corre en un
|
||||
directorio descartable y se le toma huella (ruta + bit de ejecución + sha256) a todo lo que
|
||||
escribió. El caso `10-escribe-arbol.sh` diverge **sólo en el árbol** — es la demostración de que
|
||||
esta columna hace falta.
|
||||
2. **El esperado es el control, no una aserción.** Un caso no declara resultado: declara material.
|
||||
Eso permite tirarle al banco las 1130 fases del corpus y 26 `configure`/`libtool` reales sin
|
||||
escribir una línea de expectativa.
|
||||
3. **Toda divergencia se CONFIRMA** volviendo a correr control y candidato. Un caso que imprime un
|
||||
PID diverge siempre y no dice nada (pasó en el primer intento con la card de arje, que imprimía
|
||||
`$$`); un shell que diverge **de sí mismo** es un hallazgo distinto y peor. Sin esto el banco se
|
||||
llena de ruido y deja de leerse, que es como muere un banco.
|
||||
|
||||
Además: todos los shells se invocan por un symlink `sh` (argv[0] igual para todos, que es como se
|
||||
despliegan), con el **mismo userland busybox en el `PATH`** —las divergencias tienen que venir del
|
||||
shell, no de un `sed` distinto— y con `PATH` apuntando primero al shell bajo prueba, así un `sh`
|
||||
recursivo cae en ÉL y no en el del sistema.
|
||||
|
||||
## Lo medido — 1273 casos, 5,5 s
|
||||
|
||||
```
|
||||
scripts/sh-banco-diferencial.py --gen --phases \
|
||||
--files '/work/sergio/work/sources/*/configure' \
|
||||
--files '/work/sergio/work/sources/*/libtool' \
|
||||
control=…-busybox/bin/busybox brush=…-brush/usr/bin/brush bash=…-bash/bin/bash
|
||||
```
|
||||
|
||||
| fuente | casos | modo | brush | bash |
|
||||
|---|---|---|---|---|
|
||||
| `cases` — regresiones conocidas | 10 | ejecutado | **4 divergen** | 0 |
|
||||
| `gen` — tortura de comillas | 107 | ejecutado | **31 divergen** | 8 |
|
||||
| `phases` — las fases de las 549 recetas | 1130 | `sh -n` | **0** | 0 |
|
||||
| `files` — `configure` y `libtool` reales | 26 | `sh -n` | **0** | 0 |
|
||||
|
||||
**Los 1156 casos de parseo no divergen en ningún shell.** Confirma lo que el paso 3 ya decía —el
|
||||
parser de brush está bien— y lo dice ahora con bash de tercero en discordia y con el árbol
|
||||
comparado. **Sirve sobre todo para saber dónde NO buscar.**
|
||||
|
||||
## Lo que el generador encontró SOLO
|
||||
|
||||
El generador barre el producto cartesiano `forma de sustitución × contexto × profundidad de barras
|
||||
invertidas × carácter objetivo` (107 casos). **No sabe que el bug #1394 existe, y lo escupe entero:**
|
||||
|
||||
```
|
||||
✗ [brush] tortura/bt-desnudo-b1-var control: [hola] brush: [$V]
|
||||
✗ [brush] tortura/bt-desnudo-b2-var control: [$V] brush: [\hola]
|
||||
✗ [brush] tortura/bt-desnudo-b2-literal control: [x] brush: [\x]
|
||||
✗ [brush] libtool/case-de-config-status ← el bucle REAL de config.status, divergente
|
||||
```
|
||||
|
||||
Ése es el punto del ejercicio: **el bug de brush apareció en el primer build real, después de
|
||||
sellar la receta y de montar un lab entero. El generador lo habría dado el día cero, en cinco
|
||||
segundos, sin saber qué buscaba.**
|
||||
|
||||
## Un hallazgo NUEVO, y no es del frente A
|
||||
|
||||
El fixture `08-trap-exit-y-estado.sh` destapó algo que no estaba en el veredicto del paso 3:
|
||||
|
||||
```sh
|
||||
( trap "echo trap-sub" EXIT; true ); echo despues
|
||||
# busybox → trap-sub / despues bash → trap-sub / despues brush → despues
|
||||
```
|
||||
|
||||
**brush no ejecuta NUNCA el `trap … EXIT` de un subshell**, ni con `exit` explícito. Es independiente
|
||||
de #1394 y, a diferencia de aquél, **golpea el frente B**: un script de limpieza que confía en su
|
||||
trap no limpia, en silencio, con `rc=0`. La frase del veredicto —«en el producto brush no tiene
|
||||
ninguna evidencia en contra»— ya no se sostiene: ahora la tiene. **Sin reportar upstream todavía.**
|
||||
|
||||
Y se confirmó la tercera, ya conocida: `shift` más allá de `$#` devuelve 2 (POSIX/busybox/bash: 1).
|
||||
|
||||
## Las 8 de bash, que enseñan a leer el banco
|
||||
|
||||
bash diverge en 8 casos del generador, **todos de la misma familia**: comilla o backtick sin cerrar
|
||||
dentro de `` `…` ``, donde busybox da `rc=2` y bash acepta. No es que bash esté mal — es que **el
|
||||
control es una referencia, no la verdad**. Cuando el candidato diverge, hay que ir a POSIX; el banco
|
||||
dice dónde mirar, no quién tiene razón.
|
||||
|
||||
## Latencia, de paso
|
||||
|
||||
300 invocaciones de `sh -c :` por shell, en el hub:
|
||||
|
||||
| shell | µs/invocación | binario |
|
||||
|---|---|---|
|
||||
| busybox | 464 | 1,17 MB |
|
||||
| bash | 864 | 1,57 MB |
|
||||
| brush | 1724 | 6,92 MB |
|
||||
|
||||
**brush/busybox = 3,7×** acá, contra el 8,9× medido en el worker: esta cifra incluye el `fork`+`exec`
|
||||
de Python y comprime el factor. Sirve para comparar, no como número absoluto. La dirección es la
|
||||
misma y el argumento del veredicto no cambia.
|
||||
|
||||
## Lo que este banco NO mide (y hay que decirlo así)
|
||||
|
||||
- **No EJECUTA las fases del corpus** — sólo las parsea. Ejecutarlas necesita el sandbox y sus
|
||||
fuentes, que es la extensión natural y la que cerraría el caso: correr un `configure` real bajo el
|
||||
candidato y **comparar el árbol generado** contra el del control. La maquinaria de comparación ya
|
||||
está; falta engancharla al sandbox.
|
||||
- **El generador sólo tortura comillas.** Faltan familias enteras: expansión de parámetros, `IFS` y
|
||||
división en campos, aritmética, here-docs, señales y control de trabajos.
|
||||
- **Nada de interactivo** — ni edición de línea, ni historial, ni `job control`.
|
||||
- **El árbol no mira permisos finos ni mtimes**, sólo ruta + bit de ejecución + contenido.
|
||||
- **El banco no prueba suficiencia.** Es la misma advertencia de siempre, y ahora con dos
|
||||
instrumentos que la ilustran en vez de uno.
|
||||
|
||||
Reference in New Issue
Block a user