use std::sync::OnceLock;
use std::time::{Duration, Instant};
use dope_core::backend::Sqe;
use dope_core::driver;
use dope_core::driver::Driver;
use dope_core::driver::completion::Completion;
use dope_core::driver::ext::DriverExt;
use dope_core::driver::submission::Submission;
use dope_core::driver::token::{Epoch, SlotIndex, Token, kind};
use dope_core::platform::{Mismatch, Platform, Snapshot};
use dope_test::{throughput_cfg, with_driver};
const ROUTE: u8 = 7;
fn target() -> Token {
Token::new(ROUTE, SlotIndex::new(0), Epoch::INITIAL)
}
type TimerSpec = <Driver as Platform>::TimerSpec;
fn spec() -> &'static TimerSpec {
static SPEC: OnceLock<TimerSpec> = OnceLock::new();
SPEC.get_or_init(|| TimerSpec::from(Duration::from_millis(10)))
}
#[test]
fn interval_emits_repeated_completions_and_can_be_cancelled() {
with_driver(|mut driver| {
driver
.push(Sqe::interval(spec(), target()))
.expect("arm interval");
let deadline = Instant::now() + Duration::from_millis(500);
let mut ticks = 0;
let mut completions = [dope_core::io::Cqe::ZERO; 16];
while ticks < 2 && Instant::now() < deadline {
driver
.wait(Some(Duration::from_millis(50)))
.expect("wait interval");
let n = driver.drain(&mut completions);
ticks += completions[..n]
.iter()
.filter(|cqe| {
Token::try_from_raw(cqe.user_data)
.is_some_and(|token| token.same_target(target()))
&& cqe.result != -libc::ECANCELED
})
.count();
}
assert!(ticks >= 2, "interval produced only {ticks} completion(s)");
driver
.push(Sqe::cancel(target(), kind::TIMER))
.expect("cancel interval");
#[cfg(target_os = "linux")]
{
let deadline = Instant::now() + Duration::from_millis(500);
let mut cancelled = false;
while !cancelled && Instant::now() < deadline {
driver
.wait(Some(Duration::from_millis(50)))
.expect("wait cancellation");
let n = driver.drain(&mut completions);
cancelled = completions[..n].iter().any(|cqe| {
Token::try_from_raw(cqe.user_data)
.is_some_and(|token| token.same_target(target()))
&& cqe.result == -libc::ECANCELED
});
}
assert!(cancelled, "interval cancellation did not complete");
}
});
}
type Case = (&'static str, fn(&mut Snapshot), fn(Mismatch));
fn saturated_snapshot() -> Snapshot {
let mut snap = Driver::snapshot().expect("detect");
snap.rlimit_nofile = u64::MAX;
snap.syncookies = true;
snap.max_syn_backlog = u32::MAX;
snap.somaxconn = u32::MAX;
snap
}
#[test]
fn snapshot_detect_ok() {
let snap = Driver::snapshot().expect("detect");
assert!(snap.rlimit_nofile > 0);
assert!(snap.somaxconn > 0);
}
#[test]
fn compat_check_baseline_cfg_passes_on_production_host() {
let snap = Driver::snapshot().expect("detect");
if !snap.syncookies && snap.max_syn_backlog < 4096 {
return;
}
if snap.somaxconn < 4096 {
return;
}
snap.check_slots(throughput_cfg().fixed_file_slots())
.expect("baseline profile must pass on a properly tuned host");
}
#[test]
fn compat_check_rlimit_too_low_fails() {
let cfg = throughput_cfg();
let rows: [Case; 3] = [
(
"must reject 1-fd rlimit",
|snap| snap.rlimit_nofile = 1,
|err| match err {
Mismatch::NoFileTooLow { rlimit, .. } => assert_eq!(rlimit, 1),
other => panic!("expected NoFileTooLow, got {other:?}"),
},
),
(
"must reject SYN-flood vulnerable host",
|snap| {
snap.syncookies = false;
snap.max_syn_backlog = 128;
},
|err| match err {
Mismatch::SynFloodVulnerable { backlog, .. } => assert_eq!(backlog, 128),
other => panic!("expected SynFloodVulnerable, got {other:?}"),
},
),
(
"must reject low somaxconn",
|snap| snap.somaxconn = 128,
|err| match err {
Mismatch::SomaxconnTooLow { kernel, .. } => assert_eq!(kernel, 128),
other => panic!("expected SomaxconnTooLow, got {other:?}"),
},
),
];
for (reject, degrade, verify) in rows {
let mut snap = saturated_snapshot();
degrade(&mut snap);
verify(snap.check_slots(cfg.fixed_file_slots()).expect_err(reject));
}
}
#[test]
fn mismatch_to_io_error() {
let err: std::io::Error = Mismatch::NoFileTooLow {
requested: 100,
rlimit: 50,
}
.into();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
let msg = err.to_string();
assert!(msg.contains("100"));
assert!(msg.contains("50"));
}
#[test]
fn driver_does_not_raise_nofile() {
let before = Driver::snapshot().expect("detect before").rlimit_nofile;
let driver = Driver::new(driver::Config::for_quic_udp(1, 8)).expect("driver");
let after = Driver::snapshot().expect("detect after").rlimit_nofile;
assert_eq!(after, before);
drop(driver);
}