tests(client): cleanup unnecessary tests and hooks
Rust CI / cargo fmt (push) Successful in 14s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 32s
Rust CI / cargo clippy (push) Successful in 34s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Failing after 34s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 7s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 10s
Rust CI / cargo doc (push) Successful in 26s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 32s
Supply chain / cargo-deny advisories (push) Successful in 6s
Supply chain / cargo-deny bans licenses sources (push) Successful in 5s
Supply chain / actionlint (push) Successful in 2s
Netem Interop / netem interop (push) Successful in 1m3s
Rust CI / cargo fmt (push) Successful in 14s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 32s
Rust CI / cargo clippy (push) Successful in 34s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Failing after 34s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 7s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 10s
Rust CI / cargo doc (push) Successful in 26s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 32s
Supply chain / cargo-deny advisories (push) Successful in 6s
Supply chain / cargo-deny bans licenses sources (push) Successful in 5s
Supply chain / actionlint (push) Successful in 2s
Netem Interop / netem interop (push) Successful in 1m3s
This commit is contained in:
27 files changed
+118
-2625
No files matched your search
@@ -157,22 +157,14 @@ impl BlockingManagedClient {
|
||||
}
|
||||
|
||||
fn join_worker(&mut self) -> Result<ManagedOutcome, BlockingManagedJoinError> {
|
||||
join_worker(
|
||||
self.worker
|
||||
.take()
|
||||
.expect("blocking managed worker is joined at most once"),
|
||||
)
|
||||
self.worker
|
||||
.take()
|
||||
.expect("blocking managed worker is joined at most once")
|
||||
.join()
|
||||
.map_err(|_| BlockingManagedJoinError::WorkerPanicked)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn join_worker(
|
||||
worker: JoinHandle<ManagedOutcome>,
|
||||
) -> Result<ManagedOutcome, BlockingManagedJoinError> {
|
||||
worker
|
||||
.join()
|
||||
.map_err(|_| BlockingManagedJoinError::WorkerPanicked)
|
||||
}
|
||||
|
||||
impl Drop for BlockingManagedClient {
|
||||
fn drop(&mut self) {
|
||||
if self.worker.is_some() {
|
||||
|
||||
@@ -12,8 +12,7 @@ use irtt_proto::{
|
||||
use tokio::{runtime::Builder, time::timeout};
|
||||
|
||||
use super::{
|
||||
blocking::{join_worker, WorkerRuntime},
|
||||
BlockingManagedClient, BlockingManagedJoinError, ManagedClientConfig, ManagedCompletionPolicy,
|
||||
blocking::WorkerRuntime, BlockingManagedClient, ManagedClientConfig, ManagedCompletionPolicy,
|
||||
ManagedEndReason, ManagedEvent, ManagedLifecycle, ManagedPacing, ManagedTargetConfig,
|
||||
ManagedTargetEndReason,
|
||||
};
|
||||
@@ -309,15 +308,6 @@ fn dropping_owner_stops_and_joins_worker() {
|
||||
server.finish();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn worker_panic_is_a_join_error() {
|
||||
let worker = thread::spawn(|| -> super::ManagedOutcome { panic!("test worker panic") });
|
||||
assert_eq!(
|
||||
join_worker(worker),
|
||||
Err(BlockingManagedJoinError::WorkerPanicked)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dropping_worker_runtime_inside_async_context_does_not_panic() {
|
||||
test_runtime().block_on(async {
|
||||
|
||||
@@ -449,199 +449,3 @@ pub(crate) fn classify_client_error(
|
||||
message: Arc::from(error.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{collections::TryReserveError, io};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn io_error() -> io::Error {
|
||||
io::Error::other("boom")
|
||||
}
|
||||
|
||||
fn try_reserve_error() -> TryReserveError {
|
||||
Vec::<u8>::new().try_reserve(usize::MAX).unwrap_err()
|
||||
}
|
||||
|
||||
fn classification_cases() -> Vec<(ClientError, ManagedTargetFailureKind)> {
|
||||
vec![
|
||||
(
|
||||
ClientError::Resolve {
|
||||
addr: "example.invalid:2112".to_owned(),
|
||||
},
|
||||
ManagedTargetFailureKind::Resolve,
|
||||
),
|
||||
(
|
||||
ClientError::Socket(io_error()),
|
||||
ManagedTargetFailureKind::Socket,
|
||||
),
|
||||
(
|
||||
ClientError::DatagramLengthMismatch {
|
||||
expected: 64,
|
||||
actual: 32,
|
||||
},
|
||||
ManagedTargetFailureKind::Socket,
|
||||
),
|
||||
(
|
||||
ClientError::SocketOption {
|
||||
operation: "set read timeout",
|
||||
remote: "127.0.0.1:2112".parse().unwrap(),
|
||||
source: io_error(),
|
||||
},
|
||||
ManagedTargetFailureKind::SocketOption,
|
||||
),
|
||||
(
|
||||
ClientError::ReadTimeoutRestore { source: io_error() },
|
||||
ManagedTargetFailureKind::SocketOption,
|
||||
),
|
||||
(
|
||||
ClientError::Protocol(irtt_proto::ProtoError::BadMagic),
|
||||
ManagedTargetFailureKind::Protocol,
|
||||
),
|
||||
(
|
||||
ClientError::ProtocolVersionMismatch {
|
||||
requested: 1,
|
||||
received: 2,
|
||||
},
|
||||
ManagedTargetFailureKind::Protocol,
|
||||
),
|
||||
(ClientError::ZeroToken, ManagedTargetFailureKind::Protocol),
|
||||
(
|
||||
ClientError::UnexpectedNoTestReply,
|
||||
ManagedTargetFailureKind::Protocol,
|
||||
),
|
||||
(
|
||||
ClientError::NonZeroNoTestToken { token: 7 },
|
||||
ManagedTargetFailureKind::Protocol,
|
||||
),
|
||||
(
|
||||
ClientError::ServerRejected,
|
||||
ManagedTargetFailureKind::Protocol,
|
||||
),
|
||||
(
|
||||
ClientError::NegotiationRejected {
|
||||
reason: "duration".to_owned(),
|
||||
},
|
||||
ManagedTargetFailureKind::Protocol,
|
||||
),
|
||||
(ClientError::OpenTimeout, ManagedTargetFailureKind::Timeout),
|
||||
(
|
||||
ClientError::InvalidConfig {
|
||||
reason: "interval".to_owned(),
|
||||
},
|
||||
ManagedTargetFailureKind::Configuration,
|
||||
),
|
||||
(
|
||||
ClientError::OpenTimeoutTooSmall {
|
||||
timeout: Duration::from_millis(1),
|
||||
minimum: Duration::from_millis(10),
|
||||
},
|
||||
ManagedTargetFailureKind::Configuration,
|
||||
),
|
||||
(
|
||||
ClientError::NoOpenTimeouts,
|
||||
ManagedTargetFailureKind::Configuration,
|
||||
),
|
||||
(
|
||||
ClientError::AllocationFailed {
|
||||
operation: "pending probes",
|
||||
source: try_reserve_error(),
|
||||
},
|
||||
ManagedTargetFailureKind::ResourceExhausted,
|
||||
),
|
||||
(
|
||||
ClientError::CounterOverflow {
|
||||
counter: "packets_sent",
|
||||
},
|
||||
ManagedTargetFailureKind::ResourceExhausted,
|
||||
),
|
||||
(
|
||||
ClientError::DurationOverflow,
|
||||
ManagedTargetFailureKind::ResourceExhausted,
|
||||
),
|
||||
(
|
||||
ClientError::PendingLimitExceeded { limit: 8 },
|
||||
ManagedTargetFailureKind::ResourceExhausted,
|
||||
),
|
||||
(
|
||||
ClientError::NoTokioRuntime,
|
||||
ManagedTargetFailureKind::InvalidState,
|
||||
),
|
||||
(ClientError::NotOpen, ManagedTargetFailureKind::InvalidState),
|
||||
(
|
||||
ClientError::AlreadyOpen,
|
||||
ManagedTargetFailureKind::InvalidState,
|
||||
),
|
||||
(
|
||||
ClientError::AlreadyCompleted,
|
||||
ManagedTargetFailureKind::InvalidState,
|
||||
),
|
||||
(
|
||||
ClientError::AlreadyClosed,
|
||||
ManagedTargetFailureKind::InvalidState,
|
||||
),
|
||||
(
|
||||
ClientError::StalePreparedProbe {
|
||||
prepared_seq: 3,
|
||||
next_wire_seq: 4,
|
||||
},
|
||||
ManagedTargetFailureKind::InvalidState,
|
||||
),
|
||||
(
|
||||
ClientError::PendingSequenceCollision { seq: 9 },
|
||||
ManagedTargetFailureKind::InvalidState,
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_client_error_classifies_to_its_documented_kind() {
|
||||
for (error, expected) in classification_cases() {
|
||||
let failure = classify_client_error(ManagedTargetFailurePhase::Opening, &error);
|
||||
assert_eq!(
|
||||
failure.kind, expected,
|
||||
"unexpected classification for {error:?}"
|
||||
);
|
||||
assert_eq!(failure.phase, ManagedTargetFailurePhase::Opening);
|
||||
assert_eq!(&*failure.message, error.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classification_preserves_the_reported_phase() {
|
||||
for phase in [
|
||||
ManagedTargetFailurePhase::Connecting,
|
||||
ManagedTargetFailurePhase::Opening,
|
||||
ManagedTargetFailurePhase::Sending,
|
||||
ManagedTargetFailurePhase::Receiving,
|
||||
ManagedTargetFailurePhase::Timing,
|
||||
ManagedTargetFailurePhase::Closing,
|
||||
] {
|
||||
let failure = classify_client_error(phase, &ClientError::OpenTimeout);
|
||||
assert_eq!(failure.phase, phase);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn phase_and_kind_display_without_debug_spelling() {
|
||||
assert_eq!(
|
||||
ManagedTargetFailurePhase::Connecting.to_string(),
|
||||
"connecting"
|
||||
);
|
||||
assert_eq!(ManagedTargetFailurePhase::Closing.to_string(), "closing");
|
||||
assert_eq!(
|
||||
ManagedTargetFailureKind::SocketOption.to_string(),
|
||||
"socket option"
|
||||
);
|
||||
assert_eq!(
|
||||
ManagedTargetFailureKind::ResourceExhausted.to_string(),
|
||||
"resource exhausted"
|
||||
);
|
||||
assert_eq!(
|
||||
ManagedTargetFailureKind::InvalidState.to_string(),
|
||||
"invalid state"
|
||||
);
|
||||
assert_eq!(ManagedTargetFailureKind::Other.to_string(), "other");
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,14 @@ impl ProbeSchedule {
|
||||
) -> Result<ScheduleCommit, ClientError> {
|
||||
let (scheduled_at, next_send_at) =
|
||||
advance_cadence(scheduled_at, self.interval, permission_at)?;
|
||||
Ok(self.finish_commit(scheduled_at, next_send_at))
|
||||
Ok(ScheduleCommit {
|
||||
scheduled_at,
|
||||
next_send_at: if self.end_at.is_some_and(|end| next_send_at >= end) {
|
||||
None
|
||||
} else {
|
||||
Some(next_send_at)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn commit(&mut self, commit: ScheduleCommit) {
|
||||
@@ -90,17 +97,6 @@ impl ProbeSchedule {
|
||||
pub(crate) fn is_finished(&self) -> bool {
|
||||
self.next_send_at.is_none()
|
||||
}
|
||||
|
||||
fn finish_commit(&self, scheduled_at: Instant, next_send_at: Instant) -> ScheduleCommit {
|
||||
ScheduleCommit {
|
||||
scheduled_at,
|
||||
next_send_at: if self.end_at.is_some_and(|end| next_send_at >= end) {
|
||||
None
|
||||
} else {
|
||||
Some(next_send_at)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn advance_cadence(
|
||||
|
||||
@@ -181,6 +181,7 @@ pub struct ManagedClientHandle {
|
||||
update_submission_hook: Option<Arc<UpdateSubmissionTestHook>>,
|
||||
}
|
||||
|
||||
// Pauses between admission and enqueue to exercise stop/seal linearization.
|
||||
#[cfg(test)]
|
||||
#[derive(Default)]
|
||||
struct UpdateSubmissionTestHook {
|
||||
@@ -402,10 +403,9 @@ impl Future for ManagedStopReceipt {
|
||||
type ConnectFuture =
|
||||
Pin<Box<dyn Future<Output = Result<AsyncClient, ClientError>> + Send + 'static>>;
|
||||
type WakeFuture = Pin<Box<dyn Future<Output = Option<watch::Receiver<()>>> + Send + 'static>>;
|
||||
// Snapshots at publication verify status is visible before the corresponding event.
|
||||
#[cfg(test)]
|
||||
type EventObservations = Arc<std::sync::Mutex<Vec<(ManagedEvent, Arc<ManagedStatus>)>>>;
|
||||
#[cfg(test)]
|
||||
type StaggerObservations = Arc<std::sync::Mutex<Vec<(usize, Duration)>>>;
|
||||
|
||||
fn arm_wake(mut receiver: watch::Receiver<()>) -> WakeFuture {
|
||||
Box::pin(async move {
|
||||
@@ -525,17 +525,7 @@ impl TargetRuntime {
|
||||
/// The client owns `packets_sent`; this runtime only mirrors it so a
|
||||
/// terminal outcome can report it after the client is gone.
|
||||
fn sync_packets_sent(&mut self, client: &AsyncClient) {
|
||||
self.record_packets_sent(client.packets_sent());
|
||||
}
|
||||
|
||||
/// Adopt an already-read authoritative sent count.
|
||||
fn record_packets_sent(&mut self, packets_sent: u64) {
|
||||
self.counters.packets_sent = packets_sent;
|
||||
}
|
||||
|
||||
/// Account for one client event against this target's durable counters.
|
||||
fn observe(&mut self, event: &ClientEvent) {
|
||||
self.counters.observe(event);
|
||||
self.counters.packets_sent = client.packets_sent();
|
||||
}
|
||||
|
||||
/// Build this target's durable outcome.
|
||||
@@ -723,21 +713,17 @@ pub struct ManagedClientTask {
|
||||
#[cfg(test)]
|
||||
event_observations: Option<EventObservations>,
|
||||
#[cfg(test)]
|
||||
stagger_observations: Option<StaggerObservations>,
|
||||
#[cfg(test)]
|
||||
drain_test_hook: DrainTestHook,
|
||||
// Counts work that cannot be observed through lossy presentation events.
|
||||
#[cfg(test)]
|
||||
timeout_inspections: usize,
|
||||
#[cfg(test)]
|
||||
deadline_inspections: usize,
|
||||
}
|
||||
|
||||
// Holds queued replies past the drain deadline to exercise the final receive budget.
|
||||
#[cfg(test)]
|
||||
#[derive(Default)]
|
||||
struct DrainTestHook {
|
||||
defer_work_until_deadline: bool,
|
||||
fail_receive: bool,
|
||||
fail_close: bool,
|
||||
}
|
||||
|
||||
impl ManagedClientTask {
|
||||
@@ -837,7 +823,7 @@ impl ManagedClientTask {
|
||||
|
||||
fn publish_client_events(&mut self, index: usize, events: Vec<ClientEvent>) {
|
||||
for event in events {
|
||||
self.targets[index].observe(&event);
|
||||
self.targets[index].counters.observe(&event);
|
||||
self.publish_event(ManagedEvent::Client {
|
||||
target: self.targets[index].instance.clone(),
|
||||
event,
|
||||
@@ -1421,10 +1407,6 @@ impl ManagedClientTask {
|
||||
return true;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
let injected_receive_failure = self.take_drain_receive_failure();
|
||||
#[cfg(not(test))]
|
||||
let injected_receive_failure = None;
|
||||
let mut retained_state_changed = false;
|
||||
let received = match client.poll_recv(cx) {
|
||||
Poll::Pending => false,
|
||||
@@ -1441,13 +1423,7 @@ impl ManagedClientTask {
|
||||
return self.begin_close(index, client, primary_end, cleanup_failure, now);
|
||||
}
|
||||
};
|
||||
if let Some(error) = injected_receive_failure {
|
||||
self.targets[index].sync_packets_sent(&client);
|
||||
cleanup_failure.get_or_insert_with(|| {
|
||||
classify_client_error(ManagedTargetFailurePhase::Receiving, &error)
|
||||
});
|
||||
return self.begin_close(index, client, primary_end, cleanup_failure, now);
|
||||
}
|
||||
|
||||
if client.is_peer_closed() {
|
||||
self.targets[index].sync_packets_sent(&client);
|
||||
self.finish_target(index, ManagedTargetEndReason::PeerClosed, cleanup_failure);
|
||||
@@ -1525,17 +1501,7 @@ impl ManagedClientTask {
|
||||
Poll::Ready(Ok(events)) => {
|
||||
self.targets[index].sync_packets_sent(&client);
|
||||
self.publish_client_events(index, events);
|
||||
#[cfg(test)]
|
||||
let injected_close_failure = self.take_drain_close_failure().map(|error| {
|
||||
classify_client_error(ManagedTargetFailurePhase::Closing, &error)
|
||||
});
|
||||
#[cfg(not(test))]
|
||||
let injected_close_failure = None;
|
||||
self.finish_target(
|
||||
index,
|
||||
primary_end,
|
||||
cleanup_failure.or(injected_close_failure),
|
||||
);
|
||||
self.finish_target(index, primary_end, cleanup_failure);
|
||||
false
|
||||
}
|
||||
Poll::Ready(Err(error)) => {
|
||||
@@ -1616,26 +1582,6 @@ impl ManagedClientTask {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn take_drain_receive_failure(&mut self) -> Option<ClientError> {
|
||||
if !mem::take(&mut self.drain_test_hook.fail_receive) {
|
||||
return None;
|
||||
}
|
||||
Some(ClientError::Socket(std::io::Error::other(
|
||||
"injected drain receive failure",
|
||||
)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn take_drain_close_failure(&mut self) -> Option<ClientError> {
|
||||
if !mem::take(&mut self.drain_test_hook.fail_close) {
|
||||
return None;
|
||||
}
|
||||
Some(ClientError::Socket(std::io::Error::other(
|
||||
"injected drain close failure",
|
||||
)))
|
||||
}
|
||||
|
||||
fn active_count(&self) -> usize {
|
||||
self.targets
|
||||
.iter()
|
||||
@@ -1643,7 +1589,7 @@ impl ManagedClientTask {
|
||||
.count()
|
||||
}
|
||||
|
||||
fn active_stagger_spacing(&self) -> Option<(usize, Duration)> {
|
||||
fn active_stagger_spacing(&self) -> Option<Duration> {
|
||||
let mut active = 0;
|
||||
let mut minimum: Option<Duration> = None;
|
||||
for target in &self.targets {
|
||||
@@ -1661,7 +1607,7 @@ impl ManagedClientTask {
|
||||
.interval();
|
||||
minimum = minimum.into_iter().chain(Some(interval)).min();
|
||||
}
|
||||
minimum.map(|interval| (active, stagger_spacing(interval, active)))
|
||||
minimum.map(|interval| stagger_spacing(interval, active))
|
||||
}
|
||||
|
||||
fn stagger_target_added(&mut self, now: Instant) {
|
||||
@@ -1672,7 +1618,7 @@ impl ManagedClientTask {
|
||||
let candidate = self
|
||||
.last_stagger_send
|
||||
.zip(self.active_stagger_spacing())
|
||||
.and_then(|(last, (_, spacing))| last.checked_add(spacing))
|
||||
.and_then(|(last, spacing)| last.checked_add(spacing))
|
||||
.filter(|gate| *gate > now);
|
||||
self.send_gate = candidate.map(|candidate| existing.min(candidate));
|
||||
}
|
||||
@@ -1708,21 +1654,17 @@ impl ManagedClientTask {
|
||||
fn record_stagger_acceptance(
|
||||
&mut self,
|
||||
result: SendResult,
|
||||
stagger_spacing: Option<(usize, Duration)>,
|
||||
stagger_spacing: Option<Duration>,
|
||||
accepted_at: Instant,
|
||||
) {
|
||||
if !result.accepted() {
|
||||
return;
|
||||
}
|
||||
let Some((_active, spacing)) = stagger_spacing else {
|
||||
let Some(spacing) = stagger_spacing else {
|
||||
return;
|
||||
};
|
||||
self.last_stagger_send = Some(accepted_at);
|
||||
self.send_gate = self.next_stagger_gate(spacing, accepted_at);
|
||||
#[cfg(test)]
|
||||
if let Some(observations) = &self.stagger_observations {
|
||||
observations.lock().unwrap().push((_active, spacing));
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_one_send(
|
||||
@@ -1730,7 +1672,7 @@ impl ManagedClientTask {
|
||||
index: usize,
|
||||
cx: &mut Context<'_>,
|
||||
now: Instant,
|
||||
stagger_spacing: Option<(usize, Duration)>,
|
||||
stagger_spacing: Option<Duration>,
|
||||
) -> SendResult {
|
||||
if !self.targets[index].desired || self.targets[index].retirement.is_some() {
|
||||
self.targets[index].send_waiting = false;
|
||||
@@ -1832,7 +1774,7 @@ impl ManagedClientTask {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.targets[index].record_packets_sent(after);
|
||||
self.targets[index].counters.packets_sent = after;
|
||||
match result {
|
||||
Poll::Pending => {
|
||||
self.targets[index].send_waiting = true;
|
||||
@@ -2109,11 +2051,7 @@ impl ManagedClientTask {
|
||||
.all(|target| matches!(target.state, TargetState::Terminal))
|
||||
}
|
||||
|
||||
fn next_deadline(&mut self) -> Option<Instant> {
|
||||
#[cfg(test)]
|
||||
{
|
||||
self.deadline_inspections += self.targets.len();
|
||||
}
|
||||
fn next_deadline(&self) -> Option<Instant> {
|
||||
let mut non_send_deadline = None;
|
||||
let mut send_deadline = None;
|
||||
for target in &self.targets {
|
||||
@@ -2541,13 +2479,9 @@ fn build_task(
|
||||
#[cfg(test)]
|
||||
event_observations: None,
|
||||
#[cfg(test)]
|
||||
stagger_observations: None,
|
||||
#[cfg(test)]
|
||||
drain_test_hook: DrainTestHook::default(),
|
||||
#[cfg(test)]
|
||||
timeout_inspections: 0,
|
||||
#[cfg(test)]
|
||||
deadline_inspections: 0,
|
||||
};
|
||||
let handle = ManagedClientHandle {
|
||||
stop,
|
||||
|
||||
@@ -16,7 +16,7 @@ use irtt_proto::{
|
||||
use tokio::runtime::{Builder, Runtime};
|
||||
|
||||
use super::*;
|
||||
use crate::{socket::resolution_call_counts, ClientAuthConfig, NegotiationPolicy, RunMode};
|
||||
use crate::{ClientAuthConfig, NegotiationPolicy, RunMode};
|
||||
|
||||
const TOKEN: u64 = 0x1234_5678_90ab_cdef;
|
||||
|
||||
@@ -388,44 +388,6 @@ fn assert_rotated_without_catchup(sends: &[(TargetInstance, u32, Instant, Instan
|
||||
}
|
||||
}
|
||||
|
||||
fn run_negotiated_stagger_case(
|
||||
requested: Duration,
|
||||
negotiated: &[Duration],
|
||||
) -> (Vec<(usize, Duration)>, Vec<u64>) {
|
||||
let servers = negotiated
|
||||
.iter()
|
||||
.map(|interval| start_server_negotiating(ServerBehavior::Echo, None, Some(*interval)))
|
||||
.collect::<Vec<_>>();
|
||||
let mut managed = config(ManagedPacing::Staggered);
|
||||
managed.client.duration = Some(Duration::from_millis(190));
|
||||
managed.client.interval = requested;
|
||||
managed.client.negotiation_policy = NegotiationPolicy::Loose;
|
||||
let targets = servers
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, server)| target(&format!("target-{index}"), server.addr))
|
||||
.collect();
|
||||
let (mut task, _) = ManagedClient::task(managed, targets).unwrap();
|
||||
let observations = Arc::new(Mutex::new(Vec::new()));
|
||||
task.stagger_observations = Some(Arc::clone(&observations));
|
||||
let outcome = runtime().block_on(task);
|
||||
let packets_sent = (0..servers.len())
|
||||
.map(|index| {
|
||||
outcome
|
||||
.recent_target_outcomes
|
||||
.iter()
|
||||
.find(|target| target.target.id.as_ref() == format!("target-{index}"))
|
||||
.unwrap()
|
||||
.packets_sent
|
||||
})
|
||||
.collect();
|
||||
for server in servers {
|
||||
server.finish();
|
||||
}
|
||||
let spacings = observations.lock().unwrap().clone();
|
||||
(spacings, packets_sent)
|
||||
}
|
||||
|
||||
fn run_deferred_drain_burst(packet_count: usize) -> ManagedOutcome {
|
||||
let server = start_server(ServerBehavior::DeferredBurst(packet_count), None);
|
||||
let mut managed = config(ManagedPacing::Staggered);
|
||||
@@ -459,13 +421,11 @@ fn run_deferred_drain_burst(packet_count: usize) -> ManagedOutcome {
|
||||
|
||||
#[test]
|
||||
fn construction_is_runtime_and_io_free() {
|
||||
let before = resolution_call_counts();
|
||||
let (task, handle) = ManagedClient::task(
|
||||
config(ManagedPacing::Staggered),
|
||||
vec![ManagedTargetConfig::new("dns", "no-such-host.invalid")],
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(resolution_call_counts(), before);
|
||||
assert_eq!(handle.status().lifecycle, ManagedLifecycle::NotStarted);
|
||||
drop(task);
|
||||
}
|
||||
@@ -1400,7 +1360,6 @@ fn first_poll_without_runtime_fails_durably() {
|
||||
|
||||
#[test]
|
||||
fn pre_poll_stop() {
|
||||
let before = resolution_call_counts();
|
||||
let (task, handle) = ManagedClient::task(
|
||||
config(ManagedPacing::Staggered),
|
||||
vec![ManagedTargetConfig::new("one", "no-such-host.invalid")],
|
||||
@@ -1413,7 +1372,6 @@ fn pre_poll_stop() {
|
||||
};
|
||||
assert!(poll_once(receipt.as_mut()).is_ready());
|
||||
assert_eq!(outcome.end_reason, ManagedEndReason::StopRequested);
|
||||
assert_eq!(resolution_call_counts(), before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1604,45 +1562,6 @@ fn staggered_pacing_fairness() {
|
||||
second.finish();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn staggered_pacing_uses_all_active_negotiated_intervals() {
|
||||
let requested = Duration::from_millis(120);
|
||||
|
||||
let (single, single_packets) =
|
||||
run_negotiated_stagger_case(requested, &[Duration::from_millis(40)]);
|
||||
assert!(single
|
||||
.iter()
|
||||
.all(|entry| *entry == (1, Duration::from_millis(40))));
|
||||
assert!(single_packets[0] >= 4);
|
||||
|
||||
let (different, different_packets) = run_negotiated_stagger_case(
|
||||
requested,
|
||||
&[Duration::from_millis(30), Duration::from_millis(90)],
|
||||
);
|
||||
let while_both_active = different
|
||||
.iter()
|
||||
.filter(|(active, _)| *active == 2)
|
||||
.collect::<Vec<_>>();
|
||||
assert!(!while_both_active.is_empty());
|
||||
assert!(while_both_active
|
||||
.iter()
|
||||
.all(|(_, spacing)| *spacing == Duration::from_millis(15)));
|
||||
assert!(different_packets[0] > different_packets[1]);
|
||||
|
||||
let (equal, _) = run_negotiated_stagger_case(
|
||||
requested,
|
||||
&[Duration::from_millis(60), Duration::from_millis(60)],
|
||||
);
|
||||
let while_both_active = equal
|
||||
.iter()
|
||||
.filter(|(active, _)| *active == 2)
|
||||
.collect::<Vec<_>>();
|
||||
assert!(!while_both_active.is_empty());
|
||||
assert!(while_both_active
|
||||
.iter()
|
||||
.all(|(_, spacing)| *spacing == Duration::from_millis(30)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stagger_gate_tracks_active_membership() {
|
||||
let first =
|
||||
@@ -1690,7 +1609,7 @@ fn stagger_gate_tracks_active_membership() {
|
||||
let accepted_at = Instant::now();
|
||||
task.record_stagger_acceptance(
|
||||
SendResult::Failed { accepted: true },
|
||||
Some((2, Duration::from_millis(50))),
|
||||
Some(Duration::from_millis(50)),
|
||||
accepted_at,
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -1752,11 +1671,7 @@ fn the_stagger_gate_advances_by_one_spacing_per_send_without_accumulating_latene
|
||||
let start = Instant::now();
|
||||
let lateness = Duration::from_millis(3);
|
||||
|
||||
task.record_stagger_acceptance(
|
||||
SendResult::Ready { accepted: true },
|
||||
Some((1, spacing)),
|
||||
start,
|
||||
);
|
||||
task.record_stagger_acceptance(SendResult::Ready { accepted: true }, Some(spacing), start);
|
||||
assert_eq!(task.send_gate, Some(start + spacing), "first send anchors");
|
||||
|
||||
// Every later send is observed `lateness` after the gate that released it.
|
||||
@@ -1766,7 +1681,7 @@ fn the_stagger_gate_advances_by_one_spacing_per_send_without_accumulating_latene
|
||||
let released_at = start + spacing * slot;
|
||||
task.record_stagger_acceptance(
|
||||
SendResult::Ready { accepted: true },
|
||||
Some((1, spacing)),
|
||||
Some(spacing),
|
||||
released_at + lateness,
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -1782,7 +1697,7 @@ fn the_stagger_gate_advances_by_one_spacing_per_send_without_accumulating_latene
|
||||
let resumed_at = stalled_gate + spacing;
|
||||
task.record_stagger_acceptance(
|
||||
SendResult::Ready { accepted: true },
|
||||
Some((1, spacing)),
|
||||
Some(spacing),
|
||||
resumed_at,
|
||||
);
|
||||
assert_eq!(task.send_gate, Some(resumed_at + spacing));
|
||||
@@ -1926,69 +1841,6 @@ fn pending_limit_failure_drains_reply_and_closes_session() {
|
||||
assert!(has_close(&records));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drain_failures_preserve_primary_outcome_and_first_cleanup_failure() {
|
||||
let completed_server = start_server(ServerBehavior::Echo, None);
|
||||
let (mut completed_task, _) = ManagedClient::task(
|
||||
config(ManagedPacing::Staggered),
|
||||
vec![target("completed", completed_server.addr)],
|
||||
)
|
||||
.unwrap();
|
||||
completed_task.drain_test_hook.fail_receive = true;
|
||||
completed_task.drain_test_hook.fail_close = true;
|
||||
let completed = runtime().block_on(completed_task);
|
||||
let completed_target = &completed.recent_target_outcomes[0];
|
||||
assert!(matches!(
|
||||
completed_target.end_reason,
|
||||
ManagedTargetEndReason::TestComplete
|
||||
));
|
||||
assert_eq!(completed.failed_target_outcomes, 0);
|
||||
assert!(matches!(
|
||||
completed_target.cleanup_failure,
|
||||
Some(ManagedTargetFailure {
|
||||
phase: ManagedTargetFailurePhase::Receiving,
|
||||
kind: ManagedTargetFailureKind::Socket,
|
||||
..
|
||||
})
|
||||
));
|
||||
completed_server.finish();
|
||||
|
||||
let stopped_server = start_server(ServerBehavior::Echo, None);
|
||||
let mut managed = config(ManagedPacing::Staggered);
|
||||
managed.completion = ManagedCompletionPolicy::ExplicitStop;
|
||||
managed.client.duration = None;
|
||||
let (mut stopped_task, handle) =
|
||||
ManagedClient::task(managed, vec![target("stopped", stopped_server.addr)]).unwrap();
|
||||
stopped_task.drain_test_hook.fail_receive = true;
|
||||
let seen = Arc::clone(&stopped_server.probe_seen);
|
||||
let stopper = thread::spawn(move || {
|
||||
let (flag, ready) = &*seen;
|
||||
let guard = flag.lock().unwrap();
|
||||
let (guard, timeout) = ready
|
||||
.wait_timeout_while(guard, Duration::from_secs(2), |seen| !*seen)
|
||||
.unwrap();
|
||||
assert!(*guard && !timeout.timed_out());
|
||||
drop(handle.stop());
|
||||
});
|
||||
let stopped = runtime().block_on(stopped_task);
|
||||
stopper.join().unwrap();
|
||||
let stopped_target = &stopped.recent_target_outcomes[0];
|
||||
assert!(matches!(
|
||||
stopped_target.end_reason,
|
||||
ManagedTargetEndReason::Stopped
|
||||
));
|
||||
assert_eq!(stopped.failed_target_outcomes, 0);
|
||||
assert!(matches!(
|
||||
stopped_target.cleanup_failure,
|
||||
Some(ManagedTargetFailure {
|
||||
phase: ManagedTargetFailurePhase::Receiving,
|
||||
kind: ManagedTargetFailureKind::Socket,
|
||||
..
|
||||
})
|
||||
));
|
||||
stopped_server.finish();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn final_drain_overflow_is_rejected_and_preserves_success() {
|
||||
let mut invalid = config(ManagedPacing::Staggered);
|
||||
|
||||
Reference in new issue
Block a user