tests: clean up more tests across the project
Rust CI / cargo fmt (push) Successful in 17s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 33s
Rust CI / cargo clippy (push) Successful in 34s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Failing after 33s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 9s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 8s
Rust CI / cargo doc (push) Successful in 22s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 27s
Supply chain / cargo-deny advisories (push) Successful in 7s
Supply chain / cargo-deny bans licenses sources (push) Successful in 6s
Supply chain / actionlint (push) Successful in 3s
Netem Interop / netem interop (push) Successful in 1m3s
Rust CI / cargo fmt (push) Successful in 17s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 33s
Rust CI / cargo clippy (push) Successful in 34s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Failing after 33s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 9s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 8s
Rust CI / cargo doc (push) Successful in 22s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 27s
Supply chain / cargo-deny advisories (push) Successful in 7s
Supply chain / cargo-deny bans licenses sources (push) Successful in 6s
Supply chain / actionlint (push) Successful in 3s
Netem Interop / netem interop (push) Successful in 1m3s
This commit is contained in:
17 files changed
+28
-1435
No files matched your search
@@ -204,15 +204,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ipdv_tracker_duplicate_sequence_does_not_emit_pair_again() {
|
||||
let mut tracker = IpdvTracker::new(None);
|
||||
assert!(tracker.insert(ipdv_sample(0, 10)).is_empty());
|
||||
assert_eq!(tracker.insert(ipdv_sample(1, 14)).len(), 1);
|
||||
|
||||
assert!(tracker.insert(ipdv_sample(1, 18)).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ipdv_tracker_bounded_mode_limits_sequence_state() {
|
||||
let limit = 4;
|
||||
|
||||
@@ -91,21 +91,3 @@ pub(crate) fn loss_stats(packets: PacketCounts) -> LossStats {
|
||||
fn percent(numerator: f64, denominator: f64) -> f64 {
|
||||
100.0 * numerator / denominator
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn duplicate_and_late_percentages_use_packets_received_denominator() {
|
||||
let loss = loss_stats(PacketCounts {
|
||||
packets_received: 4,
|
||||
duplicates: 1,
|
||||
late_packets: 2,
|
||||
..PacketCounts::default()
|
||||
});
|
||||
|
||||
assert_eq!(loss.duplicate_percent, 25.0);
|
||||
assert_eq!(loss.late_packets_percent, 50.0);
|
||||
}
|
||||
}
|
||||
@@ -34,11 +34,6 @@ const HASH_ENTRY_OVERHEAD: u64 = 1;
|
||||
/// Adjusted RTT, one-way delay, send/receive IPDV, and server processing only
|
||||
/// receive a sample when the negotiated session supplies the corresponding
|
||||
/// optional measurement, so counting all eleven is the upper bound.
|
||||
///
|
||||
/// This count must track the metrics `CoreStats::new` builds with exact
|
||||
/// retention. `exact_mode_retains_one_sample_per_counted_metric` below counts
|
||||
/// the metrics a fully measured probe actually retains, so the two cannot
|
||||
/// drift apart unnoticed.
|
||||
pub(crate) const EXACT_SAMPLES_PER_PROBE: u64 = 11;
|
||||
|
||||
/// Normalized events a probe usually contributes to a rolling window: one send
|
||||
@@ -104,150 +99,3 @@ fn rolling_count_bytes(config: &StatsConfig, probe_count: u64) -> u64 {
|
||||
fn rolling_bytes_per_event() -> u64 {
|
||||
(size_of::<StatsEvent>() + size_of::<ReplySample>()) as u64
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::{Duration, Instant, UNIX_EPOCH};
|
||||
|
||||
use irtt_client::{
|
||||
ClientEvent, ClientTimestamp, OneWayDelaySample, PacketMeta, ReceivedStatsSample,
|
||||
RttSample, ServerTiming, SignedDuration,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use crate::{Snapshot, StatsCollector, TimeStats};
|
||||
|
||||
/// Every public timing metric a snapshot exposes.
|
||||
fn timing_metrics(snapshot: &Snapshot) -> Vec<&TimeStats> {
|
||||
vec![
|
||||
&snapshot.send_call,
|
||||
&snapshot.timer_error,
|
||||
&snapshot.rtt.primary,
|
||||
&snapshot.rtt.raw,
|
||||
&snapshot.rtt.adjusted,
|
||||
&snapshot.ipdv.round_trip,
|
||||
&snapshot.ipdv.send,
|
||||
&snapshot.ipdv.receive,
|
||||
&snapshot.one_way_delay.send_delay,
|
||||
&snapshot.one_way_delay.receive_delay,
|
||||
&snapshot.server_processing.processing,
|
||||
]
|
||||
}
|
||||
|
||||
fn timestamp(offset_ms: u64) -> ClientTimestamp {
|
||||
ClientTimestamp {
|
||||
mono: Instant::now() + Duration::from_millis(offset_ms),
|
||||
wall: UNIX_EPOCH + Duration::from_millis(offset_ms),
|
||||
}
|
||||
}
|
||||
|
||||
fn sent(seq: u32) -> ClientEvent {
|
||||
let sent_at = timestamp(u64::from(seq) * 10);
|
||||
ClientEvent::EchoSent {
|
||||
seq,
|
||||
remote: "127.0.0.1:2112".parse().unwrap(),
|
||||
scheduled_at: Some(sent_at.mono),
|
||||
sent_at,
|
||||
bytes: 32,
|
||||
send_call: Duration::from_micros(10 + u64::from(seq)),
|
||||
timer_error: Some(Duration::from_micros(2 + u64::from(seq))),
|
||||
}
|
||||
}
|
||||
|
||||
/// A reply carrying every optional measurement, so one probe exercises the
|
||||
/// upper bound the estimate assumes.
|
||||
fn fully_measured_reply(seq: u32) -> ClientEvent {
|
||||
let sent_at = timestamp(u64::from(seq) * 10);
|
||||
let raw = Duration::from_millis(10 + u64::from(seq));
|
||||
let received_at = ClientTimestamp {
|
||||
mono: sent_at.mono + raw,
|
||||
wall: sent_at.wall + raw,
|
||||
};
|
||||
let base_ns = i64::from(seq) * 10_000_000;
|
||||
ClientEvent::EchoReply {
|
||||
seq,
|
||||
remote: "127.0.0.1:2112".parse().unwrap(),
|
||||
sent_at,
|
||||
received_at,
|
||||
rtt: RttSample {
|
||||
raw,
|
||||
adjusted: Some(SignedDuration::from_duration(raw)),
|
||||
effective: SignedDuration::from_duration(raw),
|
||||
},
|
||||
server_timing: Some(ServerTiming {
|
||||
receive_mono_ns: Some(base_ns + 1_000_000),
|
||||
send_mono_ns: Some(base_ns + 2_000_000),
|
||||
receive_wall_ns: Some(base_ns + 1_000_000),
|
||||
send_wall_ns: Some(base_ns + 2_000_000),
|
||||
midpoint_mono_ns: None,
|
||||
midpoint_wall_ns: None,
|
||||
processing: Some(Duration::from_millis(1 + u64::from(seq))),
|
||||
}),
|
||||
one_way: Some(OneWayDelaySample {
|
||||
client_to_server: Some(SignedDuration::from_nanos(1_000_000 + i128::from(seq))),
|
||||
server_to_client: Some(SignedDuration::from_nanos(2_000_000 + i128::from(seq))),
|
||||
}),
|
||||
received_stats: Some(ReceivedStatsSample {
|
||||
count: Some(seq + 1),
|
||||
window: Some(0xff),
|
||||
}),
|
||||
bytes: 64,
|
||||
packet_meta: PacketMeta::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Two probes fill every metric, including the IPDV pair metrics that need
|
||||
/// adjacent replies.
|
||||
fn fully_measured_snapshot(config: StatsConfig) -> Snapshot {
|
||||
let mut collector = StatsCollector::new(config);
|
||||
for seq in 0..2 {
|
||||
collector.process(&sent(seq));
|
||||
collector.process(&fully_measured_reply(seq));
|
||||
}
|
||||
collector.snapshot()
|
||||
}
|
||||
|
||||
/// The estimate is only honest while it counts the metrics that actually
|
||||
/// retain samples. Adding or removing an exact-retaining public timing
|
||||
/// metric without revisiting [`EXACT_SAMPLES_PER_PROBE`] fails here.
|
||||
#[test]
|
||||
fn exact_mode_retains_one_sample_per_counted_metric() {
|
||||
let snapshot = fully_measured_snapshot(StatsConfig::finite());
|
||||
let with_medians = timing_metrics(&snapshot)
|
||||
.into_iter()
|
||||
.filter(|stats| stats.median_ns.is_some())
|
||||
.count() as u64;
|
||||
|
||||
assert_eq!(
|
||||
with_medians, EXACT_SAMPLES_PER_PROBE,
|
||||
"the estimate counts {EXACT_SAMPLES_PER_PROBE} exact sample streams per probe, \
|
||||
but {with_medians} public timing metrics retained samples"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn running_only_retains_no_exact_samples() {
|
||||
let snapshot = fully_measured_snapshot(StatsConfig::continuous());
|
||||
|
||||
for stats in timing_metrics(&snapshot) {
|
||||
assert!(stats.count > 0, "the fixture should populate every metric");
|
||||
assert_eq!(
|
||||
stats.median_ns, None,
|
||||
"running-only mode retains no exact samples"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn per_sample_components_are_non_zero() {
|
||||
assert!(ipdv_tracker_bytes_per_sample() > 0);
|
||||
assert!(rolling_bytes_per_event() > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn running_only_stops_growing_at_the_bounded_sequence_limit() {
|
||||
let limit = CONTINUOUS_SEQUENCE_LIMIT as u64;
|
||||
assert_eq!(running_only_bytes(limit), running_only_bytes(limit * 100));
|
||||
assert!(running_only_bytes(limit - 1) < running_only_bytes(limit));
|
||||
}
|
||||
}
|
||||
@@ -27,33 +27,6 @@ fn exact_mode_grows_with_probe_count() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exact_mode_scales_linearly_with_probe_count() {
|
||||
let config = StatsConfig::finite();
|
||||
let single = config.estimated_retained_bytes(1);
|
||||
|
||||
assert_eq!(config.estimated_retained_bytes(1_000), single * 1_000);
|
||||
}
|
||||
|
||||
/// Exact mode retains eleven `i128` timing streams per probe alongside its
|
||||
/// IPDV tracker state, so the timing samples alone are at least that much.
|
||||
///
|
||||
/// This checks the estimate against the storage it claims to model. It is an
|
||||
/// implementation test, not a stable public constant: the figure is free to
|
||||
/// move whenever the retention model does.
|
||||
#[test]
|
||||
fn exact_estimate_covers_every_retained_timing_sample() {
|
||||
const EXACT_TIMING_STREAMS: u64 = 11;
|
||||
let per_probe = StatsConfig::finite().estimated_retained_bytes(1);
|
||||
let timing_sample_bytes = EXACT_TIMING_STREAMS * std::mem::size_of::<i128>() as u64;
|
||||
|
||||
assert!(
|
||||
per_probe > timing_sample_bytes,
|
||||
"the estimate should cover {EXACT_TIMING_STREAMS} retained timing samples plus \
|
||||
IPDV tracker state, but a probe estimated only {per_probe} bytes"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enormous_probe_counts_saturate_instead_of_wrapping() {
|
||||
for config in [StatsConfig::finite(), StatsConfig::continuous()] {
|
||||
@@ -90,27 +63,6 @@ fn running_only_does_not_report_unbounded_per_probe_retention() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finite_config_estimate_is_deterministic_and_usable_for_planning() {
|
||||
let config = StatsConfig::finite();
|
||||
let probes = 1_000_000;
|
||||
let first = config.estimated_retained_bytes(probes);
|
||||
|
||||
assert_eq!(first, config.estimated_retained_bytes(probes));
|
||||
|
||||
// A million probes should land in a range a caller can reason about: well
|
||||
// over a megabyte, and well under a terabyte. The exact figure is free to
|
||||
// move with the retention model.
|
||||
assert!(
|
||||
first > 1024 * 1024,
|
||||
"a million probes should estimate more than a MiB, got {first}"
|
||||
);
|
||||
assert!(
|
||||
first < 1024 * 1024 * 1024 * 1024,
|
||||
"a million probes should estimate less than a TiB, got {first}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rolling_count_adds_bounded_retention() {
|
||||
let probes = 1_000_000;
|
||||
|
||||
Reference in new issue
Block a user