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