ci: refresh uvc coverage hygiene baseline
This commit is contained in:
parent
6cbe78e576
commit
4988956f9c
@ -903,12 +903,12 @@
|
|||||||
"server/src/bin/lesavka_uvc/coverage_model.rs": {
|
"server/src/bin/lesavka_uvc/coverage_model.rs": {
|
||||||
"clippy_warnings": 0,
|
"clippy_warnings": 0,
|
||||||
"doc_debt": 0,
|
"doc_debt": 0,
|
||||||
"loc": 141
|
"loc": 286
|
||||||
},
|
},
|
||||||
"server/src/bin/lesavka_uvc/coverage_startup.rs": {
|
"server/src/bin/lesavka_uvc/coverage_startup.rs": {
|
||||||
"clippy_warnings": 0,
|
"clippy_warnings": 0,
|
||||||
"doc_debt": 5,
|
"doc_debt": 0,
|
||||||
"loc": 229
|
"loc": 447
|
||||||
},
|
},
|
||||||
"server/src/bin/lesavka_uvc/payload_limits.rs": {
|
"server/src/bin/lesavka_uvc/payload_limits.rs": {
|
||||||
"clippy_warnings": 0,
|
"clippy_warnings": 0,
|
||||||
@ -1078,7 +1078,7 @@
|
|||||||
"server/src/main/relay_service_coverage/relay_trait_impl.rs": {
|
"server/src/main/relay_service_coverage/relay_trait_impl.rs": {
|
||||||
"clippy_warnings": 0,
|
"clippy_warnings": 0,
|
||||||
"doc_debt": 0,
|
"doc_debt": 0,
|
||||||
"loc": 372
|
"loc": 373
|
||||||
},
|
},
|
||||||
"server/src/main/relay_service_tests.rs": {
|
"server/src/main/relay_service_tests.rs": {
|
||||||
"clippy_warnings": 0,
|
"clippy_warnings": 0,
|
||||||
|
|||||||
@ -193,6 +193,11 @@ struct UvcVideoStats {
|
|||||||
|
|
||||||
#[cfg(coverage)]
|
#[cfg(coverage)]
|
||||||
impl UvcVideoStream {
|
impl UvcVideoStream {
|
||||||
|
/// Builds an inert coverage-mode stream seeded with the idle JPEG frame.
|
||||||
|
///
|
||||||
|
/// Inputs: the already-open UVC file descriptor, currently unused in this
|
||||||
|
/// harness. Output: stream state that mirrors production defaults. Why:
|
||||||
|
/// coverage tests need the stream bookkeeping without touching hardware.
|
||||||
fn new(_fd: i32) -> Self {
|
fn new(_fd: i32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
buffers: Vec::new(),
|
buffers: Vec::new(),
|
||||||
@ -207,6 +212,11 @@ impl UvcVideoStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reloads the latest spooled MJPEG frame when it is fresh and safe to use.
|
||||||
|
///
|
||||||
|
/// Inputs: the configured frame path and current buffer limits. Output:
|
||||||
|
/// updated cached frame plus rejection counters. Why: stale or oversized
|
||||||
|
/// frames should degrade to known-safe idle content instead of freezing RCT.
|
||||||
fn refresh_latest_frame(&mut self) {
|
fn refresh_latest_frame(&mut self) {
|
||||||
let stale = frame_spool_is_stale(&self.frame_path, frame_spool_max_age());
|
let stale = frame_spool_is_stale(&self.frame_path, frame_spool_max_age());
|
||||||
if stale && looks_like_mjpeg_frame(&self.latest_frame) {
|
if stale && looks_like_mjpeg_frame(&self.latest_frame) {
|
||||||
@ -246,6 +256,10 @@ impl UvcVideoStream {
|
|||||||
.min(self.frame_max_bytes)
|
.min(self.frame_max_bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Chooses the safest frame payload for the active UVC buffer.
|
||||||
|
///
|
||||||
|
/// Inputs: one buffer length. Output: current, idle, or minimal JPEG bytes.
|
||||||
|
/// Why: the gadget must always queue valid MJPEG, even with tiny buffers.
|
||||||
fn frame_for_buffer(&self, buffer_len: usize) -> &[u8] {
|
fn frame_for_buffer(&self, buffer_len: usize) -> &[u8] {
|
||||||
if self.latest_frame.len() <= buffer_len && looks_like_mjpeg_frame(&self.latest_frame) {
|
if self.latest_frame.len() <= buffer_len && looks_like_mjpeg_frame(&self.latest_frame) {
|
||||||
&self.latest_frame
|
&self.latest_frame
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
/// Runs only the coverage-mode startup probes and refuses the live control loop.
|
||||||
|
///
|
||||||
|
/// Inputs: CLI/env device settings. Output: successful validation until the
|
||||||
|
/// disabled loop guard. Why: contracts need startup coverage without driving USB.
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let (dev, _cfg) = parse_args()?;
|
let (dev, _cfg) = parse_args()?;
|
||||||
let _ = load_interfaces();
|
let _ = load_interfaces();
|
||||||
@ -18,6 +22,10 @@ fn parse_args() -> Result<(String, UvcConfig)> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(coverage)]
|
#[cfg(coverage)]
|
||||||
|
/// Finds the UVC device from `--device`, `-d`, or a positional path argument.
|
||||||
|
///
|
||||||
|
/// Inputs: raw CLI arguments. Output: the selected device path when present.
|
||||||
|
/// Why: manual probes and contract tests both exercise the same device syntax.
|
||||||
fn parse_device_arg(args: &[String]) -> Option<String> {
|
fn parse_device_arg(args: &[String]) -> Option<String> {
|
||||||
args
|
args
|
||||||
.windows(2)
|
.windows(2)
|
||||||
@ -34,6 +42,11 @@ fn parse_device_arg(args: &[String]) -> Option<String> {
|
|||||||
|
|
||||||
#[cfg(coverage)]
|
#[cfg(coverage)]
|
||||||
impl UvcConfig {
|
impl UvcConfig {
|
||||||
|
/// Builds a bounded UVC mode from environment overrides.
|
||||||
|
///
|
||||||
|
/// Inputs: `LESAVKA_UVC_*` mode variables. Output: a packet-safe mode
|
||||||
|
/// snapshot. Why: coverage contracts must pin descriptor math without
|
||||||
|
/// requiring the real kernel gadget.
|
||||||
fn from_env() -> Self {
|
fn from_env() -> Self {
|
||||||
let width = env_u32("LESAVKA_UVC_WIDTH", 1280);
|
let width = env_u32("LESAVKA_UVC_WIDTH", 1280);
|
||||||
let height = env_u32("LESAVKA_UVC_HEIGHT", 720);
|
let height = env_u32("LESAVKA_UVC_HEIGHT", 720);
|
||||||
@ -72,6 +85,10 @@ impl UvcConfig {
|
|||||||
|
|
||||||
#[cfg(coverage)]
|
#[cfg(coverage)]
|
||||||
impl UvcState {
|
impl UvcState {
|
||||||
|
/// Creates the control-state mirrors used by UVC request handling tests.
|
||||||
|
///
|
||||||
|
/// Inputs: one validated UVC mode. Output: default/probe/commit buffers.
|
||||||
|
/// Why: tests need deterministic state transitions for host negotiation.
|
||||||
fn new(cfg: UvcConfig) -> Self {
|
fn new(cfg: UvcConfig) -> Self {
|
||||||
let ctrl_len = stream_ctrl_len();
|
let ctrl_len = stream_ctrl_len();
|
||||||
let default = build_streaming_control(&cfg, ctrl_len);
|
let default = build_streaming_control(&cfg, ctrl_len);
|
||||||
@ -101,6 +118,10 @@ fn read_interface(path: &str) -> Option<u8> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(coverage)]
|
#[cfg(coverage)]
|
||||||
|
/// Opens the UVC control device with safe coverage defaults.
|
||||||
|
///
|
||||||
|
/// Inputs: device path and read/write env knobs. Output: opened file handle.
|
||||||
|
/// Why: tests should default to non-blocking read-only access unless opted in.
|
||||||
fn open_with_retry(path: &str) -> Result<std::fs::File> {
|
fn open_with_retry(path: &str) -> Result<std::fs::File> {
|
||||||
let read_only = uvc_control_read_only();
|
let read_only = uvc_control_read_only();
|
||||||
let mut opts = OpenOptions::new();
|
let mut opts = OpenOptions::new();
|
||||||
@ -202,6 +223,10 @@ fn derived_uvc_frame_max_bytes_for_transport(fps: u32, max_packet: u32, bulk: bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(coverage)]
|
#[cfg(coverage)]
|
||||||
|
/// Returns the effective MJPEG byte budget for the selected USB transport.
|
||||||
|
///
|
||||||
|
/// Inputs: negotiated max packet size and bulk/isochronous mode. Output: bytes
|
||||||
|
/// per second. Why: frame-size checks should mirror the transport budget.
|
||||||
fn effective_uvc_mjpeg_budget_bytes_per_sec(max_packet: u32, bulk: bool) -> u32 {
|
fn effective_uvc_mjpeg_budget_bytes_per_sec(max_packet: u32, bulk: bool) -> u32 {
|
||||||
let configured = env_u32(
|
let configured = env_u32(
|
||||||
"LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC",
|
"LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC",
|
||||||
@ -218,6 +243,10 @@ fn effective_uvc_mjpeg_budget_bytes_per_sec(max_packet: u32, bulk: bool) -> u32
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(coverage)]
|
#[cfg(coverage)]
|
||||||
|
/// Computes the high-speed isochronous payload budget.
|
||||||
|
///
|
||||||
|
/// Inputs: endpoint packet size and `LESAVKA_UVC_ISOCHRONOUS_LIMIT_PCT`.
|
||||||
|
/// Output: capped bytes per second. Why: coverage locks the safety margin.
|
||||||
fn uvc_isochronous_budget_bytes_per_sec(max_packet: u32) -> u32 {
|
fn uvc_isochronous_budget_bytes_per_sec(max_packet: u32) -> u32 {
|
||||||
let pct = env_u32(
|
let pct = env_u32(
|
||||||
"LESAVKA_UVC_ISOCHRONOUS_LIMIT_PCT",
|
"LESAVKA_UVC_ISOCHRONOUS_LIMIT_PCT",
|
||||||
@ -232,6 +261,10 @@ fn uvc_isochronous_budget_bytes_per_sec(max_packet: u32) -> u32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(coverage)]
|
#[cfg(coverage)]
|
||||||
|
/// Parses a boolean environment flag with a default fallback.
|
||||||
|
///
|
||||||
|
/// Inputs: variable name and default value. Output: parsed boolean. Why:
|
||||||
|
/// UVC safety switches must handle common true/false spellings consistently.
|
||||||
fn env_flag_enabled(name: &str, default: bool) -> bool {
|
fn env_flag_enabled(name: &str, default: bool) -> bool {
|
||||||
env::var(name)
|
env::var(name)
|
||||||
.ok()
|
.ok()
|
||||||
@ -262,6 +295,10 @@ fn uvc_frame_size_guard_enabled() -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(coverage)]
|
#[cfg(coverage)]
|
||||||
|
/// Decides whether the coverage helper may use bulk transport.
|
||||||
|
///
|
||||||
|
/// Inputs: env override plus configfs capabilities. Output: effective mode.
|
||||||
|
/// Why: tests should not advertise bulk if the gadget tree lacks support.
|
||||||
fn uvc_bulk_transfer_enabled() -> bool {
|
fn uvc_bulk_transfer_enabled() -> bool {
|
||||||
if !env_flag_enabled("LESAVKA_UVC_BULK", true) {
|
if !env_flag_enabled("LESAVKA_UVC_BULK", true) {
|
||||||
return false;
|
return false;
|
||||||
@ -290,6 +327,10 @@ fn uvc_frame_size_for_active_mode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(coverage)]
|
#[cfg(coverage)]
|
||||||
|
/// Returns the optional JSON stats path for the UVC coverage helper.
|
||||||
|
///
|
||||||
|
/// Inputs: `LESAVKA_UVC_STATS_PATH`. Output: path or disabled state. Why:
|
||||||
|
/// tests and probes need a cheap health artifact without forcing writes.
|
||||||
fn uvc_stats_path() -> Option<std::path::PathBuf> {
|
fn uvc_stats_path() -> Option<std::path::PathBuf> {
|
||||||
match std::env::var("LESAVKA_UVC_STATS_PATH") {
|
match std::env::var("LESAVKA_UVC_STATS_PATH") {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
@ -305,6 +346,10 @@ fn uvc_stats_path() -> Option<std::path::PathBuf> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(coverage)]
|
#[cfg(coverage)]
|
||||||
|
/// Writes a text artifact through a temporary file and atomic rename.
|
||||||
|
///
|
||||||
|
/// Inputs: target path and payload. Output: persisted file. Why: readers should
|
||||||
|
/// never observe partially-written UVC stats JSON.
|
||||||
fn write_atomic_text(path: &std::path::Path, text: &str) -> Result<()> {
|
fn write_atomic_text(path: &std::path::Path, text: &str) -> Result<()> {
|
||||||
if let Some(parent) = path.parent() {
|
if let Some(parent) = path.parent() {
|
||||||
std::fs::create_dir_all(parent)?;
|
std::fs::create_dir_all(parent)?;
|
||||||
@ -316,6 +361,10 @@ fn write_atomic_text(path: &std::path::Path, text: &str) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(coverage)]
|
#[cfg(coverage)]
|
||||||
|
/// Serializes the compact UVC video-stat snapshot consumed by probes.
|
||||||
|
///
|
||||||
|
/// Inputs: current counters and frame cap. Output: one JSON object string. Why:
|
||||||
|
/// shell probes need stable fields without depending on Rust serialization.
|
||||||
fn uvc_stats_snapshot_json(stats: &UvcVideoStats, frame_cap: usize) -> String {
|
fn uvc_stats_snapshot_json(stats: &UvcVideoStats, frame_cap: usize) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{{\"queued\":{},\"reloaded\":{},\"stale_replay\":{},\"rejected_oversize\":{},\"rejected_invalid\":{},\"fallback_idle\":{},\"latest_bytes\":{},\"frame_cap\":{},\"last_rejected_oversize_bytes\":{},\"last_rejected_oversize_cap\":{},\"paced_sleeps\":{},\"paced_sleep_ms\":{}}}\n",
|
"{{\"queued\":{},\"reloaded\":{},\"stale_replay\":{},\"rejected_oversize\":{},\"rejected_invalid\":{},\"fallback_idle\":{},\"latest_bytes\":{},\"frame_cap\":{},\"last_rejected_oversize_bytes\":{},\"last_rejected_oversize_cap\":{},\"paced_sleeps\":{},\"paced_sleep_ms\":{}}}\n",
|
||||||
@ -335,6 +384,10 @@ fn uvc_stats_snapshot_json(stats: &UvcVideoStats, frame_cap: usize) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(coverage)]
|
#[cfg(coverage)]
|
||||||
|
/// Returns the optional periodic stats write interval.
|
||||||
|
///
|
||||||
|
/// Inputs: `LESAVKA_UVC_STATS_INTERVAL_MS`. Output: duration or disabled state.
|
||||||
|
/// Why: probes can ask for telemetry without slowing every coverage test.
|
||||||
fn uvc_stats_interval() -> Option<std::time::Duration> {
|
fn uvc_stats_interval() -> Option<std::time::Duration> {
|
||||||
match env_u64("LESAVKA_UVC_STATS_INTERVAL_MS", DEFAULT_UVC_STATS_INTERVAL_MS) {
|
match env_u64("LESAVKA_UVC_STATS_INTERVAL_MS", DEFAULT_UVC_STATS_INTERVAL_MS) {
|
||||||
0 => None,
|
0 => None,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user