//! **Un proceso de verdad: arranca, habla, se muere, y el anfitrión se entera.** //! //! Lo demás del cable se prueba sobre un par de sockets, que es rápido y basta. //! Esto no: la afirmación de F5 es que `vida::lanzar` **lanza un proceso** y le //! tiende el caño, y eso con un hilo no se prueba — un hilo comparte la memoria, //! nunca falta el binario, y nunca sale con un código. //! //! El huésped es **este mismo binario**, re-ejecutado con una variable puesta. //! Evita agregar un ejecutable de mentira al crate, que después alguien encuentra //! y no sabe qué es. //! //! `harness = false`: hace falta un `main` propio para poder desviarse a modo //! huésped antes de que libtest quiera correr nada. use std::time::Duration; use llimphi_cable::vida::{como_murio, lanzar, Motivo, Reintentos}; use llimphi_cable::{Anfitrion, DelAnfitrion, Huesped}; use llimphi_wire_escena::{EscenaWire, MedidaTexto, PeticionMedida}; /// La variable que convierte a este binario en el huésped. const SOCKET: &str = "LLIMPHI_CABLE_HUESPED"; /// Con qué código sale el huésped después de hablar. const CODIGO: &str = "LLIMPHI_CABLE_HUESPED_CODIGO"; fn medida() -> MedidaTexto { MedidaTexto { width: 8.0, full_width: 8.0, height: 12.0 } } fn main() { if let Ok(ruta) = std::env::var(SOCKET) { return modo_huesped(&ruta); } arranca_habla_y_se_muere(); una_muerte_con_error_se_distingue_de_una_salida_limpia(); println!("ok · vida con proceso: 2 casos"); } /// El huésped: se conecta, pide una medida, manda un cuadro y se va. fn modo_huesped(ruta: &str) { let flujo = std::os::unix::net::UnixStream::connect(ruta).expect("conectar al anfitrión"); let mut hue = Huesped::new(flujo); // Pedir una medida ANTES del cuadro es lo que hace un huésped real: el layout // no puede seguir sin saber cuánto mide una etiqueta. let m = hue.medir(&PeticionMedida { cadena: "hola".into(), estilo: Default::default(), ancho_max: None, }); assert!(m.is_ok(), "el anfitrión tiene que contestar la medida"); hue.enviar_cuadro(&EscenaWire::new()).expect("mandar el cuadro"); let codigo: i32 = std::env::var(CODIGO).ok().and_then(|c| c.parse().ok()).unwrap_or(0); std::process::exit(codigo); } fn lanzar_huesped(codigo: i32) -> (std::process::Child, Anfitrion) { let yo = std::env::current_exe().expect("mi propio binario"); let (hijo, flujo) = lanzar("prueba", |ruta| { let mut c = std::process::Command::new(&yo); c.env(SOCKET, ruta).env(CODIGO, codigo.to_string()); c }) .expect("lanzar el huésped"); (hijo, Anfitrion::new(flujo)) } /// **El recorrido entero con un proceso real.** Arranca, contesta su medida, /// entrega un cuadro, y después el anfitrión ve la muerte. fn arranca_habla_y_se_muere() { let (mut hijo, mut anf) = lanzar_huesped(0); let mut medidas = 0; let cuadro = anf .atender_hasta(Duration::from_secs(10), |_p| { medidas += 1; medida() }) .expect("atender al huésped"); assert!(cuadro.is_some(), "el proceso mandó su cuadro"); assert_eq!(medidas, 1, "y su medida se contestó de camino"); // Ahora se va. Se espera a que termine para que la muerte sea un hecho y no // una carrera: `como_murio` no bloquea a propósito, y un test que dependiera // del timing sería el peor de los tests. let estado = hijo.wait().expect("esperar al hijo"); assert!(estado.success(), "salió limpio"); assert_eq!(como_murio(&mut hijo), Some(Motivo::Salio(Some(0)))); // Y el caño queda cerrado: eso es lo que el anfitrión ve en su próximo turno. let e = anf.recibir().expect_err("el huésped ya no está"); assert_eq!(e.kind(), std::io::ErrorKind::UnexpectedEof); } /// **Salir con error no es lo mismo que cerrarse solo**, y el motivo lo dice. /// Si los dos dieran la misma frase, el operador no sabría si tiene que hacer /// algo. fn una_muerte_con_error_se_distingue_de_una_salida_limpia() { let (mut hijo, mut anf) = lanzar_huesped(3); let cuadro = anf .atender_hasta(Duration::from_secs(10), |_p| medida()) .expect("atender"); assert!(cuadro.is_some(), "habló antes de irse"); hijo.wait().expect("esperar"); let motivo = como_murio(&mut hijo).expect("se murió"); assert_eq!(motivo, Motivo::Salio(Some(3))); assert_ne!( motivo.frase(), Motivo::Salio(Some(0)).frase(), "salir con 3 no puede leerse igual que cerrarse solo" ); // Y la política lo toma como una muerte más: reintenta, sin rendirse todavía. let mut r = Reintentos::new(); assert!(!r.murio(motivo, Duration::from_secs(0)).rendido()); // El anfitrión puede seguir hablándole a un caño muerto sin romperse: da // error, no pánico. Es lo que separa «el huésped se fue» de «se cayó shuma». let _ = anf.enviar(&DelAnfitrion::Adios); assert!( anf.atender_uno(|_p| medida()).is_err(), "hablarle a un muerto es un error, no un pánico" ); }