lesavka/testing/tests/server_upstream_media_v2_handoff_contract.rs

150 lines
5.7 KiB
Rust

//! Static contract for the v2 bundled upstream media handoff.
//!
//! Scope: guard `StreamWebcamMedia` against reintroducing timed sink sleeps in
//! the gRPC receive loop.
//! Targets: `server/src/main/relay_service.rs` and
//! `server/src/main/relay_service/upstream_media_rpc.rs`.
//! Why: client->server freshness depends on draining bundled media as it
//! arrives; presentation timing belongs in bounded handoff workers.
const RELAY_SERVICE: &str = include_str!("../../server/src/main/relay_service.rs");
const WEBCAM_RPC: &str = include_str!("../../server/src/main/relay_service/upstream_media_rpc.rs");
const WEBCAM_SINK: &str = include_str!("../../server/src/video_sinks/webcam_sink.rs");
const MJPEG_SPOOL: &str = include_str!("../../server/src/video_sinks/mjpeg_spool.rs");
const PROFILE_OFFSETS: &str = include_str!("../../server/src/calibration/profile_offsets.rs");
const MATRIX_SCRIPT: &str = include_str!("../../scripts/manual/run_server_to_rc_mode_matrix.sh");
#[test]
fn bundled_receive_loop_enqueues_instead_of_sleeping_for_handoff() {
for expected in [
"tokio::sync::mpsc::channel::<MediaV2ScheduledAudio>(32)",
"tokio::sync::mpsc::channel::<MediaV2ScheduledVideo>(32)",
"tokio::spawn(run_media_v2_audio_handoff",
"tokio::spawn(run_media_v2_video_handoff",
"let bundle_epoch = bundle_arrived_at + schedule.common_delay;",
"let bundle_base_remote_pts_us = facts.capture_start_us;",
"prepare_media_v2_audio(",
"prepare_media_v2_video(",
"bundle_base_remote_pts_us",
"bundle_epoch",
".send(scheduled_audio)",
".send(scheduled_video)",
] {
assert!(
WEBCAM_RPC.contains(expected),
"v2 bundled RPC should enqueue handoff work with marker {expected}"
);
}
for forbidden in [
"push_media_v2_audio(",
"feed_media_v2_video(",
"sleep_until_media_v2(",
"MediaV2Clock",
] {
assert!(
!WEBCAM_RPC.contains(forbidden),
"v2 bundled RPC receive loop must not perform timed sink handoff: {forbidden}"
);
}
}
#[test]
fn handoff_workers_own_timing_and_presentation_telemetry() {
for expected in [
"async fn run_media_v2_audio_handoff",
"async fn run_media_v2_video_handoff",
"sleep_until_media_v2(item.due_at).await;",
"sink.push(&audio);",
"sink.finish();",
"relay.feed(item.packet);",
"mark_audio_presented(pts, item.due_at)",
"mark_video_presented(presented_pts, item.due_at)",
"Why: sleeping in the receive loop created HTTP/2 backlog",
] {
assert!(
RELAY_SERVICE.contains(expected),
"handoff workers should preserve timing/telemetry marker {expected}"
);
}
}
#[test]
fn stream_shutdown_drains_handoff_workers_before_releasing_leases() {
let audio_drop = WEBCAM_RPC.find("drop(audio_handoff_tx);").unwrap();
let video_drop = WEBCAM_RPC.find("drop(video_handoff_tx);").unwrap();
let audio_join = WEBCAM_RPC.find("audio_worker.await").unwrap();
let video_join = WEBCAM_RPC.find("video_worker.await").unwrap();
let close_camera = WEBCAM_RPC
.rfind("upstream_media_rt.close_camera(camera_lease.generation);")
.unwrap();
let close_microphone = WEBCAM_RPC
.rfind("upstream_media_rt.close_microphone(microphone_lease.generation);")
.unwrap();
assert!(audio_drop < audio_join);
assert!(video_drop < video_join);
assert!(audio_join < close_camera);
assert!(video_join < close_microphone);
}
#[test]
fn hevc_ingress_decodes_to_existing_mjpeg_uvc_path() {
for expected in [
"let use_hevc = matches!(cfg.codec, CameraCodec::Hevc);",
"video/x-h265",
"h265parse",
"pick_hevc_decoder()",
"jpegenc",
"LESAVKA_UVC_HEVC_JPEG_QUALITY",
"LESAVKA_UVC_HEVC_SPOOL_PULL_TIMEOUT_MS",
"image/jpeg",
"hevc_mjpeg_spool_sink",
".property(\"sync\", clock_align_enabled)",
"failed to spool decoded HEVC frame for UVC helper",
"HEVC camera uplink will be decoded and emitted as MJPEG/UVC",
] {
assert!(
WEBCAM_SINK.contains(expected) || MJPEG_SPOOL.contains(expected),
"HEVC UVC sink should preserve decode-to-MJPEG marker {expected}"
);
}
assert!(
WEBCAM_SINK.find("video/x-h265").unwrap() < WEBCAM_SINK.find("jpegenc").unwrap(),
"HEVC should be decoded before MJPEG encoding for the existing UVC output path"
);
}
#[test]
fn mjpeg_ingress_remains_passthrough_and_profile_calibrated() {
for expected in [
"let use_mjpeg = matches!(cfg.codec, CameraCodec::Mjpeg);",
"image/jpeg",
"src.set_caps(Some(&caps_mjpeg));",
"MjpegSpoolTiming::mjpeg_passthrough(pkt.pts)",
"spool_mjpeg_frame_with_timing(path, &pkt.data, Some(timing))",
"FACTORY_MJPEG_VIDEO_MODE_OFFSETS_US",
"FACTORY_HEVC_VIDEO_MODE_OFFSETS_US",
"LESAVKA_UPSTREAM_MJPEG_VIDEO_PLAYOUT_MODE_OFFSETS_US",
"LESAVKA_UPSTREAM_HEVC_VIDEO_PLAYOUT_MODE_OFFSETS_US",
"LESAVKA_SERVER_RC_PROFILE=${LESAVKA_SERVER_RC_PROFILE:-mjpeg}",
"LESAVKA_SERVER_RC_NORMALIZED_PROFILE=hevc",
] {
assert!(
WEBCAM_SINK.contains(expected)
|| PROFILE_OFFSETS.contains(expected)
|| MATRIX_SCRIPT.contains(expected),
"MJPEG/HEVC profile separation should contain marker {expected}"
);
}
assert!(
PROFILE_OFFSETS
.find("FACTORY_MJPEG_VIDEO_MODE_OFFSETS_US")
.unwrap()
< PROFILE_OFFSETS
.find("FACTORY_HEVC_VIDEO_MODE_OFFSETS_US")
.unwrap(),
"MJPEG factory map should stay separate from the additive HEVC map"
);
}