blob: ed737cbcb9abc84d595e60bb7387917714052cd0 [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}
58} // namespace
59
60class StaticFlatbuffersTest : public ::testing::Test {
61 protected:
62 template <typename T>
63 void VerifyJson(const std::string_view data) {
64 Builder<T> json_builder = aos::JsonToStaticFlatbuffer<T>(data);
65
66 EXPECT_EQ(data, aos::FlatbufferToJson(json_builder.AsFlatbufferSpan(),
67 {.multi_line = true}));
68 }
69 aos::FlatbufferSpan<reflection::Schema> test_schema_{TestTableSchema()};
70 aos::FlatbufferSpan<reflection::Schema> interesting_schemas_{
71 UnsupportedSchema()};
72};
73
74// Test that compiles the same code that is used by an example in
75// //aos/documentation/aos/docs/flatbuffers.md.
76TEST_F(StaticFlatbuffersTest, DocumentationExample) {
77 aos::fbs::VectorAllocator allocator;
78 Builder<TestTableStatic> builder(&allocator);
79 TestTableStatic *object = builder.get();
80 object->set_scalar(123);
81 {
82 auto vector = object->add_vector_of_scalars();
83 CHECK(vector->emplace_back(4));
84 CHECK(vector->emplace_back(5));
85 }
86 {
87 auto string = object->add_string();
88 string->SetString("Hello, World!");
89 }
90 {
91 auto vector_of_strings = object->add_vector_of_strings();
92 auto sub_string = CHECK_NOTNULL(vector_of_strings->emplace_back());
93 CHECK(sub_string->emplace_back('D'));
94 }
95 { object->set_substruct({971, 254}); }
96 {
97 auto subtable = object->add_subtable();
98 subtable->set_foo(1234);
99 }
100 {
101 auto vector = object->add_vector_of_structs();
102 CHECK(vector->emplace_back({48, 67}));
103 CHECK(vector->emplace_back({118, 148}));
104 CHECK(vector->emplace_back({971, 973}));
105 // Max vector size is three; this should fail.
106 CHECK(!vector->emplace_back({1114, 2056}));
107 }
108 {
109 auto vector = object->add_vector_of_tables();
110 auto subobject = vector->emplace_back();
111 subobject->set_foo(222);
112 }
113 {
114 auto subtable = object->add_included_table();
115 subtable->set_foo(included::TestEnum::B);
116 }
117 ASSERT_TRUE(builder.AsFlatbufferSpan().Verify());
118 LOG(INFO) << aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
119 {.multi_line = true});
120 LOG(INFO) << AnnotateBinaries(test_schema_, builder.buffer());
121}
122
123// Test that compiles the same code that is used by an example in
124// //aos/documentation/aos/docs/flatbuffers.md showing how to convert a
125// Populate*() method for adding a subtable to a flatbuffer.
126namespace {
127flatbuffers::Offset<SubTable> PopulateOld(flatbuffers::FlatBufferBuilder *fbb) {
128 SubTable::Builder builder(*fbb);
129 builder.add_foo(1234);
130 return builder.Finish();
131}
132void PopulateStatic(SubTableStatic *subtable) { subtable->set_foo(1234); }
133} // namespace
134TEST_F(StaticFlatbuffersTest, PopulateMethodConversionExample) {
135 // Using a FlatBufferBuilder:
136 flatbuffers::FlatBufferBuilder fbb;
137 // Note: the PopulateOld() *must* be called prior to creating the builder.
138 const flatbuffers::Offset<SubTable> subtable_offset = PopulateOld(&fbb);
139 TestTable::Builder testtable_builder(fbb);
140 testtable_builder.add_subtable(subtable_offset);
141 fbb.Finish(testtable_builder.Finish());
142 aos::FlatbufferDetachedBuffer<TestTable> fbb_finished = fbb.Release();
143
144 // Using the static flatbuffer API.
145 aos::fbs::VectorAllocator allocator;
146 Builder<TestTableStatic> static_builder(&allocator);
147 PopulateStatic(CHECK_NOTNULL(static_builder.get()->add_subtable()));
148
149 // And confirm that they both contain the expected flatbuffer:
150 const std::string expected = R"json({ "subtable": { "foo": 1234 } })json";
151 EXPECT_EQ(expected, aos::FlatbufferToJson(fbb_finished));
152 EXPECT_EQ(expected, aos::FlatbufferToJson(static_builder.AsFlatbufferSpan()));
153}
154
155TEST_F(StaticFlatbuffersTest, UnsupportedSchema) {
156 const reflection::Schema *schema = &interesting_schemas_.message();
157 EXPECT_DEATH(
158 GenerateCodeForObject(
159 schema, GetObjectByName(schema, "aos.fbs.testing.TableWithUnion")),
160 "Union not supported");
161 GenerateCodeForObject(
162 schema, GetObjectByName(schema, "aos.fbs.testing.MissingVectorLength"));
163 EXPECT_DEATH(
164 GenerateCodeForObject(
165 schema,
166 GetObjectByName(schema, "aos.fbs.testing.NonIntegerVectorLength")),
167 "vector_badlength must specify a positive integer for the "
168 "static_length attribute.");
169 EXPECT_DEATH(GenerateCodeForObject(
170 schema, GetObjectByName(
171 schema, "aos.fbs.testing.NegativeVectorLength")),
172 "Field vector_badlength must have a non-negative "
173 "static_length.");
174 GenerateCodeForObject(
175 schema, GetObjectByName(schema, "aos.fbs.testing.ZeroVectorLength"));
176 GenerateCodeForObject(
177 schema, GetObjectByName(schema, "aos.fbs.testing.MissingStringLength"));
178 GenerateCodeForObject(
179 schema,
180 GetObjectByName(schema, "aos.fbs.testing.MissingSubStringLength"));
181}
182
183// Tests that we can go through and manually build up a big flatbuffer and that
184// it stays valid at all points.
185TEST_F(StaticFlatbuffersTest, ManuallyConstructFlatbuffer) {
186 {
187 aos::fbs::VectorAllocator allocator;
188 Builder<SubTableStatic> builder(&allocator);
189 SubTableStatic *object = builder.get();
190 if (!builder.AsFlatbufferSpan().Verify()) {
191 LOG(ERROR) << object->SerializationDebugString() << "\nRoot table offset "
192 << *reinterpret_cast<const uoffset_t *>(
193 builder.buffer().data())
194 << "\nraw bytes\n";
195 aos::fbs::internal::DebugBytes(builder.buffer(), std::cerr);
196 FAIL();
197 return;
198 }
199 EXPECT_EQ("{ }", aos::FlatbufferToJson(builder.AsFlatbufferSpan()));
200 object->set_foo(123);
201 object->set_baz(971);
202 CHECK(builder.AsFlatbufferSpan().Verify());
203 EXPECT_EQ(123, object->AsFlatbuffer().foo());
204 EXPECT_EQ(971, object->AsFlatbuffer().baz());
205 EXPECT_EQ(R"json({ "foo": 123, "baz": 971.0 })json",
206 aos::FlatbufferToJson(builder.AsFlatbufferSpan()));
207 }
208 {
209 // aos::FixedAllocator allocator(TestTableStatic::kUnalignedBufferSize);
210 aos::fbs::VectorAllocator allocator;
211 Builder<TestTableStatic> builder(&allocator);
212 TestTableStatic *object = builder.get();
213 const aos::fbs::testing::TestTable &fbs = object->AsFlatbuffer();
214 VLOG(1) << object->SerializationDebugString();
215 CHECK(builder.AsFlatbufferSpan().Verify());
216 EXPECT_EQ("{ }", aos::FlatbufferToJson(builder.AsFlatbufferSpan()));
217 {
218 ASSERT_FALSE(object->has_scalar());
219 object->set_scalar(123);
220 EXPECT_TRUE(fbs.has_scalar());
221 EXPECT_EQ(123, fbs.scalar());
222 }
223 EXPECT_EQ(R"json({ "scalar": 123 })json",
224 aos::FlatbufferToJson(builder.AsFlatbufferSpan()));
225 {
226 ASSERT_FALSE(object->has_vector_of_scalars());
227 auto vector = object->add_vector_of_scalars();
228 ASSERT_TRUE(vector->emplace_back(4));
229 ASSERT_TRUE(vector->emplace_back(5));
230 ASSERT_TRUE(object->has_vector_of_scalars());
231 ASSERT_TRUE(fbs.has_vector_of_scalars());
232 VLOG(1) << vector->SerializationDebugString();
233 EXPECT_TRUE(fbs.has_vector_of_scalars());
234 EXPECT_EQ(2u, fbs.vector_of_scalars()->size());
235 EXPECT_EQ(4, fbs.vector_of_scalars()->Get(0));
236 EXPECT_EQ(5, fbs.vector_of_scalars()->Get(1));
237 }
238 EXPECT_EQ(R"json({ "scalar": 123, "vector_of_scalars": [ 4, 5 ] })json",
239 aos::FlatbufferToJson(builder.AsFlatbufferSpan()));
240 {
241 EXPECT_FALSE(object->has_string());
242 auto string = object->add_string();
243 EXPECT_TRUE(object->has_string());
244 string->SetString("Hello, World!");
245 EXPECT_EQ(13u, object->string()->size());
246 ASSERT_TRUE(fbs.has_string());
247 ASSERT_EQ(13u, fbs.string()->size());
248 EXPECT_EQ("Hello, World!", fbs.string()->string_view());
249 // Check that we null-terminated correctly.
250 EXPECT_EQ(13u, strnlen(fbs.string()->c_str(), 20));
251 }
252 EXPECT_EQ(
253 R"json({ "scalar": 123, "vector_of_scalars": [ 4, 5 ], "string": "Hello, World!" })json",
254 aos::FlatbufferToJson(builder.AsFlatbufferSpan()));
255 {
256 EXPECT_FALSE(object->has_vector_of_strings());
257 auto vector_of_strings = object->add_vector_of_strings();
258 EXPECT_TRUE(object->has_vector_of_strings());
259 auto sub_string = CHECK_NOTNULL(vector_of_strings->emplace_back());
260 ASSERT_TRUE(sub_string->emplace_back('D'));
261 EXPECT_TRUE(fbs.has_vector_of_strings());
262 ASSERT_EQ(1u, fbs.vector_of_strings()->size());
263 ASSERT_EQ(1u, fbs.vector_of_strings()->Get(0)->size());
264 EXPECT_EQ('D', fbs.vector_of_strings()->Get(0)->Get(0));
265 }
266 EXPECT_EQ(
267 R"json({
268 "scalar": 123,
269 "vector_of_scalars": [
270 4,
271 5
272 ],
273 "string": "Hello, World!",
274 "vector_of_strings": [
275 "D"
276 ]
277})json",
278 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
279 {.multi_line = true}));
280 {
281 EXPECT_FALSE(object->has_substruct());
282 object->set_substruct({971, 254});
283 EXPECT_TRUE(object->has_substruct());
284 EXPECT_TRUE(fbs.has_substruct());
285 EXPECT_EQ(971, fbs.substruct()->x());
286 EXPECT_EQ(254, fbs.substruct()->y());
287 }
288 EXPECT_EQ(
289 R"json({
290 "scalar": 123,
291 "vector_of_scalars": [
292 4,
293 5
294 ],
295 "string": "Hello, World!",
296 "vector_of_strings": [
297 "D"
298 ],
299 "substruct": {
300 "x": 971.0,
301 "y": 254.0
302 }
303})json",
304 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
305 {.multi_line = true}));
306 {
307 auto subtable = object->add_subtable();
308 subtable->set_foo(1234);
309 EXPECT_TRUE(fbs.has_subtable());
310 EXPECT_EQ(1234, fbs.subtable()->foo());
311 EXPECT_FALSE(fbs.subtable()->has_baz());
312 }
313 EXPECT_EQ(
314 R"json({
315 "scalar": 123,
316 "vector_of_scalars": [
317 4,
318 5
319 ],
320 "string": "Hello, World!",
321 "vector_of_strings": [
322 "D"
323 ],
324 "substruct": {
325 "x": 971.0,
326 "y": 254.0
327 },
328 "subtable": {
329 "foo": 1234
330 }
331})json",
332 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
333 {.multi_line = true}));
334 {
335 auto vector = object->add_vector_of_structs();
336 ASSERT_TRUE(vector->emplace_back({48, 67}));
337 ASSERT_TRUE(vector->emplace_back({118, 148}));
338 ASSERT_TRUE(vector->emplace_back({971, 973}));
339 ASSERT_FALSE(vector->emplace_back({1114, 2056}));
340 EXPECT_TRUE(fbs.has_vector_of_structs());
341 EXPECT_EQ(3u, fbs.vector_of_structs()->size());
342 EXPECT_EQ(48, fbs.vector_of_structs()->Get(0)->x());
343 EXPECT_EQ(67, fbs.vector_of_structs()->Get(0)->y());
344 EXPECT_EQ(118, fbs.vector_of_structs()->Get(1)->x());
345 EXPECT_EQ(object->vector_of_structs()->at(1).x(),
346 fbs.vector_of_structs()->Get(1)->x());
347 EXPECT_EQ((*object->vector_of_structs())[1].x(),
348 fbs.vector_of_structs()->Get(1)->x());
349 EXPECT_EQ(148, fbs.vector_of_structs()->Get(1)->y());
350 EXPECT_EQ(971, fbs.vector_of_structs()->Get(2)->x());
351 EXPECT_EQ(973, fbs.vector_of_structs()->Get(2)->y());
352 }
353 EXPECT_EQ(
354 R"json({
355 "scalar": 123,
356 "vector_of_scalars": [
357 4,
358 5
359 ],
360 "string": "Hello, World!",
361 "vector_of_strings": [
362 "D"
363 ],
364 "substruct": {
365 "x": 971.0,
366 "y": 254.0
367 },
368 "subtable": {
369 "foo": 1234
370 },
371 "vector_of_structs": [
372 {
373 "x": 48.0,
374 "y": 67.0
375 },
376 {
377 "x": 118.0,
378 "y": 148.0
379 },
380 {
381 "x": 971.0,
382 "y": 973.0
383 }
384 ]
385})json",
386 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
387 {.multi_line = true}));
388 {
389 EXPECT_FALSE(object->has_vector_of_tables());
390 auto vector = object->add_vector_of_tables();
391 EXPECT_TRUE(object->has_vector_of_tables());
392 auto subobject = vector->emplace_back();
393 subobject->set_foo(222);
394 EXPECT_TRUE(fbs.has_vector_of_tables());
395 EXPECT_EQ(1u, fbs.vector_of_tables()->size());
396 EXPECT_EQ(222, fbs.vector_of_tables()->Get(0)->foo());
397 EXPECT_EQ(object->vector_of_tables()->at(0).foo(),
398 fbs.vector_of_tables()->Get(0)->foo());
399 }
400 EXPECT_EQ(
401 R"json({
402 "scalar": 123,
403 "vector_of_scalars": [
404 4,
405 5
406 ],
407 "string": "Hello, World!",
408 "vector_of_strings": [
409 "D"
410 ],
411 "substruct": {
412 "x": 971.0,
413 "y": 254.0
414 },
415 "subtable": {
416 "foo": 1234
417 },
418 "vector_of_structs": [
419 {
420 "x": 48.0,
421 "y": 67.0
422 },
423 {
424 "x": 118.0,
425 "y": 148.0
426 },
427 {
428 "x": 971.0,
429 "y": 973.0
430 }
431 ],
432 "vector_of_tables": [
433 {
434 "foo": 222
435 }
436 ]
437})json",
438 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
439 {.multi_line = true}));
440 {
441 EXPECT_FALSE(object->has_included_table());
442 auto subtable = object->add_included_table();
443 EXPECT_TRUE(object->has_included_table());
444 subtable->set_foo(included::TestEnum::B);
445 ASSERT_TRUE(fbs.has_included_table());
446 ASSERT_TRUE(fbs.included_table()->has_foo());
447 EXPECT_EQ(included::TestEnum::B, fbs.included_table()->foo());
448 }
449 EXPECT_EQ(
450 R"json({
451 "scalar": 123,
452 "vector_of_scalars": [
453 4,
454 5
455 ],
456 "string": "Hello, World!",
457 "vector_of_strings": [
458 "D"
459 ],
460 "substruct": {
461 "x": 971.0,
462 "y": 254.0
463 },
464 "subtable": {
465 "foo": 1234
466 },
467 "vector_of_structs": [
468 {
469 "x": 48.0,
470 "y": 67.0
471 },
472 {
473 "x": 118.0,
474 "y": 148.0
475 },
476 {
477 "x": 971.0,
478 "y": 973.0
479 }
480 ],
481 "vector_of_tables": [
482 {
483 "foo": 222
484 }
485 ],
486 "included_table": {
487 "foo": "B"
488 }
489})json",
490 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
491 {.multi_line = true}));
492 {
493 auto aligned_vector = object->add_vector_aligned();
494 ASSERT_EQ(64,
495 std::remove_reference<decltype(*aligned_vector)>::type::kAlign);
496 ASSERT_EQ(64, TestTableStatic::kAlign);
497 ASSERT_TRUE(aligned_vector->emplace_back(444));
498 EXPECT_TRUE(fbs.has_vector_aligned());
499 EXPECT_EQ(1u, fbs.vector_aligned()->size());
500 EXPECT_EQ(0u,
501 reinterpret_cast<size_t>(fbs.vector_aligned()->data()) % 64);
502 EXPECT_EQ(444, fbs.vector_aligned()->Get(0));
503 }
504 VLOG(1) << object->SerializationDebugString();
505 CHECK(builder.AsFlatbufferSpan().Verify());
506 const std::string expected_contents =
507 R"json({
508 "scalar": 123,
509 "vector_of_scalars": [
510 4,
511 5
512 ],
513 "string": "Hello, World!",
514 "vector_of_strings": [
515 "D"
516 ],
517 "substruct": {
518 "x": 971.0,
519 "y": 254.0
520 },
521 "subtable": {
522 "foo": 1234
523 },
524 "vector_aligned": [
525 444
526 ],
527 "vector_of_structs": [
528 {
529 "x": 48.0,
530 "y": 67.0
531 },
532 {
533 "x": 118.0,
534 "y": 148.0
535 },
536 {
537 "x": 971.0,
538 "y": 973.0
539 }
540 ],
541 "vector_of_tables": [
542 {
543 "foo": 222
544 }
545 ],
546 "included_table": {
547 "foo": "B"
548 }
549})json";
550 EXPECT_EQ(expected_contents,
551 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
552 {.multi_line = true}));
553 VLOG(1) << AnnotateBinaries(test_schema_, builder.buffer());
554 VerifyJson<TestTableStatic>(expected_contents);
555 {
556 auto aligned_vector = object->mutable_vector_aligned();
557 ASSERT_TRUE(aligned_vector->reserve(100));
558 EXPECT_EQ(100, aligned_vector->capacity());
559 ASSERT_TRUE(builder.AsFlatbufferSpan().Verify())
560 << aligned_vector->SerializationDebugString();
561 EXPECT_EQ(expected_contents,
562 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
563 {.multi_line = true}));
564 std::vector<int> scalars;
565 scalars.push_back(aligned_vector->at(0));
566 while (aligned_vector->size() < 100u) {
567 scalars.push_back(aligned_vector->size());
568 CHECK(aligned_vector->emplace_back(aligned_vector->size()));
569 }
570 VLOG(1) << aligned_vector->SerializationDebugString();
571 VLOG(1) << AnnotateBinaries(test_schema_, builder.buffer());
572 EXPECT_EQ(absl::StrFormat(
573 R"json({
574 "scalar": 123,
575 "vector_of_scalars": [
576 4,
577 5
578 ],
579 "string": "Hello, World!",
580 "vector_of_strings": [
581 "D"
582 ],
583 "substruct": {
584 "x": 971.0,
585 "y": 254.0
586 },
587 "subtable": {
588 "foo": 1234
589 },
590 "vector_aligned": [
591 %s
592 ],
593 "vector_of_structs": [
594 {
595 "x": 48.0,
596 "y": 67.0
597 },
598 {
599 "x": 118.0,
600 "y": 148.0
601 },
602 {
603 "x": 971.0,
604 "y": 973.0
605 }
606 ],
607 "vector_of_tables": [
608 {
609 "foo": 222
610 }
611 ],
612 "included_table": {
613 "foo": "B"
614 }
615})json",
616 absl::StrJoin(scalars, ",\n ")),
617 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
618 {.multi_line = true}));
619 }
620
621 {
622 auto unspecified_vector = object->add_unspecified_length_vector();
623 ASSERT_NE(nullptr, unspecified_vector);
624 ASSERT_EQ(0, unspecified_vector->capacity());
625 ASSERT_FALSE(unspecified_vector->emplace_back(0));
626 ASSERT_TRUE(unspecified_vector->reserve(2));
627 ASSERT_TRUE(unspecified_vector->emplace_back(1));
628 ASSERT_TRUE(unspecified_vector->emplace_back(2));
629 ASSERT_FALSE(unspecified_vector->emplace_back(3));
630 ASSERT_TRUE(builder.AsFlatbufferSpan().Verify());
631 }
632 }
633}
634
635// Tests that field clearing (and subsequent resetting) works properly.
636TEST_F(StaticFlatbuffersTest, ClearFields) {
637 aos::fbs::VectorAllocator allocator;
638 Builder<TestTableStatic> builder(&allocator);
639 TestTableStatic *object = builder.get();
640 // For each field, we will confirm the following:
641 // * Clearing a non-existent field causes no issues.
642 // * We can set a field, clear it, and have it not be present.
643 // * We can set the field again afterwards.
644 {
645 object->clear_scalar();
646 ASSERT_TRUE(builder.Verify());
647 object->set_scalar(123);
648 EXPECT_EQ(123, object->AsFlatbuffer().scalar());
649 object->clear_scalar();
650 ASSERT_TRUE(builder.Verify());
651 EXPECT_FALSE(object->has_scalar());
652 object->set_scalar(456);
653 EXPECT_EQ(456, object->AsFlatbuffer().scalar());
654 }
655 {
656 object->clear_vector_of_scalars();
657 ASSERT_TRUE(builder.Verify());
658 EXPECT_FALSE(object->has_vector_of_scalars());
659 auto vector = object->add_vector_of_scalars();
660 ASSERT_TRUE(vector->emplace_back(4));
661 ASSERT_TRUE(vector->emplace_back(5));
662 ASSERT_TRUE(vector->emplace_back(6));
663 // Deliberately force a resize of the vector to ensure that we can exercise
664 // what happens if we clear a non-standard size field.
665 ASSERT_FALSE(vector->emplace_back(7));
666 ASSERT_TRUE(vector->reserve(4));
667 ASSERT_TRUE(vector->emplace_back(7));
668 EXPECT_EQ(
669 R"json({
670 "scalar": 456,
671 "vector_of_scalars": [
672 4,
673 5,
674 6,
675 7
676 ]
677})json",
678 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
679 {.multi_line = true}));
680 ASSERT_TRUE(builder.Verify());
681 object->clear_vector_of_scalars();
682 ASSERT_TRUE(builder.Verify());
683 ASSERT_FALSE(object->has_vector_of_scalars())
684 << aos::FlatbufferToJson(builder.AsFlatbufferSpan());
685 vector = CHECK_NOTNULL(object->add_vector_of_scalars());
686 ASSERT_TRUE(builder.Verify());
687 EXPECT_EQ(0u, object->AsFlatbuffer().vector_of_scalars()->size());
688 ASSERT_TRUE(vector->emplace_back(9));
689 ASSERT_TRUE(vector->emplace_back(7));
690 ASSERT_TRUE(vector->emplace_back(1));
691 // This vector has no knowledge of the past resizing; it should fail to add
692 // an extra number.
693 ASSERT_FALSE(vector->emplace_back(7));
694 }
695 {
696 object->clear_substruct();
697 ASSERT_TRUE(builder.Verify());
698 EXPECT_FALSE(object->has_substruct());
699 object->set_substruct(SubStruct{2, 3});
700 EXPECT_EQ(
701 R"json({
702 "scalar": 456,
703 "vector_of_scalars": [
704 9,
705 7,
706 1
707 ],
708 "substruct": {
709 "x": 2.0,
710 "y": 3.0
711 }
712})json",
713 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
714 {.multi_line = true}));
715 object->clear_substruct();
716 ASSERT_TRUE(builder.Verify());
717 EXPECT_FALSE(object->has_substruct());
718 object->set_substruct(SubStruct{4, 5});
719 EXPECT_EQ(
720 R"json({
721 "scalar": 456,
722 "vector_of_scalars": [
723 9,
724 7,
725 1
726 ],
727 "substruct": {
728 "x": 4.0,
729 "y": 5.0
730 }
731})json",
732 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
733 {.multi_line = true}));
734 }
735 {
736 object->clear_subtable();
737 ASSERT_TRUE(builder.Verify());
738 EXPECT_FALSE(object->has_subtable());
739 auto subtable = CHECK_NOTNULL(object->add_subtable());
740 subtable->set_baz(9.71);
741 EXPECT_EQ(
742 R"json({
743 "scalar": 456,
744 "vector_of_scalars": [
745 9,
746 7,
747 1
748 ],
749 "substruct": {
750 "x": 4.0,
751 "y": 5.0
752 },
753 "subtable": {
754 "baz": 9.71
755 }
756})json",
757 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
758 {.multi_line = true}));
759 object->clear_subtable();
760 ASSERT_TRUE(builder.Verify());
761 EXPECT_FALSE(object->has_subtable());
762 subtable = CHECK_NOTNULL(object->add_subtable());
763 subtable->set_baz(16.78);
764 EXPECT_EQ(
765 R"json({
766 "scalar": 456,
767 "vector_of_scalars": [
768 9,
769 7,
770 1
771 ],
772 "substruct": {
773 "x": 4.0,
774 "y": 5.0
775 },
776 "subtable": {
777 "baz": 16.780001
778 }
779})json",
780 aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
781 {.multi_line = true}));
782 }
783}
784
785// Try to cover ~all supported scalar/flatbuffer types using JSON convenience
786// functions.
787TEST_F(StaticFlatbuffersTest, FlatbufferTypeCoverage) {
788 VerifyJson<frc971::testing::ConfigurationStatic>("{\n\n}");
789 std::string populated_config =
790 aos::util::ReadFileToStringOrDie(aos::testing::ArtifactPath(
791 "aos/flatbuffers/test_dir/type_coverage.json"));
792 // Get rid of a pesky new line.
793 populated_config = populated_config.substr(0, populated_config.size() - 1);
794 VerifyJson<frc971::testing::ConfigurationStatic>(populated_config);
795
796 // And now play around with mutating the buffer.
797 Builder<frc971::testing::ConfigurationStatic> builder =
798 aos::JsonToStaticFlatbuffer<frc971::testing::ConfigurationStatic>(
799 populated_config);
800 ASSERT_TRUE(builder.Verify());
801 builder.get()->clear_foo_float();
802 ASSERT_TRUE(builder.Verify());
803 ASSERT_FALSE(builder.get()->AsFlatbuffer().has_foo_float());
804 builder.get()->set_foo_float(1.111);
805 ASSERT_TRUE(builder.Verify());
806 ASSERT_FLOAT_EQ(1.111, builder.get()->AsFlatbuffer().foo_float());
807}
808
809// Confirm that we can use the SpanAllocator with a span that provides exactly
810// the required buffer size.
811TEST_F(StaticFlatbuffersTest, ExactSizeSpanAllocator) {
812 std::vector<uint8_t> buffer;
813 buffer.resize(Builder<TestTableStatic>::kBufferSize, 0);
814 aos::fbs::SpanAllocator allocator({buffer.data(), buffer.size()});
815 Builder<TestTableStatic> builder(&allocator);
816 TestTableStatic *object = builder.get();
817 object->set_scalar(123);
818 {
819 auto vector = object->add_vector_of_scalars();
820 ASSERT_TRUE(vector->emplace_back(4));
821 ASSERT_TRUE(vector->emplace_back(5));
822 }
823 {
824 auto string = object->add_string();
825 string->SetString("Hello, World!");
826 }
827 {
828 auto vector_of_strings = object->add_vector_of_strings();
829 auto sub_string = CHECK_NOTNULL(vector_of_strings->emplace_back());
830 ASSERT_TRUE(sub_string->emplace_back('D'));
831 }
832 { object->set_substruct({971, 254}); }
833 {
834 auto subtable = object->add_subtable();
835 subtable->set_foo(1234);
836 }
837 {
838 auto vector = object->add_vector_of_structs();
839 ASSERT_TRUE(vector->emplace_back({48, 67}));
840 ASSERT_TRUE(vector->emplace_back({118, 148}));
841 ASSERT_TRUE(vector->emplace_back({971, 973}));
842 // Max vector size is three; this should fail.
843 ASSERT_FALSE(vector->emplace_back({1114, 2056}));
844 // We don't have any extra space available.
845 ASSERT_FALSE(vector->reserve(4));
846 ASSERT_FALSE(vector->emplace_back({1114, 2056}));
847 }
848 {
849 auto vector = object->add_vector_of_tables();
850 auto subobject = vector->emplace_back();
851 subobject->set_foo(222);
852 }
853 {
854 auto subtable = object->add_included_table();
855 subtable->set_foo(included::TestEnum::B);
856 }
857 ASSERT_TRUE(builder.AsFlatbufferSpan().Verify());
858 VLOG(1) << aos::FlatbufferToJson(builder.AsFlatbufferSpan(),
859 {.multi_line = true});
860 VLOG(1) << AnnotateBinaries(test_schema_, builder.buffer());
861}
862
863// Test that when we provide too small of a span to the Builder that it
864// correctly fails.
865TEST_F(StaticFlatbuffersTest, TooSmallSpanAllocator) {
866 std::vector<uint8_t> buffer;
867 buffer.resize(10, 0);
868 aos::fbs::SpanAllocator allocator({buffer.data(), buffer.size()});
869 EXPECT_DEATH(Builder<TestTableStatic>{&allocator}, "Failed to allocate");
870}
871
872// Verify that if we create a span with extra headroom that that lets us
873// dynamically alter the size of vectors in the flatbuffers.
874TEST_F(StaticFlatbuffersTest, ExtraLargeSpanAllocator) {
875 std::vector<uint8_t> buffer;
876 buffer.resize(Builder<TestTableStatic>::kBufferSize + 10000, 0);
877 aos::fbs::SpanAllocator allocator({buffer.data(), buffer.size()});
878 Builder<TestTableStatic> builder(&allocator);
879 TestTableStatic *object = builder.get();
880 {
881 auto vector = object->add_unspecified_length_vector();
882 // Confirm that the vector does indeed start out at zero length.
883 ASSERT_FALSE(vector->emplace_back(4));
884 ASSERT_TRUE(vector->reserve(9000));
885 vector->resize(256);
886 for (size_t index = 0; index < 256; ++index) {
887 vector->at(index) = static_cast<uint8_t>(index);
888 }
889 }
890 ASSERT_EQ(256, object->AsFlatbuffer().unspecified_length_vector()->size());
891 size_t expected = 0;
892 for (const uint8_t value :
893 *object->AsFlatbuffer().unspecified_length_vector()) {
894 EXPECT_EQ(expected++, value);
895 }
896}
897} // namespace aos::fbs::testing