test: trim redundant coverage

This commit was merged in pull request #58.
This commit is contained in:
Nils Andreas Svee committed 2026-08-10 21:06:49 +07:00
1 parent 186c6eba97
commit f0f9277469
7 files changed
+59 -462

No files matched your search

@@ -206,42 +206,6 @@ fn single_target_ten_millisecond_cadence_uses_managed_deadlines() {
.any(|line| line.starts_with("session_closed,"))); .any(|line| line.starts_with("session_closed,")));
} }
#[test]
fn finite_single_target_peer_close_exits_successfully() {
let server = start_peer_close_server(test_params(
Some(Duration::from_millis(40)),
Duration::from_millis(10),
));
let mut command = Command::new(env!("CARGO_BIN_EXE_irtt-cli"));
command.args([
"--duration",
"40ms",
"--interval",
"10ms",
"--format",
"csv",
"--columns",
"seq",
"--header",
"never",
&server.addr.to_string(),
]);
let output = run_with_timeout(command, Duration::from_secs(3));
server.join();
assert!(
output.status.success(),
"status={:?}\nstderr={}",
output.status.code(),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(!stdout.trim().is_empty(), "{stdout}");
assert!(!stderr.contains("peer closure"), "{stderr}");
}
#[test] #[test]
fn continuous_single_target_peer_close_exits_nonzero() { fn continuous_single_target_peer_close_exits_nonzero() {
let server = start_peer_close_server(test_params(None, Duration::from_millis(10))); let server = start_peer_close_server(test_params(None, Duration::from_millis(10)));
+27 -170
View File
@@ -802,7 +802,7 @@ fn terminal_targets_count_toward_retained_generation_limit() {
} }
#[test] #[test]
fn pruning_preserves_target_cursor_progress() { fn pruning_leaves_no_stale_target_work() {
let (mut task, _) = ManagedClient::task( let (mut task, _) = ManagedClient::task(
config(ManagedPacing::Staggered), config(ManagedPacing::Staggered),
vec![ vec![
@@ -812,29 +812,39 @@ fn pruning_preserves_target_cursor_progress() {
], ],
) )
.unwrap(); .unwrap();
task.cursor = 5; task.cursor = 2;
task.timeout_cursor = 5; task.timeout_cursor = 2;
task.send_cursor = 5; task.send_cursor = 2;
task.scan_remaining = 3; task.scan_remaining = 3;
task.burst_remaining = 3; task.burst_remaining = 3;
task.stagger_remaining = 3; task.stagger_remaining = 3;
task.targets.truncate(2);
task.rebase_target_cursors();
assert_eq!(task.cursor, 1);
assert_eq!(task.timeout_cursor, 1);
assert_eq!(task.send_cursor, 1);
assert_eq!(task.scan_remaining, 2);
assert_eq!(task.burst_remaining, 2);
assert_eq!(task.stagger_remaining, 2);
task.targets.clear(); task.targets[2].desired = false;
task.rebase_target_cursors(); task.targets[2].state = TargetState::Terminal;
assert_eq!(task.cursor, 0); task.prune_undesired_terminal();
assert_eq!(task.timeout_cursor, 0);
assert_eq!(task.send_cursor, 0); // Timeout discovery indexes `targets` with `timeout_cursor` directly, so a
// prune must leave it safe to use; the target and send passes modulo their
// own cursors. Pending per-pass work must not outlive the targets it was
// scheduled for either.
assert_eq!(task.targets.len(), 2);
assert!(task.timeout_cursor < task.targets.len());
assert!(task.scan_remaining <= task.targets.len());
assert!(task.burst_remaining <= task.targets.len());
assert!(task.stagger_remaining <= task.targets.len());
assert!(!task.poll_timeout_pass(Instant::now()));
for target in &mut task.targets {
target.desired = false;
target.state = TargetState::Terminal;
}
task.prune_undesired_terminal();
assert!(task.targets.is_empty());
assert_eq!(task.scan_remaining, 0); assert_eq!(task.scan_remaining, 0);
assert_eq!(task.burst_remaining, 0); assert_eq!(task.burst_remaining, 0);
assert_eq!(task.stagger_remaining, 0); assert_eq!(task.stagger_remaining, 0);
assert!(!task.poll_timeout_pass(Instant::now()));
} }
#[test] #[test]
@@ -1761,91 +1771,6 @@ fn timeout_discovery_inspects_at_most_one_budget_of_targets() {
assert!(!task.poll_timeout_pass(Instant::now())); assert!(!task.poll_timeout_pass(Instant::now()));
assert_eq!(task.timeout_inspections, TIMEOUT_WORK_BUDGET); assert_eq!(task.timeout_inspections, TIMEOUT_WORK_BUDGET);
assert_eq!(task.timeout_cursor, TIMEOUT_WORK_BUDGET);
}
#[test]
fn idle_timeout_discovery_inspects_one_target_once() {
let (mut task, _) = ManagedClient::task(
config(ManagedPacing::Burst),
vec![ManagedTargetConfig::new("idle", "127.0.0.1:9")],
)
.unwrap();
task.timeout_inspections = 0;
assert!(!task.poll_timeout_pass(Instant::now()));
assert_eq!(task.timeout_inspections, 1);
assert_eq!(task.timeout_cursor, 0);
}
#[test]
fn idle_timeout_discovery_sweeps_small_target_set_once() {
let target_count = 8;
let mut managed = config(ManagedPacing::Burst);
managed.max_live_target_generations = target_count;
let targets = (0..target_count)
.map(|index| ManagedTargetConfig::new(format!("idle-{index}"), "127.0.0.1:9"))
.collect();
let (mut task, _) = ManagedClient::task(managed, targets).unwrap();
task.timeout_inspections = 0;
assert!(!task.poll_timeout_pass(Instant::now()));
assert_eq!(task.timeout_inspections, target_count);
assert_eq!(task.timeout_cursor, 0);
}
#[test]
fn sparse_timeout_backlog_progresses_within_one_poll() {
let server = start_server(ServerBehavior::Echo, None);
let target_count = TIMEOUT_WORK_BUDGET + 1;
let late_index = target_count - 1;
let mut managed = config(ManagedPacing::Burst);
managed.completion = ManagedCompletionPolicy::ExplicitStop;
managed.client.duration = None;
managed.max_live_target_generations = target_count;
let targets = (0..target_count)
.map(|index| {
if index == late_index {
target("late", server.addr)
} else {
ManagedTargetConfig::new(format!("target-{index}"), "127.0.0.1:9")
}
})
.collect();
let (mut task, _) = ManagedClient::task(managed, targets).unwrap();
for target in &mut task.targets[..late_index] {
target.state = TargetState::Terminal;
}
let observations = Arc::new(Mutex::new(Vec::new()));
task.event_observations = Some(Arc::clone(&observations));
let runtime = runtime();
open_target_for_timeout_test(&runtime, &mut task, late_index);
let now = Instant::now();
for seq in 0..u32::try_from(TIMEOUT_WORK_BUDGET + 64).unwrap() {
replace_pending_for_timeout_test(
&mut task,
late_index,
timeout_probe(
seq,
now - Duration::from_secs(2),
now - Duration::from_secs(1),
),
);
}
assert!(!task.poll_timeout_pass(now));
assert!(task.poll_timeout_pass(now));
assert_eq!(
timeout_losses_for(&observations, "late"),
TIMEOUT_WORK_BUDGET
);
assert_eq!(task.timeout_backlog.len(), 1);
close_timeout_test_target(&runtime, &mut task, late_index);
drop(task);
server.finish();
} }
#[test] #[test]
@@ -1889,7 +1814,6 @@ fn sparse_timeout_backlog_persists_across_polls() {
TIMEOUT_WORK_BUDGET TIMEOUT_WORK_BUDGET
); );
assert_eq!(task.timeout_backlog.len(), 1); assert_eq!(task.timeout_backlog.len(), 1);
assert_eq!(task.timeout_cursor, TIMEOUT_WORK_BUDGET);
assert!(task.poll_timeout_pass(now)); assert!(task.poll_timeout_pass(now));
assert_eq!( assert_eq!(
@@ -1897,75 +1821,11 @@ fn sparse_timeout_backlog_persists_across_polls() {
2 * TIMEOUT_WORK_BUDGET 2 * TIMEOUT_WORK_BUDGET
); );
assert_eq!(task.timeout_backlog.len(), 1); assert_eq!(task.timeout_backlog.len(), 1);
assert_eq!(task.timeout_cursor, 2 * TIMEOUT_WORK_BUDGET);
close_timeout_test_target(&runtime, &mut task, 0); close_timeout_test_target(&runtime, &mut task, 0);
drop(task); drop(task);
server.finish(); server.finish();
} }
#[test]
fn timeout_backlog_round_robins_known_sparse_targets() {
let first = start_server(ServerBehavior::Echo, None);
let second = start_server(ServerBehavior::Echo, None);
let target_count = TIMEOUT_WORK_BUDGET + 2;
let mut managed = config(ManagedPacing::Burst);
managed.completion = ManagedCompletionPolicy::ExplicitStop;
managed.client.duration = None;
managed.max_live_target_generations = target_count;
let targets = vec![target("first", first.addr), target("second", second.addr)]
.into_iter()
.chain(
(2..target_count)
.map(|index| ManagedTargetConfig::new(format!("target-{index}"), "127.0.0.1:9")),
)
.collect();
let (mut task, _) = ManagedClient::task(managed, targets).unwrap();
for target in &mut task.targets[2..] {
target.state = TargetState::Terminal;
}
let observations = Arc::new(Mutex::new(Vec::new()));
task.event_observations = Some(Arc::clone(&observations));
let runtime = runtime();
open_target_for_timeout_test(&runtime, &mut task, 0);
open_target_for_timeout_test(&runtime, &mut task, 1);
let now = Instant::now();
for index in 0..2 {
for seq in 0..u32::try_from(TIMEOUT_WORK_BUDGET + 1).unwrap() {
replace_pending_for_timeout_test(
&mut task,
index,
timeout_probe(
seq,
now - Duration::from_secs(2),
now - Duration::from_secs(1),
),
);
}
task.timeout_backlog.push_back(TimeoutBacklogEntry {
index,
generation: task.targets[index].instance.generation,
});
}
task.timeout_cursor = 2;
assert!(task.poll_timeout_pass(now));
assert_eq!(
timeout_losses_for(&observations, "first"),
TIMEOUT_WORK_BUDGET / 2
);
assert_eq!(
timeout_losses_for(&observations, "second"),
TIMEOUT_WORK_BUDGET / 2
);
assert_eq!(task.timeout_backlog.len(), 2);
close_timeout_test_target(&runtime, &mut task, 0);
close_timeout_test_target(&runtime, &mut task, 1);
drop(task);
first.finish();
second.finish();
}
#[test] #[test]
fn timeout_backlog_does_not_starve_discovery() { fn timeout_backlog_does_not_starve_discovery() {
let first = start_server(ServerBehavior::Echo, None); let first = start_server(ServerBehavior::Echo, None);
@@ -2021,11 +1881,9 @@ fn timeout_backlog_does_not_starve_discovery() {
assert!(task.poll_timeout_pass(now)); assert!(task.poll_timeout_pass(now));
assert_eq!(timeout_losses_for(&observations, "late"), 0); assert_eq!(timeout_losses_for(&observations, "late"), 0);
assert_eq!(task.timeout_cursor, TIMEOUT_WORK_BUDGET + 1);
assert!(task.poll_timeout_pass(now)); assert!(task.poll_timeout_pass(now));
assert_eq!(timeout_losses_for(&observations, "late"), 1); assert_eq!(timeout_losses_for(&observations, "late"), 1);
assert_eq!(task.timeout_cursor, 0);
assert_eq!(task.timeout_backlog.len(), 1); assert_eq!(task.timeout_backlog.len(), 1);
close_timeout_test_target(&runtime, &mut task, 0); close_timeout_test_target(&runtime, &mut task, 0);
close_timeout_test_target(&runtime, &mut task, late_index); close_timeout_test_target(&runtime, &mut task, late_index);
@@ -2166,7 +2024,6 @@ fn timeout_discovery_reaches_later_target_after_budget_slice() {
task.timeout_inspections = 0; task.timeout_inspections = 0;
assert!(!task.poll_timeout_pass(now)); assert!(!task.poll_timeout_pass(now));
assert_eq!(task.timeout_inspections, TIMEOUT_WORK_BUDGET); assert_eq!(task.timeout_inspections, TIMEOUT_WORK_BUDGET);
assert_eq!(task.timeout_cursor, late_index);
assert_eq!(timeout_losses_for(&observations, "late"), 0); assert_eq!(timeout_losses_for(&observations, "late"), 0);
task.timeout_inspections = 0; task.timeout_inspections = 0;
+13 -97
View File
@@ -1060,7 +1060,6 @@ impl SessionMachine {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::client::{schedule::ProbeSchedule, validate_datagram_length};
fn open_machine(max_pending_probes: usize, probe_timeout: Duration) -> SessionMachine { fn open_machine(max_pending_probes: usize, probe_timeout: Duration) -> SessionMachine {
let config = ClientConfig { let config = ClientConfig {
@@ -1584,25 +1583,22 @@ mod tests {
} }
#[test] #[test]
fn dropping_prepared_probe_changes_nothing() { fn uncommitted_probe_preparation_changes_no_authoritative_state() {
let machine = open_machine(4, Duration::from_secs(1));
let prepared = machine.prepare_probe().unwrap().unwrap();
assert_eq!(prepared.seq, 0);
drop(prepared);
let session = active(&machine);
assert_eq!(session.next_wire_seq, 0);
assert_eq!(session.packets_sent, 0);
assert_eq!(session.pending.len(), 0);
}
#[test]
fn repeated_uncommitted_preflight_changes_no_logical_state() {
let mut machine = open_machine(4, Duration::from_secs(1)); let mut machine = open_machine(4, Duration::from_secs(1));
let prepared = machine.prepare_probe().unwrap().unwrap(); let prepared = machine.prepare_probe().unwrap().unwrap();
assert_eq!(prepared.seq, 0);
let _first_preflight = machine.preflight_probe_commit(&prepared).unwrap(); // Repeating preflight, and finalizing a commit that is never applied,
let _second_preflight = machine.preflight_probe_commit(&prepared).unwrap(); // must both leave the session exactly as it was.
{
let _discarded_preflight = machine.preflight_probe_commit(&prepared).unwrap();
}
{
let preflight = machine.preflight_probe_commit(&prepared).unwrap();
let _discarded_commit = machine
.finalize_probe_commit(preflight, timestamp(Instant::now()))
.unwrap();
}
let session = active(&machine); let session = active(&machine);
assert_eq!(session.next_wire_seq, 0); assert_eq!(session.next_wire_seq, 0);
@@ -1636,66 +1632,6 @@ mod tests {
assert_eq!(session.pending.capacity(), capacity); assert_eq!(session.pending.capacity(), capacity);
} }
#[test]
fn simulated_send_error_leaves_machine_and_schedule_unchanged() {
let mut machine = open_machine(4, Duration::from_secs(1));
let start = Instant::now();
let mut schedule = ProbeSchedule::new(start, &active(&machine).negotiated).unwrap();
assert!(schedule.permit_probe_at(start));
let prepared = machine.prepare_probe().unwrap().unwrap();
let sent_at = timestamp(start);
let machine_preflight = machine.preflight_probe_commit(&prepared).unwrap();
let schedule_commit = schedule
.preflight_caller_commit(machine_preflight.next_packets_sent)
.unwrap();
let machine_commit = machine
.finalize_probe_commit(machine_preflight, sent_at)
.unwrap();
let _uncommitted_machine = machine_commit;
let _uncommitted_schedule = schedule_commit;
let session = active(&machine);
assert_eq!(session.next_wire_seq, 0);
assert_eq!(session.packets_sent, 0);
assert_eq!(session.pending.len(), 0);
assert_eq!(schedule.next_send_deadline(), Some(start));
}
#[test]
fn short_success_commits_before_reporting_transport_invariant() {
let mut machine = open_machine(4, Duration::from_secs(1));
let start = Instant::now();
let mut schedule = ProbeSchedule::new(start, &active(&machine).negotiated).unwrap();
assert!(schedule.permit_probe_at(start));
let prepared = machine.prepare_probe().unwrap().unwrap();
let machine_preflight = machine.preflight_probe_commit(&prepared).unwrap();
let machine_commit = machine
.finalize_probe_commit(machine_preflight, timestamp(start))
.unwrap();
let schedule_commit = schedule.preflight_managed_commit(start, start).unwrap();
let expected = prepared.bytes.len();
let actual = expected - 1;
machine.commit_probe_sent(machine_commit, actual);
schedule.commit(schedule_commit);
let result = validate_datagram_length(expected, actual);
assert!(matches!(
result,
Err(ClientError::DatagramLengthMismatch { expected, actual })
if actual + 1 == expected
));
let session = active(&machine);
assert_eq!(session.next_wire_seq, 1);
assert_eq!(session.packets_sent, 1);
assert_eq!(session.pending.len(), 1);
assert_eq!(
schedule.next_send_deadline(),
Some(start + machine.config.interval)
);
}
#[test] #[test]
fn repeated_would_block_style_preflight_commits_once() { fn repeated_would_block_style_preflight_commits_once() {
let mut machine = open_machine(4, Duration::from_secs(1)); let mut machine = open_machine(4, Duration::from_secs(1));
@@ -1737,26 +1673,6 @@ mod tests {
assert_eq!(sent.bytes, prepared.bytes.len()); assert_eq!(sent.bytes, prepared.bytes.len());
} }
#[test]
fn dropping_preflight_or_finalized_commit_changes_no_authoritative_state() {
let mut machine = open_machine(4, Duration::from_secs(1));
let prepared = machine.prepare_probe().unwrap().unwrap();
{
let _preflight = machine.preflight_probe_commit(&prepared).unwrap();
}
{
let preflight = machine.preflight_probe_commit(&prepared).unwrap();
let _commit = machine
.finalize_probe_commit(preflight, timestamp(Instant::now()))
.unwrap();
}
let session = active(&machine);
assert_eq!(session.next_wire_seq, 0);
assert_eq!(session.packets_sent, 0);
assert_eq!(session.pending.len(), 0);
}
#[test] #[test]
fn commit_uses_reserved_capacity_and_prevalidated_counter() { fn commit_uses_reserved_capacity_and_prevalidated_counter() {
let mut machine = open_machine(2, Duration::from_secs(1)); let mut machine = open_machine(2, Duration::from_secs(1));
+1 -40
View File
@@ -39,37 +39,6 @@ fn assert_reply_bytes(event: &ClientEvent, expected: usize) {
} }
} }
// ─── Config/client validation ───
#[test]
fn length_zero_baseline() {
let params = params_with_length(0);
let expected = test_echo_packet_len(false, &params);
let header = echo_header_len(false, &params);
assert_eq!(expected, header);
}
#[test]
fn length_one_accepted() {
let params = params_with_length(1);
let expected = test_echo_packet_len(false, &params);
assert!(expected >= 1);
}
#[test]
fn length_1472_accepted() {
let params = params_with_length(1472);
let expected = test_echo_packet_len(false, &params);
assert_eq!(expected, 1472);
}
#[test]
fn length_4096_accepted() {
let params = params_with_length(4096);
let expected = test_echo_packet_len(false, &params);
assert_eq!(expected, 4096);
}
// ─── Echo request size: FakeServer observes exact packet size ─── // ─── Echo request size: FakeServer observes exact packet size ───
#[test] #[test]
@@ -98,21 +67,13 @@ fn echo_request_size_matches_echo_packet_len_above_header() {
// ─── Echo reply handling ─── // ─── Echo reply handling ───
#[test]
fn echo_reply_decoded_for_large_length() {
let params = params_with_length(4096);
let run = run_one_probe(params.clone(), TimestampFields::default());
let expected = test_echo_packet_len(false, &params);
assert_reply(&run.reply);
assert_reply_bytes(&run.reply, expected);
}
#[test] #[test]
fn echo_reply_bytes_consistent_with_echo_packet_len_for_various_lengths() { fn echo_reply_bytes_consistent_with_echo_packet_len_for_various_lengths() {
for length in [0, 1472, 4096] { for length in [0, 1472, 4096] {
let params = params_with_length(length); let params = params_with_length(length);
let run = run_one_probe(params.clone(), TimestampFields::default()); let run = run_one_probe(params.clone(), TimestampFields::default());
let expected = test_echo_packet_len(false, &params); let expected = test_echo_packet_len(false, &params);
assert_reply(&run.reply);
assert_reply_bytes(&run.reply, expected); assert_reply_bytes(&run.reply, expected);
} }
} }
@@ -369,16 +369,3 @@ fn assert_one_way_presence(
assert_eq!(sample.client_to_server.is_some(), client_to_server); assert_eq!(sample.client_to_server.is_some(), client_to_server);
assert_eq!(sample.server_to_client.is_some(), server_to_client); assert_eq!(sample.server_to_client.is_some(), server_to_client);
} }
#[test]
fn dscp_configured_open_close_smoke() {
let mut params = default_params();
params.dscp = 46;
let server = start_open_server(params.clone(), None);
let mut client = Client::connect(config_for_params(server.addr, &params)).unwrap();
let outcome = client.open().unwrap();
assert!(matches!(outcome, irtt_client::OpenOutcome::Started { .. }));
client.close().unwrap();
server.join();
}
@@ -22,27 +22,6 @@ fn backend_basic_open_echo_close() {
client.close().unwrap(); client.close().unwrap();
} }
#[test]
fn backend_ttl_smoke() {
let params = default_params();
let peer = BackendPeer::start_open_echo(params.clone(), None);
let mut config = config_for_params(peer.addr(), &params);
config.socket_config.ttl = Some(64);
let mut client = Client::connect(config).unwrap();
let outcome = client.open().unwrap();
assert!(matches!(outcome, irtt_client::OpenOutcome::Started { .. }));
let sent = client.send_probe().unwrap();
assert_eq!(sent.len(), 1);
let events = client.recv_once().unwrap();
assert_eq!(events.len(), 1);
assert!(matches!(events[0], ClientEvent::EchoReply { .. }));
client.close().unwrap();
}
#[test] #[test]
fn backend_received_stats_smoke() { fn backend_received_stats_smoke() {
for mode in [ for mode in [
+18 -85
View File
@@ -185,81 +185,6 @@ mod tests {
} }
} }
#[test]
fn ipdv_tracker_completes_adjacent_pair() {
let mut tracker = IpdvTracker::new(None);
assert!(tracker.insert(ipdv_sample(0, 10)).is_empty());
let pairs = tracker.insert(ipdv_sample(1, 14));
assert_eq!(
pairs,
vec![CompletedIpdvPair {
previous_seq: 0,
current_seq: 1,
rtt_ipdv_ns: 4,
send_ipdv_ns: None,
receive_ipdv_ns: None,
}]
);
}
#[test]
fn ipdv_tracker_gap_fill_completes_both_adjacent_pairs() {
let mut tracker = IpdvTracker::new(None);
assert!(tracker.insert(ipdv_sample(0, 10)).is_empty());
assert!(tracker.insert(ipdv_sample(2, 20)).is_empty());
let pairs = tracker.insert(ipdv_sample(1, 13));
assert_eq!(
pairs,
vec![
CompletedIpdvPair {
previous_seq: 0,
current_seq: 1,
rtt_ipdv_ns: 3,
send_ipdv_ns: None,
receive_ipdv_ns: None,
},
CompletedIpdvPair {
previous_seq: 1,
current_seq: 2,
rtt_ipdv_ns: 7,
send_ipdv_ns: None,
receive_ipdv_ns: None,
},
]
);
}
#[test]
fn ipdv_tracker_completes_wrapped_adjacent_pair() {
let mut tracker = IpdvTracker::new(None);
assert!(tracker.insert(ipdv_sample(u32::MAX, 10)).is_empty());
let pairs = tracker.insert(ipdv_sample(0, 14));
assert_eq!(
pairs,
vec![CompletedIpdvPair {
previous_seq: u32::MAX,
current_seq: 0,
rtt_ipdv_ns: 4,
send_ipdv_ns: None,
receive_ipdv_ns: None,
}]
);
}
#[test]
fn ipdv_tracker_wrap_gap_does_not_complete_pair() {
let mut tracker = IpdvTracker::new(None);
assert!(tracker.insert(ipdv_sample(u32::MAX - 1, 10)).is_empty());
assert!(tracker.insert(ipdv_sample(0, 14)).is_empty());
}
#[test] #[test]
fn ipdv_tracker_late_wrapped_previous_completes_pair() { fn ipdv_tracker_late_wrapped_previous_completes_pair() {
let mut tracker = IpdvTracker::new(None); let mut tracker = IpdvTracker::new(None);
@@ -289,17 +214,25 @@ mod tests {
} }
#[test] #[test]
fn ipdv_tracker_bounded_mode_evicts_old_sequence_state() { fn ipdv_tracker_bounded_mode_limits_sequence_state() {
let mut tracker = IpdvTracker::new(Some(2)); let limit = 4;
assert!(tracker.insert(ipdv_sample(0, 10)).is_empty()); let mut tracker = IpdvTracker::new(Some(limit));
assert_eq!(tracker.insert(ipdv_sample(1, 14)).len(), 1);
let pairs = tracker.insert(ipdv_sample(2, 20)); // Far more inserts than the limit, so eviction runs repeatedly. Every
// per-sequence collection must stay bounded by the limit rather than
// growing with the number of samples seen, and eviction must not stop
// adjacent sequences from still completing a pair.
for seq in 0..512_u32 {
let pairs = tracker.insert(ipdv_sample(seq, i128::from(seq)));
assert_eq!(pairs.len(), 1); assert_eq!(pairs.len(), usize::from(seq > 0), "seq {seq}");
assert!(!tracker.samples.contains_key(&0)); assert!(tracker.samples.len() <= limit, "seq {seq}");
assert!(!tracker.completed_pairs.contains(&0)); assert_eq!(
assert!(!tracker.completed_pairs.contains(&1)); tracker.sample_order.len(),
assert!(tracker.completed_pairs.contains(&2)); tracker.samples.len(),
"seq {seq}"
);
assert!(tracker.completed_pairs.len() <= limit, "seq {seq}");
}
} }
} }