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