blob: 2acc3c87605b316cb38b7a9d3a7970ca7d11c313 [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 {
218 // aos::FixedAllocator allocator(TestTableStatic::kUnalignedBufferSize);
219 aos::fbs::VectorAllocator allocator;
220 Builder<TestTableStatic> builder(&allocator);
221 TestTableStatic *object = builder.get();
222 const aos::fbs::testing::TestTable &fbs = object->AsFlatbuffer();
223 VLOG(1) << object->SerializationDebugString();
224 CHECK(builder.AsFlatbufferSpan().Verify());
225 EXPECT_EQ("{ }", aos::FlatbufferToJson(builder.AsFlatbufferSpan()));
226 {
227 ASSERT_FALSE(object->has_scalar());
228 object->set_scalar(123);
229 EXPECT_TRUE(fbs.has_scalar());
230 EXPECT_EQ(123, fbs.scalar());
231 }
232 EXPECT_EQ(R"json({ "scalar": 123 })json",
233 aos::FlatbufferToJson(builder.AsFlatbufferSpan()));
234 {
235 ASSERT_FALSE(object->has_vector_of_scalars());
236 auto vector = object->add_vector_of_scalars();
237 ASSERT_TRUE(vector->emplace_back(4));
238 ASSERT_TRUE(vector->emplace_back(5));
239 ASSERT_TRUE(object->has_vector_of_scalars());
240 ASSERT_TRUE(fbs.has_vector_of_scalars());
241 VLOG(1) << vector->SerializationDebugString();
242 EXPECT_TRUE(fbs.has_vector_of_scalars());
243 EXPECT_EQ(2u, fbs.vector_of_scalars()->size());
244 EXPECT_EQ(4, fbs.vector_of_scalars()->Get(0));
245 EXPECT_EQ(5, fbs.vector_of_scalars()->Get(1));
246 }
247 EXPECT_EQ(R"json({ "scalar": 123, "vector_of_scalars": [ 4, 5 ] })json",
248 aos::FlatbufferToJson(builder.AsFlatbufferSpan()));
249 {
250 EXPECT_FALSE(object->has_string());
251 auto string = object->add_string();
252 EXPECT_TRUE(object->has_string());
253 string->SetString("Hello, World!");
254 EXPECT_EQ(13u, object->string()->size());
255 ASSERT_TRUE(fbs.has_string());
256 ASSERT_EQ(13u, fbs.string()->size());
257 EXPECT_EQ("Hello, World!", fbs.string()->string_view());
258 // Check that we null-terminated correctly.
259 EXPECT_EQ(13u, strnlen(fbs.string()->c_str(), 20));
260 }
261 EXPECT_EQ(
262 R"json({ "scalar": 123, "vector_of_scalars": [ 4, 5 ], "string": "Hello, World!" })json",
263 aos::FlatbufferToJson(builder.AsFlatbufferSpan()));
264 {
265 EXPECT_FALSE(object->has_vector_of_strings());
266 auto vector_of_strings = object->add_vector_of_strings();
267 EXPECT_TRUE(object->has_vector_of_strings());
268 auto sub_string = CHECK_NOTNULL(vector_of_strings->emplace_back());
269 ASSERT_TRUE(sub_string->emplace_back('D'));
270 EXPECT_TRUE(fbs.has_vector_of_strings());
271 ASSERT_EQ(1u, fbs.vector_of_strings()->size());
272 ASSERT_EQ(1u, fbs.vector_of_strings()->Get(0)->size());
273 EXPECT_EQ('D', fbs.vector_of_strings()->Get(0)->Get(0));
274 }
275 EXPECT_EQ(
276 R"json({
277 "scalar": 123,
278 "vector_of_scalars": [
279 4,
280 5
281 ],
282 "string": "Hello, World!",
283 "vector_of_strings": [
284 "D"
285 ]
286})json",
287 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
288 {.multi_line = true}));
289 {
290 EXPECT_FALSE(object->has_substruct());
291 object->set_substruct({971, 254});
292 EXPECT_TRUE(object->has_substruct());
293 EXPECT_TRUE(fbs.has_substruct());
294 EXPECT_EQ(971, fbs.substruct()->x());
295 EXPECT_EQ(254, fbs.substruct()->y());
296 }
297 EXPECT_EQ(
298 R"json({
299 "scalar": 123,
300 "vector_of_scalars": [
301 4,
302 5
303 ],
304 "string": "Hello, World!",
305 "vector_of_strings": [
306 "D"
307 ],
308 "substruct": {
309 "x": 971.0,
310 "y": 254.0
311 }
312})json",
313 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
314 {.multi_line = true}));
315 {
316 auto subtable = object->add_subtable();
317 subtable->set_foo(1234);
318 EXPECT_TRUE(fbs.has_subtable());
319 EXPECT_EQ(1234, fbs.subtable()->foo());
320 EXPECT_FALSE(fbs.subtable()->has_baz());
321 }
322 EXPECT_EQ(
323 R"json({
324 "scalar": 123,
325 "vector_of_scalars": [
326 4,
327 5
328 ],
329 "string": "Hello, World!",
330 "vector_of_strings": [
331 "D"
332 ],
333 "substruct": {
334 "x": 971.0,
335 "y": 254.0
336 },
337 "subtable": {
338 "foo": 1234
339 }
340})json",
341 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
342 {.multi_line = true}));
343 {
344 auto vector = object->add_vector_of_structs();
345 ASSERT_TRUE(vector->emplace_back({48, 67}));
346 ASSERT_TRUE(vector->emplace_back({118, 148}));
347 ASSERT_TRUE(vector->emplace_back({971, 973}));
348 ASSERT_FALSE(vector->emplace_back({1114, 2056}));
349 EXPECT_TRUE(fbs.has_vector_of_structs());
350 EXPECT_EQ(3u, fbs.vector_of_structs()->size());
351 EXPECT_EQ(48, fbs.vector_of_structs()->Get(0)->x());
352 EXPECT_EQ(67, fbs.vector_of_structs()->Get(0)->y());
353 EXPECT_EQ(118, fbs.vector_of_structs()->Get(1)->x());
354 EXPECT_EQ(object->vector_of_structs()->at(1).x(),
355 fbs.vector_of_structs()->Get(1)->x());
356 EXPECT_EQ((*object->vector_of_structs())[1].x(),
357 fbs.vector_of_structs()->Get(1)->x());
358 EXPECT_EQ(148, fbs.vector_of_structs()->Get(1)->y());
359 EXPECT_EQ(971, fbs.vector_of_structs()->Get(2)->x());
360 EXPECT_EQ(973, fbs.vector_of_structs()->Get(2)->y());
361 }
362 EXPECT_EQ(
363 R"json({
364 "scalar": 123,
365 "vector_of_scalars": [
366 4,
367 5
368 ],
369 "string": "Hello, World!",
370 "vector_of_strings": [
371 "D"
372 ],
373 "substruct": {
374 "x": 971.0,
375 "y": 254.0
376 },
377 "subtable": {
378 "foo": 1234
379 },
380 "vector_of_structs": [
381 {
382 "x": 48.0,
383 "y": 67.0
384 },
385 {
386 "x": 118.0,
387 "y": 148.0
388 },
389 {
390 "x": 971.0,
391 "y": 973.0
392 }
393 ]
394})json",
395 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
396 {.multi_line = true}));
397 {
398 EXPECT_FALSE(object->has_vector_of_tables());
399 auto vector = object->add_vector_of_tables();
400 EXPECT_TRUE(object->has_vector_of_tables());
401 auto subobject = vector->emplace_back();
402 subobject->set_foo(222);
403 EXPECT_TRUE(fbs.has_vector_of_tables());
404 EXPECT_EQ(1u, fbs.vector_of_tables()->size());
405 EXPECT_EQ(222, fbs.vector_of_tables()->Get(0)->foo());
406 EXPECT_EQ(object->vector_of_tables()->at(0).foo(),
407 fbs.vector_of_tables()->Get(0)->foo());
408 }
409 EXPECT_EQ(
410 R"json({
411 "scalar": 123,
412 "vector_of_scalars": [
413 4,
414 5
415 ],
416 "string": "Hello, World!",
417 "vector_of_strings": [
418 "D"
419 ],
420 "substruct": {
421 "x": 971.0,
422 "y": 254.0
423 },
424 "subtable": {
425 "foo": 1234
426 },
427 "vector_of_structs": [
428 {
429 "x": 48.0,
430 "y": 67.0
431 },
432 {
433 "x": 118.0,
434 "y": 148.0
435 },
436 {
437 "x": 971.0,
438 "y": 973.0
439 }
440 ],
441 "vector_of_tables": [
442 {
443 "foo": 222
444 }
445 ]
446})json",
447 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
448 {.multi_line = true}));
449 {
450 EXPECT_FALSE(object->has_included_table());
451 auto subtable = object->add_included_table();
452 EXPECT_TRUE(object->has_included_table());
453 subtable->set_foo(included::TestEnum::B);
454 ASSERT_TRUE(fbs.has_included_table());
455 ASSERT_TRUE(fbs.included_table()->has_foo());
456 EXPECT_EQ(included::TestEnum::B, fbs.included_table()->foo());
457 }
458 EXPECT_EQ(
459 R"json({
460 "scalar": 123,
461 "vector_of_scalars": [
462 4,
463 5
464 ],
465 "string": "Hello, World!",
466 "vector_of_strings": [
467 "D"
468 ],
469 "substruct": {
470 "x": 971.0,
471 "y": 254.0
472 },
473 "subtable": {
474 "foo": 1234
475 },
476 "vector_of_structs": [
477 {
478 "x": 48.0,
479 "y": 67.0
480 },
481 {
482 "x": 118.0,
483 "y": 148.0
484 },
485 {
486 "x": 971.0,
487 "y": 973.0
488 }
489 ],
490 "vector_of_tables": [
491 {
492 "foo": 222
493 }
494 ],
495 "included_table": {
496 "foo": "B"
497 }
498})json",
499 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
500 {.multi_line = true}));
501 {
502 auto aligned_vector = object->add_vector_aligned();
503 ASSERT_EQ(64,
504 std::remove_reference<decltype(*aligned_vector)>::type::kAlign);
505 ASSERT_EQ(64, TestTableStatic::kAlign);
506 ASSERT_TRUE(aligned_vector->emplace_back(444));
507 EXPECT_TRUE(fbs.has_vector_aligned());
508 EXPECT_EQ(1u, fbs.vector_aligned()->size());
509 EXPECT_EQ(0u,
510 reinterpret_cast<size_t>(fbs.vector_aligned()->data()) % 64);
511 EXPECT_EQ(444, fbs.vector_aligned()->Get(0));
512 }
513 VLOG(1) << object->SerializationDebugString();
514 CHECK(builder.AsFlatbufferSpan().Verify());
515 const std::string expected_contents =
516 R"json({
517 "scalar": 123,
518 "vector_of_scalars": [
519 4,
520 5
521 ],
522 "string": "Hello, World!",
523 "vector_of_strings": [
524 "D"
525 ],
526 "substruct": {
527 "x": 971.0,
528 "y": 254.0
529 },
530 "subtable": {
531 "foo": 1234
532 },
533 "vector_aligned": [
534 444
535 ],
536 "vector_of_structs": [
537 {
538 "x": 48.0,
539 "y": 67.0
540 },
541 {
542 "x": 118.0,
543 "y": 148.0
544 },
545 {
546 "x": 971.0,
547 "y": 973.0
548 }
549 ],
550 "vector_of_tables": [
551 {
552 "foo": 222
553 }
554 ],
555 "included_table": {
556 "foo": "B"
557 }
558})json";
559 EXPECT_EQ(expected_contents,
560 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
561 {.multi_line = true}));
562 VLOG(1) << AnnotateBinaries(test_schema_, builder.buffer());
563 VerifyJson<TestTableStatic>(expected_contents);
564 {
565 auto aligned_vector = object->mutable_vector_aligned();
566 ASSERT_TRUE(aligned_vector->reserve(100));
567 EXPECT_EQ(100, aligned_vector->capacity());
568 ASSERT_TRUE(builder.AsFlatbufferSpan().Verify())
569 << aligned_vector->SerializationDebugString();
570 EXPECT_EQ(expected_contents,
571 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
572 {.multi_line = true}));
573 std::vector<int> scalars;
574 scalars.push_back(aligned_vector->at(0));
575 while (aligned_vector->size() < 100u) {
576 scalars.push_back(aligned_vector->size());
577 CHECK(aligned_vector->emplace_back(aligned_vector->size()));
578 }
579 VLOG(1) << aligned_vector->SerializationDebugString();
580 VLOG(1) << AnnotateBinaries(test_schema_, builder.buffer());
581 EXPECT_EQ(absl::StrFormat(
582 R"json({
583 "scalar": 123,
584 "vector_of_scalars": [
585 4,
586 5
587 ],
588 "string": "Hello, World!",
589 "vector_of_strings": [
590 "D"
591 ],
592 "substruct": {
593 "x": 971.0,
594 "y": 254.0
595 },
596 "subtable": {
597 "foo": 1234
598 },
599 "vector_aligned": [
600 %s
601 ],
602 "vector_of_structs": [
603 {
604 "x": 48.0,
605 "y": 67.0
606 },
607 {
608 "x": 118.0,
609 "y": 148.0
610 },
611 {
612 "x": 971.0,
613 "y": 973.0
614 }
615 ],
616 "vector_of_tables": [
617 {
618 "foo": 222
619 }
620 ],
621 "included_table": {
622 "foo": "B"
623 }
624})json",
625 absl::StrJoin(scalars, ",\n ")),
626 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
627 {.multi_line = true}));
628 }
629
630 {
631 auto unspecified_vector = object->add_unspecified_length_vector();
632 ASSERT_NE(nullptr, unspecified_vector);
633 ASSERT_EQ(0, unspecified_vector->capacity());
634 ASSERT_FALSE(unspecified_vector->emplace_back(0));
635 ASSERT_TRUE(unspecified_vector->reserve(2));
636 ASSERT_TRUE(unspecified_vector->emplace_back(1));
637 ASSERT_TRUE(unspecified_vector->emplace_back(2));
638 ASSERT_FALSE(unspecified_vector->emplace_back(3));
639 ASSERT_TRUE(builder.AsFlatbufferSpan().Verify());
640 }
James Kuszmaul1c9693f2023-12-08 09:45:26 -0800641 TestMemory(builder.buffer());
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700642 }
643}
644
645// Tests that field clearing (and subsequent resetting) works properly.
646TEST_F(StaticFlatbuffersTest, ClearFields) {
647 aos::fbs::VectorAllocator allocator;
648 Builder<TestTableStatic> builder(&allocator);
649 TestTableStatic *object = builder.get();
650 // For each field, we will confirm the following:
651 // * Clearing a non-existent field causes no issues.
652 // * We can set a field, clear it, and have it not be present.
653 // * We can set the field again afterwards.
654 {
655 object->clear_scalar();
656 ASSERT_TRUE(builder.Verify());
657 object->set_scalar(123);
658 EXPECT_EQ(123, object->AsFlatbuffer().scalar());
659 object->clear_scalar();
660 ASSERT_TRUE(builder.Verify());
661 EXPECT_FALSE(object->has_scalar());
662 object->set_scalar(456);
663 EXPECT_EQ(456, object->AsFlatbuffer().scalar());
664 }
665 {
666 object->clear_vector_of_scalars();
667 ASSERT_TRUE(builder.Verify());
668 EXPECT_FALSE(object->has_vector_of_scalars());
669 auto vector = object->add_vector_of_scalars();
670 ASSERT_TRUE(vector->emplace_back(4));
671 ASSERT_TRUE(vector->emplace_back(5));
672 ASSERT_TRUE(vector->emplace_back(6));
673 // Deliberately force a resize of the vector to ensure that we can exercise
674 // what happens if we clear a non-standard size field.
675 ASSERT_FALSE(vector->emplace_back(7));
676 ASSERT_TRUE(vector->reserve(4));
677 ASSERT_TRUE(vector->emplace_back(7));
678 EXPECT_EQ(
679 R"json({
680 "scalar": 456,
681 "vector_of_scalars": [
682 4,
683 5,
684 6,
685 7
686 ]
687})json",
688 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
689 {.multi_line = true}));
690 ASSERT_TRUE(builder.Verify());
691 object->clear_vector_of_scalars();
692 ASSERT_TRUE(builder.Verify());
693 ASSERT_FALSE(object->has_vector_of_scalars())
694 << aos::FlatbufferToJson(builder.AsFlatbufferSpan());
695 vector = CHECK_NOTNULL(object->add_vector_of_scalars());
696 ASSERT_TRUE(builder.Verify());
697 EXPECT_EQ(0u, object->AsFlatbuffer().vector_of_scalars()->size());
698 ASSERT_TRUE(vector->emplace_back(9));
699 ASSERT_TRUE(vector->emplace_back(7));
700 ASSERT_TRUE(vector->emplace_back(1));
701 // This vector has no knowledge of the past resizing; it should fail to add
702 // an extra number.
703 ASSERT_FALSE(vector->emplace_back(7));
704 }
705 {
706 object->clear_substruct();
707 ASSERT_TRUE(builder.Verify());
708 EXPECT_FALSE(object->has_substruct());
709 object->set_substruct(SubStruct{2, 3});
710 EXPECT_EQ(
711 R"json({
712 "scalar": 456,
713 "vector_of_scalars": [
714 9,
715 7,
716 1
717 ],
718 "substruct": {
719 "x": 2.0,
720 "y": 3.0
721 }
722})json",
723 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
724 {.multi_line = true}));
725 object->clear_substruct();
726 ASSERT_TRUE(builder.Verify());
727 EXPECT_FALSE(object->has_substruct());
728 object->set_substruct(SubStruct{4, 5});
729 EXPECT_EQ(
730 R"json({
731 "scalar": 456,
732 "vector_of_scalars": [
733 9,
734 7,
735 1
736 ],
737 "substruct": {
738 "x": 4.0,
739 "y": 5.0
740 }
741})json",
742 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
743 {.multi_line = true}));
744 }
745 {
746 object->clear_subtable();
747 ASSERT_TRUE(builder.Verify());
748 EXPECT_FALSE(object->has_subtable());
749 auto subtable = CHECK_NOTNULL(object->add_subtable());
750 subtable->set_baz(9.71);
751 EXPECT_EQ(
752 R"json({
753 "scalar": 456,
754 "vector_of_scalars": [
755 9,
756 7,
757 1
758 ],
759 "substruct": {
760 "x": 4.0,
761 "y": 5.0
762 },
763 "subtable": {
764 "baz": 9.71
765 }
766})json",
767 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
768 {.multi_line = true}));
769 object->clear_subtable();
770 ASSERT_TRUE(builder.Verify());
771 EXPECT_FALSE(object->has_subtable());
772 subtable = CHECK_NOTNULL(object->add_subtable());
773 subtable->set_baz(16.78);
774 EXPECT_EQ(
775 R"json({
776 "scalar": 456,
777 "vector_of_scalars": [
778 9,
779 7,
780 1
781 ],
782 "substruct": {
783 "x": 4.0,
784 "y": 5.0
785 },
786 "subtable": {
787 "baz": 16.780001
788 }
789})json",
790 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
791 {.multi_line = true}));
792 }
James Kuszmaul1c9693f2023-12-08 09:45:26 -0800793 TestMemory(builder.buffer());
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700794}
795
796// Try to cover ~all supported scalar/flatbuffer types using JSON convenience
797// functions.
798TEST_F(StaticFlatbuffersTest, FlatbufferTypeCoverage) {
799 VerifyJson<frc971::testing::ConfigurationStatic>("{\n\n}");
800 std::string populated_config =
801 aos::util::ReadFileToStringOrDie(aos::testing::ArtifactPath(
802 "aos/flatbuffers/test_dir/type_coverage.json"));
803 // Get rid of a pesky new line.
804 populated_config = populated_config.substr(0, populated_config.size() - 1);
805 VerifyJson<frc971::testing::ConfigurationStatic>(populated_config);
806
807 // And now play around with mutating the buffer.
808 Builder<frc971::testing::ConfigurationStatic> builder =
809 aos::JsonToStaticFlatbuffer<frc971::testing::ConfigurationStatic>(
810 populated_config);
811 ASSERT_TRUE(builder.Verify());
812 builder.get()->clear_foo_float();
813 ASSERT_TRUE(builder.Verify());
814 ASSERT_FALSE(builder.get()->AsFlatbuffer().has_foo_float());
815 builder.get()->set_foo_float(1.111);
816 ASSERT_TRUE(builder.Verify());
817 ASSERT_FLOAT_EQ(1.111, builder.get()->AsFlatbuffer().foo_float());
James Kuszmaul1c9693f2023-12-08 09:45:26 -0800818 TestMemory(builder.buffer());
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700819}
820
James Kuszmaula75cd7c2023-12-07 15:52:51 -0800821TEST_F(StaticFlatbuffersTest, MinimallyAlignedTable) {
822 VerifyJson<MinimallyAlignedTableStatic>("{\n \"field\": 123\n}");
823 static_assert(4u == alignof(uoffset_t),
824 "The alignment of a uoffset_t is expected to be 4.");
825 ASSERT_EQ(alignof(uoffset_t), MinimallyAlignedTableStatic::kAlign)
826 << "No table should have an alignment of less than the alignment of the "
827 "table's root offset.";
828}
829
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700830// Confirm that we can use the SpanAllocator with a span that provides exactly
831// the required buffer size.
832TEST_F(StaticFlatbuffersTest, ExactSizeSpanAllocator) {
James Kuszmaul1c9693f2023-12-08 09:45:26 -0800833 uint8_t buffer[Builder<TestTableStatic>::kBufferSize];
834 aos::fbs::SpanAllocator allocator({buffer, sizeof(buffer)});
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700835 Builder<TestTableStatic> builder(&allocator);
836 TestTableStatic *object = builder.get();
837 object->set_scalar(123);
838 {
839 auto vector = object->add_vector_of_scalars();
840 ASSERT_TRUE(vector->emplace_back(4));
841 ASSERT_TRUE(vector->emplace_back(5));
842 }
843 {
844 auto string = object->add_string();
845 string->SetString("Hello, World!");
846 }
847 {
848 auto vector_of_strings = object->add_vector_of_strings();
849 auto sub_string = CHECK_NOTNULL(vector_of_strings->emplace_back());
850 ASSERT_TRUE(sub_string->emplace_back('D'));
851 }
852 { object->set_substruct({971, 254}); }
853 {
854 auto subtable = object->add_subtable();
855 subtable->set_foo(1234);
856 }
857 {
858 auto vector = object->add_vector_of_structs();
859 ASSERT_TRUE(vector->emplace_back({48, 67}));
860 ASSERT_TRUE(vector->emplace_back({118, 148}));
861 ASSERT_TRUE(vector->emplace_back({971, 973}));
862 // Max vector size is three; this should fail.
863 ASSERT_FALSE(vector->emplace_back({1114, 2056}));
864 // We don't have any extra space available.
865 ASSERT_FALSE(vector->reserve(4));
866 ASSERT_FALSE(vector->emplace_back({1114, 2056}));
867 }
868 {
869 auto vector = object->add_vector_of_tables();
870 auto subobject = vector->emplace_back();
871 subobject->set_foo(222);
872 }
873 {
874 auto subtable = object->add_included_table();
875 subtable->set_foo(included::TestEnum::B);
876 }
877 ASSERT_TRUE(builder.AsFlatbufferSpan().Verify());
878 VLOG(1) << aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
879 {.multi_line = true});
880 VLOG(1) << AnnotateBinaries(test_schema_, builder.buffer());
James Kuszmaul1c9693f2023-12-08 09:45:26 -0800881 TestMemory(builder.buffer());
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700882}
883
884// Test that when we provide too small of a span to the Builder that it
885// correctly fails.
886TEST_F(StaticFlatbuffersTest, TooSmallSpanAllocator) {
887 std::vector<uint8_t> buffer;
888 buffer.resize(10, 0);
889 aos::fbs::SpanAllocator allocator({buffer.data(), buffer.size()});
890 EXPECT_DEATH(Builder<TestTableStatic>{&allocator}, "Failed to allocate");
891}
892
893// Verify that if we create a span with extra headroom that that lets us
894// dynamically alter the size of vectors in the flatbuffers.
895TEST_F(StaticFlatbuffersTest, ExtraLargeSpanAllocator) {
James Kuszmaul1c9693f2023-12-08 09:45:26 -0800896 uint8_t buffer[Builder<TestTableStatic>::kBufferSize + 10000];
897 aos::fbs::SpanAllocator allocator({buffer, sizeof(buffer)});
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700898 Builder<TestTableStatic> builder(&allocator);
899 TestTableStatic *object = builder.get();
900 {
901 auto vector = object->add_unspecified_length_vector();
902 // Confirm that the vector does indeed start out at zero length.
903 ASSERT_FALSE(vector->emplace_back(4));
904 ASSERT_TRUE(vector->reserve(9000));
905 vector->resize(256);
906 for (size_t index = 0; index < 256; ++index) {
907 vector->at(index) = static_cast<uint8_t>(index);
908 }
909 }
910 ASSERT_EQ(256, object->AsFlatbuffer().unspecified_length_vector()->size());
911 size_t expected = 0;
912 for (const uint8_t value :
913 *object->AsFlatbuffer().unspecified_length_vector()) {
914 EXPECT_EQ(expected++, value);
915 }
James Kuszmaul22448052023-12-14 15:55:14 -0800916 expected = 0;
917 for (const uint8_t value : *object->unspecified_length_vector()) {
918 EXPECT_EQ(expected++, value);
919 }
James Kuszmaul1c9693f2023-12-08 09:45:26 -0800920 TestMemory(builder.buffer());
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700921}
James Kuszmaul22448052023-12-14 15:55:14 -0800922
923// Tests that the iterators on the Vector type work.
924TEST_F(StaticFlatbuffersTest, IteratorTest) {
925 Builder<TestTableStatic> builder(std::make_unique<VectorAllocator>());
926 {
927 auto vector = builder->add_unspecified_length_vector();
928 ASSERT_TRUE(vector->reserve(9000));
929 vector->resize(256);
930 uint8_t set_value = 0;
931 for (uint8_t &destination : *vector) {
932 destination = set_value;
933 ++set_value;
934 }
935 uint8_t expected = 0;
936 for (const uint8_t value : *builder->unspecified_length_vector()) {
937 EXPECT_EQ(expected, value);
938 ++expected;
939 }
940 // Exercise some of the random access iterator functionality to ensure that
941 // we have it implemented.
942 auto begin_it = vector->begin();
943 EXPECT_EQ(begin_it + 256, vector->end());
944 EXPECT_EQ(7, *(begin_it + 7));
945 EXPECT_EQ(255, *(vector->end() - 1));
946 EXPECT_EQ(256, vector->end() - vector->begin());
947 EXPECT_EQ(-256, vector->begin() - vector->end());
948 static_assert(std::random_access_iterator<decltype(vector->begin())>,
949 "The vector iterator does not meet the requirements of a "
950 "random access iterator.");
951 }
952 {
953 auto vector = builder->add_vector_of_structs();
954 vector->resize(3);
955 double set_value = 0;
956 for (SubStruct &destination : *vector) {
957 destination.mutate_x(set_value);
958 destination.mutate_y(-set_value);
959 set_value += 1.0;
960 }
961 double expected = 0;
962 for (const SubStruct &value : *builder->vector_of_structs()) {
963 EXPECT_EQ(expected, value.x());
964 EXPECT_EQ(-expected, value.y());
965 expected += 1.0;
966 }
967 static_assert(std::random_access_iterator<decltype(vector->begin())>,
968 "The vector iterator does not meet the requirements of a "
969 "random access iterator.");
970 }
971 {
972 auto vector = builder->add_vector_of_tables();
973 vector->resize(3);
974 int set_value = 0;
975 for (SubTableStatic &destination : *vector) {
976 destination.set_foo(set_value);
977 set_value += 1;
978 }
979 int expected = 0;
980 for (const SubTableStatic &value : *builder->vector_of_tables()) {
981 EXPECT_EQ(expected, value.foo());
982 EXPECT_FALSE(value.has_baz());
983 expected += 1;
984 }
985 static_assert(std::random_access_iterator<decltype(vector->begin())>,
986 "The vector iterator does not meet the requirements of a "
987 "random access iterator.");
988 }
989}
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700990} // namespace aos::fbs::testing