lesavka/testing/tests/server_uvc_runtime_contract.rs

120 lines
3.9 KiB
Rust
Raw Normal View History

//! Integration coverage for the UVC runtime supervision contract.
//!
//! Scope: exercise the long-running UVC helper supervisor in both successful
//! spawn and spawn-failure modes.
//! Targets: `server/src/uvc_runtime.rs`.
//! Why: the helper supervisor is operationally critical and should be covered
//! through top-level integration behavior, not only unit checks.
use lesavka_server::uvc_runtime::supervise_uvc_control;
use serial_test::serial;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::time::Duration;
use temp_env::with_var;
use tempfile::tempdir;
use tokio::runtime::Runtime;
#[test]
#[serial]
fn supervise_uvc_control_starts_helper_when_device_is_set() {
let dir = tempdir().expect("create temp dir");
let marker = dir.path().join("marker.log");
let helper = dir.path().join("helper.sh");
fs::write(
&helper,
format!(
"#!/usr/bin/env bash\nset -euo pipefail\necho \"$*\" >> '{}'\n",
marker.display()
),
)
.expect("write helper script");
let mut perms = fs::metadata(&helper)
.expect("helper metadata")
.permissions();
perms.set_mode(0o755);
fs::set_permissions(&helper, perms).expect("chmod helper script");
let helper_path = helper.to_string_lossy().to_string();
with_var("LESAVKA_UVC_DEV", Some("/dev/video-loop"), || {
let rt = Runtime::new().expect("runtime");
let result = rt.block_on(async {
tokio::time::timeout(
Duration::from_millis(350),
supervise_uvc_control(helper_path),
)
.await
});
assert!(result.is_err(), "supervisor should still be running");
});
let calls = fs::read_to_string(marker).expect("read helper marker");
assert!(
calls.contains("--device /dev/video-loop"),
"expected helper to receive device args, got: {calls}"
);
}
#[test]
#[serial]
fn supervise_uvc_control_restarts_helper_after_exit() {
let dir = tempdir().expect("create temp dir");
let marker = dir.path().join("marker.log");
let helper = dir.path().join("helper.sh");
fs::write(
&helper,
format!(
"#!/usr/bin/env bash\nset -euo pipefail\necho \"$*\" >> '{}'\nexit 0\n",
marker.display()
),
)
.expect("write helper script");
let mut perms = fs::metadata(&helper)
.expect("helper metadata")
.permissions();
perms.set_mode(0o755);
fs::set_permissions(&helper, perms).expect("chmod helper script");
let helper_path = helper.to_string_lossy().to_string();
with_var("LESAVKA_UVC_DEV", Some("/dev/video-loop"), || {
let rt = Runtime::new().expect("runtime");
let result = rt.block_on(async {
tokio::time::timeout(
Duration::from_millis(2_450),
supervise_uvc_control(helper_path),
)
.await
});
assert!(result.is_err(), "supervisor should still be running");
});
let calls = fs::read_to_string(marker).expect("read helper marker");
let restart_count = calls
.lines()
.filter(|line| line.contains("--device /dev/video-loop"))
.count();
assert!(
restart_count >= 2,
"expected helper restart loop to run at least twice, got {restart_count}: {calls}"
);
}
#[test]
#[serial]
fn supervise_uvc_control_survives_missing_helper_binary() {
with_var("LESAVKA_UVC_DEV", Some("/dev/video-loop"), || {
let rt = Runtime::new().expect("runtime");
let result = rt.block_on(async {
tokio::time::timeout(
Duration::from_millis(350),
supervise_uvc_control(String::from("/definitely/missing/lesavka-uvc")),
)
.await
});
assert!(
result.is_err(),
"supervisor should continue retrying forever"
);
});
}