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