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