add syscall unit tests
Add tests for io_uring_setup, io_uring_register and io_uring_enter. The test coverage is nowhere near complete and the reporting is not uniform. But, it's a start. Signed-off-by: Jeff Moyer <jmoyer@redhat.com> Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
282
test/io_uring_enter.c
Normal file
282
test/io_uring_enter.c
Normal file
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* io_uring_enter.c
|
||||
*
|
||||
* Description: Unit tests for the io_uring_enter system call.
|
||||
*
|
||||
* Copyright 2019, Red Hat, Inc.
|
||||
* Author: Jeff Moyer <jmoyer@redhat.com>
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/sysinfo.h>
|
||||
#include <poll.h>
|
||||
#include <assert.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/mman.h>
|
||||
#include <linux/mman.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
#include <limits.h>
|
||||
#include <sys/time.h>
|
||||
#include "../src/liburing.h"
|
||||
#include "../src/barrier.h"
|
||||
|
||||
#define IORING_MAX_ENTRIES 4096
|
||||
|
||||
int
|
||||
expect_failed_submit(struct io_uring *ring, int error)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = io_uring_submit(ring);
|
||||
if (ret == 1) {
|
||||
printf("expected failure, but io_uring_submit succeeded.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (errno != error) {
|
||||
printf("expected %d, got %d\n", error, errno);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
expect_fail(int fd, unsigned int to_submit, unsigned int min_complete,
|
||||
unsigned int flags, sigset_t *sig, int error)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = io_uring_enter(fd, to_submit, min_complete, flags, sig);
|
||||
if (ret != -1) {
|
||||
printf("expected %s, but call succeeded\n", strerror(error));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (errno != error) {
|
||||
printf("expected %d, got %d\n", error, errno);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
try_io_uring_enter(int fd, unsigned int to_submit, unsigned int min_complete,
|
||||
unsigned int flags, sigset_t *sig, int expect, int error)
|
||||
{
|
||||
int ret;
|
||||
|
||||
printf("io_uring_enter(%d, %u, %u, %u, %p)\n", fd, to_submit,
|
||||
min_complete, flags, sig);
|
||||
|
||||
if (expect == -1)
|
||||
return expect_fail(fd, to_submit, min_complete,
|
||||
flags, sig, error);
|
||||
|
||||
ret = io_uring_enter(fd, to_submit, min_complete, flags, sig);
|
||||
if (ret != expect) {
|
||||
printf("Expected %d, got %d\n", expect, errno);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* prep a read I/O. index is treated like a block number.
|
||||
*/
|
||||
int
|
||||
setup_file(off_t len)
|
||||
{
|
||||
int fd, ret;
|
||||
static char template[32] = "/tmp/io_uring_enter-test.XXXXXX";
|
||||
char buf[4096];
|
||||
|
||||
fd = mkstemp(template);
|
||||
if (fd < 0) {
|
||||
perror("mkstemp");
|
||||
exit(1);
|
||||
}
|
||||
ret = ftruncate(fd, len);
|
||||
if (ret < 0) {
|
||||
perror("ftruncate");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
ret = read(fd, buf, 4096);
|
||||
if (ret != 4096) {
|
||||
printf("read returned %d, expected 4096\n", ret);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
void
|
||||
io_prep_read(struct io_uring_sqe *sqe, int fd, off_t offset, size_t len)
|
||||
{
|
||||
struct iovec *iov;
|
||||
|
||||
iov = malloc(sizeof(*iov));
|
||||
assert(iov);
|
||||
|
||||
iov->iov_base = malloc(len);
|
||||
assert(iov->iov_base);
|
||||
iov->iov_len = len;
|
||||
|
||||
io_uring_prep_readv(sqe, fd, iov, 1, offset);
|
||||
io_uring_sqe_set_data(sqe, iov); // free on completion
|
||||
}
|
||||
|
||||
void
|
||||
reap_events(struct io_uring *ring, unsigned nr)
|
||||
{
|
||||
int ret;
|
||||
unsigned left = nr;
|
||||
struct io_uring_cqe *cqe;
|
||||
struct iovec *iov;
|
||||
struct timeval start, now, elapsed;
|
||||
|
||||
printf("Reaping %u I/Os\n", nr);
|
||||
gettimeofday(&start, NULL);
|
||||
while (left) {
|
||||
ret = io_uring_wait_completion(ring, &cqe);
|
||||
if (ret < 0) {
|
||||
printf("io_uring_wait_completion returned %d\n", ret);
|
||||
printf("expected success\n");
|
||||
exit(1);
|
||||
}
|
||||
if (cqe->res != 4096)
|
||||
printf("cqe->res: %d, expected 4096\n", cqe->res);
|
||||
iov = (struct iovec *)cqe->user_data;
|
||||
free(iov->iov_base);
|
||||
free(iov);
|
||||
left--;
|
||||
|
||||
gettimeofday(&now, NULL);
|
||||
timersub(&now, &start, &elapsed);
|
||||
if (elapsed.tv_sec > 10) {
|
||||
printf("Timed out waiting for I/Os to complete.\n");
|
||||
printf("%u expected, %u completed\n", nr, left);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
submit_io(struct io_uring *ring, unsigned nr)
|
||||
{
|
||||
int fd, ret;
|
||||
off_t file_len;
|
||||
unsigned i;
|
||||
struct io_uring_sqe *sqe;
|
||||
|
||||
printf("Allocating %u sqes\n", nr);
|
||||
file_len = nr * 4096;
|
||||
fd = setup_file(file_len);
|
||||
for (i = 0; i < nr; i++) {
|
||||
/* allocate an sqe */
|
||||
sqe = io_uring_get_sqe(ring);
|
||||
/* fill it in */
|
||||
io_prep_read(sqe, fd, i * 4096, 4096);
|
||||
}
|
||||
|
||||
/* submit the I/Os */
|
||||
printf("Submitting %u I/Os\n", nr);
|
||||
ret = io_uring_submit(ring);
|
||||
if (ret < 0) {
|
||||
perror("io_uring_enter");
|
||||
exit(1);
|
||||
}
|
||||
printf("Done\n");
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int ret;
|
||||
unsigned int status = 0;
|
||||
struct io_uring ring;
|
||||
struct io_uring_sq *sq = &ring.sq;
|
||||
unsigned ktail, mask, index;
|
||||
unsigned sq_entries;
|
||||
unsigned completed, dropped;
|
||||
|
||||
ret = io_uring_queue_init(IORING_MAX_ENTRIES, &ring, 0);
|
||||
if (ret < 0) {
|
||||
perror("io_uring_queue_init");
|
||||
exit(1);
|
||||
}
|
||||
mask = *sq->kring_mask;
|
||||
|
||||
/* invalid flags */
|
||||
status |= try_io_uring_enter(ring.ring_fd, 1, 0, ~0U, NULL, -1, EINVAL);
|
||||
|
||||
/* invalid fd, EBADF */
|
||||
status |= try_io_uring_enter(-1, 0, 0, 0, NULL, -1, EBADF);
|
||||
|
||||
/* valid, non-ring fd, EOPNOTSUPP */
|
||||
status |= try_io_uring_enter(0, 0, 0, 0, NULL, -1, EOPNOTSUPP);
|
||||
|
||||
/* to_submit: 0, flags: 0; should get back 0. */
|
||||
status |= try_io_uring_enter(ring.ring_fd, 1, 0, 0, NULL, 0, 0);
|
||||
|
||||
/* fill the sq ring */
|
||||
sq_entries = *ring.sq.kring_entries;
|
||||
submit_io(&ring, sq_entries);
|
||||
printf("Waiting for %u events\n", sq_entries);
|
||||
ret = io_uring_enter(ring.ring_fd, 0, sq_entries,
|
||||
IORING_ENTER_GETEVENTS, NULL);
|
||||
if (ret < 0) {
|
||||
perror("io_uring_enter");
|
||||
status = 1;
|
||||
} else {
|
||||
/*
|
||||
* This is a non-IOPOLL ring, which means that io_uring_enter
|
||||
* should not return until min_complete events are available
|
||||
* in the completion queue.
|
||||
*/
|
||||
completed = *ring.cq.ktail - *ring.cq.khead;
|
||||
if (completed != sq_entries) {
|
||||
printf("Submitted %u I/Os, but only got %u completions\n",
|
||||
sq_entries, completed);
|
||||
status = 1;
|
||||
}
|
||||
reap_events(&ring, sq_entries);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add an invalid index to the submission queue. This should
|
||||
* result in the dropped counter increasing.
|
||||
*/
|
||||
printf("Submitting invalid sqe index.\n");
|
||||
index = *sq->kring_entries + 1; // invalid index
|
||||
dropped = *sq->kdropped;
|
||||
ktail = *sq->ktail;
|
||||
sq->array[ktail & mask] = index;
|
||||
++ktail;
|
||||
write_barrier();
|
||||
*sq->ktail = ktail;
|
||||
write_barrier();
|
||||
|
||||
ret = io_uring_enter(ring.ring_fd, 1, 0, 0, NULL);
|
||||
/* now check to see if our sqe was dropped */
|
||||
if (*sq->kdropped == dropped) {
|
||||
printf("dropped counter did not increase\n");
|
||||
status = 1;
|
||||
}
|
||||
|
||||
if (!status) {
|
||||
printf("PASS\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("FAIL\n");
|
||||
return -1;
|
||||
}
|
||||
Reference in New Issue
Block a user