blob: 0603b3361f043431026db4ab2564e18713b4a37a [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
Austin Schuh95460cc2023-06-26 11:53:10 -070028MATCHER_P(FileEq, o, "") { return arg.name == o.name && arg.size == o.size; }
29
Alexei Strots01395492023-03-20 13:59:56 -070030TEST(LogBackendTest, CreateSimpleFile) {
31 const std::string logevent = aos::testing::TestTmpDir() + "/logevent/";
Alexei Strotsf08b8fb2023-04-21 19:46:08 -070032 const std::string filename = "test.bfbs";
colleen61276dc2023-06-01 09:23:29 -070033 FileBackend backend(logevent, false);
Alexei Strotsf08b8fb2023-04-21 19:46:08 -070034 auto file = backend.RequestFile(filename);
Alexei Strots01395492023-03-20 13:59:56 -070035 ASSERT_EQ(file->OpenForWrite(), WriteCode::kOk);
Alexei Strotsbc082d82023-05-03 08:43:42 -070036 auto result = Write(file.get(), "test");
37 EXPECT_EQ(result.code, WriteCode::kOk);
38 EXPECT_EQ(result.messages_written, 1);
Alexei Strots01395492023-03-20 13:59:56 -070039 EXPECT_EQ(file->Close(), WriteCode::kOk);
Alexei Strotsf08b8fb2023-04-21 19:46:08 -070040 EXPECT_TRUE(std::filesystem::exists(logevent + filename));
41
Austin Schuh95460cc2023-06-26 11:53:10 -070042 EXPECT_THAT(backend.ListFiles(),
43 ::testing::ElementsAre(FileEq(LogSource::File{
44 .name = filename,
45 .size = 4,
46 })));
Alexei Strotsf08b8fb2023-04-21 19:46:08 -070047
48 auto decoder = backend.GetDecoder(filename);
49 std::vector<uint8_t> buffer;
50 buffer.resize(10);
51 const auto count = decoder->Read(buffer.data(), buffer.data() + 10);
52 ASSERT_EQ(count, 4);
53 buffer.resize(4);
54 std::string view(buffer.begin(), buffer.end());
55 EXPECT_EQ(view, "test");
Alexei Strots01395492023-03-20 13:59:56 -070056}
57
58TEST(LogBackendTest, CreateRenamableFile) {
59 const std::string logevent = aos::testing::TestTmpDir() + "/logevent/";
colleen61276dc2023-06-01 09:23:29 -070060 RenamableFileBackend backend(logevent, false);
Alexei Strots01395492023-03-20 13:59:56 -070061 auto file = backend.RequestFile("test.log");
62 ASSERT_EQ(file->OpenForWrite(), WriteCode::kOk);
Alexei Strotsbc082d82023-05-03 08:43:42 -070063 auto result = Write(file.get(), "test");
64 EXPECT_EQ(result.code, WriteCode::kOk);
65 EXPECT_EQ(result.messages_written, 1);
Alexei Strots01395492023-03-20 13:59:56 -070066 EXPECT_EQ(file->Close(), WriteCode::kOk);
67 EXPECT_TRUE(std::filesystem::exists(logevent + "test.log"));
68}
69
70TEST(LogBackendTest, UseTempRenamableFile) {
71 const std::string logevent = aos::testing::TestTmpDir() + "/logevent/";
colleen61276dc2023-06-01 09:23:29 -070072 RenamableFileBackend backend(logevent, false);
Alexei Strots01395492023-03-20 13:59:56 -070073 backend.EnableTempFiles();
74 auto file = backend.RequestFile("test.log");
75 ASSERT_EQ(file->OpenForWrite(), WriteCode::kOk);
Alexei Strotsbc082d82023-05-03 08:43:42 -070076 auto result = Write(file.get(), "test");
77 EXPECT_EQ(result.code, WriteCode::kOk);
78 EXPECT_EQ(result.messages_written, 1);
Alexei Strots01395492023-03-20 13:59:56 -070079 EXPECT_TRUE(std::filesystem::exists(logevent + "test.log.tmp"));
80
81 EXPECT_EQ(file->Close(), WriteCode::kOk);
82 // Check that file is renamed.
83 EXPECT_TRUE(std::filesystem::exists(logevent + "test.log"));
84}
85
86TEST(LogBackendTest, RenameBaseAfterWrite) {
87 const std::string logevent = aos::testing::TestTmpDir() + "/logevent/";
colleen61276dc2023-06-01 09:23:29 -070088 RenamableFileBackend backend(logevent, false);
Alexei Strots01395492023-03-20 13:59:56 -070089 auto file = backend.RequestFile("test.log");
90 ASSERT_EQ(file->OpenForWrite(), WriteCode::kOk);
Alexei Strotsbc082d82023-05-03 08:43:42 -070091 auto result = Write(file.get(), "test");
92 EXPECT_EQ(result.code, WriteCode::kOk);
93 EXPECT_EQ(result.messages_written, 1);
Alexei Strots01395492023-03-20 13:59:56 -070094 EXPECT_TRUE(std::filesystem::exists(logevent + "test.log"));
95
96 std::string renamed = aos::testing::TestTmpDir() + "/renamed/";
97 backend.RenameLogBase(renamed);
98
99 EXPECT_FALSE(std::filesystem::exists(logevent + "test.log"));
100 EXPECT_TRUE(std::filesystem::exists(renamed + "test.log"));
101
102 EXPECT_EQ(file->Close(), WriteCode::kOk);
103 // Check that file is renamed.
104 EXPECT_TRUE(std::filesystem::exists(renamed + "test.log"));
105}
106
107TEST(LogBackendTest, UseTestAndRenameBaseAfterWrite) {
108 const std::string logevent = aos::testing::TestTmpDir() + "/logevent/";
colleen61276dc2023-06-01 09:23:29 -0700109 RenamableFileBackend backend(logevent, false);
Alexei Strots01395492023-03-20 13:59:56 -0700110 backend.EnableTempFiles();
111 auto file = backend.RequestFile("test.log");
112 ASSERT_EQ(file->OpenForWrite(), WriteCode::kOk);
Alexei Strotsbc082d82023-05-03 08:43:42 -0700113 auto result = Write(file.get(), "test");
114 EXPECT_EQ(result.code, WriteCode::kOk);
115 EXPECT_EQ(result.messages_written, 1);
Alexei Strots01395492023-03-20 13:59:56 -0700116 EXPECT_TRUE(std::filesystem::exists(logevent + "test.log.tmp"));
117
118 std::string renamed = aos::testing::TestTmpDir() + "/renamed/";
119 backend.RenameLogBase(renamed);
120
121 EXPECT_FALSE(std::filesystem::exists(logevent + "test.log.tmp"));
122 EXPECT_TRUE(std::filesystem::exists(renamed + "test.log.tmp"));
123
124 EXPECT_EQ(file->Close(), WriteCode::kOk);
125 // Check that file is renamed.
126 EXPECT_TRUE(std::filesystem::exists(renamed + "test.log"));
127}
128
Alexei Strotsa0b99d72023-04-11 15:12:42 -0700129TEST(QueueAlignmentTest, Cases) {
130 QueueAligner aligner;
Philipp Schradera279df42023-12-21 10:50:39 -0800131
132 // Get a 512-byte-aligned pointer to a buffer. That buffer needs to be at
133 // least 3 sectors big for the purposes of this test.
134 uint8_t buffer[FileHandler::kSector * 4];
135 void *aligned_start = buffer;
136 size_t size = sizeof(buffer);
137 ASSERT_TRUE(std::align(FileHandler::kSector, FileHandler::kSector * 3,
138 aligned_start, size) != nullptr);
139 ASSERT_GE(size, FileHandler::kSector * 3);
140
141 uint8_t *start = static_cast<uint8_t *>(aligned_start);
Alexei Strotsa0b99d72023-04-11 15:12:42 -0700142 {
143 // Only prefix
144 std::vector<absl::Span<const uint8_t>> queue;
145 const uint8_t *current = start + 1;
146 queue.emplace_back(current, FileHandler::kSector - 2);
147 aligner.FillAlignedQueue(queue);
148 ASSERT_EQ(aligner.aligned_queue().size(), 1);
149 const auto &prefix = aligner.aligned_queue()[0];
150 EXPECT_FALSE(prefix.aligned);
151 EXPECT_EQ(prefix.size, FileHandler::kSector - 2);
152 }
153 {
154 // Only main
155 std::vector<absl::Span<const uint8_t>> queue;
156 const uint8_t *current = start;
157 queue.emplace_back(current, FileHandler::kSector);
158 aligner.FillAlignedQueue(queue);
159 ASSERT_EQ(aligner.aligned_queue().size(), 1);
160 const auto &main = aligner.aligned_queue()[0];
161 EXPECT_TRUE(main.aligned);
162 EXPECT_EQ(main.size, FileHandler::kSector);
163 }
164 {
165 // Empty
166 std::vector<absl::Span<const uint8_t>> queue;
167 const uint8_t *current = start;
168 queue.emplace_back(current, 0);
169 EXPECT_DEATH(aligner.FillAlignedQueue(queue),
170 "Nobody should be sending empty messages");
171 }
172 {
173 // Main and suffix
174 std::vector<absl::Span<const uint8_t>> queue;
175 const uint8_t *current = start;
176 queue.emplace_back(current, FileHandler::kSector + 1);
177 aligner.FillAlignedQueue(queue);
178 ASSERT_EQ(aligner.aligned_queue().size(), 2);
179
180 const auto &main = aligner.aligned_queue()[0];
181 EXPECT_TRUE(main.aligned);
182 EXPECT_EQ(main.size, FileHandler::kSector);
183
184 const auto &suffix = aligner.aligned_queue()[1];
185 EXPECT_FALSE(suffix.aligned);
186 EXPECT_EQ(suffix.size, 1);
187 }
188 {
189 // Prefix, main
190 std::vector<absl::Span<const uint8_t>> queue;
191 const uint8_t *current = start + 1;
192 queue.emplace_back(current, 2 * FileHandler::kSector - 1);
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 &main = aligner.aligned_queue()[1];
201 EXPECT_TRUE(main.aligned);
202 EXPECT_EQ(main.size, FileHandler::kSector);
203 }
204 {
205 // Prefix and suffix
206 std::vector<absl::Span<const uint8_t>> queue;
207 const uint8_t *current = start + 1;
208 queue.emplace_back(current, 2 * FileHandler::kSector - 2);
209 aligner.FillAlignedQueue(queue);
210 ASSERT_EQ(aligner.aligned_queue().size(), 2);
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 &suffix = aligner.aligned_queue()[1];
217 EXPECT_FALSE(suffix.aligned);
218 EXPECT_EQ(suffix.size, FileHandler::kSector - 1);
219 }
220 {
221 // Prefix, main and suffix
222 std::vector<absl::Span<const uint8_t>> queue;
223 const uint8_t *current = start + 1;
224 queue.emplace_back(current, 3 * FileHandler::kSector - 2);
225 aligner.FillAlignedQueue(queue);
226 ASSERT_EQ(aligner.aligned_queue().size(), 3);
227
228 const auto &prefix = aligner.aligned_queue()[0];
229 EXPECT_FALSE(prefix.aligned);
230 EXPECT_EQ(prefix.size, FileHandler::kSector - 1);
231
232 const auto &main = aligner.aligned_queue()[1];
233 EXPECT_TRUE(main.aligned);
234 EXPECT_EQ(main.size, FileHandler::kSector);
235
236 const auto &suffix = aligner.aligned_queue()[2];
237 EXPECT_FALSE(suffix.aligned);
238 EXPECT_EQ(suffix.size, FileHandler::kSector - 1);
239 }
240}
241
Austin Schuh3ebaf782023-04-07 16:03:28 -0700242// It represents calls to Write function (batching of calls and messages) where
243// int values are sizes of each message in the queue.
244using WriteRecipe = std::vector<std::vector<int>>;
245
246struct FileWriteTestBase : public ::testing::Test {
247 uint8_t NextRandom() { return distribution(engine); }
248
Austin Schuhd9336bc2024-04-29 18:41:23 -0700249 AllocatorResizeableBuffer<AlignedReallocator<aos::logger::FileHandler::kSector
Austin Schuh3ebaf782023-04-07 16:03:28 -0700250
Austin Schuhd9336bc2024-04-29 18:41:23 -0700251 >>
252 buffer;
Austin Schuh3ebaf782023-04-07 16:03:28 -0700253
254 void TestRecipe(const WriteRecipe &recipe) {
255 VLOG(1) << "Starting";
256 for (const std::vector<int> &r : recipe) {
257 VLOG(1) << " chunk " << absl::StrJoin(r, ", ");
258 }
259 size_t requested_size = 0;
260 for (const auto &call : recipe) {
261 for (const auto &message_size : call) {
262 requested_size += message_size;
263 }
264 }
265
266 // Grow the cached buffer if it needs to grow. Building a random buffer is
267 // the most expensive part of the test.
268 if (buffer.size() < requested_size) {
269 // Make sure it is 512 aligned... That makes sure we have the best chance
270 // of transitioning to and from being aligned.
271 buffer.resize((requested_size + FileHandler::kSector - 1) &
272 (~(FileHandler::kSector - 1)));
273 std::generate(std::begin(buffer), std::end(buffer),
274 [this]() { return NextRandom(); });
275 }
276
277 // Back align the data to the buffer so we make sure the contents of the
278 // file keep changing in case a file somehow doesn't get deleted, or
279 // collides with something else.
280 const uint8_t *adjusted_start =
281 buffer.data() + buffer.size() - requested_size;
282
283 // logevent has to end with '/' to be recognized as a folder.
284 const std::string logevent = aos::testing::TestTmpDir() + "/";
285 const auto file = std::filesystem::path(logevent) / "test.log";
286 std::filesystem::remove_all(file);
287 VLOG(1) << "Writing to " << file.c_str();
288
colleen61276dc2023-06-01 09:23:29 -0700289 FileBackend backend(logevent, false);
Austin Schuh3ebaf782023-04-07 16:03:28 -0700290 auto handler = backend.RequestFile("test.log");
291 ASSERT_EQ(handler->OpenForWrite(), WriteCode::kOk);
292
293 // Build arguments for Write.
294 size_t position = 0;
295 for (const auto &call : recipe) {
296 std::vector<absl::Span<const uint8_t>> queue;
297 for (const auto &message_size : call) {
298 const uint8_t *current = adjusted_start + position;
299 queue.emplace_back(current, message_size);
300 position += message_size;
301 }
302 auto result = handler->Write(queue);
303 EXPECT_EQ(result.code, WriteCode::kOk);
304 EXPECT_EQ(result.messages_written, call.size());
305 }
306
307 ASSERT_EQ(handler->Close(), WriteCode::kOk);
308 EXPECT_TRUE(std::filesystem::exists(file));
309 EXPECT_EQ(std::filesystem::file_size(file), requested_size);
310
311 // Confirm that the contents then match the original buffer.
312 std::ifstream file_stream(file, std::ios::in | std::ios::binary);
313 std::vector<uint8_t> content((std::istreambuf_iterator<char>(file_stream)),
314 std::istreambuf_iterator<char>());
315 ASSERT_EQ(content.size(), requested_size);
316 bool matches = true;
317 for (size_t i = 0; i < content.size(); ++i) {
318 if (content[i] != adjusted_start[i]) {
319 matches = false;
320 break;
321 }
322 }
323 if (!matches) {
324 ASSERT_TRUE(false);
325 }
326 }
327
328 std::random_device random;
329 std::mt19937 engine{random()};
330 std::uniform_int_distribution<uint8_t> distribution{0, 0xFF};
331};
332
333// Tests that random sets of reads and writes always result in all the data
334// being written.
335TEST_F(FileWriteTestBase, RandomTest) {
336 std::mt19937 engine2{random()};
337 std::uniform_int_distribution<int> count_distribution{1, 5};
338
339 // Pick a bunch of lengths that will result in things that add up to multiples
340 // of 512 and end up transitioning across the aligned and unaligned boundary.
341 const std::vector<int> lengths = {
342 0x100b5, 0xff4b, 0x10000, 1024 - 7, 1024 - 6, 1024 - 5, 1024 - 4,
343 1024 - 3, 1024 - 2, 1024 - 1, 1024, 1024 + 1, 1024 + 2, 1024 + 3,
344 1024 + 4, 1024 + 5, 1024 + 6, 1024 + 7, 512 - 7, 512 - 6, 512 - 5,
345 512 - 4, 512 - 3, 512 - 2, 512 - 1, 512, 512 + 1, 512 + 2,
346 512 + 3, 512 + 4, 512 + 5, 512 + 6, 512 + 7};
347 std::uniform_int_distribution<int> lengths_distribution{
348 0, static_cast<int>(lengths.size() - 1)};
349
Austin Schuh9e8df9e2023-05-03 08:28:29 -0700350 for (int i = 0; i < 1000; ++i) {
Austin Schuh3ebaf782023-04-07 16:03:28 -0700351 WriteRecipe recipe;
352 int number_of_writes = count_distribution(engine2);
353 for (int j = 0; j < number_of_writes; ++j) {
354 int number_of_chunks = count_distribution(engine2);
355 std::vector<int> r;
356 for (int k = 0; k < number_of_chunks; ++k) {
357 r.emplace_back(lengths[lengths_distribution(engine2)]);
358 }
359 recipe.emplace_back(std::move(r));
360 }
361
362 TestRecipe(recipe);
363 }
364}
365
366// Test an aligned to unaligned transition to make sure everything works.
367TEST_F(FileWriteTestBase, AlignedToUnaligned) {
Austin Schuhd9336bc2024-04-29 18:41:23 -0700368 AllocatorResizeableBuffer<AlignedReallocator<512>> aligned_buffer;
369 AllocatorResizeableBuffer<AlignedReallocator<512>> unaligned_buffer;
Austin Schuh3ebaf782023-04-07 16:03:28 -0700370
371 aligned_buffer.resize(FileHandler::kSector * 4);
372 std::generate(std::begin(aligned_buffer), std::end(aligned_buffer),
373 [this]() { return NextRandom(); });
374
375 unaligned_buffer.resize(FileHandler::kSector * 4);
376 std::generate(std::begin(unaligned_buffer), std::end(unaligned_buffer),
377 [this]() { return NextRandom(); });
378
379 const size_t kOffset = 53;
380 absl::Span<const uint8_t> unaligned_span(unaligned_buffer.data() + kOffset,
381 aligned_buffer.size() - kOffset);
382
383 std::vector<absl::Span<const uint8_t>> queue;
384
385 queue.emplace_back(aligned_buffer.data(), aligned_buffer.size());
386 queue.emplace_back(unaligned_span);
387 LOG(INFO) << "Queue 0 " << queue[0].size();
388 LOG(INFO) << "Queue 1 " << queue[1].size();
389
390 const std::string logevent = aos::testing::TestTmpDir() + "/";
391 const auto file = std::filesystem::path(logevent) / "test.log";
392 std::filesystem::remove_all(file);
393 VLOG(1) << "Writing to " << file.c_str();
394
colleen61276dc2023-06-01 09:23:29 -0700395 FileBackend backend(logevent, false);
Austin Schuh3ebaf782023-04-07 16:03:28 -0700396 auto handler = backend.RequestFile("test.log");
397 ASSERT_EQ(handler->OpenForWrite(), WriteCode::kOk);
398
399 auto result = handler->Write(queue);
400 EXPECT_EQ(result.code, WriteCode::kOk);
401 EXPECT_EQ(result.messages_written, queue.size());
Alexei Strotsbc082d82023-05-03 08:43:42 -0700402 FileHandler *file_handler = reinterpret_cast<FileHandler *>(handler.get());
403 EXPECT_GT(file_handler->written_aligned(), 0);
Austin Schuh3ebaf782023-04-07 16:03:28 -0700404
405 ASSERT_EQ(handler->Close(), WriteCode::kOk);
406 EXPECT_TRUE(std::filesystem::exists(file));
407 const size_t requested_size = queue[0].size() + queue[1].size();
408 EXPECT_EQ(std::filesystem::file_size(file), requested_size);
409
410 // Confirm that the contents then match the original buffer.
411 std::ifstream file_stream(file, std::ios::in | std::ios::binary);
412 std::vector<uint8_t> content((std::istreambuf_iterator<char>(file_stream)),
413 std::istreambuf_iterator<char>());
414 ASSERT_EQ(content.size(), requested_size);
415 bool matches = true;
416 for (size_t i = 0; i < queue[0].size(); ++i) {
417 if (content[i] != aligned_buffer.data()[i]) {
418 matches = false;
419 break;
420 }
421 }
422 for (size_t i = 0; i < queue[1].size(); ++i) {
423 if (content[i + queue[0].size()] != unaligned_span.data()[i]) {
424 matches = false;
425 break;
426 }
427 }
428 if (!matches) {
429 ASSERT_TRUE(false);
430 }
431}
432
433struct FileWriteTestFixture : public ::testing::WithParamInterface<WriteRecipe>,
434 public FileWriteTestBase {};
435
436TEST_P(FileWriteTestFixture, CheckSizeOfWrittenFile) {
437 auto recipe = GetParam();
438 TestRecipe(recipe);
439}
440
441// Try out some well known failure cases transitioning across the alignment
442// boundary.
443INSTANTIATE_TEST_SUITE_P(
444 FileWriteTest, FileWriteTestFixture,
445 ::testing::Values(WriteRecipe{{0x10000}}, WriteRecipe{{0x10000, 0x1000b5}},
446 WriteRecipe{{0x10000, 0x1000b5}, {0xfff4b, 0x10000}},
447 WriteRecipe{{0x1000b5, 0xfff4b}, {0x10000}},
448 WriteRecipe{{65536, 517, 65717}},
449 WriteRecipe{{65536, 517, 518, 511},
450 {514},
451 {505, 514},
452 {505, 514, 65355, 519}},
453 WriteRecipe{{65536, 518, 511, 511},
454 {65717},
455 {65717, 65717, 518},
456 {65536, 65536, 508, 65355},
457 {515, 519}},
458 WriteRecipe{{0x1000b5, 0xfff4b, 0x100000}, {0x10000}}));
459
460} // namespace aos::logger::testing