2026-04-12 19:54:04 -03:00
|
|
|
//! Integration coverage for server process startup behavior.
|
|
|
|
|
//!
|
2026-04-22 22:10:39 -03:00
|
|
|
//! Scope: launch the real `lesavka-server` binary and assert startup stays
|
|
|
|
|
//! resilient in this non-gadget test environment.
|
2026-04-12 19:54:04 -03:00
|
|
|
//! Targets: `server/src/main.rs`.
|
2026-04-22 22:10:39 -03:00
|
|
|
//! Why: missing gadget endpoints should not crash the relay; the server keeps
|
|
|
|
|
//! running and opens HID lazily when the device nodes appear.
|
2026-04-12 19:54:04 -03:00
|
|
|
|
|
|
|
|
use serial_test::serial;
|
2026-04-22 22:10:39 -03:00
|
|
|
use std::fs;
|
2026-04-12 19:54:04 -03:00
|
|
|
use std::path::PathBuf;
|
|
|
|
|
use std::process::Command;
|
|
|
|
|
use std::time::{Duration, Instant};
|
|
|
|
|
|
2026-04-22 22:10:39 -03:00
|
|
|
fn cargo_binary(name: &str) -> Option<PathBuf> {
|
|
|
|
|
let key = format!("CARGO_BIN_EXE_{name}");
|
|
|
|
|
option_env!("CARGO_BIN_EXE_lesavka-server")
|
|
|
|
|
.filter(|_| name == "lesavka-server")
|
|
|
|
|
.map(PathBuf::from)
|
|
|
|
|
.or_else(|| std::env::var_os(key).map(PathBuf::from))
|
|
|
|
|
.filter(|path| path.exists() && path.is_file())
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 19:54:04 -03:00
|
|
|
fn candidate_dirs() -> Vec<PathBuf> {
|
|
|
|
|
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<PathBuf> {
|
2026-04-22 22:10:39 -03:00
|
|
|
cargo_binary(name).or_else(|| {
|
|
|
|
|
candidate_dirs()
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|dir| dir.join(name))
|
|
|
|
|
.find(|path| path.exists() && path.is_file())
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn server_package_version() -> Option<String> {
|
|
|
|
|
let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
|
|
|
.parent()?
|
|
|
|
|
.join("server/Cargo.toml");
|
|
|
|
|
fs::read_to_string(manifest).ok()?.lines().find_map(|line| {
|
|
|
|
|
let trimmed = line.trim();
|
|
|
|
|
trimmed
|
|
|
|
|
.strip_prefix("version")
|
|
|
|
|
.and_then(|value| value.split('=').nth(1))
|
|
|
|
|
.map(str::trim)
|
|
|
|
|
.and_then(|value| value.strip_prefix('"')?.strip_suffix('"'))
|
|
|
|
|
.map(str::to_string)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn wait_for_log(path: &PathBuf, needle: &str, deadline: Instant) -> String {
|
|
|
|
|
loop {
|
|
|
|
|
let log = fs::read_to_string(path).unwrap_or_default();
|
|
|
|
|
if log.contains(needle) {
|
|
|
|
|
return log;
|
|
|
|
|
}
|
|
|
|
|
assert!(
|
|
|
|
|
Instant::now() < deadline,
|
|
|
|
|
"timed out waiting for log line {needle:?}; log was:\n{log}"
|
|
|
|
|
);
|
|
|
|
|
std::thread::sleep(Duration::from_millis(50));
|
|
|
|
|
}
|
2026-04-12 19:54:04 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
2026-04-22 22:10:39 -03:00
|
|
|
fn server_binary_stays_up_with_missing_hid_nodes_and_current_version() {
|
2026-04-12 19:54:04 -03:00
|
|
|
let Some(bin) = find_binary("lesavka-server") else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
2026-04-22 22:10:39 -03:00
|
|
|
let log_path = PathBuf::from("/tmp/lesavka-server.log");
|
|
|
|
|
let _ = fs::remove_file(&log_path);
|
2026-04-12 19:54:04 -03:00
|
|
|
|
|
|
|
|
let mut child = Command::new(bin)
|
|
|
|
|
.env("LESAVKA_DISABLE_UVC", "1")
|
|
|
|
|
.spawn()
|
|
|
|
|
.expect("spawn lesavka-server");
|
|
|
|
|
|
|
|
|
|
let deadline = Instant::now() + Duration::from_secs(3);
|
2026-04-22 22:10:39 -03:00
|
|
|
if let Some(version) = server_package_version() {
|
|
|
|
|
let _ = wait_for_log(
|
|
|
|
|
&log_path,
|
|
|
|
|
&format!("lesavka_server v{version} starting up"),
|
|
|
|
|
deadline,
|
|
|
|
|
);
|
2026-04-12 19:54:04 -03:00
|
|
|
}
|
2026-04-22 22:10:39 -03:00
|
|
|
let log = wait_for_log(
|
|
|
|
|
&log_path,
|
|
|
|
|
"HID endpoints are not ready; relay will keep running and open them lazily",
|
|
|
|
|
deadline,
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
child.try_wait().expect("poll child").is_none(),
|
|
|
|
|
"server should stay alive with lazy HID recovery; log was:\n{log}"
|
|
|
|
|
);
|
|
|
|
|
let _ = child.kill();
|
|
|
|
|
let _ = child.wait();
|
2026-04-12 19:54:04 -03:00
|
|
|
}
|