test(client): avoid scheduler-dependent timing assertions

This commit is contained in:
Nils Andreas Svee committed 2026-10-02 23:16:48 +07:00
1 parent bfaa0a436f
commit d571315068
2 files changed
+57 -38

No files matched your search

+5 -2
View File
@@ -108,8 +108,11 @@ fn assert_kernel_send_endpoint(event: &ClientEvent, before_send: SystemTime) {
let selected = let selected =
i128::from(timing.receive_wall_ns.unwrap()) - one_way.client_to_server.unwrap().as_nanos(); i128::from(timing.receive_wall_ns.unwrap()) - one_way.client_to_server.unwrap().as_nanos();
assert!(selected >= ns(before_send)); assert!(selected >= ns(before_send));
assert!( // TX_SOFTWARE may arrive before or after send returns; only equality
selected < ns(sent_at.wall), // identifies the userspace fallback. Receipt remains the upper bound.
assert_ne!(
selected,
ns(sent_at.wall),
"TX timestamp was not observed; userspace fallback is insufficient" "TX timestamp was not observed; userspace fallback is insufficient"
); );
assert!(selected <= ns(received_at.wall)); assert!(selected <= ns(received_at.wall));
@@ -1,6 +1,10 @@
mod support; mod support;
use std::{collections::HashMap, time::Duration}; use std::{
collections::HashMap,
future::{poll_fn, Future},
time::Duration,
};
use irtt_client::{managed::*, ClientConfig, ClientEvent}; use irtt_client::{managed::*, ClientConfig, ClientEvent};
use irtt_server::ServerConfig; use irtt_server::ServerConfig;
@@ -29,48 +33,60 @@ async fn both_pacing_modes_serve_every_target_on_its_negotiated_grid() {
["first", "second"].map(|id| ManagedTargetConfig::new(id, server.addr.to_string())); ["first", "second"].map(|id| ManagedTargetConfig::new(id, server.addr.to_string()));
let (task, handle) = ManagedClient::task(config, targets.into()).unwrap(); let (task, handle) = ManagedClient::task(config, targets.into()).unwrap();
let mut events = handle.subscribe().unwrap(); let mut events = handle.subscribe().unwrap();
let run = tokio::spawn(task); let mut task = Box::pin(task);
let mut sends: HashMap<TargetInstance, Vec<_>> = HashMap::new(); let mut sends: HashMap<TargetInstance, Vec<_>> = HashMap::new();
let mut actual_sends = Vec::new(); let mut release_batches = Vec::new();
while let Ok(event) = events.recv().await { let outcome = poll_fn(|cx| {
if let ManagedEvent::Client { let result = task.as_mut().poll(cx);
target, let mut released = 0;
event: while let Ok(event) = events.try_recv() {
ClientEvent::EchoSent { if let ManagedEvent::Client {
scheduled_at, target,
sent_at, event:
.. ClientEvent::EchoSent {
}, scheduled_at,
} = event sent_at,
{ ..
let scheduled = scheduled_at.expect("managed probes expose their cadence"); },
assert!(sent_at.mono >= scheduled); } = event
actual_sends.push((target.clone(), sent_at.mono)); {
let previous = sends.entry(target).or_default(); let scheduled = scheduled_at.expect("managed probes expose their cadence");
if let Some(last) = previous.last() { assert!(sent_at.mono >= scheduled);
let elapsed: Duration = scheduled.duration_since(*last); released += 1;
assert!(!elapsed.is_zero()); let previous = sends.entry(target).or_default();
assert_eq!(elapsed.as_nanos() % Duration::from_millis(100).as_nanos(), 0); if let Some(last) = previous.last() {
let elapsed: Duration = scheduled.duration_since(*last);
assert!(!elapsed.is_zero());
assert_eq!(
elapsed.as_nanos() % Duration::from_millis(100).as_nanos(),
0
);
}
previous.push(scheduled);
} }
previous.push(scheduled);
} }
} if released > 0 {
let outcome = run.await.unwrap(); release_batches.push(released);
}
result
})
.await;
assert_eq!(outcome.end_reason, ManagedEndReason::TargetsComplete); assert_eq!(outcome.end_reason, ManagedEndReason::TargetsComplete);
assert_eq!(outcome.successful_target_outcomes, 2); assert_eq!(outcome.successful_target_outcomes, 2);
assert_eq!(sends.len(), 2); assert_eq!(sends.len(), 2);
actual_sends.sort_by_key(|(_, at)| *at); // Drive the public Future and observe the events each poll releases.
let cross_target_gaps = actual_sends.windows(2) // Burst serves ready targets together; staggered releases one before
.filter(|pair| pair[0].0 != pair[1].0) // yielding to its next gate. Descheduling cannot stretch/compress an
.map(|pair| pair[1].1.duration_since(pair[0].1)).collect::<Vec<_>>(); // observed wall-time gap into a different pacing classification.
assert!(cross_target_gaps.len() >= 2);
// Allow timer wake jitter while separating the nominal 50 ms
// stagger spacing from back-to-back burst sends.
match pacing { match pacing {
ManagedPacing::Burst => assert!(cross_target_gaps.iter().filter(|gap| **gap < Duration::from_millis(25)).count() >= 2, ManagedPacing::Burst => assert!(
"burst must repeatedly send different targets back-to-back: {cross_target_gaps:?}"), release_batches.iter().filter(|count| **count == 2).count() >= 2,
ManagedPacing::Staggered => assert!(cross_target_gaps.iter().all(|gap| *gap >= Duration::from_millis(35)), "burst must repeatedly release both ready targets: {release_batches:?}"
"staggered must space different targets across the interval: {cross_target_gaps:?}"), ),
ManagedPacing::Staggered => assert!(
release_batches.iter().all(|count| *count == 1),
"staggered must release targets separately: {release_batches:?}"
),
} }
for target in outcome.recent_target_outcomes.iter() { for target in outcome.recent_target_outcomes.iter() {
assert!(sends[&target.target].len() >= 2); assert!(sends[&target.target].len() >= 2);