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 a_ring_negotiates_a_version_no_higher_than_the_hosts_maximum() {
let ring = IoRing::new(64, 128).expect("create ring");
let caps = capabilities().expect("capabilities");
assert!(ring.version() <= caps.max_version);
assert!(ring.version() <= RingVersion::HIGHEST_KNOWN);
}
#[test]
fn a_negotiated_ring_reports_its_version_back_through_get_ring_info() {
let ring = IoRing::new(64, 128).expect("create ring");
let info = ring.info().expect("GetIoRingInfo");
assert_eq!(info.version, ring.version());
}
#[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 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 reserve_user_data_increments_outstanding_and_never_repeats_an_id() {
let mut ring = IoRing::new(64, 128).expect("create ring");
let a = ring.reserve_user_data().expect("reserve a");
let b = ring.reserve_user_data().expect("reserve b");
assert_ne!(a, b);
assert_eq!(ring.outstanding(), 2);
ring.record_completion();
ring.record_completion();
}
#[test]
fn run_down_is_a_no_op_when_nothing_is_outstanding() {
let mut ring = IoRing::new(64, 128).expect("create ring");
ring.run_down().expect("run_down with nothing outstanding");
assert_eq!(ring.outstanding(), 0);
}
#[test]
fn run_down_returns_once_a_recorded_completion_zeroes_the_count() {
let mut ring = IoRing::new(64, 128).expect("create ring");
ring.reserve_user_data().expect("reserve");
assert_eq!(ring.outstanding(), 1);
ring.record_completion();
ring.run_down()
.expect("run_down with the count already settled");
assert_eq!(ring.outstanding(), 0);
}
#[test]
fn record_completion_saturates_rather_than_underflowing() {
let mut ring = IoRing::new(64, 128).expect("create ring");
assert_eq!(ring.outstanding(), 0);
ring.record_completion();
assert_eq!(
ring.outstanding(),
0,
"recording more completions than were ever reserved must not wrap"
);
}
#[test]
fn dropping_a_ring_with_nothing_outstanding_does_not_hang() {
let ring = IoRing::new(64, 128).expect("create ring");
drop(ring);
}
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 = loop {
if let Some(completion) = ring.try_pop().expect("pop") {
break 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::new(16, 16).expect("create ring");
let mut batch = Batch::new(&mut ring);
let token =
unsafe { batch.read_raw(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 = loop {
if let Some(completion) = ring.try_pop().expect("pop") {
break 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 = token
.claim_if(&injected)
.expect("a failed completion still claims its own token");
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 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());
}