Skip to content

Commit

Permalink
add tests and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
fathyb committed Mar 18, 2023
1 parent daea4d4 commit 3b09e68
Show file tree
Hide file tree
Showing 5 changed files with 261 additions and 92 deletions.
3 changes: 2 additions & 1 deletion io-uring-test/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ fn test<S: squeue::EntryMarker, C: cqueue::EntryMarker>(

// register
tests::register::test_register_files_sparse(&mut ring, &test)?;
tests::register::test_register_buffers(&mut ring, &test)?;
tests::register_buffers::test_register_buffers(&mut ring, &test)?;
tests::register_buffers::test_register_buffers_update_tag(&mut ring, &test)?;
tests::register_buf_ring::test_register_buf_ring(&mut ring, &test)?;

// fs
Expand Down
1 change: 1 addition & 0 deletions io-uring-test/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ pub mod poll;
pub mod queue;
pub mod register;
pub mod register_buf_ring;
pub mod register_buffers;
pub mod regression;
pub mod timeout;
91 changes: 0 additions & 91 deletions io-uring-test/src/tests/register.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::io;

use crate::Test;
use io_uring::{cqueue, opcode, squeue, IoUring};

Expand Down Expand Up @@ -70,92 +68,3 @@ pub fn test_register_files_sparse<S: squeue::EntryMarker, C: cqueue::EntryMarker

Ok(())
}

pub fn test_register_buffers<S: squeue::EntryMarker, C: cqueue::EntryMarker>(
ring: &mut IoUring<S, C>,
test: &Test,
) -> anyhow::Result<()> {
test_register("register_buffers_sparse", ring, test, |ring| {
ring.submitter().register_buffers_sparse(4)
})?;
test_register("register_buffers", ring, test, |ring| {
let buf = [0xde, 0xed, 0xbe, 0xef];

unsafe {
ring.submitter().register_buffers(&[libc::iovec {
iov_base: buf.as_ptr() as _,
iov_len: buf.len(),
}])
}
})?;
test_register("register_buffers_tags", ring, test, |ring| {
let buf = [0xde, 0xed, 0xbe, 0xef];

unsafe {
ring.submitter().register_buffers_tags(
&[libc::iovec {
iov_base: buf.as_ptr() as _,
iov_len: buf.len(),
}],
&[0],
)
}
})?;

return Ok(());

fn test_register<
S: squeue::EntryMarker,
C: cqueue::EntryMarker,
F: FnMut(&mut IoUring<S, C>) -> io::Result<()>,
>(
name: &str,
ring: &mut IoUring<S, C>,
test: &Test,
mut register: F,
) -> anyhow::Result<()> {
// register_files_sparse was introduced in kernel 5.19, as was the opcode for UringCmd16.
// So require the UringCmd16 to avoid running this test on earlier kernels.
require!(
test;
test.probe.is_supported(opcode::UringCmd16::CODE);
);

println!("test {name}");

if let Ok(()) = ring.submitter().unregister_buffers() {
return Err(anyhow::anyhow!(
"unregister_buffers should fail if not buffer table has been setup"
));
}

if let Err(e) = register(ring) {
return Err(anyhow::anyhow!("{name} failed: {}", e));
}

// See that same call again, with any value, will fail because a direct table cannot be built
// over an existing one.

if let Ok(()) = register(ring) {
return Err(anyhow::anyhow!(
"{name} should not have succeeded twice in a row"
));
}

// See that the direct table can be removed.

if let Err(e) = ring.submitter().unregister_buffers() {
return Err(anyhow::anyhow!("unregister_buffers failed: {}", e));
}

// See that a second attempt to remove the direct table would fail.

if let Ok(()) = ring.submitter().unregister_buffers() {
return Err(anyhow::anyhow!(
"unregister_buffers should not have succeeded twice in a row"
));
}

Ok(())
}
}
248 changes: 248 additions & 0 deletions io-uring-test/src/tests/register_buffers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
use std::{
fs::File,
io::{self, Error, Write},
os::fd::{AsRawFd, FromRawFd},
};

use crate::Test;
use io_uring::{cqueue, opcode, squeue, types, IoUring};
use libc::EFAULT;

pub fn test_register_buffers<S: squeue::EntryMarker, C: cqueue::EntryMarker>(
ring: &mut IoUring<S, C>,
test: &Test,
) -> anyhow::Result<()> {
let mut buf = [0xde, 0xed, 0xbe, 0xef];
let tags = [0];
let iovecs = [libc::iovec {
iov_len: buf.len(),
iov_base: buf.as_mut_ptr() as _,
}];

test_register(ring, test, "register_buffers", None, |ring| {
ring.submitter().register_buffers(&iovecs)
})?;
test_register(
ring,
test,
"register_buffers_tags",
ring.params().is_feature_resource_tagging(),
|ring| unsafe { ring.submitter().register_buffers_tags(&iovecs, &tags) },
)?;
test_register(
ring,
test,
"register_buffers_sparse",
ring.params().is_feature_resource_tagging(),
|ring| ring.submitter().register_buffers_sparse(4),
)?;

return Ok(());

fn test_register<
S: squeue::EntryMarker,
C: cqueue::EntryMarker,
F: FnMut(&mut IoUring<S, C>) -> io::Result<()>,
P: Into<Option<bool>>,
>(
ring: &mut IoUring<S, C>,
test: &Test,
name: &str,
probe: P,
mut register: F,
) -> anyhow::Result<()> {
require!(
test;
test.probe.is_supported(opcode::ReadFixed::CODE);
probe.into().unwrap_or(true);
);

println!("test {name}");

ring.submitter().unregister_buffers().err().ok_or_else(|| {
anyhow::anyhow!("unregister_buffers should fail if not buffer table has been setup")
})?;

register(ring).map_err(|e| anyhow::anyhow!("{name} failed: {e}"))?;

// See that same call again, with any value, will fail because a direct table cannot be built
// over an existing one.
register(ring)
.err()
.ok_or_else(|| anyhow::anyhow!("{name} should not have succeeded twice in a row"))?;

// See that the direct table can be removed.
ring.submitter()
.unregister_buffers()
.map_err(|e| anyhow::anyhow!("unregister_buffers failed: {e}"))?;

// See that a second attempt to remove the direct table would fail.
ring.submitter().unregister_buffers().err().ok_or_else(|| {
anyhow::anyhow!("unregister_buffers should not have succeeded twice in a row")
})?;

Ok(())
}
}

const BUFFER_TAG: u64 = 0xbadcafe;
const TIMEOUT_TAG: u64 = 0xbadf00d;

pub fn test_register_buffers_update_tag<S: squeue::EntryMarker, C: cqueue::EntryMarker>(
ring: &mut IoUring<S, C>,
test: &Test,
) -> anyhow::Result<()> {
require!(
test;
test.probe.is_supported(opcode::ReadFixed::CODE);
ring.params().is_feature_resource_tagging();
);

println!("test register_buffers_update_tag");

let (read, mut write) = create_pipe()?;
let mut buf = [0xde, 0xed, 0xbe, 0xef];
let mut tags = [BUFFER_TAG];
let iovecs = [libc::iovec {
iov_len: buf.len(),
iov_base: buf.as_mut_ptr() as _,
}];
let timeout = types::Timespec::new().nsec(50 * 1_000_000);
let timeout = opcode::Timeout::new(&timeout as _)
.build()
.user_data(TIMEOUT_TAG)
.into();
let read_sqe = opcode::ReadFixed::new(
types::Fd(read.as_raw_fd()),
buf.as_mut_ptr(),
buf.len() as _,
5,
)
.build()
.user_data(42)
.into();

// Register a buffer table and then immediately unregister it
ring.submitter().register_buffers_sparse(1)?;
ring.submitter().unregister_buffers()?;

// Push a timeout of 50ms
unsafe { ring.submission().push(&timeout).unwrap() };

// We should not receive any other entries than the timeout
check_only_timeout(ring)?;

// Register a sparse buffer table of 10 elements
ring.submitter().register_buffers_sparse(10)?;

// Try read the pipe using a sparse buffer
let cqe = {
write.write("yo".as_bytes())?;

unsafe { ring.submission().push(&read_sqe).unwrap() };

ring.submit_and_wait(1)?;

ring.completion().next().unwrap().into()
};

// We should get the correct user_data
if cqe.user_data() != 42 {
return Err(anyhow::anyhow!("unexpected completion queue entry"));
}

// EFAULT is to be expected with incorrect fixed buffers
if cqe.result() != -EFAULT {
return Err(anyhow::anyhow!("unexpected read result: {}", cqe.result()));
}

// Register a buffer at the index 5
unsafe {
ring.submitter()
.register_buffers_update_tag(5, &iovecs, &tags)?;

// Push a timeout of 50ms
ring.submission().push(&timeout).unwrap();
}

// We should not receive any other entries than the timeout
check_only_timeout(ring)?;

// Register a buffer at the same index 5, but this time with an empty tag.
let cqe = {
tags[0] = 0;

unsafe {
ring.submitter()
.register_buffers_update_tag(5, &iovecs, &tags)?;
}
ring.submit_and_wait(1)?;

ring.completion().next().unwrap().into()
};

// We should get a cqe with the first tag because we registered a
// new buffer at an index where a buffer was already registered.
if cqe.user_data() != BUFFER_TAG {
return Err(anyhow::anyhow!(
"expected completion queue to contain a buffer unregistered event"
));
}

// Try reading now that the buffer is registered at index 5
let cqe = {
unsafe {
ring.submission().push(&read_sqe).unwrap();
}

ring.submit_and_wait(1)?;

ring.completion().next().unwrap().into()
};

// We should get the correct user_data
if cqe.user_data() != 42 {
return Err(anyhow::anyhow!("unexpected completion queue entry"));
}

// We should read exactly two bytes
if cqe.result() != 2 {
return Err(anyhow::anyhow!("unexpected read result: {}", cqe.result()));
}

// The first two bytes of `buf` should be "yo"
if &buf[0..2] != "yo".as_bytes() {
return Err(anyhow::anyhow!("unexpected read buffer data: {:x?}", &buf));
}

return Ok(());
}

/// Create a pipe and return both ends as RAII `File` handles
fn create_pipe() -> io::Result<(File, File)> {
let mut fds = [-1, -1];

unsafe {
if libc::pipe(fds.as_mut_ptr()) == -1 {
Err(Error::last_os_error())
} else {
Ok((File::from_raw_fd(fds[0]), File::from_raw_fd(fds[1])))
}
}
}

/// Submit sqes and asserts the only cqe is a timeout entry
fn check_only_timeout<S: squeue::EntryMarker, C: cqueue::EntryMarker>(
ring: &mut IoUring<S, C>,
) -> Result<(), anyhow::Error> {
ring.submit_and_wait(1)?;

if Into::<cqueue::Entry>::into(ring.completion().next().unwrap()).user_data() == TIMEOUT_TAG {
// There should not be any more entries in the queue
if ring.completion().next().is_none() {
return Ok(());
}
}

Err(anyhow::anyhow!("unexpected completion queue entry"))
}
Loading

0 comments on commit 3b09e68

Please sign in to comment.