//! Integration coverage for `lesavka-uvc` process startup parsing. //! //! Scope: launch the real `lesavka-uvc` binary with controlled arguments and //! environment overrides to exercise argument/config parsing and early startup. //! Targets: `server/src/bin/lesavka-uvc.rs`. //! Why: command-line/environment startup behavior should fail fast and remain //! deterministic without a physical UVC gadget node in CI. use serial_test::serial; use std::path::{Path, PathBuf}; use std::process::{Child, Command, ExitStatus}; use std::time::{Duration, Instant}; use tempfile::NamedTempFile; 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: Child, timeout: Duration) -> ExitStatus { let deadline = Instant::now() + timeout; loop { if let Some(status) = child.try_wait().expect("poll child") { return status; } if Instant::now() >= deadline { let _ = child.kill(); let _ = child.wait(); panic!("lesavka-uvc did not exit within timeout"); } std::thread::sleep(Duration::from_millis(50)); } } #[test] #[serial] fn uvc_binary_requires_device_argument_or_env() { let Some(bin) = find_binary("lesavka-uvc") else { return; }; let status = Command::new(Path::new(&bin)) .env_remove("LESAVKA_UVC_DEV") .status() .expect("spawn lesavka-uvc"); assert!( !status.success(), "uvc binary should fail without a device path" ); } #[test] #[serial] fn uvc_binary_applies_env_config_and_fails_fast_on_non_v4l2_node() { let Some(bin) = find_binary("lesavka-uvc") else { return; }; let fake_device = NamedTempFile::new().expect("temp device"); let child = Command::new(Path::new(&bin)) .arg("--device") .arg(fake_device.path()) .env("LESAVKA_UVC_MAXPAYLOAD_LIMIT", "256") .env("LESAVKA_UVC_MAXPACKET", "4096") .env("LESAVKA_UVC_BULK", "1") .env("LESAVKA_UVC_FPS", "30") .spawn() .expect("spawn lesavka-uvc"); let status = wait_for_exit(child, Duration::from_secs(3)); assert!( !status.success(), "uvc binary should fail on non-v4l2 test file" ); } #[test] #[serial] fn uvc_binary_accepts_positional_device_argument() { let Some(bin) = find_binary("lesavka-uvc") else { return; }; let fake_device = NamedTempFile::new().expect("temp device"); let child = Command::new(Path::new(&bin)) .arg(fake_device.path()) .env_remove("LESAVKA_UVC_BULK") .env("LESAVKA_UVC_MAXPAYLOAD_LIMIT", "2048") .env("LESAVKA_UVC_MAXPACKET", "1024") .spawn() .expect("spawn lesavka-uvc"); let status = wait_for_exit(child, Duration::from_secs(3)); assert!( !status.success(), "uvc binary should fail on non-v4l2 test file" ); }