//! 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" ); }); }); } }