tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
⎇ 168cd187519c0ad575d3ce63baee2f164c38d9d1
llimphi-cable/tests/vida_con_proceso.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
chore: refresco desde el monorepo — 128 miembros, los 76 widgets y los 11 módulos
1//! **Un proceso de verdad: arranca, habla, se muere, y el anfitrión se entera.**
2//!
3//! Lo demás del cable se prueba sobre un par de sockets, que es rápido y basta.
4//! Esto no: la afirmación de F5 es que `vida::lanzar` **lanza un proceso** y le
5//! tiende el caño, y eso con un hilo no se prueba — un hilo comparte la memoria,
6//! nunca falta el binario, y nunca sale con un código.
7//!
8//! El huésped es **este mismo binario**, re-ejecutado con una variable puesta.
9//! Evita agregar un ejecutable de mentira al crate, que después alguien encuentra
10//! y no sabe qué es.
11//!
12//! `harness = false`: hace falta un `main` propio para poder desviarse a modo
13//! huésped antes de que libtest quiera correr nada.
14
15use std::time::Duration;
16
17use llimphi_cable::vida::{como_murio, lanzar, Motivo, Reintentos};
18use llimphi_cable::{Anfitrion, DelAnfitrion, Huesped};
19use llimphi_wire_escena::{EscenaWire, MedidaTexto, PeticionMedida};
20
21/// La variable que convierte a este binario en el huésped.
22const SOCKET: &str = "LLIMPHI_CABLE_HUESPED";
23/// Con qué código sale el huésped después de hablar.
24const CODIGO: &str = "LLIMPHI_CABLE_HUESPED_CODIGO";
25
26fn medida() -> MedidaTexto {
27 MedidaTexto { width: 8.0, full_width: 8.0, height: 12.0 }
28}
29
30fn main() {
31 if let Ok(ruta) = std::env::var(SOCKET) {
32 return modo_huesped(&ruta);
33 }
34 arranca_habla_y_se_muere();
35 una_muerte_con_error_se_distingue_de_una_salida_limpia();
36 println!("ok · vida con proceso: 2 casos");
37}
38
39/// El huésped: se conecta, pide una medida, manda un cuadro y se va.
40fn modo_huesped(ruta: &str) {
41 let flujo = std::os::unix::net::UnixStream::connect(ruta).expect("conectar al anfitrión");
42 let mut hue = Huesped::new(flujo);
43 // Pedir una medida ANTES del cuadro es lo que hace un huésped real: el layout
44 // no puede seguir sin saber cuánto mide una etiqueta.
45 let m = hue.medir(&PeticionMedida {
46 cadena: "hola".into(),
47 estilo: Default::default(),
48 ancho_max: None,
49 });
50 assert!(m.is_ok(), "el anfitrión tiene que contestar la medida");
51 hue.enviar_cuadro(&EscenaWire::new()).expect("mandar el cuadro");
52 let codigo: i32 = std::env::var(CODIGO).ok().and_then(|c| c.parse().ok()).unwrap_or(0);
53 std::process::exit(codigo);
54}
55
56fn lanzar_huesped(codigo: i32) -> (std::process::Child, Anfitrion<std::os::unix::net::UnixStream>) {
57 let yo = std::env::current_exe().expect("mi propio binario");
58 let (hijo, flujo) = lanzar("prueba", |ruta| {
59 let mut c = std::process::Command::new(&yo);
60 c.env(SOCKET, ruta).env(CODIGO, codigo.to_string());
61 c
62 })
63 .expect("lanzar el huésped");
64 (hijo, Anfitrion::new(flujo))
65}
66
67/// **El recorrido entero con un proceso real.** Arranca, contesta su medida,
68/// entrega un cuadro, y después el anfitrión ve la muerte.
69fn arranca_habla_y_se_muere() {
70 let (mut hijo, mut anf) = lanzar_huesped(0);
71
72 let mut medidas = 0;
73 let cuadro = anf
74 .atender_hasta(Duration::from_secs(10), |_p| {
75 medidas += 1;
76 medida()
77 })
78 .expect("atender al huésped");
79 assert!(cuadro.is_some(), "el proceso mandó su cuadro");
80 assert_eq!(medidas, 1, "y su medida se contestó de camino");
81
82 // Ahora se va. Se espera a que termine para que la muerte sea un hecho y no
83 // una carrera: `como_murio` no bloquea a propósito, y un test que dependiera
84 // del timing sería el peor de los tests.
85 let estado = hijo.wait().expect("esperar al hijo");
86 assert!(estado.success(), "salió limpio");
87 assert_eq!(como_murio(&mut hijo), Some(Motivo::Salio(Some(0))));
88
89 // Y el caño queda cerrado: eso es lo que el anfitrión ve en su próximo turno.
90 let e = anf.recibir().expect_err("el huésped ya no está");
91 assert_eq!(e.kind(), std::io::ErrorKind::UnexpectedEof);
92}
93
94/// **Salir con error no es lo mismo que cerrarse solo**, y el motivo lo dice.
95/// Si los dos dieran la misma frase, el operador no sabría si tiene que hacer
96/// algo.
97fn una_muerte_con_error_se_distingue_de_una_salida_limpia() {
98 let (mut hijo, mut anf) = lanzar_huesped(3);
99 let cuadro = anf
100 .atender_hasta(Duration::from_secs(10), |_p| medida())
101 .expect("atender");
102 assert!(cuadro.is_some(), "habló antes de irse");
103 hijo.wait().expect("esperar");
104
105 let motivo = como_murio(&mut hijo).expect("se murió");
106 assert_eq!(motivo, Motivo::Salio(Some(3)));
107 assert_ne!(
108 motivo.frase(),
109 Motivo::Salio(Some(0)).frase(),
110 "salir con 3 no puede leerse igual que cerrarse solo"
111 );
112
113 // Y la política lo toma como una muerte más: reintenta, sin rendirse todavía.
114 let mut r = Reintentos::new();
115 assert!(!r.murio(motivo, Duration::from_secs(0)).rendido());
116
117 // El anfitrión puede seguir hablándole a un caño muerto sin romperse: da
118 // error, no pánico. Es lo que separa «el huésped se fue» de «se cayó shuma».
119 let _ = anf.enviar(&DelAnfitrion::Adios);
120 assert!(
121 anf.atender_uno(|_p| medida()).is_err(),
122 "hablarle a un muerto es un error, no un pánico"
123 );
124}