tawasuyu

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 229TOML 101Markdown 82Shell 24052 Definiciones

⎇ main widgets/tablado/tests/el_explorador_enjaulado.rs Rust · 149 líneas · 6.2 KB Historia Quién tocó qué crudo

chore: refresco desde el monorepo — 132 miembros, 79 widgets, la galería vuelve y licencia dual d8edd9a9c · Sergio ·

1//! **El explorador entero corriendo en otro proceso, medido.**
2//!
3//! `el_lazo_cerrado` prueba el mecanismo con el huésped más chico posible.
4//! Éste lo prueba con **la app real**: `nahual-shell-llimphi` —veinte visores,
5//! dos sidebars, el listado del directorio— compuesto hacia el cable en vez de
6//! hacia una ventana.
7//!
8//! Es la pregunta que ninguna de las piezas contesta por separado: *treinta y
9//! dos crates cruzaron el backend, ¿el conjunto pinta algo?*
10//!
11//! **Sin ventana** (regla 8): lo que hay que saber son números —cuántas órdenes
12//! cruzaron, cuánto texto se shapeó de este lado, cuántas medidas preguntó— y
13//! los números se leen mejor que un pantallazo. Un tablado gris y uno con una
14//! escena vacía se ven idénticos.
15//!
16//! ```bash
17//! ./scripts/check-jaula-linux.sh
18//! ```
19
20use std::time::Duration;
21
22use llimphi_cable::vida::lanzar;
23use llimphi_cable::Anfitrion;
24use llimphi_compositor::llimphi_layout::taffy::prelude::{length, Rect as TRect, Size, Style};
25use llimphi_compositor::llimphi_layout::LayoutTree;
26use llimphi_compositor::{mount, paint, View};
27use llimphi_text::{Lienzo, Typesetter};
28use llimphi_widget_tablado::Tablado;
29
30const ANCHO: f32 = 1200.0;
31const ALTO: f32 = 800.0;
32/// El tablado va **corrido**, para que las coordenadas locales no coincidan con
33/// las absolutas por casualidad.
34const ORIGEN: (f32, f32) = (24.0, 16.0);
35const CAJA: (f32, f32) = (1100.0, 740.0);
36
37/// Dónde quedó el huésped. Vive en el `target` de SU workspace: está excluido
38/// del principal porque enciende `llimphi-text/wire`, excluyente con `vello`.
39fn binario_del_huesped() -> Option<std::path::PathBuf> {
40    let raiz = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).ancestors().nth(4)?.to_path_buf();
41    let p = raiz.join("02_ruway/llimphi/llimphi-jaula/target/debug/nahual-shell-jailed");
42    p.exists().then_some(p)
43}
44
45/// ¿Alguien exige que este test corra de verdad?
46///
47/// Lo pone `scripts/check-jaula-linux.sh`, que compila el huésped justo antes.
48/// Existe porque saltearse **informando `ok`** es la peor manera de fallar: ya
49/// pasó una vez acá mismo, con la ruta mal por un nivel.
50fn exigido() -> bool {
51    std::env::var_os("LLIMPHI_JAULA_REQUIRE").is_some()
52}
53
54#[test]
55fn el_explorador_entero_cruza_y_pinta() {
56    let Some(binario) = binario_del_huesped() else {
57        assert!(!exigido(), "el explorador enjaulado tendría que estar compilado y no está");
58        eprintln!("salteado: falta el huésped. Generalo con\n  ./scripts/check-jaula-linux.sh");
59        return;
60    };
61    eprintln!("explorador · huésped: {}", binario.display());
62
63    // Se lo apunta al repo mismo: una carpeta con cientos de entradas de
64    // verdad, no un directorio de mentira con tres archivos. Lo que se quiere
65    // saber es si un árbol REAL cruza.
66    let carpeta = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).ancestors().nth(4).unwrap().to_path_buf();
67    let (mut hijo, flujo) = lanzar("prueba-explorador", |ruta| {
68        let mut c = std::process::Command::new(&binario);
69        c.arg(ruta).arg(&carpeta);
70        c
71    })
72    .expect("lanzar el explorador enjaulado");
73
74    let mut anfitrion = Anfitrion::new(flujo);
75    let mut tablado = Tablado::nuevo();
76    let mut ts = Typesetter::new();
77    let mut medidas = 0u32;
78
79    // El plazo es generoso a propósito: el explorador lee un directorio real en
80    // su primer cuadro, y un plazo corto convertiría un disco lento en un test
81    // que falla sin decir por qué.
82    let cuadro = anfitrion
83        .atender_hasta(Duration::from_secs(30), |p| {
84            medidas += 1;
85            // **El que contesta es el que pinta**: el mismo typesetter que va a
86            // shapear. Medir con otro movería la caja de línea.
87            llimphi_reproductor::medir_texto(&mut ts, &p.cadena, &p.estilo, p.ancho_max)
88        })
89        .expect("atender al explorador");
90    tablado.poner_cuadro(*cuadro.expect("el explorador tiene que mandar su primer cuadro"));
91
92    // Montar el árbol del anfitrión con el tablado adentro y pintarlo de verdad.
93    let hijo_vista = tablado.vista::<()>(Style {
94        size: Size { width: length(CAJA.0), height: length(CAJA.1) },
95        ..Default::default()
96    });
97    let raiz = View::new(Style {
98        size: Size { width: length(ANCHO), height: length(ALTO) },
99        padding: TRect {
100            left: length(ORIGEN.0),
101            right: length(0.0),
102            top: length(ORIGEN.1),
103            bottom: length(0.0),
104        },
105        ..Default::default()
106    })
107    .children(vec![hijo_vista]);
108    let mut arbol = LayoutTree::new();
109    let montado = mount(&mut arbol, raiz);
110    let computado = arbol.compute(montado.root, (ANCHO, ALTO)).expect("layout");
111    let mut lienzo = Lienzo::new();
112    let mut ts2 = Typesetter::new();
113    paint(&mut lienzo, &montado, &computado, &mut ts2, None, None);
114
115    let c = tablado.contadores();
116    eprintln!(
117        "explorador · {} órdenes · {} textos shapeados acá · {} medidas preguntadas · {} salteadas",
118        c.ordenes(),
119        c.textos(),
120        medidas,
121        c.salteadas()
122    );
123
124    // Un explorador con un directorio real adentro no puede pintar cuatro
125    // rectángulos: si el número es chico, cruzó el chrome y no el contenido.
126    assert!(
127        c.ordenes() > 100,
128        "el explorador compuso su interfaz entera: {} órdenes es demasiado poco \
129         para dos sidebars más un listado de carpeta",
130        c.ordenes()
131    );
132    assert!(
133        c.textos() > 20,
134        "y el texto se shapeó DE ESTE LADO: {} nodos de texto es demasiado poco \
135         — es lo que prueba que el reparto funciona (el huésped mandó cadenas, no glifos)",
136        c.textos()
137    );
138    assert_eq!(c.salteadas(), 0, "nada vino mal formado");
139    assert_eq!(c.cuadros_pintados(), 1);
140    assert_eq!(c.pintadas_sin_escena(), 0, "hubo escena desde el primer cuadro");
141    assert!(medidas > 0, "el huésped preguntó las medidas en vez de shapear");
142
143    // La muerte se ve y borra el cuadro: un proceso muerto que deja su interfaz
144    // pegada es la peor versión, porque el operador le hace clic.
145    anfitrion.despedirse().expect("decirle que se vaya");
146    let _ = hijo.wait();
147    tablado.olvidar();
148    assert!(!tablado.hay_cuadro(), "un proceso muerto no deja su interfaz pegada");
149}