xref: /aosp_15_r20/system/update_engine/liburing_cpp/tests/BasicTests.cpp (revision 5a9231315b4521097b8dc3750bc806fcafe0c72f)
1 //
2 // Copyright (C) 2022 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 #include <gtest/gtest.h>
18 #include <liburing_cpp/IoUring.h>
19 
20 #include <linux/fs.h>
21 #include <stdio.h>
22 
23 #include <fcntl.h>
24 #include <sys/mman.h>
25 #include <sys/utsname.h>
26 #include <unistd.h>
27 
28 #include <algorithm>
29 #include <array>
30 #include <cstring>
31 #include <vector>
32 
33 using namespace io_uring_cpp;
34 
35 class IoUringTest : public ::testing::Test {
36  public:
IoUringTest()37   IoUringTest() { fp = tmpfile(); }
~IoUringTest()38   ~IoUringTest() {
39     if (fp) {
40       fclose(fp);
41     }
42   }
SetUp()43   void SetUp() override {
44     struct utsname buffer {};
45 
46     ASSERT_EQ(uname(&buffer), 0)
47         << strerror(errno) << "Failed to get kernel version number";
48     int major = 0;
49     int minor = 0;
50     const auto matched = sscanf(buffer.release, "%d.%d", &major, &minor);
51     ASSERT_EQ(matched, 2) << "Unexpected kernel version format: "
52                           << buffer.release;
53 
54     if (major < 5 || (major == 5 && minor < 6)) {
55       GTEST_SKIP() << "Kernel version does not support io_uring "
56                    << buffer.release;
57       return;
58     }
59 
60     ring = IoUringInterface::CreateLinuxIoUring(4096, 0);
61     ASSERT_NE(ring, nullptr);
62   }
Write(int fd,const void * data,const size_t len)63   void Write(int fd, const void* data, const size_t len) {
64     const auto buf = static_cast<const char*>(data);
65     constexpr size_t IO_BATCH_SIZE = 4096;
66     size_t i = 0;
67     for (i = 0; i < len; i += IO_BATCH_SIZE) {
68       const auto sqe = ring->PrepWrite(fd, buf + i, IO_BATCH_SIZE, i);
69       ASSERT_TRUE(sqe.IsOk());
70     }
71     const auto bytes_remaining = len - i;
72     if (bytes_remaining) {
73       ASSERT_TRUE(ring->PrepWrite(fd, buf + i, bytes_remaining, i).IsOk());
74     }
75     const auto ret = ring->Submit();
76     ASSERT_TRUE(ret.IsOk()) << ret.ErrMsg();
77     for (size_t i = (len + IO_BATCH_SIZE - 1) / IO_BATCH_SIZE; i > 0; i--) {
78       const auto cqe = ring->PopCQE();
79       ASSERT_TRUE(cqe.IsOk());
80       ASSERT_GT(cqe.GetResult().res, 0);
81     }
82   }
83   std::unique_ptr<IoUringInterface> ring;
84   FILE* fp = nullptr;
85 };
86 
TEST_F(IoUringTest,SmallRead)87 TEST_F(IoUringTest, SmallRead) {
88   int fd = open("/proc/self/maps", O_RDONLY);
89   std::array<char, 1024> buf{};
90   const auto sqe = ring->PrepRead(fd, buf.data(), buf.size(), 0);
91   ASSERT_TRUE(sqe.IsOk()) << "Submission Queue is full!";
92   const auto ret = ring->Submit();
93   ASSERT_TRUE(ret.IsOk()) << ret.ErrMsg();
94   const auto cqe = ring->PopCQE();
95   ASSERT_TRUE(cqe.IsOk()) << cqe.GetError();
96   ASSERT_GT(cqe.GetResult().res, 0);
97 }
98 
TEST_F(IoUringTest,SmallWrite)99 TEST_F(IoUringTest, SmallWrite) {
100   auto fp = tmpfile();
101   int fd = fileno(fp);
102   std::string buffer(256, 'A');
103   const auto sqe = ring->PrepWrite(fd, buffer.data(), buffer.size(), 0);
104   ASSERT_TRUE(sqe.IsOk()) << "Submission Queue is full!";
105   const auto ret = ring->Submit();
106   ASSERT_TRUE(ret.IsOk()) << ret.ErrMsg();
107   const auto cqe = ring->PopCQE();
108   ASSERT_TRUE(cqe.IsOk()) << cqe.GetError();
109 
110   const auto bytes_read = pread(fd, buffer.data(), buffer.size(), 0);
111 
112   ASSERT_EQ(bytes_read, buffer.size());
113 
114   ASSERT_TRUE(std::all_of(buffer.begin(), buffer.end(), [](const auto& a) {
115     return a == 'A';
116   })) << buffer;
117   fclose(fp);
118 }
119 
TEST_F(IoUringTest,ChunkedWrite)120 TEST_F(IoUringTest, ChunkedWrite) {
121   int fd = fileno(fp);
122   std::string buffer(16 * 1024 * 1024, 'A');
123   ASSERT_NO_FATAL_FAILURE(Write(fd, buffer.data(), buffer.size()));
124 
125   const auto bytes_read = pread(fd, buffer.data(), buffer.size(), 0);
126 
127   ASSERT_EQ(bytes_read, buffer.size());
128 
129   ASSERT_TRUE(std::all_of(buffer.begin(), buffer.end(), [](const auto& a) {
130     return a == 'A';
131   })) << buffer;
132 }
133 
134 // Page size doesn't really matter. We can replace 4096 with any value.
135 static constexpr size_t kBlockSize = 4096;
GetArbitraryPageData()136 constexpr std::array<unsigned char, 4096> GetArbitraryPageData() {
137   std::array<unsigned char, kBlockSize> arr{};
138   int i = 0;
139   for (auto& a : arr) {
140     a = i++;
141   }
142   return arr;
143 }
144 
WriteTestData(int fd,const size_t offset,const size_t size)145 void WriteTestData(int fd, const size_t offset, const size_t size) {
146   ASSERT_EQ(size % kBlockSize, 0);
147   static const auto data = GetArbitraryPageData();
148   size_t bytes_written = 0;
149   size_t cur_offset = offset;
150   while (bytes_written < size) {
151     const auto ret = pwrite(fd, data.data(), kBlockSize, cur_offset);
152     ASSERT_GT(ret, 0) << "Failed to pwrite " << strerror(errno);
153     bytes_written += ret;
154     cur_offset += ret;
155   }
156 }
157 
TEST_F(IoUringTest,ExtentRead)158 TEST_F(IoUringTest, ExtentRead) {
159   const int fd = fileno(fp);
160   ASSERT_NO_FATAL_FAILURE(WriteTestData(fd, kBlockSize * 3, kBlockSize));
161   ASSERT_NO_FATAL_FAILURE(WriteTestData(fd, kBlockSize * 5, kBlockSize));
162   ASSERT_NO_FATAL_FAILURE(WriteTestData(fd, kBlockSize * 8, kBlockSize));
163   ASSERT_NO_FATAL_FAILURE(WriteTestData(fd, kBlockSize * 13, kBlockSize));
164   fsync(fd);
165 
166   std::vector<unsigned char> data;
167   data.resize(kBlockSize * 4);
168 
169   ASSERT_TRUE(
170       ring->PrepRead(fd, data.data(), kBlockSize, 3 * kBlockSize).IsOk());
171   ASSERT_TRUE(
172       ring->PrepRead(fd, data.data() + kBlockSize, kBlockSize, 5 * kBlockSize)
173           .IsOk());
174   ASSERT_TRUE(
175       ring->PrepRead(
176               fd, data.data() + kBlockSize * 2, kBlockSize, 8 * kBlockSize)
177           .IsOk());
178   ASSERT_TRUE(
179       ring->PrepRead(
180               fd, data.data() + kBlockSize * 3, kBlockSize, 13 * kBlockSize)
181           .IsOk());
182   ring->SubmitAndWait(4);
183   const auto cqes = ring->PopCQE(4);
184   if (cqes.IsErr()) {
185     FAIL() << cqes.GetError().ErrMsg();
186     return;
187   }
188   for (const auto& cqe : cqes.GetResult()) {
189     ASSERT_GT(cqe.res, 0);
190   }
191   for (int i = 0; i < data.size(); ++i) {
192     ASSERT_EQ(data[i], i % 256);
193   }
194 }