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