lesavka/tests/integration/server/video_sinks/hevc_mjpeg_spool_integration.rs

104 lines
3.4 KiB
Rust

// Integration coverage for the MJPEG spool used by UVC output.
//
// Scope: write real frame and metadata files through the same spool helper
// used by direct MJPEG ingress and decoded HEVC ingress.
// Targets: server/src/video_sinks/mjpeg_spool.rs.
// Why: both profiles share the UVC helper handoff path, so metadata must keep
// them separated without changing the atomic frame publication behavior.
use std::fs;
use serial_test::serial;
#[allow(dead_code, clippy::items_after_test_module)]
mod spool {
include!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/server/src/video_sinks/mjpeg_spool.rs"
));
pub fn write_hevc(
path: &std::path::Path,
data: &[u8],
source_pts_us: u64,
decoded_pts_us: Option<u64>,
) -> anyhow::Result<()> {
spool_mjpeg_frame_with_timing(
path,
data,
Some(MjpegSpoolTiming::hevc_decoded_mjpeg(
source_pts_us,
decoded_pts_us,
)),
)
}
pub fn write_mjpeg(
path: &std::path::Path,
data: &[u8],
source_pts_us: u64,
) -> anyhow::Result<()> {
spool_mjpeg_frame_with_timing(
path,
data,
Some(MjpegSpoolTiming::mjpeg_passthrough(source_pts_us)),
)
}
}
#[test]
#[serial]
fn hevc_decoded_mjpeg_spool_writes_frame_and_timing_metadata() {
let dir = tempfile::tempdir().expect("tempdir");
let frame_path = dir.path().join("uvc-frame.mjpg");
let log_path = dir.path().join("uvc-frame.jsonl");
let log_path_text = log_path.to_string_lossy().to_string();
let frame = [0xff, 0xd8, 0x11, 0x22, 0xff, 0xd9];
temp_env::with_vars(
[
("LESAVKA_UVC_FRAME_META", Some("1")),
(
"LESAVKA_UVC_FRAME_META_LOG_PATH",
Some(log_path_text.as_str()),
),
("LESAVKA_UVC_FRAME_META_PATH", None),
],
|| {
spool::write_hevc(&frame_path, &frame, 42_000, Some(44_000))
.expect("spool decoded HEVC frame");
},
);
assert_eq!(fs::read(&frame_path).expect("frame bytes"), frame);
let sidecar = frame_path.with_extension("mjpg.meta.json");
let sidecar_text = fs::read_to_string(sidecar).expect("sidecar metadata");
let log_text = fs::read_to_string(log_path).expect("metadata log");
for text in [sidecar_text.as_str(), log_text.as_str()] {
assert!(text.contains("\"profile\":\"hevc-decoded-mjpeg\""));
assert!(text.contains("\"bytes\":6"));
assert!(text.contains("\"source_pts_us\":42000"));
assert!(text.contains("\"decoded_pts_us\":44000"));
}
}
#[test]
#[serial]
fn mjpeg_passthrough_spool_keeps_decode_timing_null() {
let dir = tempfile::tempdir().expect("tempdir");
let frame_path = dir.path().join("uvc-frame.mjpg");
let frame = [0xff, 0xd8, 0x33, 0x44, 0xff, 0xd9];
temp_env::with_var("LESAVKA_UVC_FRAME_META", Some("1"), || {
spool::write_mjpeg(&frame_path, &frame, 55_000).expect("spool MJPEG frame");
});
assert_eq!(fs::read(&frame_path).expect("frame bytes"), frame);
let sidecar_text =
fs::read_to_string(frame_path.with_extension("mjpg.meta.json")).expect("sidecar metadata");
assert!(sidecar_text.contains("\"profile\":\"mjpeg-passthrough\""));
assert!(sidecar_text.contains("\"source_pts_us\":55000"));
assert!(sidecar_text.contains("\"decoded_pts_us\":null"));
}