test: strengthen durable behavior coverage after review
This commit is contained in:
14 files changed
+1063
-85
No files matched your search
@@ -199,10 +199,23 @@ mod tests {
|
||||
) {
|
||||
let mut tracker = IpdvTracker::new(Some(limit));
|
||||
for seq in seqs {
|
||||
tracker.insert(ipdv_sample(seq, i128::from(seq)));
|
||||
let completed_before = tracker.completed_pairs.clone();
|
||||
let pairs = tracker.insert(ipdv_sample(seq, i128::from(seq)));
|
||||
let emitted: HashSet<_> = pairs.iter().map(|pair| pair.current_seq).collect();
|
||||
prop_assert_eq!(emitted.len(), pairs.len());
|
||||
prop_assert!(emitted.is_disjoint(&completed_before), "a retained pair must not be emitted twice");
|
||||
prop_assert!(tracker.samples.len() <= limit);
|
||||
prop_assert!(tracker.sample_order.len() <= limit);
|
||||
prop_assert!(tracker.completed_pairs.len() <= limit);
|
||||
// Eviction must remove stale pair markers as well as samples;
|
||||
// otherwise retained state can prevent future adjacent pairs.
|
||||
prop_assert_eq!(tracker.sample_order.len(), tracker.samples.len());
|
||||
prop_assert_eq!(tracker.sample_order.iter().collect::<HashSet<_>>().len(), tracker.samples.len());
|
||||
prop_assert!(tracker.sample_order.iter().all(|seq| tracker.samples.contains_key(seq)));
|
||||
prop_assert!(tracker.completed_pairs.iter().all(|seq| {
|
||||
tracker.samples.contains_key(seq)
|
||||
&& tracker.samples.contains_key(&seq.wrapping_sub(1))
|
||||
}), "completed pairs must retain both sequence endpoints");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
use std::time::{Duration, Instant, UNIX_EPOCH};
|
||||
|
||||
use irtt_client::{
|
||||
ClientEvent, ClientTimestamp, PacketMeta, RttSample, ServerTiming, SignedDuration,
|
||||
};
|
||||
use irtt_stats::{StatsCollector, StatsConfig};
|
||||
|
||||
fn reply(
|
||||
base: Instant,
|
||||
seq: u32,
|
||||
send_ms: u64,
|
||||
receive_ms: u64,
|
||||
timing: ServerTiming,
|
||||
) -> ClientEvent {
|
||||
let raw = Duration::from_millis(receive_ms - send_ms);
|
||||
ClientEvent::EchoReply {
|
||||
seq,
|
||||
remote: "127.0.0.1:2112".parse().unwrap(),
|
||||
sent_at: ClientTimestamp {
|
||||
mono: base + Duration::from_millis(send_ms),
|
||||
wall: UNIX_EPOCH + Duration::from_millis(200 + u64::from(seq) * 10),
|
||||
},
|
||||
received_at: ClientTimestamp {
|
||||
mono: base + Duration::from_millis(receive_ms),
|
||||
wall: UNIX_EPOCH + Duration::from_millis(240 + u64::from(seq) * 20),
|
||||
},
|
||||
rtt: RttSample {
|
||||
raw,
|
||||
adjusted: None,
|
||||
effective: SignedDuration::from_duration(raw),
|
||||
},
|
||||
server_timing: Some(timing),
|
||||
one_way: None,
|
||||
received_stats: None,
|
||||
bytes: 64,
|
||||
packet_meta: PacketMeta::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn directional_ipdv_uses_matching_clock_domains_and_preserves_zero_and_absence() {
|
||||
let first = ServerTiming {
|
||||
receive_mono_ns: Some(100_000_000),
|
||||
send_mono_ns: Some(101_000_000),
|
||||
receive_wall_ns: Some(1_000_000_000),
|
||||
send_wall_ns: Some(1_001_000_000),
|
||||
midpoint_mono_ns: None,
|
||||
midpoint_wall_ns: None,
|
||||
processing: None,
|
||||
};
|
||||
let second = ServerTiming {
|
||||
receive_mono_ns: Some(105_000_000),
|
||||
send_mono_ns: Some(108_000_000),
|
||||
receive_wall_ns: Some(1_020_000_000),
|
||||
send_wall_ns: Some(1_026_000_000),
|
||||
..first
|
||||
};
|
||||
// Wall and monotonic deltas deliberately disagree: availability alone
|
||||
// cannot prove the formulas or which clock domain wins.
|
||||
let mut wall_only = second;
|
||||
wall_only.receive_mono_ns = None;
|
||||
wall_only.send_mono_ns = None;
|
||||
let mut no_send = wall_only;
|
||||
no_send.receive_wall_ns = None;
|
||||
let mut no_receive = wall_only;
|
||||
no_receive.send_wall_ns = None;
|
||||
let zero = ServerTiming {
|
||||
receive_mono_ns: Some(110_000_000),
|
||||
send_mono_ns: Some(111_000_000),
|
||||
..second
|
||||
};
|
||||
for (label, current, send_ms, expected_send_ms, expected_receive_ms) in [
|
||||
("monotonic preferred", second, 30, Some(5), Some(3)),
|
||||
("wall fallback", wall_only, 30, Some(10), Some(5)),
|
||||
("missing send endpoint", no_send, 30, None, Some(5)),
|
||||
("missing receive endpoint", no_receive, 30, Some(10), None),
|
||||
("reordered client send times", second, 10, Some(15), Some(3)),
|
||||
("zero variation", zero, 30, Some(0), Some(0)),
|
||||
] {
|
||||
let base = Instant::now();
|
||||
let mut collector = StatsCollector::new(StatsConfig::finite());
|
||||
assert!(collector
|
||||
.process(&reply(base, 0, 20, 40, first))
|
||||
.ipdv_pairs
|
||||
.is_empty());
|
||||
let update = collector.process(&reply(base, 1, send_ms, 50, current));
|
||||
assert_eq!(update.ipdv_pairs.len(), 1, "{label}");
|
||||
let pair = &update.ipdv_pairs[0];
|
||||
assert_eq!(
|
||||
pair.send_ipdv,
|
||||
expected_send_ms.map(Duration::from_millis),
|
||||
"{label}"
|
||||
);
|
||||
assert_eq!(
|
||||
pair.receive_ipdv,
|
||||
expected_receive_ms.map(Duration::from_millis),
|
||||
"{label}"
|
||||
);
|
||||
let snapshot = collector.snapshot();
|
||||
for (expected, actual) in [
|
||||
(expected_send_ms, snapshot.ipdv.send),
|
||||
(expected_receive_ms, snapshot.ipdv.receive),
|
||||
] {
|
||||
assert_eq!(actual.count, u64::from(expected.is_some()), "{label}");
|
||||
assert_eq!(
|
||||
actual.total_ns,
|
||||
i128::from(expected.unwrap_or(0)) * 1_000_000,
|
||||
"{label}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,6 +319,19 @@ fn ipdv_wraparound_sequence_is_adjacent() {
|
||||
assert!(wrapped.contributed_sample);
|
||||
assert_one_ipdv_pair(&wrapped, u32::MAX, 0, Duration::from_millis(4));
|
||||
|
||||
// The client classifies duplicate datagrams before publishing events;
|
||||
// their accounting must not emit an adjacent pair a second time.
|
||||
for seq in [u32::MAX, 0] {
|
||||
let duplicate = collector.process(&ClientEvent::DuplicateReply {
|
||||
seq,
|
||||
remote: "127.0.0.1:2112".parse().unwrap(),
|
||||
received_at: ts(30),
|
||||
bytes: 64,
|
||||
});
|
||||
assert!(!duplicate.contributed_sample);
|
||||
assert_no_ipdv_pairs(&duplicate);
|
||||
}
|
||||
|
||||
let snapshot = collector.snapshot();
|
||||
assert_eq!(snapshot.ipdv.round_trip.count, 1);
|
||||
assert_eq!(snapshot.ipdv.round_trip.total_ns, 4_000_000);
|
||||
@@ -355,19 +368,6 @@ fn late_reply_can_complete_ipdv_pair() {
|
||||
assert_eq!(snapshot.ipdv.round_trip.total_ns, 10_000_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_exposes_directional_ipdv_when_available() {
|
||||
let mut collector = StatsCollector::new(StatsConfig::finite());
|
||||
collector.process(&unadjusted_reply(0, 10));
|
||||
let update = collector.process(&unadjusted_reply(1, 13));
|
||||
|
||||
assert!(update.contributed_sample);
|
||||
|
||||
let pair = assert_one_ipdv_pair(&update, 0, 1, Duration::from_millis(3));
|
||||
assert!(pair.send_ipdv.is_some());
|
||||
assert!(pair.receive_ipdv.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gap_fill_update_exposes_both_completed_ipdv_pairs() {
|
||||
let mut collector = StatsCollector::new(StatsConfig::finite());
|
||||
|
||||
Reference in new issue
Block a user