lesavka/testing/tests/server_upstream_media_contract.rs

289 lines
10 KiB
Rust

//! End-to-end server coverage for shared upstream media stream helpers.
//!
//! Scope: run local helper-level and output-delay probe checks around the public
//! upstream media RPC implementation.
//! Targets: `server/src/main.rs`, `server/src/output_delay_probe.rs`.
//! Why: the coverage harness should keep freshness decisions and probe plumbing
//! stable without physical UVC, HDMI, or ALSA hardware in CI.
#[cfg(coverage)]
#[allow(warnings)]
mod server_upstream_media {
include!(env!("LESAVKA_SERVER_MAIN_SRC"));
include!("../support/server_upstream_media_harness.rs");
use serial_test::serial;
use temp_env::with_var;
#[test]
fn coverage_relay_freshness_helpers_keep_live_media_bounded() {
temp_env::with_var("LESAVKA_UPSTREAM_STALE_DROP_MS", Some("42"), || {
assert_eq!(
upstream_stale_drop_budget(),
std::time::Duration::from_millis(42)
);
});
let mut video = std::collections::VecDeque::from([
VideoPacket {
pts: 1,
..Default::default()
},
VideoPacket {
pts: 2,
..Default::default()
},
VideoPacket {
pts: 3,
..Default::default()
},
]);
assert_eq!(retain_freshest_video_packet(&mut video), 2);
assert_eq!(video.len(), 1);
assert_eq!(video[0].pts, 3);
let mut audio = (0..12)
.map(|pts| AudioPacket {
pts,
..Default::default()
})
.collect::<std::collections::VecDeque<_>>();
assert_eq!(retain_freshest_audio_packet(&mut audio), 4);
assert_eq!(audio.front().map(|packet| packet.pts), Some(4));
assert_eq!(audio.len(), AUDIO_PENDING_LIVE_WINDOW_PACKETS);
let plan = lesavka_server::upstream_media_runtime::PlannedUpstreamPacket {
local_pts_us: 7,
due_at: tokio::time::Instant::now(),
late_by: std::time::Duration::ZERO,
source_lag: std::time::Duration::ZERO,
};
assert_eq!(
coverage_playable_plan(
lesavka_server::upstream_media_runtime::UpstreamPlanDecision::Play(plan)
)
.map(|plan| plan.local_pts_us),
Some(7)
);
assert!(
coverage_playable_plan(
lesavka_server::upstream_media_runtime::UpstreamPlanDecision::AwaitingPair
)
.is_none()
);
assert!(
coverage_playable_plan(
lesavka_server::upstream_media_runtime::UpstreamPlanDecision::DropBeforeOverlap
)
.is_none()
);
assert!(
coverage_playable_plan(
lesavka_server::upstream_media_runtime::UpstreamPlanDecision::DropStale("stale")
)
.is_none()
);
assert!(
coverage_playable_plan(
lesavka_server::upstream_media_runtime::UpstreamPlanDecision::StartupFailed("cold")
)
.is_none()
);
let mut requeued_audio = std::collections::VecDeque::new();
assert!(coverage_requeue_audio_packet(
&mut requeued_audio,
AudioPacket {
pts: 11,
..Default::default()
},
false,
));
assert_eq!(requeued_audio.front().map(|packet| packet.pts), Some(11));
assert!(!coverage_requeue_audio_packet(
&mut requeued_audio,
AudioPacket {
pts: 12,
..Default::default()
},
true,
));
assert_eq!(requeued_audio.len(), 1);
let mut requeued_video = std::collections::VecDeque::new();
assert!(coverage_requeue_video_packet(
&mut requeued_video,
VideoPacket {
pts: 21,
..Default::default()
},
false,
));
assert_eq!(requeued_video.front().map(|packet| packet.pts), Some(21));
assert!(!coverage_requeue_video_packet(
&mut requeued_video,
VideoPacket {
pts: 22,
..Default::default()
},
true,
));
requeued_video.push_back(VideoPacket {
pts: 23,
..Default::default()
});
assert_eq!(coverage_drop_late_video_packet(&mut requeued_video), 1);
assert_eq!(requeued_video.front().map(|packet| packet.pts), Some(23));
let wait_runtime = UpstreamMediaRuntime::new();
coverage_record_video_wait_failure(&wait_runtime);
assert_eq!(wait_runtime.snapshot().video_freezes, 1);
let fresh_plan = lesavka_server::upstream_media_runtime::PlannedUpstreamPacket {
local_pts_us: 31,
due_at: tokio::time::Instant::now(),
late_by: std::time::Duration::from_millis(1),
source_lag: std::time::Duration::ZERO,
};
let mut pending_audio = std::collections::VecDeque::new();
assert_eq!(
coverage_audio_plan_from_decision(
lesavka_server::upstream_media_runtime::UpstreamPlanDecision::Play(fresh_plan),
&mut pending_audio,
AudioPacket {
pts: 31,
..Default::default()
},
false,
std::time::Duration::from_millis(2),
)
.map(|plan| plan.local_pts_us),
Some(31)
);
assert!(
coverage_audio_plan_from_decision(
lesavka_server::upstream_media_runtime::UpstreamPlanDecision::AwaitingPair,
&mut pending_audio,
AudioPacket {
pts: 32,
..Default::default()
},
false,
std::time::Duration::from_millis(2),
)
.is_none()
);
assert_eq!(pending_audio.front().map(|packet| packet.pts), Some(32));
assert!(
coverage_audio_plan_from_decision(
lesavka_server::upstream_media_runtime::UpstreamPlanDecision::Play(
lesavka_server::upstream_media_runtime::PlannedUpstreamPacket {
local_pts_us: 33,
due_at: tokio::time::Instant::now(),
late_by: std::time::Duration::from_millis(5),
source_lag: std::time::Duration::ZERO,
},
),
&mut pending_audio,
AudioPacket {
pts: 33,
..Default::default()
},
false,
std::time::Duration::from_millis(2),
)
.is_none()
);
let mut pending_video = std::collections::VecDeque::from([
VideoPacket {
pts: 40,
..Default::default()
},
VideoPacket {
pts: 41,
..Default::default()
},
]);
assert!(
coverage_video_plan_from_decision(
lesavka_server::upstream_media_runtime::UpstreamPlanDecision::AwaitingPair,
&mut pending_video,
VideoPacket {
pts: 42,
..Default::default()
},
false,
std::time::Duration::from_millis(2),
)
.is_none()
);
assert_eq!(pending_video.front().map(|packet| packet.pts), Some(42));
assert!(
coverage_video_plan_from_decision(
lesavka_server::upstream_media_runtime::UpstreamPlanDecision::Play(
lesavka_server::upstream_media_runtime::PlannedUpstreamPacket {
local_pts_us: 43,
due_at: tokio::time::Instant::now(),
late_by: std::time::Duration::from_millis(5),
source_lag: std::time::Duration::ZERO,
},
),
&mut pending_video,
VideoPacket {
pts: 43,
..Default::default()
},
false,
std::time::Duration::from_millis(2),
)
.is_none()
);
assert_eq!(pending_video.len(), 1);
let master_runtime = UpstreamMediaRuntime::new();
assert!(coverage_audio_master_ready(&master_runtime, true));
assert!(!coverage_audio_master_ready(&master_runtime, false));
assert_eq!(master_runtime.snapshot().video_freezes, 1);
}
#[test]
#[serial]
fn run_output_delay_probe_streams_coverage_summary() {
let rt = tokio::runtime::Runtime::new().expect("runtime");
with_var("LESAVKA_CAPTURE_POWER_UNIT", Some("none"), || {
with_var("LESAVKA_DISABLE_UVC", None::<&str>, || {
rt.block_on(async {
let (_dir, handler) = build_handler_for_tests();
let (server, mut cli) = serve_handler(handler).await;
let mut response = cli
.run_output_delay_probe(tonic::Request::new(OutputDelayProbeRequest {
duration_seconds: 2,
warmup_seconds: 0,
pulse_period_ms: 500,
pulse_width_ms: 100,
event_width_codes: "1,2,3".to_string(),
audio_delay_us: 0,
video_delay_us: 0,
}))
.await
.expect("output delay probe should open")
.into_inner();
let reply =
tokio::time::timeout(std::time::Duration::from_secs(1), response.message())
.await
.expect("output delay probe timeout")
.expect("output delay probe grpc")
.expect("output delay probe item");
assert!(reply.ok);
assert!(reply.detail.contains("video_frames=1"));
assert!(reply.server_timeline_json.contains("lesavka.output-delay"));
server.abort();
});
});
});
}
}