197 lines
6.6 KiB
Rust
197 lines
6.6 KiB
Rust
//! Integration coverage for server process startup behavior.
|
|
//!
|
|
//! Scope: launch the real `lesavka-server` binary and assert startup stays
|
|
//! resilient in this non-gadget test environment.
|
|
//! Targets: `server/src/main.rs`.
|
|
//! Why: missing gadget endpoints should not crash the relay; the server keeps
|
|
//! running and opens HID lazily when the device nodes appear.
|
|
|
|
use serial_test::serial;
|
|
use std::fs;
|
|
use std::io::Read;
|
|
use std::net::TcpListener;
|
|
use std::path::PathBuf;
|
|
use std::process::{Command, Stdio};
|
|
use std::time::{Duration, Instant};
|
|
|
|
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())
|
|
}
|
|
|
|
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 workspace_root() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
.parent()
|
|
.expect("workspace root")
|
|
.to_path_buf()
|
|
}
|
|
|
|
fn build_current_binary(name: &str) -> Option<PathBuf> {
|
|
if name != "lesavka-server" {
|
|
return None;
|
|
}
|
|
let cargo = option_env!("CARGO").unwrap_or("cargo");
|
|
let target_dir = workspace_root().join("target/process-contract-debug");
|
|
let _ = fs::remove_dir_all(&target_dir);
|
|
let mut command = Command::new(cargo);
|
|
let status = command
|
|
.current_dir(workspace_root())
|
|
.env_remove("CARGO_BUILD_RUSTFLAGS")
|
|
.env_remove("CARGO_ENCODED_RUSTFLAGS")
|
|
.env_remove("CARGO_LLVM_COV")
|
|
.env_remove("CARGO_LLVM_COV_TARGET_DIR")
|
|
.env_remove("CARGO_TARGET_DIR")
|
|
.env_remove("LLVM_COV")
|
|
.env_remove("RUSTFLAGS")
|
|
.env_remove("RUSTDOCFLAGS")
|
|
.env_remove("LLVM_PROFILE_FILE")
|
|
.arg("build")
|
|
.arg("--target-dir")
|
|
.arg(&target_dir)
|
|
.args(["-p", "lesavka_server", "--bin", name])
|
|
.status()
|
|
.ok()?;
|
|
status
|
|
.success()
|
|
.then(|| target_dir.join("debug").join(name))
|
|
.filter(|path| path.exists() && path.is_file())
|
|
}
|
|
|
|
fn find_binary(name: &str) -> Option<PathBuf> {
|
|
build_current_binary(name)
|
|
.or_else(|| 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));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn server_binary_stays_up_with_missing_hid_nodes_and_current_version() {
|
|
let Some(bin) = find_binary("lesavka-server") else {
|
|
return;
|
|
};
|
|
let log_path = PathBuf::from("/tmp/lesavka-server.log");
|
|
let _ = fs::remove_file(&log_path);
|
|
let listener = TcpListener::bind("127.0.0.1:0").expect("reserve local test port");
|
|
let bind_addr = listener.local_addr().expect("local test addr");
|
|
drop(listener);
|
|
|
|
if cfg!(coverage) {
|
|
let mut child = Command::new(bin)
|
|
.env("LESAVKA_DISABLE_UVC", "1")
|
|
.env("LESAVKA_SERVER_BIND_ADDR", bind_addr.to_string())
|
|
.stderr(Stdio::piped())
|
|
.spawn()
|
|
.expect("spawn coverage-mode lesavka-server");
|
|
let deadline = Instant::now() + Duration::from_secs(3);
|
|
loop {
|
|
if let Some(status) = child.try_wait().expect("poll coverage-mode server") {
|
|
let mut stderr = String::new();
|
|
if let Some(mut pipe) = child.stderr.take() {
|
|
let _ = pipe.read_to_string(&mut stderr);
|
|
}
|
|
assert!(
|
|
!status.success(),
|
|
"coverage-mode server should not report success before the live-loop proof"
|
|
);
|
|
assert!(
|
|
stderr.contains("coverage mode skips live gRPC serve loop"),
|
|
"coverage-mode server should report the intentional live-loop skip; stderr was:\n{stderr}"
|
|
);
|
|
return;
|
|
}
|
|
|
|
let log = fs::read_to_string(&log_path).unwrap_or_default();
|
|
if log.contains(
|
|
"HID endpoints are not ready; relay will keep running and open them lazily",
|
|
) {
|
|
let _ = child.kill();
|
|
let _ = child.wait();
|
|
return;
|
|
}
|
|
assert!(
|
|
Instant::now() < deadline,
|
|
"timed out waiting for coverage stub exit or live startup log; log was:\n{log}"
|
|
);
|
|
std::thread::sleep(Duration::from_millis(50));
|
|
}
|
|
}
|
|
|
|
let mut child = Command::new(bin)
|
|
.env("LESAVKA_DISABLE_UVC", "1")
|
|
.env("LESAVKA_SERVER_BIND_ADDR", bind_addr.to_string())
|
|
.spawn()
|
|
.expect("spawn lesavka-server");
|
|
|
|
let deadline = Instant::now() + Duration::from_secs(3);
|
|
if let Some(version) = server_package_version() {
|
|
let _ = wait_for_log(
|
|
&log_path,
|
|
&format!("lesavka_server v{version} starting up"),
|
|
deadline,
|
|
);
|
|
}
|
|
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();
|
|
}
|