blob: ca1cf423dda71d810ce6ae51461189597d8ea5ff [file] [log] [blame]
James Kuszmaulf5eb4682023-09-22 17:16:59 -07001#include "aos/flatbuffers/static_flatbuffers.h"
2
3#include "absl/strings/str_format.h"
4#include "absl/strings/str_join.h"
5#include "external/com_github_google_flatbuffers/src/annotated_binary_text_gen.h"
6#include "external/com_github_google_flatbuffers/src/binary_annotator.h"
7#include "gmock/gmock.h"
8#include "gtest/gtest.h"
9
10#include "aos/flatbuffers.h"
11#include "aos/flatbuffers/builder.h"
12#include "aos/flatbuffers/interesting_schemas.h"
13#include "aos/flatbuffers/test_dir/type_coverage_static.h"
14#include "aos/flatbuffers/test_schema.h"
15#include "aos/flatbuffers/test_static.h"
16#include "aos/json_to_flatbuffer.h"
17#include "aos/testing/path.h"
18#include "aos/testing/tmpdir.h"
19#include "aos/util/file.h"
20
21namespace aos::fbs::testing {
22
23namespace {
24// Uses the binary schema to annotate a provided flatbuffer. Returns the
25// annotated flatbuffer.
26std::string AnnotateBinaries(
27 const aos::NonSizePrefixedFlatbuffer<reflection::Schema> &schema,
28 flatbuffers::span<uint8_t> binary_data) {
29 flatbuffers::BinaryAnnotator binary_annotator(
30 schema.span().data(), schema.span().size(), binary_data.data(),
31 binary_data.size());
32
33 auto annotations = binary_annotator.Annotate();
34 const std::string schema_filename =
35 aos::testing::TestTmpDir() + "/schema.bfbs";
36
37 aos::WriteFlatbufferToFile(schema_filename, schema);
38
39 flatbuffers::AnnotatedBinaryTextGenerator text_generator(
40 flatbuffers::AnnotatedBinaryTextGenerator::Options{}, annotations,
41 binary_data.data(), binary_data.size());
42
43 text_generator.Generate(aos::testing::TestTmpDir() + "/foo.bfbs",
44 schema_filename);
45
46 return aos::util::ReadFileToStringOrDie(aos::testing::TestTmpDir() +
47 "/foo.afb");
48}
49const reflection::Object *GetObjectByName(const reflection::Schema *schema,
50 std::string_view name) {
51 for (const reflection::Object *object : *schema->objects()) {
52 if (object->name()->string_view() == name) {
53 return object;
54 }
55 }
56 return nullptr;
57}
James Kuszmaul1c9693f2023-12-08 09:45:26 -080058
59// Accesses all the values in the supplied span. Used to ensure that memory
60// sanitizers can observe uninitialized memory.
61void TestMemory(std::span<uint8_t> memory) {
62 std::stringstream str;
63 internal::DebugBytes(memory, str);
64 EXPECT_LT(0u, str.view().size());
65}
James Kuszmaulf5eb4682023-09-22 17:16:59 -070066} // namespace
67
68class StaticFlatbuffersTest : public ::testing::Test {
69 protected:
70 template <typename T>
71 void VerifyJson(const std::string_view data) {
72 Builder<T> json_builder = aos::JsonToStaticFlatbuffer<T>(data);
73
74 EXPECT_EQ(data, aos::FlatbufferToJson(json_builder.AsFlatbufferSpan(),
75 {.multi_line = true}));
76 }
77 aos::FlatbufferSpan<reflection::Schema> test_schema_{TestTableSchema()};
78 aos::FlatbufferSpan<reflection::Schema> interesting_schemas_{
79 UnsupportedSchema()};
80};
81
82// Test that compiles the same code that is used by an example in
83// //aos/documentation/aos/docs/flatbuffers.md.
84TEST_F(StaticFlatbuffersTest, DocumentationExample) {
85 aos::fbs::VectorAllocator allocator;
86 Builder<TestTableStatic> builder(&allocator);
87 TestTableStatic *object = builder.get();
88 object->set_scalar(123);
89 {
90 auto vector = object->add_vector_of_scalars();
91 CHECK(vector->emplace_back(4));
92 CHECK(vector->emplace_back(5));
93 }
94 {
95 auto string = object->add_string();
96 string->SetString("Hello, World!");
97 }
98 {
99 auto vector_of_strings = object->add_vector_of_strings();
100 auto sub_string = CHECK_NOTNULL(vector_of_strings->emplace_back());
101 CHECK(sub_string->emplace_back('D'));
102 }
103 { object->set_substruct({971, 254}); }
104 {
105 auto subtable = object->add_subtable();
106 subtable->set_foo(1234);
107 }
108 {
109 auto vector = object->add_vector_of_structs();
110 CHECK(vector->emplace_back({48, 67}));
111 CHECK(vector->emplace_back({118, 148}));
112 CHECK(vector->emplace_back({971, 973}));
113 // Max vector size is three; this should fail.
114 CHECK(!vector->emplace_back({1114, 2056}));
115 }
116 {
117 auto vector = object->add_vector_of_tables();
118 auto subobject = vector->emplace_back();
119 subobject->set_foo(222);
120 }
121 {
122 auto subtable = object->add_included_table();
123 subtable->set_foo(included::TestEnum::B);
124 }
125 ASSERT_TRUE(builder.AsFlatbufferSpan().Verify());
126 LOG(INFO) << aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
127 {.multi_line = true});
128 LOG(INFO) << AnnotateBinaries(test_schema_, builder.buffer());
129}
130
131// Test that compiles the same code that is used by an example in
132// //aos/documentation/aos/docs/flatbuffers.md showing how to convert a
133// Populate*() method for adding a subtable to a flatbuffer.
134namespace {
135flatbuffers::Offset<SubTable> PopulateOld(flatbuffers::FlatBufferBuilder *fbb) {
136 SubTable::Builder builder(*fbb);
137 builder.add_foo(1234);
138 return builder.Finish();
139}
140void PopulateStatic(SubTableStatic *subtable) { subtable->set_foo(1234); }
141} // namespace
142TEST_F(StaticFlatbuffersTest, PopulateMethodConversionExample) {
143 // Using a FlatBufferBuilder:
144 flatbuffers::FlatBufferBuilder fbb;
145 // Note: the PopulateOld() *must* be called prior to creating the builder.
146 const flatbuffers::Offset<SubTable> subtable_offset = PopulateOld(&fbb);
147 TestTable::Builder testtable_builder(fbb);
148 testtable_builder.add_subtable(subtable_offset);
149 fbb.Finish(testtable_builder.Finish());
150 aos::FlatbufferDetachedBuffer<TestTable> fbb_finished = fbb.Release();
151
152 // Using the static flatbuffer API.
153 aos::fbs::VectorAllocator allocator;
154 Builder<TestTableStatic> static_builder(&allocator);
155 PopulateStatic(CHECK_NOTNULL(static_builder.get()->add_subtable()));
156
157 // And confirm that they both contain the expected flatbuffer:
158 const std::string expected = R"json({ "subtable": { "foo": 1234 } })json";
159 EXPECT_EQ(expected, aos::FlatbufferToJson(fbb_finished));
160 EXPECT_EQ(expected, aos::FlatbufferToJson(static_builder.AsFlatbufferSpan()));
161}
162
163TEST_F(StaticFlatbuffersTest, UnsupportedSchema) {
164 const reflection::Schema *schema = &interesting_schemas_.message();
165 EXPECT_DEATH(
166 GenerateCodeForObject(
167 schema, GetObjectByName(schema, "aos.fbs.testing.TableWithUnion")),
168 "Union not supported");
169 GenerateCodeForObject(
170 schema, GetObjectByName(schema, "aos.fbs.testing.MissingVectorLength"));
171 EXPECT_DEATH(
172 GenerateCodeForObject(
173 schema,
174 GetObjectByName(schema, "aos.fbs.testing.NonIntegerVectorLength")),
175 "vector_badlength must specify a positive integer for the "
176 "static_length attribute.");
177 EXPECT_DEATH(GenerateCodeForObject(
178 schema, GetObjectByName(
179 schema, "aos.fbs.testing.NegativeVectorLength")),
180 "Field vector_badlength must have a non-negative "
181 "static_length.");
182 GenerateCodeForObject(
183 schema, GetObjectByName(schema, "aos.fbs.testing.ZeroVectorLength"));
184 GenerateCodeForObject(
185 schema, GetObjectByName(schema, "aos.fbs.testing.MissingStringLength"));
186 GenerateCodeForObject(
187 schema,
188 GetObjectByName(schema, "aos.fbs.testing.MissingSubStringLength"));
189}
190
191// Tests that we can go through and manually build up a big flatbuffer and that
192// it stays valid at all points.
193TEST_F(StaticFlatbuffersTest, ManuallyConstructFlatbuffer) {
194 {
195 aos::fbs::VectorAllocator allocator;
196 Builder<SubTableStatic> builder(&allocator);
197 SubTableStatic *object = builder.get();
198 if (!builder.AsFlatbufferSpan().Verify()) {
199 LOG(ERROR) << object->SerializationDebugString() << "\nRoot table offset "
200 << *reinterpret_cast<const uoffset_t *>(
201 builder.buffer().data())
202 << "\nraw bytes\n";
203 aos::fbs::internal::DebugBytes(builder.buffer(), std::cerr);
204 FAIL();
205 return;
206 }
207 EXPECT_EQ("{ }", aos::FlatbufferToJson(builder.AsFlatbufferSpan()));
208 object->set_foo(123);
209 object->set_baz(971);
210 CHECK(builder.AsFlatbufferSpan().Verify());
211 EXPECT_EQ(123, object->AsFlatbuffer().foo());
212 EXPECT_EQ(971, object->AsFlatbuffer().baz());
213 EXPECT_EQ(R"json({ "foo": 123, "baz": 971.0 })json",
214 aos::FlatbufferToJson(builder.AsFlatbufferSpan()));
James Kuszmaul1c9693f2023-12-08 09:45:26 -0800215 TestMemory(builder.buffer());
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700216 }
217 {
Maxwell Hendersonfb1e3bc2024-02-04 13:55:22 -0800218 // aos::FixedAllocator
219 // allocator(TestTableStatic::kUnalignedBufferSize);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700220 aos::fbs::VectorAllocator allocator;
221 Builder<TestTableStatic> builder(&allocator);
222 TestTableStatic *object = builder.get();
223 const aos::fbs::testing::TestTable &fbs = object->AsFlatbuffer();
224 VLOG(1) << object->SerializationDebugString();
225 CHECK(builder.AsFlatbufferSpan().Verify());
226 EXPECT_EQ("{ }", aos::FlatbufferToJson(builder.AsFlatbufferSpan()));
227 {
228 ASSERT_FALSE(object->has_scalar());
229 object->set_scalar(123);
230 EXPECT_TRUE(fbs.has_scalar());
231 EXPECT_EQ(123, fbs.scalar());
232 }
233 EXPECT_EQ(R"json({ "scalar": 123 })json",
234 aos::FlatbufferToJson(builder.AsFlatbufferSpan()));
235 {
236 ASSERT_FALSE(object->has_vector_of_scalars());
237 auto vector = object->add_vector_of_scalars();
238 ASSERT_TRUE(vector->emplace_back(4));
239 ASSERT_TRUE(vector->emplace_back(5));
240 ASSERT_TRUE(object->has_vector_of_scalars());
241 ASSERT_TRUE(fbs.has_vector_of_scalars());
242 VLOG(1) << vector->SerializationDebugString();
243 EXPECT_TRUE(fbs.has_vector_of_scalars());
244 EXPECT_EQ(2u, fbs.vector_of_scalars()->size());
245 EXPECT_EQ(4, fbs.vector_of_scalars()->Get(0));
246 EXPECT_EQ(5, fbs.vector_of_scalars()->Get(1));
247 }
248 EXPECT_EQ(R"json({ "scalar": 123, "vector_of_scalars": [ 4, 5 ] })json",
249 aos::FlatbufferToJson(builder.AsFlatbufferSpan()));
250 {
251 EXPECT_FALSE(object->has_string());
252 auto string = object->add_string();
253 EXPECT_TRUE(object->has_string());
254 string->SetString("Hello, World!");
255 EXPECT_EQ(13u, object->string()->size());
256 ASSERT_TRUE(fbs.has_string());
257 ASSERT_EQ(13u, fbs.string()->size());
258 EXPECT_EQ("Hello, World!", fbs.string()->string_view());
259 // Check that we null-terminated correctly.
260 EXPECT_EQ(13u, strnlen(fbs.string()->c_str(), 20));
261 }
262 EXPECT_EQ(
263 R"json({ "scalar": 123, "vector_of_scalars": [ 4, 5 ], "string": "Hello, World!" })json",
264 aos::FlatbufferToJson(builder.AsFlatbufferSpan()));
265 {
266 EXPECT_FALSE(object->has_vector_of_strings());
267 auto vector_of_strings = object->add_vector_of_strings();
268 EXPECT_TRUE(object->has_vector_of_strings());
269 auto sub_string = CHECK_NOTNULL(vector_of_strings->emplace_back());
270 ASSERT_TRUE(sub_string->emplace_back('D'));
271 EXPECT_TRUE(fbs.has_vector_of_strings());
272 ASSERT_EQ(1u, fbs.vector_of_strings()->size());
273 ASSERT_EQ(1u, fbs.vector_of_strings()->Get(0)->size());
274 EXPECT_EQ('D', fbs.vector_of_strings()->Get(0)->Get(0));
275 }
276 EXPECT_EQ(
277 R"json({
278 "scalar": 123,
279 "vector_of_scalars": [
280 4,
281 5
282 ],
283 "string": "Hello, World!",
284 "vector_of_strings": [
285 "D"
286 ]
287})json",
288 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
289 {.multi_line = true}));
290 {
291 EXPECT_FALSE(object->has_substruct());
292 object->set_substruct({971, 254});
293 EXPECT_TRUE(object->has_substruct());
294 EXPECT_TRUE(fbs.has_substruct());
295 EXPECT_EQ(971, fbs.substruct()->x());
296 EXPECT_EQ(254, fbs.substruct()->y());
297 }
298 EXPECT_EQ(
299 R"json({
300 "scalar": 123,
301 "vector_of_scalars": [
302 4,
303 5
304 ],
305 "string": "Hello, World!",
306 "vector_of_strings": [
307 "D"
308 ],
309 "substruct": {
310 "x": 971.0,
311 "y": 254.0
312 }
313})json",
314 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
315 {.multi_line = true}));
316 {
317 auto subtable = object->add_subtable();
318 subtable->set_foo(1234);
319 EXPECT_TRUE(fbs.has_subtable());
320 EXPECT_EQ(1234, fbs.subtable()->foo());
321 EXPECT_FALSE(fbs.subtable()->has_baz());
322 }
323 EXPECT_EQ(
324 R"json({
325 "scalar": 123,
326 "vector_of_scalars": [
327 4,
328 5
329 ],
330 "string": "Hello, World!",
331 "vector_of_strings": [
332 "D"
333 ],
334 "substruct": {
335 "x": 971.0,
336 "y": 254.0
337 },
338 "subtable": {
339 "foo": 1234
340 }
341})json",
342 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
343 {.multi_line = true}));
344 {
345 auto vector = object->add_vector_of_structs();
346 ASSERT_TRUE(vector->emplace_back({48, 67}));
347 ASSERT_TRUE(vector->emplace_back({118, 148}));
348 ASSERT_TRUE(vector->emplace_back({971, 973}));
349 ASSERT_FALSE(vector->emplace_back({1114, 2056}));
350 EXPECT_TRUE(fbs.has_vector_of_structs());
351 EXPECT_EQ(3u, fbs.vector_of_structs()->size());
352 EXPECT_EQ(48, fbs.vector_of_structs()->Get(0)->x());
353 EXPECT_EQ(67, fbs.vector_of_structs()->Get(0)->y());
354 EXPECT_EQ(118, fbs.vector_of_structs()->Get(1)->x());
355 EXPECT_EQ(object->vector_of_structs()->at(1).x(),
356 fbs.vector_of_structs()->Get(1)->x());
357 EXPECT_EQ((*object->vector_of_structs())[1].x(),
358 fbs.vector_of_structs()->Get(1)->x());
359 EXPECT_EQ(148, fbs.vector_of_structs()->Get(1)->y());
360 EXPECT_EQ(971, fbs.vector_of_structs()->Get(2)->x());
361 EXPECT_EQ(973, fbs.vector_of_structs()->Get(2)->y());
362 }
363 EXPECT_EQ(
364 R"json({
365 "scalar": 123,
366 "vector_of_scalars": [
367 4,
368 5
369 ],
370 "string": "Hello, World!",
371 "vector_of_strings": [
372 "D"
373 ],
374 "substruct": {
375 "x": 971.0,
376 "y": 254.0
377 },
378 "subtable": {
379 "foo": 1234
380 },
381 "vector_of_structs": [
382 {
383 "x": 48.0,
384 "y": 67.0
385 },
386 {
387 "x": 118.0,
388 "y": 148.0
389 },
390 {
391 "x": 971.0,
392 "y": 973.0
393 }
394 ]
395})json",
396 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
397 {.multi_line = true}));
398 {
399 EXPECT_FALSE(object->has_vector_of_tables());
400 auto vector = object->add_vector_of_tables();
401 EXPECT_TRUE(object->has_vector_of_tables());
402 auto subobject = vector->emplace_back();
403 subobject->set_foo(222);
404 EXPECT_TRUE(fbs.has_vector_of_tables());
405 EXPECT_EQ(1u, fbs.vector_of_tables()->size());
406 EXPECT_EQ(222, fbs.vector_of_tables()->Get(0)->foo());
407 EXPECT_EQ(object->vector_of_tables()->at(0).foo(),
408 fbs.vector_of_tables()->Get(0)->foo());
409 }
410 EXPECT_EQ(
411 R"json({
412 "scalar": 123,
413 "vector_of_scalars": [
414 4,
415 5
416 ],
417 "string": "Hello, World!",
418 "vector_of_strings": [
419 "D"
420 ],
421 "substruct": {
422 "x": 971.0,
423 "y": 254.0
424 },
425 "subtable": {
426 "foo": 1234
427 },
428 "vector_of_structs": [
429 {
430 "x": 48.0,
431 "y": 67.0
432 },
433 {
434 "x": 118.0,
435 "y": 148.0
436 },
437 {
438 "x": 971.0,
439 "y": 973.0
440 }
441 ],
442 "vector_of_tables": [
443 {
444 "foo": 222
445 }
446 ]
447})json",
448 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
449 {.multi_line = true}));
450 {
451 EXPECT_FALSE(object->has_included_table());
452 auto subtable = object->add_included_table();
453 EXPECT_TRUE(object->has_included_table());
454 subtable->set_foo(included::TestEnum::B);
455 ASSERT_TRUE(fbs.has_included_table());
456 ASSERT_TRUE(fbs.included_table()->has_foo());
457 EXPECT_EQ(included::TestEnum::B, fbs.included_table()->foo());
458 }
459 EXPECT_EQ(
460 R"json({
461 "scalar": 123,
462 "vector_of_scalars": [
463 4,
464 5
465 ],
466 "string": "Hello, World!",
467 "vector_of_strings": [
468 "D"
469 ],
470 "substruct": {
471 "x": 971.0,
472 "y": 254.0
473 },
474 "subtable": {
475 "foo": 1234
476 },
477 "vector_of_structs": [
478 {
479 "x": 48.0,
480 "y": 67.0
481 },
482 {
483 "x": 118.0,
484 "y": 148.0
485 },
486 {
487 "x": 971.0,
488 "y": 973.0
489 }
490 ],
491 "vector_of_tables": [
492 {
493 "foo": 222
494 }
495 ],
496 "included_table": {
497 "foo": "B"
498 }
499})json",
500 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
501 {.multi_line = true}));
502 {
503 auto aligned_vector = object->add_vector_aligned();
504 ASSERT_EQ(64,
505 std::remove_reference<decltype(*aligned_vector)>::type::kAlign);
506 ASSERT_EQ(64, TestTableStatic::kAlign);
507 ASSERT_TRUE(aligned_vector->emplace_back(444));
508 EXPECT_TRUE(fbs.has_vector_aligned());
509 EXPECT_EQ(1u, fbs.vector_aligned()->size());
510 EXPECT_EQ(0u,
511 reinterpret_cast<size_t>(fbs.vector_aligned()->data()) % 64);
512 EXPECT_EQ(444, fbs.vector_aligned()->Get(0));
513 }
514 VLOG(1) << object->SerializationDebugString();
515 CHECK(builder.AsFlatbufferSpan().Verify());
516 const std::string expected_contents =
517 R"json({
518 "scalar": 123,
519 "vector_of_scalars": [
520 4,
521 5
522 ],
523 "string": "Hello, World!",
524 "vector_of_strings": [
525 "D"
526 ],
527 "substruct": {
528 "x": 971.0,
529 "y": 254.0
530 },
531 "subtable": {
532 "foo": 1234
533 },
534 "vector_aligned": [
535 444
536 ],
537 "vector_of_structs": [
538 {
539 "x": 48.0,
540 "y": 67.0
541 },
542 {
543 "x": 118.0,
544 "y": 148.0
545 },
546 {
547 "x": 971.0,
548 "y": 973.0
549 }
550 ],
551 "vector_of_tables": [
552 {
553 "foo": 222
554 }
555 ],
556 "included_table": {
557 "foo": "B"
558 }
559})json";
560 EXPECT_EQ(expected_contents,
561 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
562 {.multi_line = true}));
563 VLOG(1) << AnnotateBinaries(test_schema_, builder.buffer());
564 VerifyJson<TestTableStatic>(expected_contents);
565 {
566 auto aligned_vector = object->mutable_vector_aligned();
567 ASSERT_TRUE(aligned_vector->reserve(100));
568 EXPECT_EQ(100, aligned_vector->capacity());
569 ASSERT_TRUE(builder.AsFlatbufferSpan().Verify())
570 << aligned_vector->SerializationDebugString();
571 EXPECT_EQ(expected_contents,
572 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
573 {.multi_line = true}));
574 std::vector<int> scalars;
575 scalars.push_back(aligned_vector->at(0));
576 while (aligned_vector->size() < 100u) {
577 scalars.push_back(aligned_vector->size());
578 CHECK(aligned_vector->emplace_back(aligned_vector->size()));
579 }
580 VLOG(1) << aligned_vector->SerializationDebugString();
581 VLOG(1) << AnnotateBinaries(test_schema_, builder.buffer());
582 EXPECT_EQ(absl::StrFormat(
583 R"json({
584 "scalar": 123,
585 "vector_of_scalars": [
586 4,
587 5
588 ],
589 "string": "Hello, World!",
590 "vector_of_strings": [
591 "D"
592 ],
593 "substruct": {
594 "x": 971.0,
595 "y": 254.0
596 },
597 "subtable": {
598 "foo": 1234
599 },
600 "vector_aligned": [
601 %s
602 ],
603 "vector_of_structs": [
604 {
605 "x": 48.0,
606 "y": 67.0
607 },
608 {
609 "x": 118.0,
610 "y": 148.0
611 },
612 {
613 "x": 971.0,
614 "y": 973.0
615 }
616 ],
617 "vector_of_tables": [
618 {
619 "foo": 222
620 }
621 ],
622 "included_table": {
623 "foo": "B"
624 }
625})json",
626 absl::StrJoin(scalars, ",\n ")),
627 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
628 {.multi_line = true}));
629 }
630
631 {
632 auto unspecified_vector = object->add_unspecified_length_vector();
633 ASSERT_NE(nullptr, unspecified_vector);
634 ASSERT_EQ(0, unspecified_vector->capacity());
635 ASSERT_FALSE(unspecified_vector->emplace_back(0));
636 ASSERT_TRUE(unspecified_vector->reserve(2));
637 ASSERT_TRUE(unspecified_vector->emplace_back(1));
638 ASSERT_TRUE(unspecified_vector->emplace_back(2));
639 ASSERT_FALSE(unspecified_vector->emplace_back(3));
640 ASSERT_TRUE(builder.AsFlatbufferSpan().Verify());
641 }
James Kuszmaul1c9693f2023-12-08 09:45:26 -0800642 TestMemory(builder.buffer());
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700643 }
644}
645
646// Tests that field clearing (and subsequent resetting) works properly.
647TEST_F(StaticFlatbuffersTest, ClearFields) {
648 aos::fbs::VectorAllocator allocator;
649 Builder<TestTableStatic> builder(&allocator);
650 TestTableStatic *object = builder.get();
651 // For each field, we will confirm the following:
652 // * Clearing a non-existent field causes no issues.
653 // * We can set a field, clear it, and have it not be present.
654 // * We can set the field again afterwards.
655 {
656 object->clear_scalar();
657 ASSERT_TRUE(builder.Verify());
658 object->set_scalar(123);
659 EXPECT_EQ(123, object->AsFlatbuffer().scalar());
660 object->clear_scalar();
661 ASSERT_TRUE(builder.Verify());
662 EXPECT_FALSE(object->has_scalar());
663 object->set_scalar(456);
664 EXPECT_EQ(456, object->AsFlatbuffer().scalar());
665 }
666 {
667 object->clear_vector_of_scalars();
668 ASSERT_TRUE(builder.Verify());
669 EXPECT_FALSE(object->has_vector_of_scalars());
670 auto vector = object->add_vector_of_scalars();
671 ASSERT_TRUE(vector->emplace_back(4));
672 ASSERT_TRUE(vector->emplace_back(5));
673 ASSERT_TRUE(vector->emplace_back(6));
674 // Deliberately force a resize of the vector to ensure that we can exercise
675 // what happens if we clear a non-standard size field.
676 ASSERT_FALSE(vector->emplace_back(7));
677 ASSERT_TRUE(vector->reserve(4));
678 ASSERT_TRUE(vector->emplace_back(7));
679 EXPECT_EQ(
680 R"json({
681 "scalar": 456,
682 "vector_of_scalars": [
683 4,
684 5,
685 6,
686 7
687 ]
688})json",
689 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
690 {.multi_line = true}));
691 ASSERT_TRUE(builder.Verify());
692 object->clear_vector_of_scalars();
693 ASSERT_TRUE(builder.Verify());
694 ASSERT_FALSE(object->has_vector_of_scalars())
695 << aos::FlatbufferToJson(builder.AsFlatbufferSpan());
696 vector = CHECK_NOTNULL(object->add_vector_of_scalars());
697 ASSERT_TRUE(builder.Verify());
698 EXPECT_EQ(0u, object->AsFlatbuffer().vector_of_scalars()->size());
699 ASSERT_TRUE(vector->emplace_back(9));
700 ASSERT_TRUE(vector->emplace_back(7));
701 ASSERT_TRUE(vector->emplace_back(1));
702 // This vector has no knowledge of the past resizing; it should fail to add
703 // an extra number.
704 ASSERT_FALSE(vector->emplace_back(7));
705 }
706 {
707 object->clear_substruct();
708 ASSERT_TRUE(builder.Verify());
709 EXPECT_FALSE(object->has_substruct());
710 object->set_substruct(SubStruct{2, 3});
711 EXPECT_EQ(
712 R"json({
713 "scalar": 456,
714 "vector_of_scalars": [
715 9,
716 7,
717 1
718 ],
719 "substruct": {
720 "x": 2.0,
721 "y": 3.0
722 }
723})json",
724 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
725 {.multi_line = true}));
726 object->clear_substruct();
727 ASSERT_TRUE(builder.Verify());
728 EXPECT_FALSE(object->has_substruct());
729 object->set_substruct(SubStruct{4, 5});
730 EXPECT_EQ(
731 R"json({
732 "scalar": 456,
733 "vector_of_scalars": [
734 9,
735 7,
736 1
737 ],
738 "substruct": {
739 "x": 4.0,
740 "y": 5.0
741 }
742})json",
743 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
744 {.multi_line = true}));
745 }
746 {
747 object->clear_subtable();
748 ASSERT_TRUE(builder.Verify());
749 EXPECT_FALSE(object->has_subtable());
750 auto subtable = CHECK_NOTNULL(object->add_subtable());
751 subtable->set_baz(9.71);
752 EXPECT_EQ(
753 R"json({
754 "scalar": 456,
755 "vector_of_scalars": [
756 9,
757 7,
758 1
759 ],
760 "substruct": {
761 "x": 4.0,
762 "y": 5.0
763 },
764 "subtable": {
765 "baz": 9.71
766 }
767})json",
768 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
769 {.multi_line = true}));
770 object->clear_subtable();
771 ASSERT_TRUE(builder.Verify());
772 EXPECT_FALSE(object->has_subtable());
773 subtable = CHECK_NOTNULL(object->add_subtable());
774 subtable->set_baz(16.78);
775 EXPECT_EQ(
776 R"json({
777 "scalar": 456,
778 "vector_of_scalars": [
779 9,
780 7,
781 1
782 ],
783 "substruct": {
784 "x": 4.0,
785 "y": 5.0
786 },
787 "subtable": {
788 "baz": 16.780001
789 }
790})json",
791 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
792 {.multi_line = true}));
793 }
James Kuszmaul1c9693f2023-12-08 09:45:26 -0800794 TestMemory(builder.buffer());
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700795}
796
797// Try to cover ~all supported scalar/flatbuffer types using JSON convenience
798// functions.
799TEST_F(StaticFlatbuffersTest, FlatbufferTypeCoverage) {
Stephan Pleines9e40c8e2024-02-07 20:58:28 -0800800 VerifyJson<aos::testing::ConfigurationStatic>("{\n\n}");
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700801 std::string populated_config =
802 aos::util::ReadFileToStringOrDie(aos::testing::ArtifactPath(
803 "aos/flatbuffers/test_dir/type_coverage.json"));
804 // Get rid of a pesky new line.
805 populated_config = populated_config.substr(0, populated_config.size() - 1);
Stephan Pleines9e40c8e2024-02-07 20:58:28 -0800806 VerifyJson<aos::testing::ConfigurationStatic>(populated_config);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700807
808 // And now play around with mutating the buffer.
Stephan Pleines9e40c8e2024-02-07 20:58:28 -0800809 Builder<aos::testing::ConfigurationStatic> builder =
810 aos::JsonToStaticFlatbuffer<aos::testing::ConfigurationStatic>(
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700811 populated_config);
812 ASSERT_TRUE(builder.Verify());
813 builder.get()->clear_foo_float();
814 ASSERT_TRUE(builder.Verify());
815 ASSERT_FALSE(builder.get()->AsFlatbuffer().has_foo_float());
816 builder.get()->set_foo_float(1.111);
817 ASSERT_TRUE(builder.Verify());
818 ASSERT_FLOAT_EQ(1.111, builder.get()->AsFlatbuffer().foo_float());
James Kuszmaul1c9693f2023-12-08 09:45:26 -0800819 TestMemory(builder.buffer());
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700820}
821
James Kuszmaula75cd7c2023-12-07 15:52:51 -0800822TEST_F(StaticFlatbuffersTest, MinimallyAlignedTable) {
823 VerifyJson<MinimallyAlignedTableStatic>("{\n \"field\": 123\n}");
824 static_assert(4u == alignof(uoffset_t),
825 "The alignment of a uoffset_t is expected to be 4.");
826 ASSERT_EQ(alignof(uoffset_t), MinimallyAlignedTableStatic::kAlign)
827 << "No table should have an alignment of less than the alignment of the "
828 "table's root offset.";
829}
830
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700831// Confirm that we can use the SpanAllocator with a span that provides exactly
832// the required buffer size.
833TEST_F(StaticFlatbuffersTest, ExactSizeSpanAllocator) {
James Kuszmaul1c9693f2023-12-08 09:45:26 -0800834 uint8_t buffer[Builder<TestTableStatic>::kBufferSize];
835 aos::fbs::SpanAllocator allocator({buffer, sizeof(buffer)});
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700836 Builder<TestTableStatic> builder(&allocator);
837 TestTableStatic *object = builder.get();
838 object->set_scalar(123);
839 {
840 auto vector = object->add_vector_of_scalars();
841 ASSERT_TRUE(vector->emplace_back(4));
842 ASSERT_TRUE(vector->emplace_back(5));
843 }
844 {
845 auto string = object->add_string();
846 string->SetString("Hello, World!");
847 }
848 {
849 auto vector_of_strings = object->add_vector_of_strings();
850 auto sub_string = CHECK_NOTNULL(vector_of_strings->emplace_back());
851 ASSERT_TRUE(sub_string->emplace_back('D'));
852 }
853 { object->set_substruct({971, 254}); }
854 {
855 auto subtable = object->add_subtable();
856 subtable->set_foo(1234);
857 }
858 {
859 auto vector = object->add_vector_of_structs();
860 ASSERT_TRUE(vector->emplace_back({48, 67}));
861 ASSERT_TRUE(vector->emplace_back({118, 148}));
862 ASSERT_TRUE(vector->emplace_back({971, 973}));
863 // Max vector size is three; this should fail.
864 ASSERT_FALSE(vector->emplace_back({1114, 2056}));
865 // We don't have any extra space available.
866 ASSERT_FALSE(vector->reserve(4));
867 ASSERT_FALSE(vector->emplace_back({1114, 2056}));
868 }
869 {
870 auto vector = object->add_vector_of_tables();
871 auto subobject = vector->emplace_back();
872 subobject->set_foo(222);
873 }
874 {
875 auto subtable = object->add_included_table();
876 subtable->set_foo(included::TestEnum::B);
877 }
878 ASSERT_TRUE(builder.AsFlatbufferSpan().Verify());
879 VLOG(1) << aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
880 {.multi_line = true});
881 VLOG(1) << AnnotateBinaries(test_schema_, builder.buffer());
James Kuszmaul1c9693f2023-12-08 09:45:26 -0800882 TestMemory(builder.buffer());
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700883}
884
885// Test that when we provide too small of a span to the Builder that it
886// correctly fails.
887TEST_F(StaticFlatbuffersTest, TooSmallSpanAllocator) {
888 std::vector<uint8_t> buffer;
889 buffer.resize(10, 0);
890 aos::fbs::SpanAllocator allocator({buffer.data(), buffer.size()});
891 EXPECT_DEATH(Builder<TestTableStatic>{&allocator}, "Failed to allocate");
892}
893
894// Verify that if we create a span with extra headroom that that lets us
895// dynamically alter the size of vectors in the flatbuffers.
896TEST_F(StaticFlatbuffersTest, ExtraLargeSpanAllocator) {
James Kuszmaul1c9693f2023-12-08 09:45:26 -0800897 uint8_t buffer[Builder<TestTableStatic>::kBufferSize + 10000];
898 aos::fbs::SpanAllocator allocator({buffer, sizeof(buffer)});
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700899 Builder<TestTableStatic> builder(&allocator);
900 TestTableStatic *object = builder.get();
901 {
902 auto vector = object->add_unspecified_length_vector();
903 // Confirm that the vector does indeed start out at zero length.
904 ASSERT_FALSE(vector->emplace_back(4));
905 ASSERT_TRUE(vector->reserve(9000));
906 vector->resize(256);
907 for (size_t index = 0; index < 256; ++index) {
908 vector->at(index) = static_cast<uint8_t>(index);
909 }
910 }
911 ASSERT_EQ(256, object->AsFlatbuffer().unspecified_length_vector()->size());
912 size_t expected = 0;
913 for (const uint8_t value :
914 *object->AsFlatbuffer().unspecified_length_vector()) {
915 EXPECT_EQ(expected++, value);
916 }
James Kuszmaul22448052023-12-14 15:55:14 -0800917 expected = 0;
918 for (const uint8_t value : *object->unspecified_length_vector()) {
919 EXPECT_EQ(expected++, value);
920 }
James Kuszmaul1c9693f2023-12-08 09:45:26 -0800921 TestMemory(builder.buffer());
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700922}
James Kuszmaul22448052023-12-14 15:55:14 -0800923
924// Tests that the iterators on the Vector type work.
925TEST_F(StaticFlatbuffersTest, IteratorTest) {
926 Builder<TestTableStatic> builder(std::make_unique<VectorAllocator>());
927 {
928 auto vector = builder->add_unspecified_length_vector();
929 ASSERT_TRUE(vector->reserve(9000));
930 vector->resize(256);
931 uint8_t set_value = 0;
932 for (uint8_t &destination : *vector) {
933 destination = set_value;
934 ++set_value;
935 }
936 uint8_t expected = 0;
937 for (const uint8_t value : *builder->unspecified_length_vector()) {
938 EXPECT_EQ(expected, value);
939 ++expected;
940 }
941 // Exercise some of the random access iterator functionality to ensure that
942 // we have it implemented.
943 auto begin_it = vector->begin();
944 EXPECT_EQ(begin_it + 256, vector->end());
945 EXPECT_EQ(7, *(begin_it + 7));
946 EXPECT_EQ(255, *(vector->end() - 1));
947 EXPECT_EQ(256, vector->end() - vector->begin());
948 EXPECT_EQ(-256, vector->begin() - vector->end());
949 static_assert(std::random_access_iterator<decltype(vector->begin())>,
950 "The vector iterator does not meet the requirements of a "
951 "random access iterator.");
952 }
953 {
954 auto vector = builder->add_vector_of_structs();
955 vector->resize(3);
956 double set_value = 0;
957 for (SubStruct &destination : *vector) {
958 destination.mutate_x(set_value);
959 destination.mutate_y(-set_value);
960 set_value += 1.0;
961 }
962 double expected = 0;
963 for (const SubStruct &value : *builder->vector_of_structs()) {
964 EXPECT_EQ(expected, value.x());
965 EXPECT_EQ(-expected, value.y());
966 expected += 1.0;
967 }
968 static_assert(std::random_access_iterator<decltype(vector->begin())>,
969 "The vector iterator does not meet the requirements of a "
970 "random access iterator.");
971 }
972 {
973 auto vector = builder->add_vector_of_tables();
974 vector->resize(3);
975 int set_value = 0;
976 for (SubTableStatic &destination : *vector) {
977 destination.set_foo(set_value);
978 set_value += 1;
979 }
980 int expected = 0;
981 for (const SubTableStatic &value : *builder->vector_of_tables()) {
982 EXPECT_EQ(expected, value.foo());
983 EXPECT_FALSE(value.has_baz());
984 expected += 1;
985 }
986 static_assert(std::random_access_iterator<decltype(vector->begin())>,
987 "The vector iterator does not meet the requirements of a "
988 "random access iterator.");
989 }
990}
Maxwell Hendersonfb1e3bc2024-02-04 13:55:22 -0800991
992// Confirm that we can use the FixedStackAllocator
993TEST_F(StaticFlatbuffersTest, FixedStackAllocator) {
994 aos::fbs::FixedStackAllocator<Builder<TestTableStatic>::kBufferSize>
995 allocator;
996 Builder<TestTableStatic> builder(&allocator);
997 TestTableStatic *object = builder.get();
998 object->set_scalar(123);
999 {
1000 auto vector = object->add_vector_of_scalars();
1001 ASSERT_TRUE(vector->emplace_back(4));
1002 ASSERT_TRUE(vector->emplace_back(5));
1003 }
1004 {
1005 auto string = object->add_string();
1006 string->SetString("Hello, World!");
1007 }
1008 {
1009 auto vector_of_strings = object->add_vector_of_strings();
1010 auto sub_string = CHECK_NOTNULL(vector_of_strings->emplace_back());
1011 ASSERT_TRUE(sub_string->emplace_back('D'));
1012 }
1013 { object->set_substruct({971, 254}); }
1014 {
1015 auto subtable = object->add_subtable();
1016 subtable->set_foo(1234);
1017 }
1018 {
1019 auto vector = object->add_vector_of_structs();
1020 ASSERT_TRUE(vector->emplace_back({48, 67}));
1021 ASSERT_TRUE(vector->emplace_back({118, 148}));
1022 ASSERT_TRUE(vector->emplace_back({971, 973}));
1023 // Max vector size is three; this should fail.
1024 ASSERT_FALSE(vector->emplace_back({1114, 2056}));
1025 // We don't have any extra space available.
1026 ASSERT_FALSE(vector->reserve(4));
1027 ASSERT_FALSE(vector->emplace_back({1114, 2056}));
1028 }
1029 {
1030 auto vector = object->add_vector_of_tables();
1031 auto subobject = vector->emplace_back();
1032 subobject->set_foo(222);
1033 }
1034 {
1035 auto subtable = object->add_included_table();
1036 subtable->set_foo(included::TestEnum::B);
1037 }
1038 ASSERT_TRUE(builder.AsFlatbufferSpan().Verify());
1039 VLOG(1) << aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
1040 {.multi_line = true});
1041 VLOG(1) << AnnotateBinaries(test_schema_, builder.buffer());
1042 TestMemory(builder.buffer());
1043}
1044
James Kuszmaul6be41022023-12-20 11:55:28 -08001045// Uses a small example to manually verify that we can copy from the flatbuffer
1046// object API.
1047TEST_F(StaticFlatbuffersTest, ObjectApiCopy) {
1048 aos::fbs::testing::TestTableT object_t;
1049 object_t.scalar = 971;
1050 object_t.vector_of_strings.push_back("971");
1051 object_t.vector_of_structs.push_back({1, 2});
1052 object_t.subtable = std::make_unique<SubTableT>();
1053 aos::fbs::VectorAllocator allocator;
1054 Builder<TestTableStatic> builder(&allocator);
1055 ASSERT_TRUE(builder->FromFlatbuffer(object_t));
1056 ASSERT_TRUE(builder.AsFlatbufferSpan().Verify());
1057 // Note that vectors and strings get set to zero-length, but present, values.
1058 EXPECT_EQ(
1059 "{ \"scalar\": 971, \"vector_of_scalars\": [ ], \"string\": \"\", "
1060 "\"vector_of_strings\": [ \"971\" ], \"subtable\": { \"foo\": 0, "
1061 "\"baz\": 0.0 }, \"vector_aligned\": [ ], \"vector_of_structs\": [ { "
1062 "\"x\": 1.0, \"y\": 2.0 } ], \"vector_of_tables\": [ ], "
1063 "\"unspecified_length_vector\": [ ], \"unspecified_length_string\": "
1064 "\"\", \"unspecified_length_vector_of_strings\": [ ] }",
1065 aos::FlatbufferToJson(builder.AsFlatbufferSpan()));
1066}
1067
1068// More completely covers our object API copying by comparing the flatbuffer
1069// Pack() methods to our FromFlatbuffer() methods.
1070TEST_F(StaticFlatbuffersTest, FlatbufferObjectTypeCoverage) {
1071 VerifyJson<aos::testing::ConfigurationStatic>("{\n\n}");
1072 std::string populated_config =
1073 aos::util::ReadFileToStringOrDie(aos::testing::ArtifactPath(
1074 "aos/flatbuffers/test_dir/type_coverage.json"));
1075 Builder<aos::testing::ConfigurationStatic> json_builder =
1076 aos::JsonToStaticFlatbuffer<aos::testing::ConfigurationStatic>(
1077 populated_config);
1078 aos::testing::ConfigurationT object_t;
1079 json_builder->AsFlatbuffer().UnPackTo(&object_t);
1080
1081 Builder<aos::testing::ConfigurationStatic> from_object_static;
1082 ASSERT_TRUE(from_object_static->FromFlatbuffer(object_t));
1083 flatbuffers::FlatBufferBuilder fbb;
1084 fbb.Finish(aos::testing::Configuration::Pack(fbb, &object_t));
1085 aos::FlatbufferDetachedBuffer<aos::testing::Configuration> from_object_raw =
1086 fbb.Release();
1087 EXPECT_EQ(aos::FlatbufferToJson(from_object_raw, {.multi_line = true}),
1088 aos::FlatbufferToJson(from_object_static, {.multi_line = true}));
1089}
1090
James Kuszmaulf5eb4682023-09-22 17:16:59 -07001091} // namespace aos::fbs::testing