blob: 30516c8ee85cdbe465b9589a1cef08a9d836fcf3 [file] [log] [blame]
William A. Kennington III5c20da22021-06-18 16:44:55 -07001#include <poll.h>
2
William A. Kennington III43e38182021-08-19 17:29:48 -07003#include <array>
William A. Kennington IIIc25b3842022-08-24 16:42:43 -07004#include <charconv>
William A. Kennington III43e38182021-08-19 17:29:48 -07005#include <chrono>
William A. Kennington IIIb95a1282021-08-19 17:46:58 -07006#include <optional>
William A. Kennington III5c20da22021-06-18 16:44:55 -07007#include <stdplus/io_uring.hpp>
William A. Kennington IIIc25b3842022-08-24 16:42:43 -07008#include <stdplus/util/cexec.hpp>
William A. Kennington IIIb95a1282021-08-19 17:46:58 -07009#include <string_view>
William A. Kennington IIIc25b3842022-08-24 16:42:43 -070010#include <sys/utsname.h>
William A. Kennington III5c20da22021-06-18 16:44:55 -070011
12#include <gmock/gmock.h>
13#include <gtest/gtest.h>
14
15namespace stdplus
16{
17
18using testing::_;
19
William A. Kennington IIIc25b3842022-08-24 16:42:43 -070020static std::string_view extractVerNum(std::string_view& str)
21{
22 auto ret = str.substr(0, str.find('.'));
23 str.remove_prefix(ret.size() + (ret.size() == str.size() ? 0 : 1));
24 return ret;
25}
26
27template <typename T>
28static T intFromStr(std::string_view str)
29{
30 T ret;
31 auto res = std::from_chars(str.data(), str.data() + str.size(), ret);
32 if (res.ec != std::errc{} || res.ptr != str.data() + str.size())
33 {
34 throw std::invalid_argument("Bad kernel version");
35 }
36 return ret;
37}
38
39static bool isKernelSafe(std::string_view ver, uint8_t smajor, uint8_t sminor)
40{
41 auto major = intFromStr<uint8_t>(extractVerNum(ver));
42 auto minor = intFromStr<uint8_t>(extractVerNum(ver));
43 return major > smajor || (major == smajor && minor >= sminor);
44}
45
46TEST(KernelInfo, VersionSafe)
47{
48 EXPECT_THROW(isKernelSafe("a", 5, 10), std::invalid_argument);
49 EXPECT_THROW(isKernelSafe("3a.3b.c", 5, 10), std::invalid_argument);
50 EXPECT_FALSE(isKernelSafe("4.11.20-nfd", 5, 10));
51 EXPECT_FALSE(isKernelSafe("4.11.20", 5, 10));
52 EXPECT_FALSE(isKernelSafe("4.11", 5, 10));
53 EXPECT_FALSE(isKernelSafe("5.9.0", 5, 10));
54 EXPECT_TRUE(isKernelSafe("5.10.1", 5, 10));
55 EXPECT_TRUE(isKernelSafe("6.0.0", 5, 10));
56 EXPECT_TRUE(isKernelSafe("6.0.0-abc", 5, 10));
57}
58
59static bool checkKernelSafe(uint8_t smajor, uint8_t sminor)
60{
61 utsname uts;
62 CHECK_ERRNO(uname(&uts), "uname");
63 return isKernelSafe(uts.release, smajor, sminor);
64}
65
William A. Kennington III43e38182021-08-19 17:29:48 -070066TEST(Convert, ChronoToKTS)
67{
68 const auto ns = 700;
69 const auto s = 5;
70 const auto kts =
71 chronoToKTS(std::chrono::nanoseconds(ns) + std::chrono::seconds(s));
72 EXPECT_EQ(kts.tv_sec, s);
73 EXPECT_EQ(kts.tv_nsec, ns);
74}
75
William A. Kennington III5c20da22021-06-18 16:44:55 -070076class MockHandler : public IoUring::CQEHandler
77{
78 public:
79 MOCK_METHOD(void, handleCQE, (io_uring_cqe&), (noexcept, override));
80};
81
82class IoUringTest : public testing::Test
83{
84 protected:
85 static void SetUpTestCase()
86 {
87 io_uring r;
88 if (io_uring_queue_init(1, &r, 0) == -ENOSYS)
89 {
90 // Not supported, skip running this test
91 exit(77);
92 }
93 io_uring_queue_exit(&r);
94 }
95 std::array<testing::StrictMock<MockHandler>, 2> h;
96 IoUring ring;
William A. Kennington IIIb95a1282021-08-19 17:46:58 -070097
98 void testFdWrite(int fd, int flags,
99 std::optional<int> expected_res = std::nullopt)
100 {
101 auto& sqe = ring.getSQE();
102 std::string_view data = "Test\n";
103 io_uring_prep_write(&sqe, fd, data.data(), data.size(), 0);
104 sqe.flags |= flags;
105 ring.setHandler(sqe, &h[0]);
106 ring.submit();
107 ring.wait();
108 EXPECT_CALL(h[0], handleCQE(_)).WillOnce([&](io_uring_cqe& cqe) {
109 EXPECT_EQ(cqe.res, expected_res.value_or(data.size()));
110 });
111 ring.process();
112 testing::Mock::VerifyAndClearExpectations(&h[0]);
113 }
William A. Kennington III5c20da22021-06-18 16:44:55 -0700114};
115
116TEST_F(IoUringTest, NullHandler)
117{
118 auto& sqe = ring.getSQE();
119 io_uring_prep_nop(&sqe);
120 ring.submit();
121 ring.process();
122}
123
124TEST_F(IoUringTest, HandlerCalled)
125{
126 {
127 auto& sqe = ring.getSQE();
128 io_uring_prep_nop(&sqe);
129 ring.setHandler(sqe, &h[0]);
130 }
131
132 // Nothing should happen without submission
133 ring.process();
134
135 {
136 auto& sqe = ring.getSQE();
137 io_uring_prep_nop(&sqe);
138 ring.setHandler(sqe, &h[1]);
139 }
140
141 // Handle all of the outstanding requests
142 ring.submit();
143 EXPECT_CALL(h[0], handleCQE(_));
144 EXPECT_CALL(h[1], handleCQE(_));
145 ring.process();
146 testing::Mock::VerifyAndClearExpectations(&h[0]);
147 testing::Mock::VerifyAndClearExpectations(&h[1]);
148}
149
150TEST_F(IoUringTest, HandlerReplacement)
151{
152 auto& sqe = ring.getSQE();
153 io_uring_prep_nop(&sqe);
154 ring.setHandler(sqe, &h[0]);
155
156 // Setting a new handler should cancel the old one
157 EXPECT_CALL(h[0], handleCQE(_));
158 ring.setHandler(sqe, &h[1]);
159 testing::Mock::VerifyAndClearExpectations(&h[0]);
160
161 // Setting a null handler should cancel the old one
162 EXPECT_CALL(h[1], handleCQE(_));
163 ring.setHandler(sqe, nullptr);
164 testing::Mock::VerifyAndClearExpectations(&h[1]);
165
166 // Set it back twice and make sure it isn't recognized idempotently
167 ring.setHandler(sqe, &h[1]);
168 ring.setHandler(sqe, &h[1]);
169
170 // Make sure it still works
171 ring.submit();
172 EXPECT_CALL(h[1], handleCQE(_));
173 ring.process();
174 testing::Mock::VerifyAndClearExpectations(&h[1]);
175}
176
177TEST_F(IoUringTest, EventFd)
178{
179 auto& efd = ring.getEventFd();
180
181 for (size_t i = 0; i < h.size(); ++i)
182 {
183 auto& sqe = ring.getSQE();
184 io_uring_prep_nop(&sqe);
185 ring.setHandler(sqe, &h[i]);
186 ring.submit();
187 }
188
189 // Our event fd should become ready
190 pollfd pfd;
191 pfd.fd = efd.get();
192 pfd.events = POLLIN;
193 ASSERT_EQ(1, poll(&pfd, 1, 100));
194
195 // Handle all of the outstanding requests
196 EXPECT_CALL(h[0], handleCQE(_));
197 EXPECT_CALL(h[1], handleCQE(_));
198 ring.processEvents();
199 testing::Mock::VerifyAndClearExpectations(&h[0]);
200 testing::Mock::VerifyAndClearExpectations(&h[1]);
201
202 // Our event fd should be empty
203 ASSERT_EQ(0, poll(&pfd, 1, 100));
204}
205
William A. Kennington III43e38182021-08-19 17:29:48 -0700206TEST_F(IoUringTest, Wait)
207{
208 auto& sqe = ring.getSQE();
209 auto kts = chronoToKTS(std::chrono::milliseconds(1));
210 io_uring_prep_timeout(&sqe, &kts, 0, 0);
211 ring.setHandler(sqe, &h[0]);
212 ring.submit();
213 ring.wait(std::chrono::seconds(1));
214 EXPECT_CALL(h[0], handleCQE(_));
215}
216
William A. Kennington III5c20da22021-06-18 16:44:55 -0700217TEST_F(IoUringTest, HandleCalledOnDestroy)
218{
219 auto& sqe = ring.getSQE();
220 io_uring_prep_nop(&sqe);
221 ring.setHandler(sqe, &h[0]);
222 EXPECT_CALL(h[0], handleCQE(_));
223}
224
William A. Kennington IIIabc6be72021-08-17 01:58:27 -0700225TEST_F(IoUringTest, RegisterFiles)
226{
William A. Kennington IIIc25b3842022-08-24 16:42:43 -0700227 // Earlier kernels had buggy support for registered files
228 if (!checkKernelSafe(5, 10))
229 {
230 GTEST_SKIP();
231 }
232
William A. Kennington IIIb95a1282021-08-19 17:46:58 -0700233 std::optional<IoUring::FileHandle> fh;
234
William A. Kennington IIIabc6be72021-08-17 01:58:27 -0700235 // Slots are always allocated linearly and re-used if invalidated
William A. Kennington IIId1b35222022-07-14 14:18:22 -0700236 fh = ring.registerFile(STDERR_FILENO);
William A. Kennington III1afd7732021-08-20 00:49:10 -0700237 EXPECT_GT(ring.getFiles().size(), 1);
William A. Kennington IIIb95a1282021-08-19 17:46:58 -0700238 EXPECT_EQ(*fh, 0);
William A. Kennington IIId1b35222022-07-14 14:18:22 -0700239 fh = ring.registerFile(STDOUT_FILENO);
William A. Kennington IIIb95a1282021-08-19 17:46:58 -0700240 EXPECT_EQ(*fh, 1);
William A. Kennington IIId1b35222022-07-14 14:18:22 -0700241 EXPECT_GT(ring.getFiles().size(), 2);
242 EXPECT_EQ(ring.getFiles()[0], -1);
243 EXPECT_EQ(ring.getFiles()[1], STDOUT_FILENO);
William A. Kennington IIIb95a1282021-08-19 17:46:58 -0700244
William A. Kennington IIIabc6be72021-08-17 01:58:27 -0700245 // The first handle should have dropped and can be replaced
William A. Kennington IIId1b35222022-07-14 14:18:22 -0700246 fh = ring.registerFile(STDERR_FILENO);
William A. Kennington IIIb95a1282021-08-19 17:46:58 -0700247 EXPECT_EQ(*fh, 0);
William A. Kennington IIId1b35222022-07-14 14:18:22 -0700248 EXPECT_GT(ring.getFiles().size(), 1);
249 EXPECT_EQ(ring.getFiles()[0], STDERR_FILENO);
250 EXPECT_EQ(ring.getFiles()[1], -1);
William A. Kennington IIIb95a1282021-08-19 17:46:58 -0700251
252 // We should be able to write to stderr via the fixed file and regular fd
William A. Kennington IIId1b35222022-07-14 14:18:22 -0700253 testFdWrite(STDERR_FILENO, 0);
William A. Kennington IIIb95a1282021-08-19 17:46:58 -0700254 testFdWrite(*fh, IOSQE_FIXED_FILE);
255
256 // Without registration we should only be able to write to the regular fd
257 fh.reset();
William A. Kennington IIId1b35222022-07-14 14:18:22 -0700258 testFdWrite(STDERR_FILENO, 0);
William A. Kennington IIIb95a1282021-08-19 17:46:58 -0700259 testFdWrite(*fh, IOSQE_FIXED_FILE, -EBADF);
260
261 std::vector<IoUring::FileHandle> fhs;
William A. Kennington III1afd7732021-08-20 00:49:10 -0700262 EXPECT_LT(ring.getFiles().size(), 9);
263 ring.reserveFiles(9);
264 EXPECT_EQ(ring.getFiles().size(), 9);
265 for (size_t i = 0; i < 9; ++i)
William A. Kennington IIIb95a1282021-08-19 17:46:58 -0700266 {
William A. Kennington IIId1b35222022-07-14 14:18:22 -0700267 fhs.emplace_back(ring.registerFile(STDERR_FILENO));
William A. Kennington IIIb95a1282021-08-19 17:46:58 -0700268 testFdWrite(fhs.back(), IOSQE_FIXED_FILE);
269 }
William A. Kennington III1afd7732021-08-20 00:49:10 -0700270 EXPECT_EQ(ring.getFiles().size(), 9);
William A. Kennington IIId1b35222022-07-14 14:18:22 -0700271 fhs.emplace_back(ring.registerFile(STDERR_FILENO));
William A. Kennington III1afd7732021-08-20 00:49:10 -0700272 testFdWrite(fhs.back(), IOSQE_FIXED_FILE);
273 EXPECT_GE(ring.getFiles().size(), 10);
William A. Kennington IIIabc6be72021-08-17 01:58:27 -0700274}
275
William A. Kennington III5c20da22021-06-18 16:44:55 -0700276} // namespace stdplus