lesavka/tests/chaos/client/uplink/uplink_backpressure_chaos_contract.rs

188 lines
5.7 KiB
Rust

// Chaos contract for upstream media backpressure behavior.
//
// Scope: prove the freshness-first code path still models stalls, queue
// pressure, and stale media drops as first-class failure modes.
// Targets: `client/src/uplink_latency_harness.rs`,
// `client/src/uplink_fresh_queue.rs`, and `client/src/app/uplink_media/*`.
// Why: real WAN calls fail by forming backlog; chaos coverage keeps the code
// biased toward bounded stutter over minutes of delayed media.
const LATENCY_HARNESS: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/client/src/uplink_latency_harness.rs"
));
const FRESH_QUEUE: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/client/src/uplink_fresh_queue.rs"
));
const DROP_LOGGING: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/client/src/app/uplink_media/drop_logging.rs"
));
#[path = "../../../../client/src/uplink_fresh_queue.rs"]
#[allow(warnings)]
mod uplink_fresh_queue;
mod input {
pub mod camera {
#[derive(Clone, Copy)]
pub enum CameraCodec {
Hevc,
}
#[derive(Clone, Copy)]
pub struct CameraConfig {
pub codec: CameraCodec,
}
}
}
use lesavka_common::lesavka::{AudioEncoding, AudioPacket, UpstreamMediaBundle, VideoPacket};
use std::time::Duration;
use uplink_fresh_queue::{FreshPacketQueue, FreshQueueConfig, FreshQueuePolicy};
include!("../../../../client/src/app/uplink_media/video_keyframes.rs");
fn bundle(seq: u64, capture_pts_us: u64, video_data: Vec<u8>) -> UpstreamMediaBundle {
UpstreamMediaBundle {
session_id: 13,
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: video_data,
client_capture_pts_us: capture_pts_us,
..Default::default()
}),
audio: vec![AudioPacket {
seq,
pts: capture_pts_us,
client_capture_pts_us: capture_pts_us,
..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,
}
}
#[test]
fn upstream_backpressure_model_prefers_bounded_drops_over_latency_debt() {
for marker in [
"freshness_max_age",
"max_queue_depth",
"preserve_backlog_camera_policy_accumulates_stale_video_after_a_stall",
"drop_oldest_policy_keeps_media_live_by_sacrificing_old_packets",
] {
assert!(
LATENCY_HARNESS.contains(marker),
"latency harness should preserve chaos marker {marker}"
);
}
for marker in [
"FreshQueuePolicy::LatestOnly",
"dropped_queue_full",
"dropped_stale",
"pop_fresh_discards_stale_packets_before_returning_live_media",
] {
assert!(
FRESH_QUEUE.contains(marker),
"fresh queue should preserve chaos marker {marker}"
);
}
assert!(
DROP_LOGGING
.contains("queue is dropping stale/superseded packets to preserve live A/V sync"),
"operator logs should explain freshness-first drops during chaos conditions"
);
}
#[tokio::test]
async fn jitter_burst_latest_only_policy_outputs_the_newest_complete_bundle() {
let queue = FreshPacketQueue::new(FreshQueueConfig {
capacity: 8,
max_age: Duration::from_millis(150),
policy: FreshQueuePolicy::LatestOnly,
});
let _ = queue.push(
bundle(1, 1_000_000, vec![0, 0, 0, 1, 0x26, 0x01]),
Duration::from_millis(120),
);
let _ = queue.push(
bundle(2, 1_033_333, vec![0, 0, 0, 1, 0x02, 0x01]),
Duration::from_millis(80),
);
let _ = queue.push(
bundle(3, 1_066_666, vec![0, 0, 0, 1, 0x26, 0x01]),
Duration::from_millis(20),
);
let popped = queue.pop_fresh().await;
let packet = popped.packet.expect("fresh bundle after jitter burst");
assert_eq!(popped.dropped_stale, 2);
assert_eq!(packet.seq, 3);
assert!(packet.video.is_some());
assert_eq!(packet.audio.len(), 1);
}
#[tokio::test]
async fn reordered_stale_arrival_is_dropped_as_a_whole_bundle() {
let queue = FreshPacketQueue::new(FreshQueueConfig {
capacity: 4,
max_age: Duration::from_millis(150),
policy: FreshQueuePolicy::DrainOldest,
});
let _ = queue.push(
bundle(10, 1_300_000, vec![0, 0, 0, 1, 0x26, 0x01]),
Duration::from_millis(220),
);
let _ = queue.push(
bundle(8, 1_233_334, vec![0, 0, 0, 1, 0x26, 0x01]),
Duration::from_millis(15),
);
let popped = queue.pop_fresh().await;
let packet = popped.packet.expect("fresh reordered bundle");
assert_eq!(popped.dropped_stale, 1);
assert_eq!(packet.seq, 8);
assert!(packet.video.is_some());
assert_eq!(packet.audio.len(), 1);
}
#[test]
fn missing_keyframe_after_capture_gap_holds_predictive_video_until_irap() {
let mut waiting_for_keyframe = false;
assert!(upstream_camera_uses_hevc(Some(
input::camera::CameraConfig {
codec: input::camera::CameraCodec::Hevc,
}
)));
note_hevc_capture_gap(true, &mut waiting_for_keyframe);
let delta = bundle(20, 2_000_000, vec![0, 0, 0, 1, 0x02, 0x01]);
let recovery = bundle(21, 2_033_333, vec![0, 0, 0, 1, 0x26, 0x01]);
assert!(waiting_for_keyframe);
assert!(should_hold_hevc_bundle_for_keyframe_recovery(
waiting_for_keyframe,
&delta
));
assert!(!should_hold_hevc_bundle_for_keyframe_recovery(
waiting_for_keyframe,
&recovery
));
assert!(bundle_has_hevc_recovery_keyframe(&recovery));
}