lesavka/tests/unit/client/uplink/client_upstream_bundle_queue_unit.rs

138 lines
5.0 KiB
Rust

// Unit coverage for client upstream bundled media queue behavior.
//
// Scope: exercise the freshness queue with complete `UpstreamMediaBundle`
// values rather than split audio/video packets.
// Targets: `client/src/uplink_fresh_queue.rs`.
// Why: the upstream sync win depends on dropping stale media as complete A/V
// bundles so the server never receives an orphaned tone or flash.
#[path = "../../../../client/src/uplink_fresh_queue.rs"]
#[allow(warnings)]
mod uplink_fresh_queue;
use lesavka_common::lesavka::{AudioEncoding, AudioPacket, UpstreamMediaBundle, VideoPacket};
use std::time::Duration;
use uplink_fresh_queue::{FreshPacketQueue, FreshQueueConfig, FreshQueuePolicy};
fn bundle(seq: u64, capture_pts_us: u64) -> UpstreamMediaBundle {
UpstreamMediaBundle {
session_id: 7,
seq,
capture_start_us: capture_pts_us.saturating_sub(20_000),
capture_end_us: capture_pts_us.saturating_add(20_000),
video: Some(VideoPacket {
seq,
pts: capture_pts_us,
data: vec![0, 0, 0, 1, 0x26, 0x01, seq as u8],
client_capture_pts_us: capture_pts_us,
client_send_pts_us: capture_pts_us.saturating_add(5_000),
client_queue_age_ms: 5,
..Default::default()
}),
audio: vec![
AudioPacket {
seq: seq.saturating_mul(2),
pts: capture_pts_us.saturating_sub(20_000),
data: vec![0x11; 1_920],
client_capture_pts_us: capture_pts_us.saturating_sub(20_000),
client_send_pts_us: capture_pts_us.saturating_add(5_000),
client_queue_age_ms: 25,
..Default::default()
},
AudioPacket {
seq: seq.saturating_mul(2).saturating_add(1),
pts: capture_pts_us.saturating_add(20_000),
data: vec![0x22; 1_920],
client_capture_pts_us: capture_pts_us.saturating_add(20_000),
client_send_pts_us: capture_pts_us.saturating_add(25_000),
client_queue_age_ms: 5,
..Default::default()
},
],
audio_sample_rate: 48_000,
audio_channels: 2,
audio_encoding: AudioEncoding::PcmS16le as i32,
video_width: 1280,
video_height: 720,
video_fps: 30,
}
}
fn assert_complete_bundle(bundle: &UpstreamMediaBundle, expected_seq: u64) {
assert_eq!(bundle.seq, expected_seq);
assert!(
bundle.video.is_some(),
"freshness queue must not split video out of its bundle"
);
assert_eq!(
bundle.audio.len(),
2,
"freshness queue must not split audio out of its bundle"
);
let video = bundle.video.as_ref().expect("video packet");
assert!(
bundle.capture_start_us <= video.client_capture_pts_us
&& bundle.capture_end_us >= video.client_capture_pts_us
);
assert!(bundle.audio.iter().all(|audio| {
bundle.capture_start_us <= audio.client_capture_pts_us
&& bundle.capture_end_us >= audio.client_capture_pts_us
}));
}
#[tokio::test]
async fn latest_only_policy_drops_superseded_bundles_as_complete_av_units() {
let queue = FreshPacketQueue::new(FreshQueueConfig {
capacity: 8,
max_age: Duration::from_secs(1),
policy: FreshQueuePolicy::LatestOnly,
});
for seq in 1..=3 {
let _ = queue.push(
bundle(seq, 1_000_000 + seq * 33_333),
Duration::from_millis(10),
);
}
let popped = queue.pop_fresh().await;
assert_eq!(popped.dropped_stale, 2);
assert_eq!(popped.queue_depth, 0);
assert_complete_bundle(&popped.packet.expect("newest bundle"), 3);
}
#[tokio::test]
async fn stale_bundle_drop_never_delivers_audio_or_video_halves() {
let queue = FreshPacketQueue::new(FreshQueueConfig {
capacity: 4,
max_age: Duration::from_millis(200),
policy: FreshQueuePolicy::DrainOldest,
});
let _ = queue.push(bundle(1, 1_000_000), Duration::from_millis(250));
let _ = queue.push(bundle(2, 1_033_333), Duration::from_millis(25));
let popped = queue.pop_fresh().await;
assert_eq!(popped.dropped_stale, 1);
assert_complete_bundle(&popped.packet.expect("fresh bundle"), 2);
}
#[tokio::test]
async fn capacity_pressure_drops_oldest_bundles_instead_of_backlogging_media() {
let queue = FreshPacketQueue::new(FreshQueueConfig {
capacity: 2,
max_age: Duration::from_secs(1),
policy: FreshQueuePolicy::DrainOldest,
});
let first = queue.push(bundle(1, 1_000_000), Duration::from_millis(5));
let second = queue.push(bundle(2, 1_033_333), Duration::from_millis(5));
let third = queue.push(bundle(3, 1_066_666), Duration::from_millis(5));
assert_eq!(first.dropped_queue_full, 0);
assert_eq!(second.queue_depth, 2);
assert_eq!(third.dropped_queue_full, 1);
assert_complete_bundle(&queue.pop_fresh().await.packet.expect("second bundle"), 2);
assert_complete_bundle(&queue.pop_fresh().await.packet.expect("third bundle"), 3);
}