//! Integration coverage for client app runtime startup paths. //! //! Scope: launch the real `lesavka-client` binary with runtime toggles that //! execute `LesavkaClientApp::run` startup branches. //! Targets: `client/src/app.rs`. //! Why: process-level startup behavior should stay deterministic in both //! headless and desktop-style launches. use serial_test::serial; use std::path::{Path, PathBuf}; use std::process::Command; use std::time::{Duration, Instant}; use tempfile::tempdir; fn candidate_dirs() -> Vec { let exe = std::env::current_exe().expect("current exe path"); let mut dirs = Vec::new(); if let Some(parent) = exe.parent() { dirs.push(parent.to_path_buf()); if let Some(grand) = parent.parent() { dirs.push(grand.to_path_buf()); } } dirs.push(PathBuf::from("target/debug")); dirs.push(PathBuf::from("target/llvm-cov-target/debug")); dirs } fn find_binary(name: &str) -> Option { candidate_dirs() .into_iter() .map(|dir| dir.join(name)) .find(|path| path.exists() && path.is_file()) } fn wait_for_exit( mut child: std::process::Child, timeout: Duration, ) -> Option { let deadline = Instant::now() + timeout; loop { if let Some(status) = child.try_wait().expect("poll child") { return Some(status); } if Instant::now() >= deadline { let _ = child.kill(); let _ = child.wait(); return None; } std::thread::sleep(Duration::from_millis(50)); } } #[test] #[serial] fn client_headless_runtime_enters_main_loop() { let Some(bin) = find_binary("lesavka-client") else { return; }; let child = Command::new(Path::new(&bin)) .arg("--no-launcher") .env("LESAVKA_HEADLESS", "1") .env("LESAVKA_SERVER_ADDR", "http://127.0.0.1:9") .spawn() .expect("spawn lesavka-client"); if let Some(status) = wait_for_exit(child, Duration::from_millis(900)) { assert!( !status.success(), "headless runtime unexpectedly exited successfully" ); } } #[test] #[serial] fn client_desktop_runtime_executes_startup_branches() { let Some(bin) = find_binary("lesavka-client") else { return; }; let runtime_dir = tempdir().expect("runtime dir"); let child = Command::new(Path::new(&bin)) .arg("--no-launcher") .env("XDG_RUNTIME_DIR", runtime_dir.path()) .env_remove("LESAVKA_HEADLESS") .env("LESAVKA_SERVER_ADDR", "not a uri") .spawn() .expect("spawn lesavka-client"); if let Some(status) = wait_for_exit(child, Duration::from_secs(3)) { assert!( !status.success(), "desktop runtime unexpectedly exited successfully" ); } }