233 lines
7.4 KiB
Rust
233 lines
7.4 KiB
Rust
//! Include-based coverage for camera capture configuration branches.
|
|
//!
|
|
//! Scope: include `client/src/input/camera.rs` and exercise encoder/source
|
|
//! selection helpers plus non-device fallbacks.
|
|
//! Targets: `client/src/input/camera.rs`.
|
|
//! Why: camera startup should remain robust across codec/env permutations.
|
|
|
|
#[allow(warnings)]
|
|
mod camera_include_contract {
|
|
include!(env!("LESAVKA_CLIENT_CAMERA_SRC"));
|
|
|
|
use serial_test::serial;
|
|
use std::os::unix::fs::symlink;
|
|
use temp_env::with_var;
|
|
use tempfile::tempdir;
|
|
|
|
fn init_gst() {
|
|
gst::init().ok();
|
|
}
|
|
|
|
#[test]
|
|
fn env_u32_parses_values_and_falls_back() {
|
|
with_var("LESAVKA_TEST_CAM_U32", Some("77"), || {
|
|
assert_eq!(env_u32("LESAVKA_TEST_CAM_U32", 11), 77);
|
|
});
|
|
with_var("LESAVKA_TEST_CAM_U32", Some("not-a-number"), || {
|
|
assert_eq!(env_u32("LESAVKA_TEST_CAM_U32", 11), 11);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn encoder_helpers_return_supported_defaults() {
|
|
init_gst();
|
|
let (enc, _caps) = CameraCapture::pick_encoder();
|
|
assert!(
|
|
matches!(
|
|
enc,
|
|
"nvh264enc" | "vaapih264enc" | "v4l2h264enc" | "x264enc"
|
|
),
|
|
"unexpected encoder: {enc}"
|
|
);
|
|
let (enc, key_prop) = CameraCapture::choose_encoder();
|
|
assert!(
|
|
matches!(
|
|
enc,
|
|
"nvh264enc" | "vaapih264enc" | "v4l2h264enc" | "x264enc"
|
|
),
|
|
"unexpected encoder: {enc}"
|
|
);
|
|
if let Some(key_prop) = key_prop {
|
|
assert!(!key_prop.is_empty());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn find_device_and_capture_detection_handle_missing_nodes() {
|
|
assert!(CameraCapture::find_device("never-matches-this-fragment").is_none());
|
|
assert!(!CameraCapture::is_capture(
|
|
"/dev/definitely-missing-camera0"
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn find_device_honors_override_roots_and_handles_non_capture_targets() {
|
|
let dir = tempdir().expect("tempdir");
|
|
let by_id = dir.path().join("by-id");
|
|
let dev_root = dir.path().join("dev-root");
|
|
std::fs::create_dir_all(&by_id).expect("create by-id");
|
|
std::fs::create_dir_all(&dev_root).expect("create dev root");
|
|
std::fs::write(dev_root.join("video42"), "").expect("create fake node");
|
|
symlink("../dev-root/video42", by_id.join("usb-Cam_42")).expect("create camera symlink");
|
|
|
|
with_var(
|
|
"LESAVKA_CAM_BY_ID_DIR",
|
|
Some(by_id.to_string_lossy().to_string()),
|
|
|| {
|
|
with_var(
|
|
"LESAVKA_CAM_DEV_ROOT",
|
|
Some(dev_root.to_string_lossy().to_string()),
|
|
|| {
|
|
let found = CameraCapture::find_device("Cam_42");
|
|
assert!(
|
|
found.is_none(),
|
|
"fake file should not pass V4L capture capability checks"
|
|
);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(coverage)]
|
|
#[serial]
|
|
fn find_device_returns_dev_path_when_fake_target_matches_capture_shape() {
|
|
let dir = tempdir().expect("tempdir");
|
|
let by_id = dir.path().join("by-id");
|
|
std::fs::create_dir_all(&by_id).expect("create by-id");
|
|
symlink("../video42", by_id.join("usb-Cam_42")).expect("create camera symlink");
|
|
|
|
with_var(
|
|
"LESAVKA_CAM_BY_ID_DIR",
|
|
Some(by_id.to_string_lossy().to_string()),
|
|
|| {
|
|
with_var("LESAVKA_CAM_DEV_ROOT", Some("/dev".to_string()), || {
|
|
let found = CameraCapture::find_device("Cam_42");
|
|
assert_eq!(found.as_deref(), Some("/dev/video42"));
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn new_covers_test_pattern_and_mjpg_source_branches() {
|
|
init_gst();
|
|
let _ = CameraCapture::new(Some("test"), None);
|
|
|
|
with_var("LESAVKA_CAM_CODEC", Some("mjpeg"), || {
|
|
let _ = CameraCapture::new(Some("test"), None);
|
|
});
|
|
|
|
let mjpeg_cfg = CameraConfig {
|
|
codec: CameraCodec::Mjpeg,
|
|
width: 640,
|
|
height: 480,
|
|
fps: 30,
|
|
};
|
|
let _ = CameraCapture::new(Some("test"), Some(mjpeg_cfg));
|
|
|
|
with_var("LESAVKA_CAM_MJPG", Some("1"), || {
|
|
let _ = CameraCapture::new(Some("/dev/video0"), None);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn new_stub_and_pull_are_stable_without_frames() {
|
|
init_gst();
|
|
let stub = CameraCapture::new_stub();
|
|
assert!(stub.pull().is_none());
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn new_covers_device_path_fragment_and_default_source_branches() {
|
|
init_gst();
|
|
|
|
let by_path = CameraCapture::new(Some("/dev/video42"), None);
|
|
assert!(by_path.is_ok() || by_path.is_err());
|
|
|
|
let by_fragment = CameraCapture::new(Some("definitely-missing-fragment"), None);
|
|
assert!(by_fragment.is_ok() || by_fragment.is_err());
|
|
|
|
let default_source = CameraCapture::new(None, None);
|
|
assert!(default_source.is_ok() || default_source.is_err());
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn new_covers_output_codec_and_mjpg_source_switches() {
|
|
init_gst();
|
|
|
|
let mjpeg_cfg = CameraConfig {
|
|
codec: CameraCodec::Mjpeg,
|
|
width: 320,
|
|
height: 240,
|
|
fps: 15,
|
|
};
|
|
let mjpeg_out = CameraCapture::new(Some("/dev/video42"), Some(mjpeg_cfg));
|
|
assert!(mjpeg_out.is_ok() || mjpeg_out.is_err());
|
|
|
|
with_var("LESAVKA_CAM_MJPG", Some("1"), || {
|
|
let h264_cfg = CameraConfig {
|
|
codec: CameraCodec::H264,
|
|
width: 640,
|
|
height: 480,
|
|
fps: 25,
|
|
};
|
|
let mjpg_source = CameraCapture::new(Some("/dev/video42"), Some(h264_cfg));
|
|
assert!(mjpg_source.is_ok() || mjpg_source.is_err());
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(coverage)]
|
|
#[serial]
|
|
fn new_covers_non_x264_encoder_option_branch_in_coverage_harness() {
|
|
init_gst();
|
|
let cfg = CameraConfig {
|
|
codec: CameraCodec::H264,
|
|
width: 640,
|
|
height: 480,
|
|
fps: 30,
|
|
};
|
|
|
|
with_var("LESAVKA_CAM_TEST_ENCODER", Some("v4l2h264enc"), || {
|
|
let result = CameraCapture::new(Some("test"), Some(cfg));
|
|
assert!(result.is_ok() || result.is_err());
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn pull_returns_packet_from_test_pattern_pipeline_when_available() {
|
|
init_gst();
|
|
let cfg = CameraConfig {
|
|
codec: CameraCodec::H264,
|
|
width: 320,
|
|
height: 240,
|
|
fps: 15,
|
|
};
|
|
match CameraCapture::new(Some("test"), Some(cfg)) {
|
|
Ok(cap) => {
|
|
for _ in 0..20 {
|
|
if let Some(pkt) = cap.pull() {
|
|
assert_eq!(pkt.id, 2);
|
|
assert!(
|
|
!pkt.data.is_empty(),
|
|
"test pattern should emit payload bytes"
|
|
);
|
|
return;
|
|
}
|
|
std::thread::sleep(std::time::Duration::from_millis(30));
|
|
}
|
|
}
|
|
Err(err) => {
|
|
assert!(!err.to_string().trim().is_empty());
|
|
}
|
|
}
|
|
}
|
|
}
|