blob: b96921886c784976ddbcd48dd87edae655296a7e [file] [log] [blame]
Alexei Strots01395492023-03-20 13:59:56 -07001#include "aos/events/logging/log_backend.h"
2
Austin Schuh3ebaf782023-04-07 16:03:28 -07003#include <algorithm>
Alexei Strots01395492023-03-20 13:59:56 -07004#include <filesystem>
Austin Schuh3ebaf782023-04-07 16:03:28 -07005#include <fstream>
6#include <random>
Alexei Strots01395492023-03-20 13:59:56 -07007
Austin Schuh3ebaf782023-04-07 16:03:28 -07008#include "absl/strings/str_cat.h"
9#include "absl/strings/str_join.h"
10#include "aos/containers/resizeable_buffer.h"
Alexei Strots01395492023-03-20 13:59:56 -070011#include "aos/testing/tmpdir.h"
Austin Schuh3ebaf782023-04-07 16:03:28 -070012#include "glog/logging.h"
13#include "gmock/gmock.h"
Alexei Strots01395492023-03-20 13:59:56 -070014#include "gtest/gtest.h"
15
16namespace aos::logger::testing {
17TEST(LogBackendTest, CreateSimpleFile) {
18 const std::string logevent = aos::testing::TestTmpDir() + "/logevent/";
19 FileBackend backend(logevent);
20 auto file = backend.RequestFile("test.log");
21 ASSERT_EQ(file->OpenForWrite(), WriteCode::kOk);
22 auto result = write(file->fd(), "test", 4);
23 EXPECT_GT(result, 0);
24 EXPECT_EQ(file->Close(), WriteCode::kOk);
25 EXPECT_TRUE(std::filesystem::exists(logevent + "test.log"));
26}
27
28TEST(LogBackendTest, CreateRenamableFile) {
29 const std::string logevent = aos::testing::TestTmpDir() + "/logevent/";
30 RenamableFileBackend backend(logevent);
31 auto file = backend.RequestFile("test.log");
32 ASSERT_EQ(file->OpenForWrite(), WriteCode::kOk);
33 auto result = write(file->fd(), "testtest", 8);
34 EXPECT_GT(result, 0);
35 EXPECT_EQ(file->Close(), WriteCode::kOk);
36 EXPECT_TRUE(std::filesystem::exists(logevent + "test.log"));
37}
38
39TEST(LogBackendTest, UseTempRenamableFile) {
40 const std::string logevent = aos::testing::TestTmpDir() + "/logevent/";
41 RenamableFileBackend backend(logevent);
42 backend.EnableTempFiles();
43 auto file = backend.RequestFile("test.log");
44 ASSERT_EQ(file->OpenForWrite(), WriteCode::kOk);
45 auto result = write(file->fd(), "testtest", 8);
46 EXPECT_GT(result, 0);
47 EXPECT_TRUE(std::filesystem::exists(logevent + "test.log.tmp"));
48
49 EXPECT_EQ(file->Close(), WriteCode::kOk);
50 // Check that file is renamed.
51 EXPECT_TRUE(std::filesystem::exists(logevent + "test.log"));
52}
53
54TEST(LogBackendTest, RenameBaseAfterWrite) {
55 const std::string logevent = aos::testing::TestTmpDir() + "/logevent/";
56 RenamableFileBackend backend(logevent);
57 auto file = backend.RequestFile("test.log");
58 ASSERT_EQ(file->OpenForWrite(), WriteCode::kOk);
59 auto result = write(file->fd(), "testtest", 8);
60 EXPECT_GT(result, 0);
61 EXPECT_TRUE(std::filesystem::exists(logevent + "test.log"));
62
63 std::string renamed = aos::testing::TestTmpDir() + "/renamed/";
64 backend.RenameLogBase(renamed);
65
66 EXPECT_FALSE(std::filesystem::exists(logevent + "test.log"));
67 EXPECT_TRUE(std::filesystem::exists(renamed + "test.log"));
68
69 EXPECT_EQ(file->Close(), WriteCode::kOk);
70 // Check that file is renamed.
71 EXPECT_TRUE(std::filesystem::exists(renamed + "test.log"));
72}
73
74TEST(LogBackendTest, UseTestAndRenameBaseAfterWrite) {
75 const std::string logevent = aos::testing::TestTmpDir() + "/logevent/";
76 RenamableFileBackend backend(logevent);
77 backend.EnableTempFiles();
78 auto file = backend.RequestFile("test.log");
79 ASSERT_EQ(file->OpenForWrite(), WriteCode::kOk);
80 auto result = write(file->fd(), "testtest", 8);
81 EXPECT_GT(result, 0);
82 EXPECT_TRUE(std::filesystem::exists(logevent + "test.log.tmp"));
83
84 std::string renamed = aos::testing::TestTmpDir() + "/renamed/";
85 backend.RenameLogBase(renamed);
86
87 EXPECT_FALSE(std::filesystem::exists(logevent + "test.log.tmp"));
88 EXPECT_TRUE(std::filesystem::exists(renamed + "test.log.tmp"));
89
90 EXPECT_EQ(file->Close(), WriteCode::kOk);
91 // Check that file is renamed.
92 EXPECT_TRUE(std::filesystem::exists(renamed + "test.log"));
93}
94
Alexei Strotsa0b99d72023-04-11 15:12:42 -070095TEST(QueueAlignmentTest, Cases) {
96 QueueAligner aligner;
97 uint8_t *start = nullptr;
98 {
99 // Only prefix
100 std::vector<absl::Span<const uint8_t>> queue;
101 const uint8_t *current = start + 1;
102 queue.emplace_back(current, FileHandler::kSector - 2);
103 aligner.FillAlignedQueue(queue);
104 ASSERT_EQ(aligner.aligned_queue().size(), 1);
105 const auto &prefix = aligner.aligned_queue()[0];
106 EXPECT_FALSE(prefix.aligned);
107 EXPECT_EQ(prefix.size, FileHandler::kSector - 2);
108 }
109 {
110 // Only main
111 std::vector<absl::Span<const uint8_t>> queue;
112 const uint8_t *current = start;
113 queue.emplace_back(current, FileHandler::kSector);
114 aligner.FillAlignedQueue(queue);
115 ASSERT_EQ(aligner.aligned_queue().size(), 1);
116 const auto &main = aligner.aligned_queue()[0];
117 EXPECT_TRUE(main.aligned);
118 EXPECT_EQ(main.size, FileHandler::kSector);
119 }
120 {
121 // Empty
122 std::vector<absl::Span<const uint8_t>> queue;
123 const uint8_t *current = start;
124 queue.emplace_back(current, 0);
125 EXPECT_DEATH(aligner.FillAlignedQueue(queue),
126 "Nobody should be sending empty messages");
127 }
128 {
129 // Main and suffix
130 std::vector<absl::Span<const uint8_t>> queue;
131 const uint8_t *current = start;
132 queue.emplace_back(current, FileHandler::kSector + 1);
133 aligner.FillAlignedQueue(queue);
134 ASSERT_EQ(aligner.aligned_queue().size(), 2);
135
136 const auto &main = aligner.aligned_queue()[0];
137 EXPECT_TRUE(main.aligned);
138 EXPECT_EQ(main.size, FileHandler::kSector);
139
140 const auto &suffix = aligner.aligned_queue()[1];
141 EXPECT_FALSE(suffix.aligned);
142 EXPECT_EQ(suffix.size, 1);
143 }
144 {
145 // Prefix, main
146 std::vector<absl::Span<const uint8_t>> queue;
147 const uint8_t *current = start + 1;
148 queue.emplace_back(current, 2 * FileHandler::kSector - 1);
149 aligner.FillAlignedQueue(queue);
150 ASSERT_EQ(aligner.aligned_queue().size(), 2);
151
152 const auto &prefix = aligner.aligned_queue()[0];
153 EXPECT_FALSE(prefix.aligned);
154 EXPECT_EQ(prefix.size, FileHandler::kSector - 1);
155
156 const auto &main = aligner.aligned_queue()[1];
157 EXPECT_TRUE(main.aligned);
158 EXPECT_EQ(main.size, FileHandler::kSector);
159 }
160 {
161 // Prefix and suffix
162 std::vector<absl::Span<const uint8_t>> queue;
163 const uint8_t *current = start + 1;
164 queue.emplace_back(current, 2 * FileHandler::kSector - 2);
165 aligner.FillAlignedQueue(queue);
166 ASSERT_EQ(aligner.aligned_queue().size(), 2);
167
168 const auto &prefix = aligner.aligned_queue()[0];
169 EXPECT_FALSE(prefix.aligned);
170 EXPECT_EQ(prefix.size, FileHandler::kSector - 1);
171
172 const auto &suffix = aligner.aligned_queue()[1];
173 EXPECT_FALSE(suffix.aligned);
174 EXPECT_EQ(suffix.size, FileHandler::kSector - 1);
175 }
176 {
177 // Prefix, main and suffix
178 std::vector<absl::Span<const uint8_t>> queue;
179 const uint8_t *current = start + 1;
180 queue.emplace_back(current, 3 * FileHandler::kSector - 2);
181 aligner.FillAlignedQueue(queue);
182 ASSERT_EQ(aligner.aligned_queue().size(), 3);
183
184 const auto &prefix = aligner.aligned_queue()[0];
185 EXPECT_FALSE(prefix.aligned);
186 EXPECT_EQ(prefix.size, FileHandler::kSector - 1);
187
188 const auto &main = aligner.aligned_queue()[1];
189 EXPECT_TRUE(main.aligned);
190 EXPECT_EQ(main.size, FileHandler::kSector);
191
192 const auto &suffix = aligner.aligned_queue()[2];
193 EXPECT_FALSE(suffix.aligned);
194 EXPECT_EQ(suffix.size, FileHandler::kSector - 1);
195 }
196}
197
Austin Schuh3ebaf782023-04-07 16:03:28 -0700198// It represents calls to Write function (batching of calls and messages) where
199// int values are sizes of each message in the queue.
200using WriteRecipe = std::vector<std::vector<int>>;
201
202struct FileWriteTestBase : public ::testing::Test {
203 uint8_t NextRandom() { return distribution(engine); }
204
205 class AlignedReallocator {
206 public:
207 static void *Realloc(void *old, size_t old_size, size_t new_capacity) {
208 void *new_memory = std::aligned_alloc(512, new_capacity);
209 if (old) {
210 memcpy(new_memory, old, old_size);
211 free(old);
212 }
213 return new_memory;
214 }
215 };
216
217 AllocatorResizeableBuffer<AlignedReallocator> buffer;
218
219 void TestRecipe(const WriteRecipe &recipe) {
220 VLOG(1) << "Starting";
221 for (const std::vector<int> &r : recipe) {
222 VLOG(1) << " chunk " << absl::StrJoin(r, ", ");
223 }
224 size_t requested_size = 0;
225 for (const auto &call : recipe) {
226 for (const auto &message_size : call) {
227 requested_size += message_size;
228 }
229 }
230
231 // Grow the cached buffer if it needs to grow. Building a random buffer is
232 // the most expensive part of the test.
233 if (buffer.size() < requested_size) {
234 // Make sure it is 512 aligned... That makes sure we have the best chance
235 // of transitioning to and from being aligned.
236 buffer.resize((requested_size + FileHandler::kSector - 1) &
237 (~(FileHandler::kSector - 1)));
238 std::generate(std::begin(buffer), std::end(buffer),
239 [this]() { return NextRandom(); });
240 }
241
242 // Back align the data to the buffer so we make sure the contents of the
243 // file keep changing in case a file somehow doesn't get deleted, or
244 // collides with something else.
245 const uint8_t *adjusted_start =
246 buffer.data() + buffer.size() - requested_size;
247
248 // logevent has to end with '/' to be recognized as a folder.
249 const std::string logevent = aos::testing::TestTmpDir() + "/";
250 const auto file = std::filesystem::path(logevent) / "test.log";
251 std::filesystem::remove_all(file);
252 VLOG(1) << "Writing to " << file.c_str();
253
254 FileBackend backend(logevent);
255 auto handler = backend.RequestFile("test.log");
256 ASSERT_EQ(handler->OpenForWrite(), WriteCode::kOk);
257
258 // Build arguments for Write.
259 size_t position = 0;
260 for (const auto &call : recipe) {
261 std::vector<absl::Span<const uint8_t>> queue;
262 for (const auto &message_size : call) {
263 const uint8_t *current = adjusted_start + position;
264 queue.emplace_back(current, message_size);
265 position += message_size;
266 }
267 auto result = handler->Write(queue);
268 EXPECT_EQ(result.code, WriteCode::kOk);
269 EXPECT_EQ(result.messages_written, call.size());
270 }
271
272 ASSERT_EQ(handler->Close(), WriteCode::kOk);
273 EXPECT_TRUE(std::filesystem::exists(file));
274 EXPECT_EQ(std::filesystem::file_size(file), requested_size);
275
276 // Confirm that the contents then match the original buffer.
277 std::ifstream file_stream(file, std::ios::in | std::ios::binary);
278 std::vector<uint8_t> content((std::istreambuf_iterator<char>(file_stream)),
279 std::istreambuf_iterator<char>());
280 ASSERT_EQ(content.size(), requested_size);
281 bool matches = true;
282 for (size_t i = 0; i < content.size(); ++i) {
283 if (content[i] != adjusted_start[i]) {
284 matches = false;
285 break;
286 }
287 }
288 if (!matches) {
289 ASSERT_TRUE(false);
290 }
291 }
292
293 std::random_device random;
294 std::mt19937 engine{random()};
295 std::uniform_int_distribution<uint8_t> distribution{0, 0xFF};
296};
297
298// Tests that random sets of reads and writes always result in all the data
299// being written.
300TEST_F(FileWriteTestBase, RandomTest) {
301 std::mt19937 engine2{random()};
302 std::uniform_int_distribution<int> count_distribution{1, 5};
303
304 // Pick a bunch of lengths that will result in things that add up to multiples
305 // of 512 and end up transitioning across the aligned and unaligned boundary.
306 const std::vector<int> lengths = {
307 0x100b5, 0xff4b, 0x10000, 1024 - 7, 1024 - 6, 1024 - 5, 1024 - 4,
308 1024 - 3, 1024 - 2, 1024 - 1, 1024, 1024 + 1, 1024 + 2, 1024 + 3,
309 1024 + 4, 1024 + 5, 1024 + 6, 1024 + 7, 512 - 7, 512 - 6, 512 - 5,
310 512 - 4, 512 - 3, 512 - 2, 512 - 1, 512, 512 + 1, 512 + 2,
311 512 + 3, 512 + 4, 512 + 5, 512 + 6, 512 + 7};
312 std::uniform_int_distribution<int> lengths_distribution{
313 0, static_cast<int>(lengths.size() - 1)};
314
Austin Schuh9e8df9e2023-05-03 08:28:29 -0700315 for (int i = 0; i < 1000; ++i) {
Austin Schuh3ebaf782023-04-07 16:03:28 -0700316 WriteRecipe recipe;
317 int number_of_writes = count_distribution(engine2);
318 for (int j = 0; j < number_of_writes; ++j) {
319 int number_of_chunks = count_distribution(engine2);
320 std::vector<int> r;
321 for (int k = 0; k < number_of_chunks; ++k) {
322 r.emplace_back(lengths[lengths_distribution(engine2)]);
323 }
324 recipe.emplace_back(std::move(r));
325 }
326
327 TestRecipe(recipe);
328 }
329}
330
331// Test an aligned to unaligned transition to make sure everything works.
332TEST_F(FileWriteTestBase, AlignedToUnaligned) {
333 AllocatorResizeableBuffer<AlignedReallocator> aligned_buffer;
334 AllocatorResizeableBuffer<AlignedReallocator> unaligned_buffer;
335
336 aligned_buffer.resize(FileHandler::kSector * 4);
337 std::generate(std::begin(aligned_buffer), std::end(aligned_buffer),
338 [this]() { return NextRandom(); });
339
340 unaligned_buffer.resize(FileHandler::kSector * 4);
341 std::generate(std::begin(unaligned_buffer), std::end(unaligned_buffer),
342 [this]() { return NextRandom(); });
343
344 const size_t kOffset = 53;
345 absl::Span<const uint8_t> unaligned_span(unaligned_buffer.data() + kOffset,
346 aligned_buffer.size() - kOffset);
347
348 std::vector<absl::Span<const uint8_t>> queue;
349
350 queue.emplace_back(aligned_buffer.data(), aligned_buffer.size());
351 queue.emplace_back(unaligned_span);
352 LOG(INFO) << "Queue 0 " << queue[0].size();
353 LOG(INFO) << "Queue 1 " << queue[1].size();
354
355 const std::string logevent = aos::testing::TestTmpDir() + "/";
356 const auto file = std::filesystem::path(logevent) / "test.log";
357 std::filesystem::remove_all(file);
358 VLOG(1) << "Writing to " << file.c_str();
359
360 FileBackend backend(logevent);
361 auto handler = backend.RequestFile("test.log");
362 ASSERT_EQ(handler->OpenForWrite(), WriteCode::kOk);
363
364 auto result = handler->Write(queue);
365 EXPECT_EQ(result.code, WriteCode::kOk);
366 EXPECT_EQ(result.messages_written, queue.size());
Alexei Strotsa0b99d72023-04-11 15:12:42 -0700367 EXPECT_GT(handler->written_aligned(), 0);
Austin Schuh3ebaf782023-04-07 16:03:28 -0700368
369 ASSERT_EQ(handler->Close(), WriteCode::kOk);
370 EXPECT_TRUE(std::filesystem::exists(file));
371 const size_t requested_size = queue[0].size() + queue[1].size();
372 EXPECT_EQ(std::filesystem::file_size(file), requested_size);
373
374 // Confirm that the contents then match the original buffer.
375 std::ifstream file_stream(file, std::ios::in | std::ios::binary);
376 std::vector<uint8_t> content((std::istreambuf_iterator<char>(file_stream)),
377 std::istreambuf_iterator<char>());
378 ASSERT_EQ(content.size(), requested_size);
379 bool matches = true;
380 for (size_t i = 0; i < queue[0].size(); ++i) {
381 if (content[i] != aligned_buffer.data()[i]) {
382 matches = false;
383 break;
384 }
385 }
386 for (size_t i = 0; i < queue[1].size(); ++i) {
387 if (content[i + queue[0].size()] != unaligned_span.data()[i]) {
388 matches = false;
389 break;
390 }
391 }
392 if (!matches) {
393 ASSERT_TRUE(false);
394 }
395}
396
397struct FileWriteTestFixture : public ::testing::WithParamInterface<WriteRecipe>,
398 public FileWriteTestBase {};
399
400TEST_P(FileWriteTestFixture, CheckSizeOfWrittenFile) {
401 auto recipe = GetParam();
402 TestRecipe(recipe);
403}
404
405// Try out some well known failure cases transitioning across the alignment
406// boundary.
407INSTANTIATE_TEST_SUITE_P(
408 FileWriteTest, FileWriteTestFixture,
409 ::testing::Values(WriteRecipe{{0x10000}}, WriteRecipe{{0x10000, 0x1000b5}},
410 WriteRecipe{{0x10000, 0x1000b5}, {0xfff4b, 0x10000}},
411 WriteRecipe{{0x1000b5, 0xfff4b}, {0x10000}},
412 WriteRecipe{{65536, 517, 65717}},
413 WriteRecipe{{65536, 517, 518, 511},
414 {514},
415 {505, 514},
416 {505, 514, 65355, 519}},
417 WriteRecipe{{65536, 518, 511, 511},
418 {65717},
419 {65717, 65717, 518},
420 {65536, 65536, 508, 65355},
421 {515, 519}},
422 WriteRecipe{{0x1000b5, 0xfff4b, 0x100000}, {0x10000}}));
423
424} // namespace aos::logger::testing