lesavka/testing/tests/server_uvc_binary_extra_contract.rs

247 lines
7.3 KiB
Rust
Raw Normal View History

//! Extra coverage for `lesavka-uvc` control/error branches.
//!
//! Scope: keep additive branch tests in a separate file so each testing module
//! remains under the 500 LOC contract.
//! Targets: `server/src/bin/lesavka-uvc.rs`.
//! Why: preserve expanded UVC branch coverage while satisfying test module contracts.
mod uvc_binary_extra {
#![allow(warnings)]
#![allow(clippy::all)]
#![allow(dead_code)]
#![allow(unused_imports)]
#![allow(unused_variables)]
include!(env!("LESAVKA_SERVER_UVC_BIN_SRC"));
use serial_test::serial;
use std::fs;
use std::path::PathBuf;
use temp_env::with_var;
use tempfile::NamedTempFile;
fn sample_cfg() -> UvcConfig {
UvcConfig {
width: 1280,
height: 720,
fps: 25,
interval: 400_000,
max_packet: 1024,
frame_size: 1_843_200,
}
}
fn sample_interfaces() -> UvcInterfaces {
UvcInterfaces {
control: UVC_STRING_CONTROL_IDX,
streaming: UVC_STRING_STREAMING_IDX,
}
}
#[test]
fn handle_setup_stalls_non_streaming_set_cur_and_non_in_requests() {
let interfaces = sample_interfaces();
let mut state = UvcState::new(sample_cfg());
let mut pending = None;
let set_cur_other_iface = UsbCtrlRequest {
b_request_type: 0x00,
b_request: UVC_SET_CUR,
w_value: (0xFEu16) << 8,
w_index: 0x00FF,
w_length: 8,
};
handle_setup(
-1,
0,
&mut state,
&mut pending,
interfaces,
set_cur_other_iface,
true,
);
assert!(pending.is_none());
let non_in_non_set_cur = UsbCtrlRequest {
b_request_type: 0x00,
b_request: UVC_GET_CUR,
w_value: (UVC_VS_PROBE_CONTROL as u16) << 8,
w_index: interfaces.streaming as u16,
w_length: 8,
};
handle_setup(
-1,
0,
&mut state,
&mut pending,
interfaces,
non_in_non_set_cur,
true,
);
assert!(pending.is_none());
}
#[test]
fn handle_setup_rejects_oversized_set_cur_payload() {
let interfaces = sample_interfaces();
let mut state = UvcState::new(sample_cfg());
let mut pending = None;
let oversized = UsbCtrlRequest {
b_request_type: 0x00,
b_request: UVC_SET_CUR,
w_value: (UVC_VS_PROBE_CONTROL as u16) << 8,
w_index: interfaces.streaming as u16,
w_length: (UVC_DATA_SIZE as u16).saturating_add(1),
};
handle_setup(-1, 0, &mut state, &mut pending, interfaces, oversized, true);
assert!(pending.is_none());
}
#[test]
fn handle_setup_stalls_unknown_in_selector() {
let interfaces = sample_interfaces();
let mut state = UvcState::new(sample_cfg());
let mut pending = None;
let req = UsbCtrlRequest {
b_request_type: USB_DIR_IN,
b_request: UVC_GET_CUR,
w_value: (0xFEu16) << 8,
w_index: interfaces.streaming as u16,
w_length: 8,
};
handle_setup(-1, 0, &mut state, &mut pending, interfaces, req, true);
assert!(pending.is_none());
}
#[test]
fn handle_data_ignores_missing_pending_and_negative_lengths() {
let interfaces = sample_interfaces();
let mut state = UvcState::new(sample_cfg());
let mut pending = None;
handle_data(
-1,
0,
&mut state,
&mut pending,
interfaces,
UvcRequestData {
length: 8,
data: [0u8; UVC_DATA_SIZE],
},
true,
);
pending = Some(PendingRequest {
interface: interfaces.streaming,
selector: UVC_VS_PROBE_CONTROL,
expected_len: STREAM_CTRL_SIZE_11,
});
handle_data(
-1,
0,
&mut state,
&mut pending,
interfaces,
UvcRequestData {
length: -1,
data: [0u8; UVC_DATA_SIZE],
},
true,
);
assert!(pending.is_none());
}
#[test]
fn handle_data_ignores_non_streaming_pending_requests() {
let interfaces = sample_interfaces();
let mut state = UvcState::new(sample_cfg());
let mut pending = Some(PendingRequest {
interface: interfaces.control,
selector: UVC_VS_PROBE_CONTROL,
expected_len: STREAM_CTRL_SIZE_11,
});
let mut payload = [0u8; UVC_DATA_SIZE];
payload[2] = 1;
handle_data(
-1,
0,
&mut state,
&mut pending,
interfaces,
UvcRequestData {
length: STREAM_CTRL_SIZE_11 as i32,
data: payload,
},
true,
);
assert!(pending.is_none());
assert_eq!(state.probe, state.default);
}
#[test]
fn build_in_response_returns_none_for_unknown_selector() {
let state = UvcState::new(sample_cfg());
let interfaces = sample_interfaces();
let response = build_in_response(
&state,
interfaces,
interfaces.streaming,
0xFE,
UVC_GET_CUR,
8,
);
assert!(response.is_none());
}
#[test]
fn sanitize_streaming_control_keeps_defaults_for_short_payload() {
let state = UvcState::new(sample_cfg());
let short = [0u8; 8];
let out = sanitize_streaming_control(&short, &state);
assert_eq!(out, state.default);
}
#[test]
fn io_helpers_return_none_for_empty_or_missing_input() {
let empty = NamedTempFile::new().expect("tmp");
fs::write(empty.path(), "\n").expect("write empty");
assert_eq!(read_u32_first(empty.path().to_str().expect("path")), None);
let missing = PathBuf::from(format!(
"/tmp/lesavka-missing-fifo-{}-{}",
std::process::id(),
std::thread::current().name().unwrap_or("anon")
));
assert_eq!(read_fifo_min(missing.to_str().expect("missing")), None);
}
#[test]
fn compute_payload_cap_clamps_limit_pct_bounds() {
with_var("LESAVKA_UVC_MAXPAYLOAD_LIMIT", None::<&str>, || {
with_var("LESAVKA_UVC_LIMIT_PCT", Some("0"), || {
let cap = compute_payload_cap(false);
if let Some(cap) = cap {
assert!(cap.pct >= 1);
}
});
with_var("LESAVKA_UVC_LIMIT_PCT", Some("250"), || {
let cap = compute_payload_cap(true);
if let Some(cap) = cap {
assert!(cap.pct <= 100);
}
});
});
}
#[test]
#[serial]
fn main_returns_error_for_non_uvc_device_node() {
with_var("LESAVKA_UVC_DEV", Some("/dev/null"), || {
with_var("LESAVKA_UVC_BLOCKING", Some("1"), || {
let result = main();
assert!(result.is_err(), "non-UVC node should fail during event subscribe");
});
});
}
}