Files
irtt-rs/crates/irtt-client/tests/managed_control.rs
T
Nils Andreas Svee 48a50d7f3a
Rust CI / cargo fmt (push) Successful in 17s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 34s
Rust CI / cargo clippy (push) Successful in 35s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 8s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 10s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 1m14s
Rust CI / cargo doc (push) Successful in 23s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 29s
Supply chain / cargo-deny advisories (push) Successful in 9s
Supply chain / cargo-deny bans licenses sources (push) Successful in 5s
Supply chain / actionlint (push) Successful in 3s
Netem Interop / netem interop (push) Successful in 1m11s
tests: rework the actually useful tests to live in the integration crate
2026-10-03 13:59:38 +07:00

161 lines
5.6 KiB
Rust

#![cfg(feature = "tokio")]
use std::{
future::Future,
pin::pin,
task::{Context, Poll, Waker},
time::Duration,
};
use irtt_client::managed::*;
fn config() -> ManagedClientConfig {
ManagedClientConfig {
completion: ManagedCompletionPolicy::ExplicitStop,
command_capacity: 3,
..ManagedClientConfig::default()
}
}
#[test]
fn stop_resolves_every_accepted_update_and_closes_admission() {
let (task, handle) = ManagedClient::task(config(), vec![]).unwrap();
let receipts: Vec<_> = (0..3)
.map(|index| {
handle
.update_targets(vec![ManagedTargetConfig::new(
format!("queued-{index}"),
"127.0.0.1:9",
)])
.unwrap()
})
.collect();
assert!(matches!(
handle.update_targets(vec![]),
Err(ManagedCommandError::QueueFull)
));
let stop = handle.stop();
assert!(matches!(
handle.update_targets(vec![]),
Err(ManagedCommandError::Stopping)
));
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
runtime.block_on(async {
tokio::time::timeout(Duration::from_secs(2), async {
let outcome = task.await;
stop.await;
for receipt in receipts {
assert!(matches!(
receipt.await,
Err(ManagedCommandApplyError::Stopping)
));
}
assert_eq!(outcome.end_reason, ManagedEndReason::StopRequested);
assert_eq!(outcome.applied_command_sequence, 0);
assert_eq!(outcome.total_target_outcomes, 0);
let status = handle.status();
assert_eq!(status.lifecycle, ManagedLifecycle::Completed);
assert!(status.stop_requested);
assert_eq!(status.final_outcome.as_deref(), Some(&outcome));
assert!(matches!(
handle.update_targets(vec![]),
Err(ManagedCommandError::DriverClosed)
));
handle.stop().await;
})
.await
.expect("stop must resolve every buffered update and repeated stop receipt");
});
}
#[test]
fn failed_driver_resolves_every_accepted_update_with_its_terminal_failure() {
let (task, handle) = ManagedClient::task(config(), vec![]).unwrap();
let receipts: Vec<_> = (0..3)
.map(|index| {
handle
.update_targets(vec![ManagedTargetConfig::new(
format!("queued-{index}"),
"127.0.0.1:9",
)])
.unwrap()
})
.collect();
let mut context = Context::from_waker(Waker::noop());
let Poll::Ready(outcome) = pin!(task).as_mut().poll(&mut context) else {
panic!("polling without Tokio must fail immediately");
};
assert_eq!(
outcome.end_reason,
ManagedEndReason::DriverFailed(ManagedDriverFailure::NoTokioRuntime)
);
for receipt in receipts {
assert!(matches!(
pin!(receipt).as_mut().poll(&mut context),
Poll::Ready(Err(ManagedCommandApplyError::DriverFailed(
ManagedDriverFailure::NoTokioRuntime
)))
));
}
let status = handle.status();
assert_eq!(status.lifecycle, ManagedLifecycle::Failed);
assert_eq!(status.final_outcome.as_deref(), Some(&outcome));
assert!(matches!(
handle.update_targets(vec![]),
Err(ManagedCommandError::DriverClosed)
));
assert!(pin!(handle.stop()).as_mut().poll(&mut context).is_ready());
}
#[test]
fn concurrent_submission_and_stop_or_seal_never_lose_an_accepted_receipt() {
let (task, handle) = ManagedClient::task(config(), vec![]).unwrap();
let barrier = std::sync::Arc::new(std::sync::Barrier::new(2));
let submit_barrier = std::sync::Arc::clone(&barrier);
let submit_handle = handle.clone();
let (sender, receiver) = std::sync::mpsc::channel();
let submit = std::thread::spawn(move || {
submit_barrier.wait();
sender.send(submit_handle.update_targets(vec![])).unwrap();
});
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
// Stop is latched before the driver starts, while submission may cross
// admission before stop, after stop, or after terminal sealing.
barrier.wait();
let stop = handle.stop();
runtime.block_on(async {
tokio::time::timeout(Duration::from_secs(2), async {
let outcome = task.await;
stop.await;
assert_eq!(outcome.end_reason, ManagedEndReason::StopRequested);
assert_eq!(outcome.applied_command_sequence, 0);
assert_eq!(outcome.total_target_outcomes, 0);
let result = receiver
.recv_timeout(Duration::from_secs(2))
.expect("concurrent submission must return");
submit.join().unwrap();
match result {
Ok(receipt) => assert!(matches!(
receipt.await,
Err(ManagedCommandApplyError::Stopping)
)),
Err(ManagedCommandError::Stopping | ManagedCommandError::DriverClosed) => {}
Err(error) => panic!("unexpected concurrent submission result: {error:?}"),
}
assert!(matches!(
handle.update_targets(vec![]),
Err(ManagedCommandError::DriverClosed)
));
})
.await
.expect("every accepted concurrent submission must receive a terminal disposition");
});
}