test(client): avoid scheduler-dependent timing assertions
This commit is contained in:
2 files changed
+57
-38
No files matched your search
@@ -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);
|
||||||
|
|||||||
Reference in new issue
Block a user