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 815Rust 56Shell 37Markdown 291229 Definiciones

⎇ 6809ef4be28f606aae856890a74667187ff75b05 crates/simi/tests/divergencias_medidas.rs Rust · 198 líneas · 7.7 KB Historia Quién tocó qué crudo

simi: el /bin/sh propio del producto — 4448 líneas, sólo libc, y CERO divergencias contra el ash 6809ef4be · Sergio ·

1//! Las divergencias que el banco diferencial MIDIÓ, convertidas en pruebas.
2//!
3//! Cada una está acá porque se rompió de verdad —en brush 0.4.0, o en la primera versión de simi—
4//! y ninguna se veía sin comparar contra un shell de control. La referencia de todas es el ash de
5//! busybox 1.36.1, medido en la misma corrida (`docs/plan-botar-busybox.md`).
6//!
7//! Corren el binario de verdad, no la librería: lo que importa es el comportamiento observable.
8
9use std::process::Command;
10
11fn correr(guion: &str) -> (i32, String, String) {
12    let salida = Command::new(env!("CARGO_BIN_EXE_simi"))
13        .arg("-c")
14        .arg(guion)
15        .output()
16        .expect("no pude ejecutar simi");
17    (
18        salida.status.code().unwrap_or(-1),
19        String::from_utf8_lossy(&salida.stdout).into_owned(),
20        String::from_utf8_lossy(&salida.stderr).into_owned(),
21    )
22}
23
24fn salida(guion: &str) -> String {
25    correr(guion).1
26}
27
28/// reubeno/brush#1394 · POSIX.1-2024 §2.6.3. La barra invertida dentro de `` ` ` `` es literal
29/// salvo ante `$`, `` ` `` y `\`. Es el bug que hacía que el `libtool` generado saliera con un
30/// nivel de escapado de menos y que 0 de 3 recetas autotools construyeran.
31#[test]
32fn barra_invertida_en_comillas_invertidas() {
33    let g = r#"V=hola
34echo "a: [`echo \$V`]"
35echo "b: [`echo \\`]"
36echo "c: [`echo \\$V`]"
37echo "d: [$(echo \\$V)]""#;
38    assert_eq!(salida(g), "a: [hola]\nb: [\\]\nc: [$V]\nd: [\\hola]\n");
39}
40
41/// reubeno/brush#1396 · el `trap EXIT` de un subshell CORRE. brush no lo ejecuta nunca —ni con
42/// `exit` explícito— y falla en silencio: `rc=0` y la limpieza sin hacer.
43#[test]
44fn trap_exit_de_subshell() {
45    assert_eq!(
46        salida(r#"trap 'echo "[padre]"' EXIT; ( trap 'echo "[sub]"' EXIT; echo dentro ); echo fuera"#),
47        "dentro\n[sub]\nfuera\n[padre]\n"
48    );
49    // con `exit` explícito y con el estado preservado
50    let (rc, out, _) = correr(r#"( trap 'echo T' EXIT; exit 3 ); echo rc=$?"#);
51    assert_eq!((rc, out.as_str()), (0, "T\nrc=3\n"));
52    // y en una sustitución de orden, que también es un subshell
53    assert_eq!(salida(r#"x=$( trap 'echo T' EXIT; echo v ); echo "[$x]""#), "[v\nT]\n");
54}
55
56/// El trap heredado NO se dispara en el subshell (POSIX §2.12): se resetea al entrar. Es el caso
57/// complementario del anterior, y confundirlos es fácil.
58#[test]
59fn trap_heredado_no_se_dispara_en_el_subshell() {
60    assert_eq!(
61        salida(r#"trap 'echo "[exit]"' EXIT; echo padre; ( echo sub ); echo vuelta"#),
62        "padre\nsub\nvuelta\n[exit]\n"
63    );
64}
65
66/// `shift` más allá de `$#`: estado 1 y los posicionales intactos. brush devolvía 2.
67#[test]
68fn shift_mas_alla_de_argc() {
69    assert_eq!(salida("set --; shift 2>/dev/null; echo rc=$?"), "rc=1\n");
70    assert_eq!(salida("set -- a b; shift 5 2>/dev/null; echo rc=$?-$#"), "rc=1-2\n");
71    assert_eq!(salida("set -- a b c; shift 2; echo $1"), "c\n");
72}
73
74/// El bucle REAL del `config.status` que genera cualquier `configure` de autotools. Es el
75/// consumidor que decide, y el que ningún banco sintético reproduce: la primera versión de simi
76/// devolvía las tres variables VACÍAS, igual que brush.
77#[test]
78fn bucle_de_config_status_de_libtool() {
79    let g = r#"ECHO="printf %s\\n"
80SED=sed
81GREP=grep
82sed_quote_subst='s/\([`"$\\]\)/\\\1/g'
83for var in ECHO SED GREP; do
84    case `eval \\$ECHO \\""\\$$var"\\"` in
85    *[\\\`\"\$]*)  eval "lt_$var=\\\"\`\$ECHO \"\$$var\" | \$SED \"\$sed_quote_subst\"\`\\\"" ;;
86    *)             eval "lt_$var=\\\"\$$var\\\"" ;;
87    esac
88done
89echo "ECHO=[$lt_ECHO]"
90echo "SED=[$lt_SED]"
91echo "GREP=[$lt_GREP]""#;
92    // La expectativa es lo que da el ash de busybox, medido en la misma corrida — no lo que uno
93    // cree que debería dar: la primera versión de esta prueba escribió `%s\n` de memoria y el
94    // control dice `%s\\n`.
95    assert_eq!(
96        salida(g),
97        "ECHO=[\"printf %s\\\\n\"]\nSED=[\"sed\"]\nGREP=[\"grep\"]\n"
98    );
99}
100
101/// Una asignación cuyo valor trae comillas o escapes sigue siendo una ASIGNACIÓN. Si el lexer
102/// funde el prefijo desnudo con el valor entrecomillado, `x="a  b"` se ejecuta como si fuera una
103/// orden — y el fallo se lee como «no encontrado», que manda a buscar al lugar equivocado.
104#[test]
105fn asignaciones_con_comillas_y_escapes() {
106    assert_eq!(salida(r#"x="a  b"; echo "[$x]"; set -- $x; echo $#"#), "[a  b]\n2\n");
107    assert_eq!(salida(r#"eval "y=\\\"hola\\\""; echo "[$y]""#), "[\"hola\"]\n");
108    assert_eq!(salida(r#"z=a'b'c; echo "[$z]""#), "[abc]\n");
109}
110
111/// `set -e` se SUSPENDE dentro de una condición, y la suspensión entra en el cuerpo de la función
112/// llamada desde ahí. Es la semántica más divergente entre shells y la que rompe los scripts de
113/// limpieza cuando está mal.
114#[test]
115fn set_e_se_suspende_en_condiciones() {
116    let g = r#"set -e
117( false ) || echo or-salva
118if false; then echo no; fi
119f() { false; echo en-funcion-tras-false; }
120f || echo funcion-abortada
121echo fin"#;
122    assert_eq!(salida(g), "or-salva\nen-funcion-tras-false\nfin\n");
123    // …y sigue abortando cuando toca
124    let (rc, out, _) = correr("set -e\necho antes\nfalse\necho despues");
125    assert_eq!((rc, out.as_str()), (1, "antes\n"));
126}
127
128/// El orden de las expansiones (§2.6): lo entrecomillado no se divide ni se globea, y una barra
129/// invertida que SALE de una expansión es un carácter literal, no un escape.
130#[test]
131fn orden_de_expansiones() {
132    assert_eq!(salida(r#"echo "/dev/nul*""#), "/dev/nul*\n");
133    assert_eq!(salida("echo /dev/nul*"), "/dev/null\n");
134    assert_eq!(salida("echo /no-existe-jamas*"), "/no-existe-jamas*\n");
135    assert_eq!(salida(r#"V=hola; echo [$(echo \\$V)]"#), "[\\hola]\n");
136    assert_eq!(salida("IFS=:; x=a:b:c; set -- $x; echo $#"), "3\n");
137    assert_eq!(salida(r#"v=arch.tar.gz; echo ${v%%.*} ${v##*.} ${#v}"#), "arch gz 11\n");
138}
139
140/// El idioma de las 23 cards de arje, entero: `test … || { echo >&2; exit 78; }; exec …`.
141#[test]
142fn el_idioma_de_las_cards_de_arje() {
143    let (rc, out, err) = correr(
144        "test -f /no-existe || { echo 'falta la config' >&2; exit 78; }; exec /bin/true",
145    );
146    assert_eq!(rc, 78);
147    assert_eq!(out, "");
148    assert_eq!(err, "falta la config\n");
149    // el camino bueno: el `exec` REEMPLAZA el proceso
150    let g = "echo $$ > /dev/null; exec /bin/echo reemplazado; echo NO-DEBERIA-LLEGAR";
151    assert_eq!(salida(g), "reemplazado\n");
152}
153
154/// El bucle de espera de las cards de compatibilidad (`arje-polkit-compat`, `arje-logind-compat`).
155#[test]
156fn bucle_de_espera_con_aritmetica() {
157    let g = "i=0; while [ ! -S /no-existe ] && [ $i -lt 5 ]; do i=$((i+1)); done; echo $i";
158    assert_eq!(salida(g), "5\n");
159}
160
161/// Los `/init` empotrados de las imágenes: `for`, `case`, funciones, aquí-documentos.
162#[test]
163fn lo_que_usan_los_init_empotrados() {
164    let g = r#"say() { echo "==> $*"; }
165say hola
166for d in proc sys dev raro; do
167  case $d in proc|sys|dev) echo "salto $d" ;; *) echo "monto $d" ;; esac
168done
169cat <<FIN
170literal $d
171FIN
172cat <<'CRUDO'
173sin expandir $d
174CRUDO"#;
175    assert_eq!(
176        salida(g),
177        "==> hola\nsalto proc\nsalto sys\nsalto dev\nmonto raro\nliteral raro\nsin expandir $d\n"
178    );
179}
180
181/// Un error de expansión en un shell no interactivo ABORTA con 2, no sigue con 1.
182#[test]
183fn errores_de_expansion_abortan_con_2() {
184    let (rc, out, _) = correr("echo antes; echo ${nada:?falta}; echo despues");
185    assert_eq!((rc, out.as_str()), (2, "antes\n"));
186    let (rc, _, _) = correr("set -u; echo $NODEFINIDA");
187    assert_eq!(rc, 2);
188    let (rc, _, _) = correr("echo [`echo \"`]");
189    assert_eq!(rc, 2, "una comilla sin cerrar en una sustitución es error de sintaxis");
190}
191
192/// `$(( ))`: la división por cero es un error, no un cero silencioso.
193#[test]
194fn division_por_cero_no_es_cero() {
195    let (rc, out, _) = correr("echo $((1/0)); echo despues");
196    assert_eq!(rc, 2);
197    assert_eq!(out, "");
198}