289 lines
10 KiB
Rust
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();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}
|