use super::{Completion, InjectedFailure, IoRing, Op, OpSupport};
use crate::IoRingErrorExt;
use crate::capability::{RingVersion, capabilities};
#[test]
fn op_support_starts_empty() {
assert!(!OpSupport::default().contains(Op::Read));
assert!(!OpSupport::default().contains(Op::Nop));
}
#[test]
fn every_named_version_the_host_supports_creates_and_closes() {
let caps = capabilities().expect("capabilities");
let mut created_at_least_one = false;
for version in [RingVersion::V1, RingVersion::V2, RingVersion::V3] {
if version > caps.max_version {
continue;
}
let ring = IoRing::with_version(version, 64, 128).expect("create at a supported version");
assert_eq!(ring.version(), version);
drop(ring);
created_at_least_one = true;
}
assert!(
created_at_least_one,
"the host should support at least IORING_VERSION_1"
);
}
#[test]
fn creating_a_ring_above_the_hosts_maximum_version_fails() {
let caps = capabilities().expect("capabilities");
let too_high = RingVersion::from_raw(caps.max_version.raw() + 1);
let error =
IoRing::with_version(too_high, 64, 128).expect_err("an unsupported version must fail");
assert_eq!(error.kind(), std::io::ErrorKind::Other);
}
#[test]
fn capability_reporting_never_claims_more_than_is_io_ring_op_supported_reports() {
let ring = IoRing::new(64, 128).expect("create ring");
for op in [
Op::Nop,
Op::Read,
Op::Write,
Op::Flush,
Op::RegisterFiles,
Op::RegisterBuffers,
Op::Cancel,
] {
assert_eq!(
ring.supports(op),
ring.supports_raw(op.code()),
"cached support for {op:?} disagrees with a direct IsIoRingOpSupported call"
);
}
}
#[test]
fn supports_reports_exactly_the_capability_set_it_was_given() {
let mut ring = IoRing::new(8, 8).expect("create ring");
ring.set_supported_ops_for_test(&[Op::Nop, Op::Read]);
assert!(ring.supports(Op::Nop));
assert!(ring.supports(Op::Read));
assert!(
!ring.supports(Op::Write),
"an op left out of the constructed set must read back as unsupported"
);
assert!(!ring.supports(Op::Cancel));
}
#[test]
fn nop_read_and_write_are_supported_on_any_real_ring() {
let ring = IoRing::new(64, 128).expect("create ring");
assert!(ring.supports(Op::Nop));
assert!(ring.supports(Op::Read));
assert!(ring.supports(Op::Write));
}
#[test]
fn run_down_returns_once_a_recorded_completion_zeroes_the_count() {
let mut ring = IoRing::new(64, 128).expect("create ring");
let op = ring.reserve_user_data().expect("reserve");
assert_eq!(ring.outstanding(), 1);
ring.record_completion(op);
ring.run_down()
.expect("run_down with the count already settled");
assert_eq!(ring.outstanding(), 0);
}
#[test]
fn dropping_a_ring_actually_runs_its_drop_body() {
let before = super::DROP_RUNS.with(std::cell::Cell::get);
let ring = IoRing::new(8, 8).expect("create ring");
drop(ring);
let after = super::DROP_RUNS.with(std::cell::Cell::get);
assert_eq!(
after,
before + 1,
"dropping one ring must run its Drop impl exactly once (before={before}, after={after})"
);
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "CloseIoRing failed")]
fn a_ring_whose_close_the_kernel_refuses_reports_the_close_failure() {
let ring = IoRing::refused_by_the_kernel();
assert_eq!(
ring.accounting.outstanding(),
0,
"a fresh ledger has nothing outstanding"
);
drop(ring);
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "IoRing rundown failed before close")]
fn a_ring_whose_rundown_the_kernel_refuses_reports_the_rundown_failure() {
let mut ring = IoRing::refused_by_the_kernel();
ring.accounting
.reserve_user_data()
.expect("a fresh ring's identity space is not exhausted");
assert_eq!(
ring.accounting.outstanding(),
1,
"rundown must have a reason to submit, or it cannot fail"
);
drop(ring);
}
fn one_buffer_info() -> Vec<windows_sys::Win32::Storage::FileSystem::IORING_BUFFER_INFO> {
vec![
windows_sys::Win32::Storage::FileSystem::IORING_BUFFER_INFO {
Address: std::ptr::null_mut(),
Length: 0,
},
]
}
fn one_file_handle() -> Vec<*mut std::ffi::c_void> {
vec![std::ptr::null_mut()]
}
fn hold_both(ring: &mut IoRing) {
let _ = ring.hold_registered_buffer_infos(one_buffer_info());
let _ = ring.hold_registered_file_handles(one_file_handle());
}
#[test]
fn the_registration_arrays_are_never_dropped_by_the_compiler() {
fn pinned(ring: &IoRing) -> &std::mem::ManuallyDrop<super::LateReadArrays> {
&ring.late_read
}
let _ = pinned;
}
#[test]
fn a_quiesced_ring_releases_its_registration_arrays() {
let before = super::LATE_READ_ARRAYS_RELEASED.with(std::cell::Cell::get);
let unwound = std::panic::catch_unwind(|| {
let mut ring = IoRing::refused_by_the_kernel();
hold_both(&mut ring);
assert_eq!(ring.accounting.outstanding(), 0, "nothing is outstanding");
panic!("deliberate: drop the ring during an unwind");
});
assert!(unwound.is_err(), "the closure must have unwound");
let after = super::LATE_READ_ARRAYS_RELEASED.with(std::cell::Cell::get);
assert_eq!(
after,
before + 2,
"a ring with nothing outstanding must release both registration arrays"
);
}
#[test]
fn a_ring_whose_rundown_fails_keeps_its_registration_arrays() {
let before = super::LATE_READ_ARRAYS_RELEASED.with(std::cell::Cell::get);
let unwound = std::panic::catch_unwind(|| {
let mut ring = IoRing::refused_by_the_kernel();
hold_both(&mut ring);
ring.accounting
.reserve_user_data()
.expect("a fresh ring's identity space is not exhausted");
panic!("deliberate: drop the ring during an unwind");
});
assert!(unwound.is_err(), "the closure must have unwound");
let after = super::LATE_READ_ARRAYS_RELEASED.with(std::cell::Cell::get);
assert_eq!(
after, before,
"a ring that could not prove quiescence must not release an array the kernel may still read"
);
}
fn two_shared_files() -> Vec<win_shared_os_owned_handle::SharedHandle> {
let exe = std::env::current_exe().expect("the test binary's path");
(0..2)
.map(|_| {
let file = std::fs::File::open(&exe).expect("open the test binary for read");
win_shared_os_owned_handle::SharedHandle::new(std::os::windows::io::OwnedHandle::from(
file,
))
})
.collect()
}
#[test]
fn the_registered_files_are_never_dropped_by_the_compiler() {
fn pinned(
ring: &IoRing,
) -> &std::mem::ManuallyDrop<Vec<win_shared_os_owned_handle::SharedHandle>> {
&ring.registered_files
}
let _ = pinned;
}
#[test]
fn a_quiesced_ring_releases_its_registered_files() {
let before = super::REGISTERED_FILES_RELEASED.with(std::cell::Cell::get);
let unwound = std::panic::catch_unwind(|| {
let mut ring = IoRing::refused_by_the_kernel();
ring.hold_registered_shared_files(two_shared_files());
assert_eq!(ring.accounting.outstanding(), 0, "nothing is outstanding");
panic!("deliberate: drop the ring during an unwind");
});
assert!(unwound.is_err(), "the closure must have unwound");
let after = super::REGISTERED_FILES_RELEASED.with(std::cell::Cell::get);
assert_eq!(
after,
before + 2,
"a ring with nothing outstanding must release every registered file"
);
}
#[test]
fn a_ring_whose_rundown_fails_keeps_its_registered_files() {
let before = super::REGISTERED_FILES_RELEASED.with(std::cell::Cell::get);
let unwound = std::panic::catch_unwind(|| {
let mut ring = IoRing::refused_by_the_kernel();
ring.hold_registered_shared_files(two_shared_files());
ring.accounting
.reserve_user_data()
.expect("a fresh ring's identity space is not exhausted");
panic!("deliberate: drop the ring during an unwind");
});
assert!(unwound.is_err(), "the closure must have unwound");
let after = super::REGISTERED_FILES_RELEASED.with(std::cell::Cell::get);
assert_eq!(
after, before,
"a ring that could not prove quiescence must not close a handle the kernel may still use"
);
}
fn real_completion(ring: &mut IoRing) -> (usize, crate::Completion) {
use crate::{Batch, FlushCoverage, FlushMode};
use std::os::windows::io::AsRawHandle;
let path = std::env::temp_dir().join(format!(
"windows-ioring-sys-injection-{}-{:?}.tmp",
std::process::id(),
std::thread::current().id()
));
std::fs::write(&path, b"x").expect("create fixture");
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.expect("open fixture");
let mut batch = Batch::new(ring);
let user_data = unsafe {
batch.flush_raw(
file.as_raw_handle(),
FlushCoverage::Unordered,
FlushMode::Default,
)
}
.expect("queue a flush");
batch.submit_and_wait(1, 30_000).expect("submit and wait");
let completion = super::pop_within(ring, "the flush's completion");
let _ = std::fs::remove_file(&path);
(user_data, completion)
}
#[test]
fn an_injected_failure_replaces_a_real_success() {
let mut ring = IoRing::new(16, 16).expect("create ring");
let (user_data, completion) = real_completion(&mut ring);
completion
.result()
.expect("the flush really did succeed, or this test proves nothing");
let injected = completion.with_injected_failure(crate::InjectedFailure::Win32(
windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED,
));
let error = injected.result().expect_err("the injected failure applies");
assert_eq!(
(crate::IoRingErrorExt::as_ioring_error(&error)
.expect("an IoRingError")
.code() as u32)
& 0xFFFF,
windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED,
"the Win32 code must survive the HRESULT wrapping the kernel would apply"
);
assert_eq!(
injected.user_data(),
user_data,
"injection must not disturb the operation's identity"
);
}
#[test]
fn an_injected_failure_preserves_the_identity_a_token_claims_against() {
use crate::{Batch, PushOptions};
use std::os::windows::io::AsRawHandle;
let path = std::env::temp_dir().join(format!(
"windows-ioring-sys-injection-claim-{}.tmp",
std::process::id()
));
std::fs::write(&path, b"hello").expect("create fixture");
let file = std::fs::OpenOptions::new()
.read(true)
.open(&path)
.expect("open fixture");
let mut ring = IoRing::<Vec<u8>>::with_inventory(16, 16).expect("create ring");
let mut batch = Batch::new(&mut ring);
unsafe {
batch.read_raw_owned(
file.as_raw_handle(),
vec![0_u8; 5],
(),
0,
PushOptions::new(),
)
}
.expect("queue a read");
batch.submit_and_wait(1, 30_000).expect("submit and wait");
let (completion, held) = ring
.pop_within(std::time::Duration::from_secs(30))
.expect("pop")
.expect("the read's completion");
completion
.result()
.expect("the read really did succeed, or this test proves nothing");
let injected = completion
.with_injected_failure(crate::InjectedFailure::Ring(crate::RingCondition::Corrupt));
assert!(injected.result().is_err(), "the injected failure applies");
let buffer = held
.map(|(payload, ())| payload.expect("a read carries a buffer"))
.expect("a failed completion still hands back its own payload");
assert_eq!(
buffer, b"hello",
"claiming a failed operation must still hand the buffer back -- that is \
exactly what stops a failure path from leaking it, which is the defect \
this seam exists to let a test reach"
);
let _ = std::fs::remove_file(&path);
}
#[test]
fn an_injected_failure_zeroes_the_transferred_byte_count() {
use crate::{Batch, PushOptions};
use std::os::windows::io::AsRawHandle;
let path = std::env::temp_dir().join(format!(
"windows-ioring-sys-injection-information-{}-{:?}.tmp",
std::process::id(),
std::thread::current().id()
));
std::fs::write(&path, b"hello").expect("create fixture");
let file = std::fs::OpenOptions::new()
.read(true)
.open(&path)
.expect("open fixture");
let mut ring = IoRing::<Vec<u8>>::with_inventory(16, 16).expect("create ring");
let mut batch = Batch::new(&mut ring);
unsafe {
batch.read_raw_owned(
file.as_raw_handle(),
vec![0_u8; 5],
(),
0,
PushOptions::new(),
)
}
.expect("queue a read");
batch.submit_and_wait(1, 30_000).expect("submit and wait");
let completion = super::pop_within(&mut ring, "the fixture read's completion");
assert_eq!(
completion.information, 5,
"the fixture must transfer five real bytes, or this test proves nothing"
);
let injected = completion
.with_injected_failure(crate::InjectedFailure::Ring(crate::RingCondition::Corrupt));
assert_eq!(
injected.information, 0,
"an injected failure must report zero transferred, not the real completion's count"
);
let _ = std::fs::remove_file(&path);
}
#[test]
fn each_spelling_of_a_failure_produces_the_condition_it_names() {
let mut ring = IoRing::new(16, 16).expect("create ring");
let (_, completion) = real_completion(&mut ring);
let ring_failure = completion.with_injected_failure(crate::InjectedFailure::Ring(
crate::RingCondition::SubmissionQueueFull,
));
let error = ring_failure.result().expect_err("fails");
assert!(
crate::IoRingErrorExt::is_submission_queue_full(&error),
"a Ring(..) injection must be recognised by the named predicate"
);
let raw = crate::RingCondition::Corrupt.code();
let hresult_failure = completion.with_injected_failure(crate::InjectedFailure::Hresult(raw));
assert_eq!(
crate::IoRingErrorExt::ring_condition(&hresult_failure.result().expect_err("fails")),
Some(crate::RingCondition::Corrupt),
"a raw HRESULT injection must resolve to the same condition"
);
}
#[test]
#[should_panic(expected = "injects failure only, never success")]
fn injecting_a_success_code_is_refused() {
let mut ring = IoRing::new(16, 16).expect("create ring");
let (_, completion) = real_completion(&mut ring);
let _ = completion.with_injected_failure(crate::InjectedFailure::Hresult(0));
}
#[test]
fn every_named_condition_injects_a_genuine_failure() {
let mut ring = IoRing::new(16, 16).expect("create ring");
let (_, completion) = real_completion(&mut ring);
for condition in [
crate::RingCondition::SubmissionQueueFull,
crate::RingCondition::Corrupt,
crate::RingCondition::VersionNotSupported,
] {
assert!(
completion
.with_injected_failure(crate::InjectedFailure::Ring(condition))
.result()
.is_err(),
"{condition:?} must inject a genuine failure"
);
}
for code in [1_u32, 5, 87, 0xFFFF] {
assert!(
completion
.with_injected_failure(crate::InjectedFailure::Win32(code))
.result()
.is_err(),
"Win32({code}) must inject a genuine failure"
);
}
}
#[test]
fn every_op_maps_to_its_own_win32_opcode() {
use windows_sys::Win32::Storage::FileSystem::{
IORING_OP_CANCEL, IORING_OP_FLUSH, IORING_OP_NOP, IORING_OP_READ,
IORING_OP_REGISTER_BUFFERS, IORING_OP_REGISTER_FILES, IORING_OP_WRITE,
};
assert_eq!(Op::Nop.code(), IORING_OP_NOP);
assert_eq!(Op::Read.code(), IORING_OP_READ);
assert_eq!(Op::Write.code(), IORING_OP_WRITE);
assert_eq!(Op::Flush.code(), IORING_OP_FLUSH);
assert_eq!(Op::Cancel.code(), IORING_OP_CANCEL);
assert_eq!(Op::RegisterFiles.code(), IORING_OP_REGISTER_FILES);
assert_eq!(Op::RegisterBuffers.code(), IORING_OP_REGISTER_BUFFERS);
let mut codes: Vec<_> = Op::ALL.iter().map(|op| op.code()).collect();
codes.sort_unstable();
let before = codes.len();
codes.dedup();
assert_eq!(codes.len(), before, "every Op must have a distinct opcode");
}
#[test]
fn op_support_reads_the_bit_belonging_to_the_op_it_was_asked_about() {
for (index, &op) in Op::ALL.iter().enumerate() {
let only_this_one = OpSupport(1 << index);
assert!(
only_this_one.contains(op),
"{op:?} must be reported supported when its own bit is set"
);
for other in Op::ALL {
if other != op {
assert!(
!only_this_one.contains(other),
"{other:?} must not be reported supported by {op:?}'s bit"
);
}
}
}
let none = OpSupport(0);
for op in Op::ALL {
assert!(!none.contains(op));
}
}
#[test]
fn a_reserved_opcode_is_not_supported() {
let ring = IoRing::new(8, 8).expect("create ring");
assert!(!ring.supports_raw(0xFFFF));
assert!(
ring.supports_raw(Op::Read.code()),
"the same call must still report a real opcode as supported"
);
}
#[test]
fn an_injected_failure_carries_the_condition_it_names() {
use windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED;
let ring = IoRing::new(8, 8).expect("create ring");
let base = Completion::synthetic(9, 0, ring.ring_id());
let win32 = base.with_injected_failure(InjectedFailure::Win32(ERROR_ACCESS_DENIED));
let error = win32.result().expect_err("an injected failure must fail");
assert_eq!(
error
.as_ioring_error()
.expect("an injected failure is an IoRing error")
.code(),
0x8007_0005_u32.cast_signed(),
"Win32(ERROR_ACCESS_DENIED) must become HRESULT_FROM_WIN32 of that code"
);
let hresult = base.with_injected_failure(InjectedFailure::Hresult(-2_147_024_882));
assert_eq!(
hresult
.result()
.expect_err("an injected failure must fail")
.as_ioring_error()
.expect("an injected failure is an IoRing error")
.code(),
-2_147_024_882,
"an HRESULT injection must be passed through unchanged"
);
assert_eq!(win32.user_data(), base.user_data());
assert_eq!(win32.ring_id(), base.ring_id());
}
#[test]
fn the_debug_rendering_names_the_ring_and_its_key_fields() {
let ring = IoRing::new(8, 8).expect("create ring");
let rendering = format!("{ring:?}");
assert!(rendering.contains("IoRing"), "got {rendering}");
assert!(
rendering.contains("version"),
"the version field name must appear: {rendering}"
);
}
use super::{CompletionWait, RingWait, SubmitWait};
fn pop_scratch(tag: &str) -> (std::path::PathBuf, std::fs::File) {
let path = std::env::temp_dir().join(format!(
"windows-ioring-sys-pop-within-{}-{tag}.tmp",
std::process::id()
));
std::fs::write(&path, b"x").expect("create fixture");
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.expect("open fixture");
(path, file)
}
fn push_flushes(ring: &mut IoRing, file: &std::fs::File, count: usize) -> Vec<usize> {
use crate::{Batch, FlushCoverage, FlushMode};
use std::os::windows::io::AsRawHandle;
let mut batch = Batch::new(ring);
let mut ids = Vec::with_capacity(count);
for _ in 0..count {
ids.push(
unsafe {
batch.flush_raw(
file.as_raw_handle(),
FlushCoverage::Unordered,
FlushMode::Default,
)
}
.expect("queue a flush"),
);
}
batch.submit().expect("submit");
ids
}
#[derive(Default)]
struct RecordingWait {
calls: usize,
last_timeout_ms: u32,
outstanding_seen: usize,
}
impl CompletionWait for RecordingWait {
fn wait(&mut self, ring: &mut RingWait<'_>, timeout_ms: u32) -> std::io::Result<()> {
self.calls += 1;
self.last_timeout_ms = timeout_ms;
self.outstanding_seen = ring.outstanding();
std::thread::sleep(std::time::Duration::from_millis(2));
Ok(())
}
}
struct FailingWait;
impl CompletionWait for FailingWait {
fn wait(&mut self, _: &mut RingWait<'_>, _: u32) -> std::io::Result<()> {
Err(std::io::Error::other("the wait refused"))
}
}
struct ImmediateWait;
impl CompletionWait for ImmediateWait {
fn wait(&mut self, _: &mut RingWait<'_>, _: u32) -> std::io::Result<()> {
Ok(())
}
}
#[test]
fn pop_within_returns_the_completion_of_a_real_operation() {
let mut ring = IoRing::new(16, 16).expect("create ring");
let (path, file) = pop_scratch("real");
let ids = push_flushes(&mut ring, &file, 1);
let (completion, _held) = ring
.pop_within(std::time::Duration::from_secs(30))
.expect("pop_within")
.expect("the flush completes well inside the bound");
assert_eq!(
completion.user_data(),
ids[0],
"the completion popped must be the one that was pushed"
);
let _ = std::fs::remove_file(&path);
}
#[test]
fn submit_wait_is_what_the_convenience_uses() {
let mut ring = IoRing::new(16, 16).expect("create ring");
let (path, file) = pop_scratch("submit-wait");
let ids = push_flushes(&mut ring, &file, 1);
let (completion, _held) = ring
.pop_within_with(&mut SubmitWait, std::time::Duration::from_secs(30))
.expect("pop_within_with")
.expect("the flush completes");
assert_eq!(completion.user_data(), ids[0]);
let _ = std::fs::remove_file(&path);
}
#[test]
fn pop_within_returns_successive_completions_one_at_a_time() {
let mut ring = IoRing::new(16, 32).expect("create ring");
let (path, file) = pop_scratch("successive");
let ids = push_flushes(&mut ring, &file, 3);
let mut seen = Vec::new();
for _ in 0..ids.len() {
let (completion, _held) = ring
.pop_within(std::time::Duration::from_secs(30))
.expect("pop_within")
.expect("each flush completes");
seen.push(completion.user_data());
}
seen.sort_unstable();
let mut expected = ids.clone();
expected.sort_unstable();
assert_eq!(
seen, expected,
"every pushed flush must be popped exactly once"
);
assert!(
ring.pop_within(std::time::Duration::ZERO)
.expect("pop_within")
.is_none(),
"the queue is empty once every completion has been taken"
);
let _ = std::fs::remove_file(&path);
}
#[test]
fn a_supplied_wait_is_not_consulted_when_nothing_can_arrive() {
let mut ring = IoRing::new(16, 16).expect("create ring");
let mut wait = RecordingWait::default();
let popped = ring
.pop_within_with(&mut wait, std::time::Duration::from_secs(30))
.expect("pop_within_with");
assert!(popped.is_none());
assert_eq!(
wait.calls, 0,
"nothing can arrive, so there is nothing to wait for"
);
}
#[test]
fn a_supplied_wait_is_consulted_when_the_queue_is_not_ready() {
let mut ring = IoRing::new(16, 16).expect("create ring");
let op = ring.reserve_user_data().expect("reserve");
let mut wait = RecordingWait::default();
let popped = ring.pop_within_with(&mut wait, std::time::Duration::from_millis(40));
ring.record_completion(op);
assert!(popped.expect("pop_within_with").is_none());
assert!(
wait.calls >= 1,
"the supplied wait must be the thing that blocks"
);
}
#[test]
fn the_deadline_is_honoured_when_an_operation_never_completes() {
let mut ring = IoRing::new(16, 16).expect("create ring");
let op = ring.reserve_user_data().expect("reserve");
let mut wait = RecordingWait::default();
let popped = ring.pop_within_with(&mut wait, std::time::Duration::from_millis(40));
ring.record_completion(op);
assert!(
popped.expect("pop_within_with").is_none(),
"no completion was ever going to arrive"
);
assert!(
wait.calls >= 1,
"the bound must be waited out, not short-circuited"
);
}
#[test]
fn a_zero_bound_does_not_block() {
let mut ring = IoRing::new(16, 16).expect("create ring");
let op = ring.reserve_user_data().expect("reserve");
let mut wait = RecordingWait::default();
let popped = ring.pop_within_with(&mut wait, std::time::Duration::ZERO);
ring.record_completion(op);
assert!(popped.expect("pop_within_with").is_none());
assert_eq!(
wait.calls, 0,
"a zero bound is one try_pop, so the wait is never reached"
);
}
#[test]
fn the_wait_is_never_handed_a_zero_timeout() {
let mut ring = IoRing::new(16, 16).expect("create ring");
let op = ring.reserve_user_data().expect("reserve");
let mut wait = RecordingWait::default();
let popped = ring.pop_within_with(&mut wait, std::time::Duration::from_micros(600));
ring.record_completion(op);
assert!(popped.expect("pop_within_with").is_none());
assert!(wait.calls >= 1, "the wait must have been reached at all");
assert!(
wait.last_timeout_ms >= 1,
"a sub-millisecond remainder must be clamped up, never passed as zero"
);
}
#[test]
fn ring_wait_reports_the_rings_outstanding_count() {
let mut ring = IoRing::new(16, 16).expect("create ring");
let a = ring.reserve_user_data().expect("reserve a");
let b = ring.reserve_user_data().expect("reserve b");
let mut wait = RecordingWait::default();
let popped = ring.pop_within_with(&mut wait, std::time::Duration::from_millis(20));
ring.record_completion(a);
ring.record_completion(b);
assert!(popped.expect("pop_within_with").is_none());
assert_eq!(
wait.outstanding_seen, 2,
"RingWait::outstanding must agree with IoRing::outstanding"
);
}
#[test]
fn a_wait_that_fails_ends_the_pop_with_its_error() {
let mut ring = IoRing::new(16, 16).expect("create ring");
let op = ring.reserve_user_data().expect("reserve");
let outcome = ring.pop_within_with(&mut FailingWait, std::time::Duration::from_secs(30));
ring.record_completion(op);
let error = outcome.expect_err("the wait's failure must reach the caller");
assert_eq!(error.to_string(), "the wait refused");
}
#[test]
fn a_wait_that_never_blocks_is_permitted_and_still_terminates() {
let mut ring = IoRing::new(16, 16).expect("create ring");
let op = ring.reserve_user_data().expect("reserve");
let popped = ring.pop_within_with(&mut ImmediateWait, std::time::Duration::from_millis(30));
ring.record_completion(op);
assert!(
popped.expect("pop_within_with").is_none(),
"the deadline is what ends a wait that never blocks"
);
}
#[test]
fn a_bound_the_clock_cannot_represent_reaches_the_wait_rather_than_panicking() {
let mut ring = IoRing::new(16, 16).expect("create ring");
let op = ring.reserve_user_data().expect("reserve");
let outcome = ring.pop_within_with(&mut FailingWait, std::time::Duration::MAX);
ring.record_completion(op);
let error = outcome.expect_err("the wait refuses, which is how this returns at all");
assert_eq!(error.to_string(), "the wait refused");
}
#[test]
fn the_wait_can_be_supplied_as_a_trait_object() {
let mut ring = IoRing::new(16, 16).expect("create ring");
let op = ring.reserve_user_data().expect("reserve");
let wait: &mut dyn CompletionWait = &mut FailingWait;
let outcome = ring.pop_within_with(wait, std::time::Duration::from_secs(30));
ring.record_completion(op);
let error = outcome.expect_err("a trait-object wait still refuses");
assert_eq!(error.to_string(), "the wait refused");
}
#[test]
fn a_completion_that_retires_nothing_is_not_quiescence() {
use super::{Entry, Held};
use crate::OperationId;
use std::time::Duration;
for owned in [false, true] {
let mut ring = IoRing::refused_by_the_kernel();
let first = ring.reserve_user_data().expect("mint the first");
let second = ring.reserve_user_data().expect("mint the second");
if owned {
for user_data in [first, second] {
ring.stow(
OperationId::new(user_data, ring.ring_id()),
Entry {
payload: None,
extra: (),
held: Held::default(),
},
);
}
}
assert!(
!ring.record_completion(usize::MAX),
"foreign (owned={owned})"
);
assert!(ring.record_completion(first), "real (owned={owned})");
if owned {
assert!(ring.reclaim(first).is_some(), "the first entry is retired");
}
assert!(!ring.record_completion(first), "duplicate (owned={owned})");
assert_eq!(
ring.outstanding(),
1,
"the second operation is still owed (owned={owned})"
);
let quiesced = ring
.run_down_within(Duration::from_millis(0))
.expect("a bound that expires is not an error");
assert!(
!quiesced,
"rundown must not report quiescence while an operation is in flight (owned={owned})"
);
assert!(
ring.record_completion(second),
"the second's real completion"
);
if owned {
assert!(
ring.reclaim(second).is_some(),
"the second entry is retired"
);
}
let quiesced = ring
.run_down_within(Duration::from_millis(0))
.expect("nothing is outstanding, so nothing is submitted");
assert!(
quiesced,
"with every identity retired, rundown must report quiescence (owned={owned})"
);
std::mem::forget(ring);
}
}
#[cfg(feature = "kernel-seam")]
struct ForgedCompletion {
user_data: Option<usize>,
}
#[cfg(feature = "kernel-seam")]
impl crate::sys::Responses for ForgedCompletion {
unsafe fn pop(
&mut self,
_ring: *mut std::ffi::c_void,
cqe: *mut windows_sys::Win32::Storage::FileSystem::IORING_CQE,
) -> windows_sys::core::HRESULT {
let Some(user_data) = self.user_data.take() else {
return windows_sys::Win32::Foundation::S_FALSE;
};
unsafe {
cqe.write(windows_sys::Win32::Storage::FileSystem::IORING_CQE {
UserData: user_data,
ResultCode: 0,
Information: 0,
});
}
windows_sys::Win32::Foundation::S_OK
}
}
#[cfg(feature = "kernel-seam")]
#[test]
#[should_panic(expected = "which is not in flight on this ring")]
fn a_completion_this_ring_never_minted_panics() {
let mut ring = IoRing::refused_by_the_kernel();
let _kernel = crate::sys::register(
&ring,
Box::new(ForgedCompletion {
user_data: Some(0xDEAD),
}),
);
let _ = ring.try_pop();
}
#[cfg(feature = "kernel-seam")]
#[test]
#[should_panic(expected = "which is not in flight on this ring")]
fn a_duplicate_of_a_retired_completion_panics() {
let mut ring = IoRing::refused_by_the_kernel();
let op = ring.reserve_user_data().expect("mint one identity");
assert!(ring.record_completion(op), "its one real completion");
let _kernel = crate::sys::register(
&ring,
Box::new(ForgedCompletion {
user_data: Some(op),
}),
);
let _ = ring.try_pop();
}
#[cfg(feature = "kernel-seam")]
#[test]
fn a_completion_this_ring_minted_does_not_panic() {
let mut ring = IoRing::refused_by_the_kernel();
let op = ring.reserve_user_data().expect("mint one identity");
let kernel = crate::sys::register(
&ring,
Box::new(ForgedCompletion {
user_data: Some(op),
}),
);
let (completion, held) = ring
.pop_within(std::time::Duration::from_secs(5))
.expect("the forged pop succeeds")
.expect("one completion was queued");
assert_eq!(completion.user_data(), op);
assert!(held.is_none(), "nothing was stowed for it");
assert_eq!(ring.outstanding(), 0, "it retired the identity it carried");
assert!(kernel.end().is_some());
std::mem::forget(ring);
}
#[cfg(feature = "kernel-seam")]
#[test]
fn an_unminted_completion_during_an_unwind_does_not_panic_again() {
type Seen = std::cell::RefCell<Option<(bool, Result<Option<usize>, String>)>>;
struct PopsWhileUnwinding<'a> {
ring: IoRing,
seen: &'a Seen,
}
impl Drop for PopsWhileUnwinding<'_> {
fn drop(&mut self) {
let popped = self
.ring
.try_pop()
.map(|popped| popped.map(|(completion, _)| completion.user_data()))
.map_err(|error| error.to_string());
*self.seen.borrow_mut() = Some((std::thread::panicking(), popped));
}
}
let seen = Seen::default();
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let ring = IoRing::refused_by_the_kernel();
let _kernel = crate::sys::register(
&ring,
Box::new(ForgedCompletion {
user_data: Some(0xBEEF),
}),
);
let _pops = PopsWhileUnwinding { ring, seen: &seen };
panic!("the original failure");
}));
let payload = outcome.expect_err("the original panic propagates");
assert_eq!(
payload.downcast_ref::<&str>().copied(),
Some("the original failure"),
"the panic that surfaces is the original one, not the defect report"
);
let (unwinding, popped) = seen.take().expect("the drop ran");
assert!(unwinding, "the pop happened during the unwind");
assert_eq!(
popped,
Ok(Some(0xBEEF)),
"the forged completion is still handed back"
);
}