io_uring: Add basic wrapper
This is a simple RAII wrapper around a ring instance that structure SQE
/ CQE callbacks to make it convenient for C++ usage.
Change-Id: I9db8b48a81bec8d8aff4a362920f4dd688c27d57
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/test/io_uring.cpp b/test/io_uring.cpp
new file mode 100644
index 0000000..5be1b91
--- /dev/null
+++ b/test/io_uring.cpp
@@ -0,0 +1,134 @@
+#include <poll.h>
+
+#include <stdplus/io_uring.hpp>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace stdplus
+{
+
+using testing::_;
+
+class MockHandler : public IoUring::CQEHandler
+{
+ public:
+ MOCK_METHOD(void, handleCQE, (io_uring_cqe&), (noexcept, override));
+};
+
+class IoUringTest : public testing::Test
+{
+ protected:
+ static void SetUpTestCase()
+ {
+ io_uring r;
+ if (io_uring_queue_init(1, &r, 0) == -ENOSYS)
+ {
+ // Not supported, skip running this test
+ exit(77);
+ }
+ io_uring_queue_exit(&r);
+ }
+ std::array<testing::StrictMock<MockHandler>, 2> h;
+ IoUring ring;
+};
+
+TEST_F(IoUringTest, NullHandler)
+{
+ auto& sqe = ring.getSQE();
+ io_uring_prep_nop(&sqe);
+ ring.submit();
+ ring.process();
+}
+
+TEST_F(IoUringTest, HandlerCalled)
+{
+ {
+ auto& sqe = ring.getSQE();
+ io_uring_prep_nop(&sqe);
+ ring.setHandler(sqe, &h[0]);
+ }
+
+ // Nothing should happen without submission
+ ring.process();
+
+ {
+ auto& sqe = ring.getSQE();
+ io_uring_prep_nop(&sqe);
+ ring.setHandler(sqe, &h[1]);
+ }
+
+ // Handle all of the outstanding requests
+ ring.submit();
+ EXPECT_CALL(h[0], handleCQE(_));
+ EXPECT_CALL(h[1], handleCQE(_));
+ ring.process();
+ testing::Mock::VerifyAndClearExpectations(&h[0]);
+ testing::Mock::VerifyAndClearExpectations(&h[1]);
+}
+
+TEST_F(IoUringTest, HandlerReplacement)
+{
+ auto& sqe = ring.getSQE();
+ io_uring_prep_nop(&sqe);
+ ring.setHandler(sqe, &h[0]);
+
+ // Setting a new handler should cancel the old one
+ EXPECT_CALL(h[0], handleCQE(_));
+ ring.setHandler(sqe, &h[1]);
+ testing::Mock::VerifyAndClearExpectations(&h[0]);
+
+ // Setting a null handler should cancel the old one
+ EXPECT_CALL(h[1], handleCQE(_));
+ ring.setHandler(sqe, nullptr);
+ testing::Mock::VerifyAndClearExpectations(&h[1]);
+
+ // Set it back twice and make sure it isn't recognized idempotently
+ ring.setHandler(sqe, &h[1]);
+ ring.setHandler(sqe, &h[1]);
+
+ // Make sure it still works
+ ring.submit();
+ EXPECT_CALL(h[1], handleCQE(_));
+ ring.process();
+ testing::Mock::VerifyAndClearExpectations(&h[1]);
+}
+
+TEST_F(IoUringTest, EventFd)
+{
+ auto& efd = ring.getEventFd();
+
+ for (size_t i = 0; i < h.size(); ++i)
+ {
+ auto& sqe = ring.getSQE();
+ io_uring_prep_nop(&sqe);
+ ring.setHandler(sqe, &h[i]);
+ ring.submit();
+ }
+
+ // Our event fd should become ready
+ pollfd pfd;
+ pfd.fd = efd.get();
+ pfd.events = POLLIN;
+ ASSERT_EQ(1, poll(&pfd, 1, 100));
+
+ // Handle all of the outstanding requests
+ EXPECT_CALL(h[0], handleCQE(_));
+ EXPECT_CALL(h[1], handleCQE(_));
+ ring.processEvents();
+ testing::Mock::VerifyAndClearExpectations(&h[0]);
+ testing::Mock::VerifyAndClearExpectations(&h[1]);
+
+ // Our event fd should be empty
+ ASSERT_EQ(0, poll(&pfd, 1, 100));
+}
+
+TEST_F(IoUringTest, HandleCalledOnDestroy)
+{
+ auto& sqe = ring.getSQE();
+ io_uring_prep_nop(&sqe);
+ ring.setHandler(sqe, &h[0]);
+ EXPECT_CALL(h[0], handleCQE(_));
+}
+
+} // namespace stdplus
diff --git a/test/meson.build b/test/meson.build
index 8a2ae6c..b303032 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -44,6 +44,16 @@
warning('Not testing file descriptor feature')
endif
+if has_io_uring
+ gtests += [
+ 'io_uring',
+ ]
+elif build_tests.enabled()
+ error('Not testing io_uring feature')
+else
+ warning('Not testing io_uring feature')
+endif
+
if gtest.found() and gmock.found()
foreach t : gtests
test(t, executable(t.underscorify(), t + '.cpp',