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