use windows_sys::Win32::Storage::FileSystem::{
FILE_FLUSH_DATA, FILE_FLUSH_DEFAULT, FILE_FLUSH_MIN_METADATA, FILE_FLUSH_NO_SYNC,
FILE_WRITE_FLAGS_NONE, FILE_WRITE_FLAGS_WRITE_THROUGH, IOSQE_FLAGS_DRAIN_PRECEDING_OPS,
IOSQE_FLAGS_NONE,
};
use super::{Batch, FlushCoverage, FlushMode, PushOptions, WriteCaching};
use crate::IoRing;
#[test]
fn default_push_options_set_no_barrier() {
assert_eq!(PushOptions::new().sqe_flags(), IOSQE_FLAGS_NONE);
}
#[test]
fn a_covering_flush_sets_the_barrier_flag_and_an_unordered_one_does_not() {
assert_eq!(
FlushCoverage::CoversPrecedingOperations.sqe_flags(),
IOSQE_FLAGS_DRAIN_PRECEDING_OPS
);
assert_eq!(FlushCoverage::Unordered.sqe_flags(), IOSQE_FLAGS_NONE);
}
#[test]
fn write_caching_maps_to_the_kernels_write_flags() {
assert_eq!(WriteCaching::Cached.raw(), FILE_WRITE_FLAGS_NONE);
assert_eq!(
WriteCaching::WriteThrough.raw(),
FILE_WRITE_FLAGS_WRITE_THROUGH
);
assert_eq!(WriteCaching::default(), WriteCaching::Cached);
}
#[test]
fn flush_mode_maps_to_the_kernels_flush_modes() {
assert_eq!(FlushMode::Default.raw(), FILE_FLUSH_DEFAULT);
assert_eq!(FlushMode::Data.raw(), FILE_FLUSH_DATA);
assert_eq!(FlushMode::MinMetadata.raw(), FILE_FLUSH_MIN_METADATA);
assert_eq!(FlushMode::NoSync.raw(), FILE_FLUSH_NO_SYNC);
assert_eq!(FlushMode::default(), FlushMode::Default);
}
#[test]
fn drain_preceding_sets_the_barrier_flag() {
assert_eq!(
PushOptions::new().drain_preceding(true).sqe_flags(),
IOSQE_FLAGS_DRAIN_PRECEDING_OPS
);
assert_eq!(
PushOptions::new()
.drain_preceding(true)
.drain_preceding(false)
.sqe_flags(),
IOSQE_FLAGS_NONE
);
}
#[test]
fn dropping_a_batch_that_queued_nothing_submits_harmlessly() {
let mut ring = IoRing::new(8, 8).expect("create ring");
let batch = Batch::new(&mut ring);
drop(batch);
}
use super::{PendingFileRegistration, RegisteredFiles};
use crate::ring::Completion;
fn registered_files(ring: &IoRing) -> RegisteredFiles {
RegisteredFiles {
base_index: 3,
count: 2,
ring_id: ring.ring_id(),
}
}
#[test]
fn registered_files_report_their_extent() {
let ring = IoRing::new(8, 8).expect("create ring");
let files = registered_files(&ring);
assert_eq!(files.len(), 2);
assert!(!files.is_empty());
let empty = RegisteredFiles {
base_index: 0,
count: 0,
ring_id: ring.ring_id(),
};
assert_eq!(empty.len(), 0);
assert!(empty.is_empty());
}
#[test]
fn registered_files_index_from_the_base_and_stop_at_the_end() {
let ring = IoRing::new(8, 8).expect("create ring");
let files = registered_files(&ring);
assert_eq!(files.get(0).expect("index 0 is in range").index(), 3);
assert_eq!(files.get(1).expect("index 1 is in range").index(), 4);
assert!(
files.get(2).is_none(),
"index 2 is one past the end of a two-handle registration"
);
assert!(files.get(u32::MAX).is_none());
}
#[test]
fn a_pending_file_registration_claims_only_its_own_completion() {
let ring = IoRing::new(8, 8).expect("create ring");
let other = IoRing::new(8, 8).expect("create second ring");
let user_data = 41_usize;
let pending = || PendingFileRegistration {
user_data,
base_index: 0,
count: 1,
ring_id: ring.ring_id(),
};
let wrong_op = Completion::synthetic(user_data.wrapping_add(1), 0, ring.ring_id());
assert!(
pending().claim_if(&wrong_op).is_err(),
"a completion naming another operation must be refused"
);
let wrong_ring = Completion::synthetic(user_data, 0, other.ring_id());
assert!(
pending().claim_if(&wrong_ring).is_err(),
"a completion from another ring must be refused even with a matching id"
);
let matching = Completion::synthetic(user_data, 0, ring.ring_id());
let files = pending()
.claim_if(&matching)
.expect("its own completion is accepted")
.expect("the registration succeeded");
assert_eq!(files.len(), 1);
}
#[test]
fn a_pending_file_registration_reports_the_user_data_it_will_claim() {
let ring = IoRing::new(8, 8).expect("create ring");
let pending = PendingFileRegistration {
user_data: 7,
base_index: 0,
count: 1,
ring_id: ring.ring_id(),
};
assert_eq!(pending.user_data(), 7);
let completion = Completion::synthetic(pending.user_data(), 0, ring.ring_id());
assert!(pending.claim_if(&completion).is_ok());
}
#[test]
fn a_pending_buffer_registration_claims_only_its_own_completion() {
let mut ring = IoRing::new(8, 8).expect("create ring");
let other = IoRing::new(8, 8).expect("create second ring");
for expected in 0..2 {
let burned = ring
.accounting_mut()
.reserve_user_data()
.expect("reserve a UserData");
assert_eq!(
burned, expected,
"a fresh ring hands out UserData from zero, in order"
);
ring.record_completion(burned);
}
let mut batch = Batch::new(&mut ring);
let pending = batch
.register_buffers(vec![vec![0_u8; 64]])
.expect("queue buffer registration");
assert_ne!(
pending.user_data(),
0,
"the registration is not operation zero, so a constant zero cannot pass"
);
let user_data = pending.user_data();
batch.submit_and_wait(1, 5_000).expect("submit");
let wrong_ring = Completion::synthetic(user_data, 0, other.ring_id());
let Err(pending) = pending.claim_if(&wrong_ring) else {
panic!("a completion from another ring must be refused");
};
let wrong_op = Completion::synthetic(user_data.wrapping_add(1), 0, ring.ring_id());
let Err(pending) = pending.claim_if(&wrong_op) else {
panic!("a completion naming another operation must be refused");
};
let real = crate::ring::pop_within(&mut ring, "the registration's completion");
assert_eq!(
pending.user_data(),
real.user_data(),
"the reported user_data must be the one the ring completed"
);
let mut buffers = pending
.claim_if(&real)
.expect("its own completion is accepted")
.expect("the registration succeeded");
assert_eq!(buffers.len(), 1);
assert!(!buffers.is_empty());
assert!(buffers.get(0).is_ok());
assert!(
buffers.get(1).is_err(),
"index 1 is one past the end of a one-buffer registration"
);
let mut batch = Batch::new(&mut ring);
let out_of_range = unsafe {
batch.read_registered_raw_owned(
std::ptr::null_mut(),
&buffers,
crate::RegisteredSpan {
buffer_index: 1,
offset: 0,
len: 8,
},
(),
0,
PushOptions::new(),
)
};
assert!(
out_of_range.is_err(),
"a span naming one past the last registered buffer must be refused"
);
}
#[test]
fn submit_reports_how_many_operations_it_queued() {
let path = std::env::temp_dir().join(format!("ioring-submit-count-{}.tmp", std::process::id()));
std::fs::write(&path, vec![1_u8; 4096]).expect("write fixture");
let file = std::fs::OpenOptions::new()
.read(true)
.open(&path)
.expect("open fixture");
let handle = std::os::windows::io::AsRawHandle::as_raw_handle(&file);
let mut ring = IoRing::<Vec<u8>>::with_inventory(16, 16).expect("create ring");
let mut batch = Batch::new(&mut ring);
for index in 0..3_u64 {
unsafe {
batch.read_raw_owned(handle, vec![0_u8; 512], (), index * 512, PushOptions::new())
}
.expect("queue read");
}
assert_eq!(
batch.submit().expect("submit"),
3,
"submit must report the number of operations it queued"
);
let mut popped = 0;
while popped < 3 {
while let Some((completion, held)) = ring.try_pop().expect("pop") {
let _ = completion.result();
assert!(held.is_some(), "the ring was holding this read's buffer");
popped += 1;
}
}
drop(file);
let _ = std::fs::remove_file(&path);
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "RegisteredBuffers dropped while an operation still references it")]
fn dropping_a_registration_with_work_outstanding_is_refused() {
let path = std::env::temp_dir().join(format!("ioring-drop-guard-{}.tmp", std::process::id()));
std::fs::write(&path, vec![2_u8; 4096]).expect("write fixture");
let file = std::fs::OpenOptions::new()
.read(true)
.open(&path)
.expect("open fixture");
let handle = std::os::windows::io::AsRawHandle::as_raw_handle(&file);
let mut ring = IoRing::new(16, 16).expect("create ring");
let mut batch = Batch::new(&mut ring);
let pending = batch
.register_buffers(vec![vec![0_u8; 512]])
.expect("queue buffer registration");
batch.submit_and_wait(1, 5_000).expect("submit");
let completion = crate::ring::pop_within(&mut ring, "the registration's completion");
let buffers = pending
.claim_if(&completion)
.expect("claims its own")
.expect("registration succeeded");
let mut batch = Batch::new(&mut ring);
unsafe {
batch.read_registered_raw_owned(
handle,
&buffers,
crate::RegisteredSpan {
buffer_index: 0,
offset: 0,
len: 512,
},
(),
0,
PushOptions::new(),
)
}
.expect("queue registered read");
batch.submit_and_wait(1, 5_000).expect("submit");
drop(buffers);
}
#[test]
fn require_refuses_an_op_the_ring_does_not_support() {
let mut ring = IoRing::new(8, 8).expect("create ring");
ring.set_supported_ops_for_test(&[crate::Op::Nop]);
let batch = Batch::new(&mut ring);
let error = batch
.require(crate::Op::Read)
.expect_err("Read was left out of the constructed capability set");
assert_eq!(error.kind(), std::io::ErrorKind::Unsupported);
batch
.require(crate::Op::Nop)
.expect("Nop is in the constructed capability set");
}
#[test]
fn windows_refuses_an_empty_buffer_registration() {
let mut ring = IoRing::new(8, 8).expect("create ring");
let mut batch = Batch::new(&mut ring);
let pending = batch
.register_buffers(Vec::<Vec<u8>>::new())
.expect("this crate queues it; the refusal comes from the kernel");
let user_data = pending.user_data();
batch.submit_and_wait(1, 5_000).expect("submit");
let completion = crate::ring::pop_within(&mut ring, "the registration's completion");
assert_eq!(completion.user_data(), user_data);
let Err(error) = pending
.claim_if(&completion)
.expect("its own completion is accepted")
else {
panic!("Windows accepted an empty buffer registration; is_empty is now reachable");
};
let code = crate::IoRingErrorExt::as_ioring_error(&error)
.expect("a kernel refusal carries an HRESULT")
.code();
assert_eq!(
code,
windows_sys::Win32::Foundation::E_INVALIDARG,
"the kernel must refuse an empty registration with E_INVALIDARG, got {error}"
);
}