blob: f081dd92f518c7c1cfcbf2c873c50af130d86b55 [file] [log] [blame]
Austin Schuh2dd86a92022-09-14 21:19:23 -07001#include "monster_test.h"
2
James Kuszmaul3b15b0c2022-11-08 14:03:16 -08003#include <limits>
Austin Schuh2dd86a92022-09-14 21:19:23 -07004#include <vector>
5
6#include "flatbuffers/flatbuffer_builder.h"
7#include "flatbuffers/idl.h"
8#include "flatbuffers/registry.h"
9#include "flatbuffers/verifier.h"
10#include "is_quiet_nan.h"
11#include "monster_extra_generated.h"
12#include "monster_test_generated.h"
13#include "test_assert.h"
14
15namespace flatbuffers {
16namespace tests {
17
18// Shortcuts for the infinity.
19static const auto infinity_f = std::numeric_limits<float>::infinity();
20static const auto infinity_d = std::numeric_limits<double>::infinity();
21
22using namespace MyGame::Example;
23
24// example of how to build up a serialized buffer algorithmically:
25flatbuffers::DetachedBuffer CreateFlatBufferTest(std::string &buffer) {
26 flatbuffers::FlatBufferBuilder builder;
27
28 auto vec = Vec3(1, 2, 3, 0, Color_Red, Test(10, 20));
29
30 auto name = builder.CreateString("MyMonster");
31
32 // Use the initializer_list specialization of CreateVector.
33 auto inventory =
34 builder.CreateVector<uint8_t>({ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
35
36 // Alternatively, create the vector first, and fill in data later:
37 // unsigned char *inv_buf = nullptr;
38 // auto inventory = builder.CreateUninitializedVector<unsigned char>(
39 // 10, &inv_buf);
40 // memcpy(inv_buf, inv_data, 10);
41
42 Test tests[] = { Test(10, 20), Test(30, 40) };
43 auto testv = builder.CreateVectorOfStructs(tests, 2);
44
45 // Create a vector of structures from a lambda.
46 auto testv2 = builder.CreateVectorOfStructs<Test>(
47 2, [&](size_t i, Test *s) -> void { *s = tests[i]; });
48
49 // create monster with very few fields set:
50 // (same functionality as CreateMonster below, but sets fields manually)
51 flatbuffers::Offset<Monster> mlocs[3];
52 auto fred = builder.CreateString("Fred");
53 auto barney = builder.CreateString("Barney");
54 auto wilma = builder.CreateString("Wilma");
55 MonsterBuilder mb1(builder);
56 mb1.add_name(fred);
57 mlocs[0] = mb1.Finish();
58 MonsterBuilder mb2(builder);
59 mb2.add_name(barney);
60 mb2.add_hp(1000);
61 mlocs[1] = mb2.Finish();
62 MonsterBuilder mb3(builder);
63 mb3.add_name(wilma);
64 mlocs[2] = mb3.Finish();
65
66 // Create an array of strings. Also test string pooling, and lambdas.
67 auto vecofstrings =
68 builder.CreateVector<flatbuffers::Offset<flatbuffers::String>>(
69 4,
70 [](size_t i, flatbuffers::FlatBufferBuilder *b)
71 -> flatbuffers::Offset<flatbuffers::String> {
72 static const char *names[] = { "bob", "fred", "bob", "fred" };
73 return b->CreateSharedString(names[i]);
74 },
75 &builder);
76
77 // Creating vectors of strings in one convenient call.
78 std::vector<std::string> names2;
79 names2.push_back("jane");
80 names2.push_back("mary");
81 auto vecofstrings2 = builder.CreateVectorOfStrings(names2);
82
83 // Creating vectors from types that are different from std::string
84 std::vector<const char *> names3;
85 names3.push_back("foo");
86 names3.push_back("bar");
87 builder.CreateVectorOfStrings(names3); // Also an accepted type
88
89#ifdef FLATBUFFERS_HAS_STRING_VIEW
90 std::vector<flatbuffers::string_view> names4;
91 names3.push_back("baz");
92 names3.push_back("quux");
93 builder.CreateVectorOfStrings(names4); // Also an accepted type
94#endif
95
96 // Make sure the template deduces an initializer as std::vector<std::string>
97 builder.CreateVectorOfStrings({ "hello", "world" });
98
99 // Create many vectors of strings
100 std::vector<std::string> manyNames;
101 for (auto i = 0; i < 100; i++) { manyNames.push_back("john_doe"); }
102 auto manyNamesVec = builder.CreateVectorOfStrings(manyNames);
103 TEST_EQ(false, manyNamesVec.IsNull());
104 auto manyNamesVec2 =
105 builder.CreateVectorOfStrings(manyNames.cbegin(), manyNames.cend());
106 TEST_EQ(false, manyNamesVec2.IsNull());
107
108 // Create an array of sorted tables, can be used with binary search when read:
109 auto vecoftables = builder.CreateVectorOfSortedTables(mlocs, 3);
110
111 // Create an array of sorted structs,
112 // can be used with binary search when read:
113 std::vector<Ability> abilities;
114 abilities.push_back(Ability(4, 40));
115 abilities.push_back(Ability(3, 30));
116 abilities.push_back(Ability(2, 20));
117 abilities.push_back(Ability(0, 0));
118 auto vecofstructs = builder.CreateVectorOfSortedStructs(&abilities);
119
120 flatbuffers::Offset<Stat> mlocs_stats[1];
121 auto miss = builder.CreateString("miss");
122 StatBuilder mb_miss(builder);
123 mb_miss.add_id(miss);
124 mb_miss.add_val(0);
125 mb_miss.add_count(0); // key
126 mlocs_stats[0] = mb_miss.Finish();
127 auto vec_of_stats = builder.CreateVectorOfSortedTables(mlocs_stats, 1);
128
129 // Create a nested FlatBuffer.
130 // Nested FlatBuffers are stored in a ubyte vector, which can be convenient
131 // since they can be memcpy'd around much easier than other FlatBuffer
132 // values. They have little overhead compared to storing the table directly.
133 // As a test, create a mostly empty Monster buffer:
134 flatbuffers::FlatBufferBuilder nested_builder;
135 auto nmloc = CreateMonster(nested_builder, nullptr, 0, 0,
136 nested_builder.CreateString("NestedMonster"));
137 FinishMonsterBuffer(nested_builder, nmloc);
138 // Now we can store the buffer in the parent. Note that by default, vectors
139 // are only aligned to their elements or size field, so in this case if the
140 // buffer contains 64-bit elements, they may not be correctly aligned. We fix
141 // that with:
142 builder.ForceVectorAlignment(nested_builder.GetSize(), sizeof(uint8_t),
143 nested_builder.GetBufferMinAlignment());
144 // If for whatever reason you don't have the nested_builder available, you
145 // can substitute flatbuffers::largest_scalar_t (64-bit) for the alignment, or
146 // the largest force_align value in your schema if you're using it.
147 auto nested_flatbuffer_vector = builder.CreateVector(
148 nested_builder.GetBufferPointer(), nested_builder.GetSize());
149
150 // Test a nested FlexBuffer:
151 flexbuffers::Builder flexbuild;
152 flexbuild.Int(1234);
153 flexbuild.Finish();
154 auto flex = builder.CreateVector(flexbuild.GetBuffer());
155 // Test vector of enums.
156 Color colors[] = { Color_Blue, Color_Green };
157 // We use this special creation function because we have an array of
158 // pre-C++11 (enum class) enums whose size likely is int, yet its declared
159 // type in the schema is byte.
160 auto vecofcolors = builder.CreateVectorScalarCast<uint8_t, Color>(colors, 2);
161
162 // shortcut for creating monster with all fields set:
163 auto mloc = CreateMonster(
164 builder, &vec, 150, 80, name, inventory, Color_Blue, Any_Monster,
165 mlocs[1].Union(), // Store a union.
166 testv, vecofstrings, vecoftables, 0, nested_flatbuffer_vector, 0, false,
167 0, 0, 0, 0, 0, 0, 0, 0, 0, 3.14159f, 3.0f, 0.0f, vecofstrings2,
168 vecofstructs, flex, testv2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
169 AnyUniqueAliases_NONE, 0, AnyAmbiguousAliases_NONE, 0, vecofcolors,
170 MyGame::Example::Race_None, 0, vec_of_stats);
171
172 FinishMonsterBuffer(builder, mloc);
173
174 // clang-format off
175 #ifdef FLATBUFFERS_TEST_VERBOSE
176 // print byte data for debugging:
177 auto p = builder.GetBufferPointer();
178 for (flatbuffers::uoffset_t i = 0; i < builder.GetSize(); i++)
179 printf("%d ", p[i]);
180 #endif
181 // clang-format on
182
183 // return the buffer for the caller to use.
184 auto bufferpointer =
185 reinterpret_cast<const char *>(builder.GetBufferPointer());
186 buffer.assign(bufferpointer, bufferpointer + builder.GetSize());
187
188 return builder.Release();
189}
190
191// example of accessing a buffer loaded in memory:
192void AccessFlatBufferTest(const uint8_t *flatbuf, size_t length, bool pooled) {
193 // First, verify the buffers integrity (optional)
194 flatbuffers::Verifier verifier(flatbuf, length);
195 std::vector<uint8_t> flex_reuse_tracker;
196 verifier.SetFlexReuseTracker(&flex_reuse_tracker);
197 TEST_EQ(VerifyMonsterBuffer(verifier), true);
198
199 // clang-format off
200 #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE
201 std::vector<uint8_t> test_buff;
202 test_buff.resize(length * 2);
203 std::memcpy(&test_buff[0], flatbuf, length);
204 std::memcpy(&test_buff[length], flatbuf, length);
205
206 flatbuffers::Verifier verifier1(&test_buff[0], length);
207 TEST_EQ(VerifyMonsterBuffer(verifier1), true);
208 TEST_EQ(verifier1.GetComputedSize(), length);
209
210 flatbuffers::Verifier verifier2(&test_buff[length], length);
211 TEST_EQ(VerifyMonsterBuffer(verifier2), true);
212 TEST_EQ(verifier2.GetComputedSize(), length);
213 #endif
214 // clang-format on
215
216 TEST_EQ(strcmp(MonsterIdentifier(), "MONS"), 0);
217 TEST_EQ(MonsterBufferHasIdentifier(flatbuf), true);
218 TEST_EQ(strcmp(MonsterExtension(), "mon"), 0);
219
220 // Access the buffer from the root.
221 auto monster = GetMonster(flatbuf);
222
223 TEST_EQ(monster->hp(), 80);
224 TEST_EQ(monster->mana(), 150); // default
225 TEST_EQ_STR(monster->name()->c_str(), "MyMonster");
226 // Can't access the following field, it is deprecated in the schema,
227 // which means accessors are not generated:
228 // monster.friendly()
229
230 auto pos = monster->pos();
231 TEST_NOTNULL(pos);
232 TEST_EQ(pos->z(), 3);
233 TEST_EQ(pos->test3().a(), 10);
234 TEST_EQ(pos->test3().b(), 20);
235
236 auto inventory = monster->inventory();
237 TEST_EQ(VectorLength(inventory), 10UL); // Works even if inventory is null.
238 TEST_NOTNULL(inventory);
239 unsigned char inv_data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
240 // Check compatibilty of iterators with STL.
241 std::vector<unsigned char> inv_vec(inventory->begin(), inventory->end());
242 size_t n = 0;
243 for (auto it = inventory->begin(); it != inventory->end(); ++it, ++n) {
244 auto indx = it - inventory->begin();
245 TEST_EQ(*it, inv_vec.at(indx)); // Use bounds-check.
246 TEST_EQ(*it, inv_data[indx]);
247 }
248 TEST_EQ(n, inv_vec.size());
249
250 n = 0;
251 for (auto it = inventory->cbegin(); it != inventory->cend(); ++it, ++n) {
252 auto indx = it - inventory->cbegin();
253 TEST_EQ(*it, inv_vec.at(indx)); // Use bounds-check.
254 TEST_EQ(*it, inv_data[indx]);
255 }
256 TEST_EQ(n, inv_vec.size());
257
258 n = 0;
259 for (auto it = inventory->rbegin(); it != inventory->rend(); ++it, ++n) {
260 auto indx = inventory->rend() - it - 1;
261 TEST_EQ(*it, inv_vec.at(indx)); // Use bounds-check.
262 TEST_EQ(*it, inv_data[indx]);
263 }
264 TEST_EQ(n, inv_vec.size());
265
266 n = 0;
267 for (auto it = inventory->crbegin(); it != inventory->crend(); ++it, ++n) {
268 auto indx = inventory->crend() - it - 1;
269 TEST_EQ(*it, inv_vec.at(indx)); // Use bounds-check.
270 TEST_EQ(*it, inv_data[indx]);
271 }
272 TEST_EQ(n, inv_vec.size());
273
274 TEST_EQ(monster->color(), Color_Blue);
275
276 // Example of accessing a union:
277 TEST_EQ(monster->test_type(), Any_Monster); // First make sure which it is.
278 auto monster2 = reinterpret_cast<const Monster *>(monster->test());
279 TEST_NOTNULL(monster2);
280 TEST_EQ_STR(monster2->name()->c_str(), "Fred");
281
282 // Example of accessing a vector of strings:
283 auto vecofstrings = monster->testarrayofstring();
284 TEST_EQ(vecofstrings->size(), 4U);
285 TEST_EQ_STR(vecofstrings->Get(0)->c_str(), "bob");
286 TEST_EQ_STR(vecofstrings->Get(1)->c_str(), "fred");
287 if (pooled) {
288 // These should have pointer equality because of string pooling.
289 TEST_EQ(vecofstrings->Get(0)->c_str(), vecofstrings->Get(2)->c_str());
290 TEST_EQ(vecofstrings->Get(1)->c_str(), vecofstrings->Get(3)->c_str());
291 }
292
293 auto vecofstrings2 = monster->testarrayofstring2();
294 if (vecofstrings2) {
295 TEST_EQ(vecofstrings2->size(), 2U);
296 TEST_EQ_STR(vecofstrings2->Get(0)->c_str(), "jane");
297 TEST_EQ_STR(vecofstrings2->Get(1)->c_str(), "mary");
298 }
299
300 // Example of accessing a vector of tables:
301 auto vecoftables = monster->testarrayoftables();
302 TEST_EQ(vecoftables->size(), 3U);
303 for (auto it = vecoftables->begin(); it != vecoftables->end(); ++it) {
304 TEST_EQ(strlen(it->name()->c_str()) >= 4, true);
305 }
306 TEST_EQ_STR(vecoftables->Get(0)->name()->c_str(), "Barney");
307 TEST_EQ(vecoftables->Get(0)->hp(), 1000);
308 TEST_EQ_STR(vecoftables->Get(1)->name()->c_str(), "Fred");
309 TEST_EQ_STR(vecoftables->Get(2)->name()->c_str(), "Wilma");
310 TEST_NOTNULL(vecoftables->LookupByKey("Barney"));
311 TEST_NOTNULL(vecoftables->LookupByKey("Fred"));
312 TEST_NOTNULL(vecoftables->LookupByKey("Wilma"));
313
314 // Test accessing a vector of sorted structs
315 auto vecofstructs = monster->testarrayofsortedstruct();
316 if (vecofstructs) { // not filled in monster_test.bfbs
317 for (flatbuffers::uoffset_t i = 0; i < vecofstructs->size() - 1; i++) {
318 auto left = vecofstructs->Get(i);
319 auto right = vecofstructs->Get(i + 1);
320 TEST_EQ(true, (left->KeyCompareLessThan(right)));
321 }
322 TEST_NOTNULL(vecofstructs->LookupByKey(0)); // test default value
323 TEST_NOTNULL(vecofstructs->LookupByKey(3));
324 TEST_EQ(static_cast<const Ability *>(nullptr),
325 vecofstructs->LookupByKey(5));
326 }
327
328 if (auto vec_of_stat = monster->scalar_key_sorted_tables()) {
329 auto stat_0 = vec_of_stat->LookupByKey(static_cast<uint16_t>(0u));
330 TEST_NOTNULL(stat_0);
331 TEST_NOTNULL(stat_0->id());
332 TEST_EQ(0, stat_0->count());
333 TEST_EQ_STR("miss", stat_0->id()->c_str());
334 }
335
336 // Test nested FlatBuffers if available:
337 auto nested_buffer = monster->testnestedflatbuffer();
338 if (nested_buffer) {
339 // nested_buffer is a vector of bytes you can memcpy. However, if you
340 // actually want to access the nested data, this is a convenient
341 // accessor that directly gives you the root table:
342 auto nested_monster = monster->testnestedflatbuffer_nested_root();
343 TEST_EQ_STR(nested_monster->name()->c_str(), "NestedMonster");
344 }
345
346 // Test flexbuffer if available:
347 auto flex = monster->flex();
348 // flex is a vector of bytes you can memcpy etc.
349 TEST_EQ(flex->size(), 4); // Encoded FlexBuffer bytes.
350 // However, if you actually want to access the nested data, this is a
351 // convenient accessor that directly gives you the root value:
352 TEST_EQ(monster->flex_flexbuffer_root().AsInt16(), 1234);
353
354 // Test vector of enums:
355 auto colors = monster->vector_of_enums();
356 if (colors) {
357 TEST_EQ(colors->size(), 2);
358 TEST_EQ(colors->Get(0), Color_Blue);
359 TEST_EQ(colors->Get(1), Color_Green);
360 }
361
362 // Since Flatbuffers uses explicit mechanisms to override the default
363 // compiler alignment, double check that the compiler indeed obeys them:
364 // (Test consists of a short and byte):
365 TEST_EQ(flatbuffers::AlignOf<Test>(), 2UL);
366 TEST_EQ(sizeof(Test), 4UL);
367
368 const flatbuffers::Vector<const Test *> *tests_array[] = {
369 monster->test4(),
370 monster->test5(),
371 };
372 for (size_t i = 0; i < sizeof(tests_array) / sizeof(tests_array[0]); ++i) {
373 auto tests = tests_array[i];
374 TEST_NOTNULL(tests);
375 auto test_0 = tests->Get(0);
376 auto test_1 = tests->Get(1);
377 TEST_EQ(test_0->a(), 10);
378 TEST_EQ(test_0->b(), 20);
379 TEST_EQ(test_1->a(), 30);
380 TEST_EQ(test_1->b(), 40);
381 for (auto it = tests->begin(); it != tests->end(); ++it) {
382 TEST_EQ(it->a() == 10 || it->a() == 30, true); // Just testing iterators.
383 }
384 }
385
386 // Checking for presence of fields:
387 TEST_EQ(flatbuffers::IsFieldPresent(monster, Monster::VT_HP), true);
388 TEST_EQ(flatbuffers::IsFieldPresent(monster, Monster::VT_MANA), false);
389
390 // Obtaining a buffer from a root:
391 TEST_EQ(GetBufferStartFromRootPointer(monster), flatbuf);
392}
393
394// Change a FlatBuffer in-place, after it has been constructed.
395void MutateFlatBuffersTest(uint8_t *flatbuf, std::size_t length) {
396 // Get non-const pointer to root.
397 auto monster = GetMutableMonster(flatbuf);
398
399 // Each of these tests mutates, then tests, then set back to the original,
400 // so we can test that the buffer in the end still passes our original test.
401 auto hp_ok = monster->mutate_hp(10);
402 TEST_EQ(hp_ok, true); // Field was present.
403 TEST_EQ(monster->hp(), 10);
404 // Mutate to default value
405 auto hp_ok_default = monster->mutate_hp(100);
406 TEST_EQ(hp_ok_default, true); // Field was present.
407 TEST_EQ(monster->hp(), 100);
408 // Test that mutate to default above keeps field valid for further mutations
409 auto hp_ok_2 = monster->mutate_hp(20);
410 TEST_EQ(hp_ok_2, true);
411 TEST_EQ(monster->hp(), 20);
412 monster->mutate_hp(80);
413
414 // Monster originally at 150 mana (default value)
415 auto mana_default_ok = monster->mutate_mana(150); // Mutate to default value.
416 TEST_EQ(mana_default_ok,
417 true); // Mutation should succeed, because default value.
418 TEST_EQ(monster->mana(), 150);
419 auto mana_ok = monster->mutate_mana(10);
420 TEST_EQ(mana_ok, false); // Field was NOT present, because default value.
421 TEST_EQ(monster->mana(), 150);
422
423 // Mutate structs.
424 auto pos = monster->mutable_pos();
425 auto test3 = pos->mutable_test3(); // Struct inside a struct.
426 test3.mutate_a(50); // Struct fields never fail.
427 TEST_EQ(test3.a(), 50);
428 test3.mutate_a(10);
429
430 // Mutate vectors.
431 auto inventory = monster->mutable_inventory();
432 inventory->Mutate(9, 100);
433 TEST_EQ(inventory->Get(9), 100);
434 inventory->Mutate(9, 9);
435
436 auto tables = monster->mutable_testarrayoftables();
437 auto first = tables->GetMutableObject(0);
438 TEST_EQ(first->hp(), 1000);
439 first->mutate_hp(0);
440 TEST_EQ(first->hp(), 0);
441 first->mutate_hp(1000);
442
James Kuszmaul3b15b0c2022-11-08 14:03:16 -0800443 // Test for each loop over mutable entries
444 for (auto item: *tables)
445 {
446 TEST_EQ(item->hp(), 1000);
447 item->mutate_hp(0);
448 TEST_EQ(item->hp(), 0);
449 item->mutate_hp(1000);
450 break; // one iteration is enough, just testing compilation
451 }
452
Austin Schuh2dd86a92022-09-14 21:19:23 -0700453 // Mutate via LookupByKey
454 TEST_NOTNULL(tables->MutableLookupByKey("Barney"));
455 TEST_EQ(static_cast<Monster *>(nullptr),
456 tables->MutableLookupByKey("DoesntExist"));
457 TEST_EQ(tables->MutableLookupByKey("Barney")->hp(), 1000);
458 TEST_EQ(tables->MutableLookupByKey("Barney")->mutate_hp(0), true);
459 TEST_EQ(tables->LookupByKey("Barney")->hp(), 0);
460 TEST_EQ(tables->MutableLookupByKey("Barney")->mutate_hp(1000), true);
461
462 // Run the verifier and the regular test to make sure we didn't trample on
463 // anything.
464 AccessFlatBufferTest(flatbuf, length);
465}
466
467// Unpack a FlatBuffer into objects.
468void ObjectFlatBuffersTest(uint8_t *flatbuf) {
469 // Optional: we can specify resolver and rehasher functions to turn hashed
470 // strings into object pointers and back, to implement remote references
471 // and such.
472 auto resolver = flatbuffers::resolver_function_t(
473 [](void **pointer_adr, flatbuffers::hash_value_t hash) {
474 (void)pointer_adr;
475 (void)hash;
476 // Don't actually do anything, leave variable null.
477 });
478 auto rehasher = flatbuffers::rehasher_function_t(
479 [](void *pointer) -> flatbuffers::hash_value_t {
480 (void)pointer;
481 return 0;
482 });
483
484 // Turn a buffer into C++ objects.
485 auto monster1 = UnPackMonster(flatbuf, &resolver);
486
487 // Re-serialize the data.
488 flatbuffers::FlatBufferBuilder fbb1;
489 fbb1.Finish(CreateMonster(fbb1, monster1.get(), &rehasher),
490 MonsterIdentifier());
491
492 // Unpack again, and re-serialize again.
493 auto monster2 = UnPackMonster(fbb1.GetBufferPointer(), &resolver);
494 flatbuffers::FlatBufferBuilder fbb2;
495 fbb2.Finish(CreateMonster(fbb2, monster2.get(), &rehasher),
496 MonsterIdentifier());
497
498 // Now we've gone full round-trip, the two buffers should match.
499 const auto len1 = fbb1.GetSize();
500 const auto len2 = fbb2.GetSize();
501 TEST_EQ(len1, len2);
502 TEST_EQ(memcmp(fbb1.GetBufferPointer(), fbb2.GetBufferPointer(), len1), 0);
503
504 // Test it with the original buffer test to make sure all data survived.
505 AccessFlatBufferTest(fbb2.GetBufferPointer(), len2, false);
506
507 // Test accessing fields, similar to AccessFlatBufferTest above.
508 CheckMonsterObject(monster2.get());
509
510 // Test object copy.
511 auto monster3 = *monster2;
512 flatbuffers::FlatBufferBuilder fbb3;
513 fbb3.Finish(CreateMonster(fbb3, &monster3, &rehasher), MonsterIdentifier());
514 const auto len3 = fbb3.GetSize();
515 TEST_EQ(len2, len3);
516 TEST_EQ(memcmp(fbb2.GetBufferPointer(), fbb3.GetBufferPointer(), len2), 0);
517 // Delete monster1 and monster2, then test accessing fields in monster3.
518 monster1.reset();
519 monster2.reset();
520 CheckMonsterObject(&monster3);
521}
522
523// Utility function to check a Monster object.
524void CheckMonsterObject(MonsterT *monster2) {
525 TEST_EQ(monster2->hp, 80);
526 TEST_EQ(monster2->mana, 150); // default
527 TEST_EQ_STR(monster2->name.c_str(), "MyMonster");
528
529 auto &pos = monster2->pos;
530 TEST_NOTNULL(pos);
531 TEST_EQ(pos->z(), 3);
532 TEST_EQ(pos->test3().a(), 10);
533 TEST_EQ(pos->test3().b(), 20);
534
535 auto &inventory = monster2->inventory;
536 TEST_EQ(inventory.size(), 10UL);
537 unsigned char inv_data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
538 for (auto it = inventory.begin(); it != inventory.end(); ++it)
539 TEST_EQ(*it, inv_data[it - inventory.begin()]);
540
541 TEST_EQ(monster2->color, Color_Blue);
542
543 auto monster3 = monster2->test.AsMonster();
544 TEST_NOTNULL(monster3);
545 TEST_EQ_STR(monster3->name.c_str(), "Fred");
546
547 auto &vecofstrings = monster2->testarrayofstring;
548 TEST_EQ(vecofstrings.size(), 4U);
549 TEST_EQ_STR(vecofstrings[0].c_str(), "bob");
550 TEST_EQ_STR(vecofstrings[1].c_str(), "fred");
551
552 auto &vecofstrings2 = monster2->testarrayofstring2;
553 TEST_EQ(vecofstrings2.size(), 2U);
554 TEST_EQ_STR(vecofstrings2[0].c_str(), "jane");
555 TEST_EQ_STR(vecofstrings2[1].c_str(), "mary");
556
557 auto &vecoftables = monster2->testarrayoftables;
558 TEST_EQ(vecoftables.size(), 3U);
559 TEST_EQ_STR(vecoftables[0]->name.c_str(), "Barney");
560 TEST_EQ(vecoftables[0]->hp, 1000);
561 TEST_EQ_STR(vecoftables[1]->name.c_str(), "Fred");
562 TEST_EQ_STR(vecoftables[2]->name.c_str(), "Wilma");
563
564 auto &tests = monster2->test4;
565 TEST_EQ(tests[0].a(), 10);
566 TEST_EQ(tests[0].b(), 20);
567 TEST_EQ(tests[1].a(), 30);
568 TEST_EQ(tests[1].b(), 40);
569}
570
571// Prefix a FlatBuffer with a size field.
572void SizePrefixedTest() {
573 // Create size prefixed buffer.
574 flatbuffers::FlatBufferBuilder fbb;
575 FinishSizePrefixedMonsterBuffer(
576 fbb, CreateMonster(fbb, nullptr, 200, 300, fbb.CreateString("bob")));
577
578 // Verify it.
579 flatbuffers::Verifier verifier(fbb.GetBufferPointer(), fbb.GetSize());
580 TEST_EQ(VerifySizePrefixedMonsterBuffer(verifier), true);
581
582 // Access it.
583 auto m = GetSizePrefixedMonster(fbb.GetBufferPointer());
584 TEST_EQ(m->mana(), 200);
585 TEST_EQ(m->hp(), 300);
586 TEST_EQ_STR(m->name()->c_str(), "bob");
587}
588
589void TestMonsterExtraFloats(const std::string &tests_data_path) {
590#if defined(FLATBUFFERS_HAS_NEW_STRTOD) && (FLATBUFFERS_HAS_NEW_STRTOD > 0)
591 TEST_EQ(is_quiet_nan(1.0), false);
592 TEST_EQ(is_quiet_nan(infinity_d), false);
593 TEST_EQ(is_quiet_nan(-infinity_f), false);
594 TEST_EQ(is_quiet_nan(std::numeric_limits<float>::quiet_NaN()), true);
595 TEST_EQ(is_quiet_nan(std::numeric_limits<double>::quiet_NaN()), true);
596
597 using namespace flatbuffers;
598 using namespace MyGame;
599 // Load FlatBuffer schema (.fbs) from disk.
600 std::string schemafile;
601 TEST_EQ(LoadFile((tests_data_path + "monster_extra.fbs").c_str(), false,
602 &schemafile),
603 true);
604 // Parse schema first, so we can use it to parse the data after.
605 Parser parser;
606 auto include_test_path = ConCatPathFileName(tests_data_path, "include_test");
607 const char *include_directories[] = { tests_data_path.c_str(),
608 include_test_path.c_str(), nullptr };
609 TEST_EQ(parser.Parse(schemafile.c_str(), include_directories), true);
610 // Create empty extra and store to json.
611 parser.opts.output_default_scalars_in_json = true;
612 parser.opts.output_enum_identifiers = true;
613 FlatBufferBuilder builder;
614 const auto def_root = MonsterExtraBuilder(builder).Finish();
615 FinishMonsterExtraBuffer(builder, def_root);
616 const auto def_obj = builder.GetBufferPointer();
617 const auto def_extra = GetMonsterExtra(def_obj);
618 TEST_NOTNULL(def_extra);
619 TEST_EQ(is_quiet_nan(def_extra->f0()), true);
620 TEST_EQ(is_quiet_nan(def_extra->f1()), true);
621 TEST_EQ(def_extra->f2(), +infinity_f);
622 TEST_EQ(def_extra->f3(), -infinity_f);
623 TEST_EQ(is_quiet_nan(def_extra->d0()), true);
624 TEST_EQ(is_quiet_nan(def_extra->d1()), true);
625 TEST_EQ(def_extra->d2(), +infinity_d);
626 TEST_EQ(def_extra->d3(), -infinity_d);
627 std::string jsongen;
628 auto result = GenerateText(parser, def_obj, &jsongen);
629 TEST_EQ(result, true);
630 // Check expected default values.
631 TEST_EQ(std::string::npos != jsongen.find("f0: nan"), true);
632 TEST_EQ(std::string::npos != jsongen.find("f1: nan"), true);
633 TEST_EQ(std::string::npos != jsongen.find("f2: inf"), true);
634 TEST_EQ(std::string::npos != jsongen.find("f3: -inf"), true);
635 TEST_EQ(std::string::npos != jsongen.find("d0: nan"), true);
636 TEST_EQ(std::string::npos != jsongen.find("d1: nan"), true);
637 TEST_EQ(std::string::npos != jsongen.find("d2: inf"), true);
638 TEST_EQ(std::string::npos != jsongen.find("d3: -inf"), true);
639 // Parse 'mosterdata_extra.json'.
640 const auto extra_base = tests_data_path + "monsterdata_extra";
641 jsongen = "";
642 TEST_EQ(LoadFile((extra_base + ".json").c_str(), false, &jsongen), true);
643 TEST_EQ(parser.Parse(jsongen.c_str()), true);
644 const auto test_file = parser.builder_.GetBufferPointer();
645 const auto test_size = parser.builder_.GetSize();
646 Verifier verifier(test_file, test_size);
647 TEST_ASSERT(VerifyMonsterExtraBuffer(verifier));
648 const auto extra = GetMonsterExtra(test_file);
649 TEST_NOTNULL(extra);
650 TEST_EQ(is_quiet_nan(extra->f0()), true);
651 TEST_EQ(is_quiet_nan(extra->f1()), true);
652 TEST_EQ(extra->f2(), +infinity_f);
653 TEST_EQ(extra->f3(), -infinity_f);
654 TEST_EQ(is_quiet_nan(extra->d0()), true);
655 TEST_EQ(extra->d1(), +infinity_d);
656 TEST_EQ(extra->d2(), -infinity_d);
657 TEST_EQ(is_quiet_nan(extra->d3()), true);
658 TEST_NOTNULL(extra->fvec());
659 TEST_EQ(extra->fvec()->size(), 4);
660 TEST_EQ(extra->fvec()->Get(0), 1.0f);
661 TEST_EQ(extra->fvec()->Get(1), -infinity_f);
662 TEST_EQ(extra->fvec()->Get(2), +infinity_f);
663 TEST_EQ(is_quiet_nan(extra->fvec()->Get(3)), true);
664 TEST_NOTNULL(extra->dvec());
665 TEST_EQ(extra->dvec()->size(), 4);
666 TEST_EQ(extra->dvec()->Get(0), 2.0);
667 TEST_EQ(extra->dvec()->Get(1), +infinity_d);
668 TEST_EQ(extra->dvec()->Get(2), -infinity_d);
669 TEST_EQ(is_quiet_nan(extra->dvec()->Get(3)), true);
670#endif
671}
672
673void EnumNamesTest() {
674 TEST_EQ_STR("Red", EnumNameColor(Color_Red));
675 TEST_EQ_STR("Green", EnumNameColor(Color_Green));
676 TEST_EQ_STR("Blue", EnumNameColor(Color_Blue));
677 // Check that Color to string don't crash while decode a mixture of Colors.
678 // 1) Example::Color enum is enum with unfixed underlying type.
679 // 2) Valid enum range: [0; 2^(ceil(log2(Color_ANY))) - 1].
680 // Consequence: A value is out of this range will lead to UB (since C++17).
681 // For details see C++17 standard or explanation on the SO:
682 // stackoverflow.com/questions/18195312/what-happens-if-you-static-cast-invalid-value-to-enum-class
683 TEST_EQ_STR("", EnumNameColor(static_cast<Color>(0)));
684 TEST_EQ_STR("", EnumNameColor(static_cast<Color>(Color_ANY - 1)));
685 TEST_EQ_STR("", EnumNameColor(static_cast<Color>(Color_ANY + 1)));
686}
687
688void TypeAliasesTest() {
689 flatbuffers::FlatBufferBuilder builder;
690
691 builder.Finish(CreateTypeAliases(
692 builder, flatbuffers::numeric_limits<int8_t>::min(),
693 flatbuffers::numeric_limits<uint8_t>::max(),
694 flatbuffers::numeric_limits<int16_t>::min(),
695 flatbuffers::numeric_limits<uint16_t>::max(),
696 flatbuffers::numeric_limits<int32_t>::min(),
697 flatbuffers::numeric_limits<uint32_t>::max(),
698 flatbuffers::numeric_limits<int64_t>::min(),
699 flatbuffers::numeric_limits<uint64_t>::max(), 2.3f, 2.3));
700
701 auto p = builder.GetBufferPointer();
702 auto ta = flatbuffers::GetRoot<TypeAliases>(p);
703
704 TEST_EQ(ta->i8(), flatbuffers::numeric_limits<int8_t>::min());
705 TEST_EQ(ta->u8(), flatbuffers::numeric_limits<uint8_t>::max());
706 TEST_EQ(ta->i16(), flatbuffers::numeric_limits<int16_t>::min());
707 TEST_EQ(ta->u16(), flatbuffers::numeric_limits<uint16_t>::max());
708 TEST_EQ(ta->i32(), flatbuffers::numeric_limits<int32_t>::min());
709 TEST_EQ(ta->u32(), flatbuffers::numeric_limits<uint32_t>::max());
710 TEST_EQ(ta->i64(), flatbuffers::numeric_limits<int64_t>::min());
711 TEST_EQ(ta->u64(), flatbuffers::numeric_limits<uint64_t>::max());
712 TEST_EQ(ta->f32(), 2.3f);
713 TEST_EQ(ta->f64(), 2.3);
714 using namespace flatbuffers; // is_same
715 static_assert(is_same<decltype(ta->i8()), int8_t>::value, "invalid type");
716 static_assert(is_same<decltype(ta->i16()), int16_t>::value, "invalid type");
717 static_assert(is_same<decltype(ta->i32()), int32_t>::value, "invalid type");
718 static_assert(is_same<decltype(ta->i64()), int64_t>::value, "invalid type");
719 static_assert(is_same<decltype(ta->u8()), uint8_t>::value, "invalid type");
720 static_assert(is_same<decltype(ta->u16()), uint16_t>::value, "invalid type");
721 static_assert(is_same<decltype(ta->u32()), uint32_t>::value, "invalid type");
722 static_assert(is_same<decltype(ta->u64()), uint64_t>::value, "invalid type");
723 static_assert(is_same<decltype(ta->f32()), float>::value, "invalid type");
724 static_assert(is_same<decltype(ta->f64()), double>::value, "invalid type");
725}
726
727// example of parsing text straight into a buffer, and generating
728// text back from it:
729void ParseAndGenerateTextTest(const std::string &tests_data_path, bool binary) {
730 // load FlatBuffer schema (.fbs) and JSON from disk
731 std::string schemafile;
732 std::string jsonfile;
733 TEST_EQ(flatbuffers::LoadFile(
734 (tests_data_path + "monster_test." + (binary ? "bfbs" : "fbs"))
735 .c_str(),
736 binary, &schemafile),
737 true);
738 TEST_EQ(flatbuffers::LoadFile(
739 (tests_data_path + "monsterdata_test.golden").c_str(), false,
740 &jsonfile),
741 true);
742
743 auto include_test_path =
744 flatbuffers::ConCatPathFileName(tests_data_path, "include_test");
745 const char *include_directories[] = { tests_data_path.c_str(),
746 include_test_path.c_str(), nullptr };
747
748 // parse schema first, so we can use it to parse the data after
749 flatbuffers::Parser parser;
750 if (binary) {
751 flatbuffers::Verifier verifier(
752 reinterpret_cast<const uint8_t *>(schemafile.c_str()),
753 schemafile.size());
754 TEST_EQ(reflection::VerifySchemaBuffer(verifier), true);
755 // auto schema = reflection::GetSchema(schemafile.c_str());
756 TEST_EQ(parser.Deserialize(
757 reinterpret_cast<const uint8_t *>(schemafile.c_str()),
758 schemafile.size()),
759 true);
760 } else {
761 TEST_EQ(parser.Parse(schemafile.c_str(), include_directories), true);
762 }
763 TEST_EQ(parser.ParseJson(jsonfile.c_str()), true);
764
765 // here, parser.builder_ contains a binary buffer that is the parsed data.
766
767 // First, verify it, just in case:
768 flatbuffers::Verifier verifier(parser.builder_.GetBufferPointer(),
769 parser.builder_.GetSize());
770 TEST_EQ(VerifyMonsterBuffer(verifier), true);
771
772 AccessFlatBufferTest(parser.builder_.GetBufferPointer(),
773 parser.builder_.GetSize(), false);
774
775 // to ensure it is correct, we now generate text back from the binary,
776 // and compare the two:
777 std::string jsongen;
778 auto result =
779 GenerateText(parser, parser.builder_.GetBufferPointer(), &jsongen);
780 TEST_EQ(result, true);
781 TEST_EQ_STR(jsongen.c_str(), jsonfile.c_str());
782
783 // We can also do the above using the convenient Registry that knows about
784 // a set of file_identifiers mapped to schemas.
785 flatbuffers::Registry registry;
786 // Make sure schemas can find their includes.
787 registry.AddIncludeDirectory(tests_data_path.c_str());
788 registry.AddIncludeDirectory(include_test_path.c_str());
789 // Call this with many schemas if possible.
790 registry.Register(MonsterIdentifier(),
791 (tests_data_path + "monster_test.fbs").c_str());
792 // Now we got this set up, we can parse by just specifying the identifier,
793 // the correct schema will be loaded on the fly:
794 auto buf = registry.TextToFlatBuffer(jsonfile.c_str(), MonsterIdentifier());
795 // If this fails, check registry.lasterror_.
796 TEST_NOTNULL(buf.data());
797 // Test the buffer, to be sure:
798 AccessFlatBufferTest(buf.data(), buf.size(), false);
799 // We can use the registry to turn this back into text, in this case it
800 // will get the file_identifier from the binary:
801 std::string text;
802 auto ok = registry.FlatBufferToText(buf.data(), buf.size(), &text);
803 // If this fails, check registry.lasterror_.
804 TEST_EQ(ok, true);
805 TEST_EQ_STR(text.c_str(), jsonfile.c_str());
806
807 // Generate text for UTF-8 strings without escapes.
808 std::string jsonfile_utf8;
809 TEST_EQ(flatbuffers::LoadFile((tests_data_path + "unicode_test.json").c_str(),
810 false, &jsonfile_utf8),
811 true);
812 TEST_EQ(parser.Parse(jsonfile_utf8.c_str(), include_directories), true);
813 // To ensure it is correct, generate utf-8 text back from the binary.
814 std::string jsongen_utf8;
815 // request natural printing for utf-8 strings
816 parser.opts.natural_utf8 = true;
817 parser.opts.strict_json = true;
818 TEST_EQ(
819 GenerateText(parser, parser.builder_.GetBufferPointer(), &jsongen_utf8),
820 true);
821 TEST_EQ_STR(jsongen_utf8.c_str(), jsonfile_utf8.c_str());
822}
823
824void UnPackTo(const uint8_t *flatbuf) {
825 // Get a monster that has a name and no enemy
826 auto orig_monster = GetMonster(flatbuf);
827 TEST_EQ_STR(orig_monster->name()->c_str(), "MyMonster");
828 TEST_ASSERT(orig_monster->enemy() == nullptr);
829
830 // Create an enemy
831 MonsterT *enemy = new MonsterT();
832 enemy->name = "Enemy";
833
834 // And create another monster owning the enemy,
835 MonsterT mon;
836 mon.name = "I'm monster 1";
837 mon.enemy.reset(enemy);
838 TEST_ASSERT(mon.enemy != nullptr);
839
840 // Assert that all the Monster objects are correct.
841 TEST_EQ_STR(mon.name.c_str(), "I'm monster 1");
842 TEST_EQ_STR(enemy->name.c_str(), "Enemy");
843 TEST_EQ_STR(mon.enemy->name.c_str(), "Enemy");
844
845 // Now unpack monster ("MyMonster") into monster
846 orig_monster->UnPackTo(&mon);
847
848 // Monster name should be from monster
849 TEST_EQ_STR(mon.name.c_str(), "MyMonster");
850
851 // The monster shouldn't have any enemies, because monster didn't.
852 TEST_ASSERT(mon.enemy == nullptr);
853}
854
855} // namespace tests
James Kuszmaul3b15b0c2022-11-08 14:03:16 -0800856} // namespace flatbuffers