#include "monster_test.h" #include #include #include "flatbuffers/base.h" #include "flatbuffers/flatbuffer_builder.h" #include "flatbuffers/flatbuffers.h" #include "flatbuffers/idl.h" #include "flatbuffers/registry.h" #include "flatbuffers/verifier.h" #include "is_quiet_nan.h" #include "monster_extra_generated.h" #include "monster_test_generated.h" #include "test_assert.h" namespace flatbuffers { namespace tests { // Shortcuts for the infinity. static const auto infinity_f = std::numeric_limits::infinity(); static const auto infinity_d = std::numeric_limits::infinity(); using namespace MyGame::Example; // example of how to build up a serialized buffer algorithmically: flatbuffers::DetachedBuffer CreateFlatBufferTest(std::string &buffer) { flatbuffers::FlatBufferBuilder builder; auto vec = Vec3(1, 2, 3, 0, Color_Red, Test(10, 20)); auto name = builder.CreateString("MyMonster"); // Use the initializer_list specialization of CreateVector. auto inventory = builder.CreateVector({ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); // Alternatively, create the vector first, and fill in data later: // unsigned char *inv_buf = nullptr; // auto inventory = builder.CreateUninitializedVector( // 10, &inv_buf); // memcpy(inv_buf, inv_data, 10); Test tests[] = { Test(10, 20), Test(30, 40) }; auto testv = builder.CreateVectorOfStructs(tests, 2); // Create a vector of structures from a lambda. auto testv2 = builder.CreateVectorOfStructs( 2, [&](size_t i, Test *s) -> void { *s = tests[i]; }); // create monster with very few fields set: // (same functionality as CreateMonster below, but sets fields manually) flatbuffers::Offset mlocs[3]; auto fred = builder.CreateString("Fred"); auto barney = builder.CreateString("Barney"); auto wilma = builder.CreateString("Wilma"); MonsterBuilder mb1(builder); mb1.add_name(fred); mlocs[0] = mb1.Finish(); MonsterBuilder mb2(builder); mb2.add_name(barney); mb2.add_hp(1000); mlocs[1] = mb2.Finish(); MonsterBuilder mb3(builder); mb3.add_name(wilma); mlocs[2] = mb3.Finish(); // Create an array of strings. Also test string pooling, and lambdas. auto vecofstrings = builder.CreateVector>( 4, [](size_t i, flatbuffers::FlatBufferBuilder *b) -> flatbuffers::Offset { static const char *names[] = { "bob", "fred", "bob", "fred" }; return b->CreateSharedString(names[i]); }, &builder); // Creating vectors of strings in one convenient call. std::vector names2; names2.push_back("jane"); names2.push_back("mary"); auto vecofstrings2 = builder.CreateVectorOfStrings(names2); // Creating vectors from types that are different from std::string std::vector names3; names3.push_back("foo"); names3.push_back("bar"); builder.CreateVectorOfStrings(names3); // Also an accepted type #ifdef FLATBUFFERS_HAS_STRING_VIEW std::vector names4; names3.push_back("baz"); names3.push_back("quux"); builder.CreateVectorOfStrings(names4); // Also an accepted type #endif // Make sure the template deduces an initializer as std::vector builder.CreateVectorOfStrings({ "hello", "world" }); // Create many vectors of strings std::vector manyNames; for (auto i = 0; i < 100; i++) { manyNames.push_back("john_doe"); } auto manyNamesVec = builder.CreateVectorOfStrings(manyNames); TEST_EQ(false, manyNamesVec.IsNull()); auto manyNamesVec2 = builder.CreateVectorOfStrings(manyNames.cbegin(), manyNames.cend()); TEST_EQ(false, manyNamesVec2.IsNull()); // Create an array of sorted tables, can be used with binary search when read: auto vecoftables = builder.CreateVectorOfSortedTables(mlocs, 3); // Create an array of sorted structs, // can be used with binary search when read: std::vector abilities; abilities.push_back(Ability(4, 40)); abilities.push_back(Ability(3, 30)); abilities.push_back(Ability(2, 20)); abilities.push_back(Ability(0, 0)); auto vecofstructs = builder.CreateVectorOfSortedStructs(&abilities); flatbuffers::Offset mlocs_stats[1]; auto miss = builder.CreateString("miss"); StatBuilder mb_miss(builder); mb_miss.add_id(miss); mb_miss.add_val(0); mb_miss.add_count(0); // key mlocs_stats[0] = mb_miss.Finish(); auto vec_of_stats = builder.CreateVectorOfSortedTables(mlocs_stats, 1); // Create a nested FlatBuffer. // Nested FlatBuffers are stored in a ubyte vector, which can be convenient // since they can be memcpy'd around much easier than other FlatBuffer // values. They have little overhead compared to storing the table directly. // As a test, create a mostly empty Monster buffer: flatbuffers::FlatBufferBuilder nested_builder; auto nmloc = CreateMonster(nested_builder, nullptr, 0, 0, nested_builder.CreateString("NestedMonster")); FinishMonsterBuffer(nested_builder, nmloc); // Now we can store the buffer in the parent. Note that by default, vectors // are only aligned to their elements or size field, so in this case if the // buffer contains 64-bit elements, they may not be correctly aligned. We fix // that with: builder.ForceVectorAlignment(nested_builder.GetSize(), sizeof(uint8_t), nested_builder.GetBufferMinAlignment()); // If for whatever reason you don't have the nested_builder available, you // can substitute flatbuffers::largest_scalar_t (64-bit) for the alignment, or // the largest force_align value in your schema if you're using it. auto nested_flatbuffer_vector = builder.CreateVector( nested_builder.GetBufferPointer(), nested_builder.GetSize()); // Test a nested FlexBuffer: flexbuffers::Builder flexbuild; flexbuild.Int(1234); flexbuild.Finish(); auto flex = builder.CreateVector(flexbuild.GetBuffer()); // Test vector of enums. Color colors[] = { Color_Blue, Color_Green }; // We use this special creation function because we have an array of // pre-C++11 (enum class) enums whose size likely is int, yet its declared // type in the schema is byte. auto vecofcolors = builder.CreateVectorScalarCast(colors, 2); // shortcut for creating monster with all fields set: auto mloc = CreateMonster( builder, &vec, 150, 80, name, inventory, Color_Blue, Any_Monster, mlocs[1].Union(), // Store a union. testv, vecofstrings, vecoftables, 0, nested_flatbuffer_vector, 0, false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3.14159f, 3.0f, 0.0f, vecofstrings2, vecofstructs, flex, testv2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, AnyUniqueAliases_NONE, 0, AnyAmbiguousAliases_NONE, 0, vecofcolors, MyGame::Example::Race_None, 0, vec_of_stats); FinishMonsterBuffer(builder, mloc); // clang-format off #ifdef FLATBUFFERS_TEST_VERBOSE // print byte data for debugging: auto p = builder.GetBufferPointer(); for (flatbuffers::uoffset_t i = 0; i < builder.GetSize(); i++) printf("%d ", p[i]); #endif // clang-format on // return the buffer for the caller to use. auto bufferpointer = reinterpret_cast(builder.GetBufferPointer()); buffer.assign(bufferpointer, bufferpointer + builder.GetSize()); return builder.Release(); } // example of accessing a buffer loaded in memory: void AccessFlatBufferTest(const uint8_t *flatbuf, size_t length, bool pooled) { // First, verify the buffers integrity (optional) flatbuffers::Verifier verifier(flatbuf, length); std::vector flex_reuse_tracker; verifier.SetFlexReuseTracker(&flex_reuse_tracker); TEST_EQ(VerifyMonsterBuffer(verifier), true); // clang-format off #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE std::vector test_buff; test_buff.resize(length * 2); std::memcpy(&test_buff[0], flatbuf, length); std::memcpy(&test_buff[length], flatbuf, length); flatbuffers::Verifier verifier1(&test_buff[0], length); TEST_EQ(VerifyMonsterBuffer(verifier1), true); TEST_EQ(verifier1.GetComputedSize(), length); flatbuffers::Verifier verifier2(&test_buff[length], length); TEST_EQ(VerifyMonsterBuffer(verifier2), true); TEST_EQ(verifier2.GetComputedSize(), length); #endif // clang-format on TEST_EQ(strcmp(MonsterIdentifier(), "MONS"), 0); TEST_EQ(MonsterBufferHasIdentifier(flatbuf), true); TEST_EQ(strcmp(MonsterExtension(), "mon"), 0); // Access the buffer from the root. auto monster = GetMonster(flatbuf); TEST_EQ(monster->hp(), 80); TEST_EQ(monster->mana(), 150); // default TEST_EQ_STR(monster->name()->c_str(), "MyMonster"); // Can't access the following field, it is deprecated in the schema, // which means accessors are not generated: // monster.friendly() auto pos = monster->pos(); TEST_NOTNULL(pos); TEST_EQ(pos->z(), 3); TEST_EQ(pos->test3().a(), 10); TEST_EQ(pos->test3().b(), 20); auto inventory = monster->inventory(); TEST_EQ(VectorLength(inventory), 10UL); // Works even if inventory is null. TEST_NOTNULL(inventory); unsigned char inv_data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // Check compatibilty of iterators with STL. std::vector inv_vec(inventory->begin(), inventory->end()); size_t n = 0; for (auto it = inventory->begin(); it != inventory->end(); ++it, ++n) { auto indx = it - inventory->begin(); TEST_EQ(*it, inv_vec.at(indx)); // Use bounds-check. TEST_EQ(*it, inv_data[indx]); } TEST_EQ(n, inv_vec.size()); n = 0; for (auto it = inventory->cbegin(); it != inventory->cend(); ++it, ++n) { auto indx = it - inventory->cbegin(); TEST_EQ(*it, inv_vec.at(indx)); // Use bounds-check. TEST_EQ(*it, inv_data[indx]); } TEST_EQ(n, inv_vec.size()); n = 0; for (auto it = inventory->rbegin(); it != inventory->rend(); ++it, ++n) { auto indx = inventory->rend() - it - 1; TEST_EQ(*it, inv_vec.at(indx)); // Use bounds-check. TEST_EQ(*it, inv_data[indx]); } TEST_EQ(n, inv_vec.size()); n = 0; for (auto it = inventory->crbegin(); it != inventory->crend(); ++it, ++n) { auto indx = inventory->crend() - it - 1; TEST_EQ(*it, inv_vec.at(indx)); // Use bounds-check. TEST_EQ(*it, inv_data[indx]); } TEST_EQ(n, inv_vec.size()); TEST_EQ(monster->color(), Color_Blue); // Example of accessing a union: TEST_EQ(monster->test_type(), Any_Monster); // First make sure which it is. auto monster2 = reinterpret_cast(monster->test()); TEST_NOTNULL(monster2); TEST_EQ_STR(monster2->name()->c_str(), "Fred"); // Example of accessing a vector of strings: auto vecofstrings = monster->testarrayofstring(); TEST_EQ(vecofstrings->size(), 4U); TEST_EQ_STR(vecofstrings->Get(0)->c_str(), "bob"); TEST_EQ_STR(vecofstrings->Get(1)->c_str(), "fred"); if (pooled) { // These should have pointer equality because of string pooling. TEST_EQ(vecofstrings->Get(0)->c_str(), vecofstrings->Get(2)->c_str()); TEST_EQ(vecofstrings->Get(1)->c_str(), vecofstrings->Get(3)->c_str()); } auto vecofstrings2 = monster->testarrayofstring2(); if (vecofstrings2) { TEST_EQ(vecofstrings2->size(), 2U); TEST_EQ_STR(vecofstrings2->Get(0)->c_str(), "jane"); TEST_EQ_STR(vecofstrings2->Get(1)->c_str(), "mary"); } // Example of accessing a vector of tables: auto vecoftables = monster->testarrayoftables(); TEST_EQ(vecoftables->size(), 3U); for (auto it = vecoftables->begin(); it != vecoftables->end(); ++it) { TEST_EQ(strlen(it->name()->c_str()) >= 4, true); } TEST_EQ_STR(vecoftables->Get(0)->name()->c_str(), "Barney"); TEST_EQ(vecoftables->Get(0)->hp(), 1000); TEST_EQ_STR(vecoftables->Get(1)->name()->c_str(), "Fred"); TEST_EQ_STR(vecoftables->Get(2)->name()->c_str(), "Wilma"); TEST_NOTNULL(vecoftables->LookupByKey("Barney")); TEST_NOTNULL(vecoftables->LookupByKey("Fred")); TEST_NOTNULL(vecoftables->LookupByKey("Wilma")); // Test accessing a vector of sorted structs auto vecofstructs = monster->testarrayofsortedstruct(); if (vecofstructs) { // not filled in monster_test.bfbs for (flatbuffers::uoffset_t i = 0; i < vecofstructs->size() - 1; i++) { auto left = vecofstructs->Get(i); auto right = vecofstructs->Get(i + 1); TEST_EQ(true, (left->KeyCompareLessThan(right))); } TEST_NOTNULL(vecofstructs->LookupByKey(0)); // test default value TEST_NOTNULL(vecofstructs->LookupByKey(3)); TEST_EQ(static_cast(nullptr), vecofstructs->LookupByKey(5)); } if (auto vec_of_stat = monster->scalar_key_sorted_tables()) { auto stat_0 = vec_of_stat->LookupByKey(static_cast(0u)); TEST_NOTNULL(stat_0); TEST_NOTNULL(stat_0->id()); TEST_EQ(0, stat_0->count()); TEST_EQ_STR("miss", stat_0->id()->c_str()); } // Test nested FlatBuffers if available: auto nested_buffer = monster->testnestedflatbuffer(); if (nested_buffer) { // nested_buffer is a vector of bytes you can memcpy. However, if you // actually want to access the nested data, this is a convenient // accessor that directly gives you the root table: auto nested_monster = monster->testnestedflatbuffer_nested_root(); TEST_EQ_STR(nested_monster->name()->c_str(), "NestedMonster"); } // Test flexbuffer if available: auto flex = monster->flex(); // flex is a vector of bytes you can memcpy etc. TEST_EQ(flex->size(), 4); // Encoded FlexBuffer bytes. // However, if you actually want to access the nested data, this is a // convenient accessor that directly gives you the root value: TEST_EQ(monster->flex_flexbuffer_root().AsInt16(), 1234); // Test vector of enums: auto colors = monster->vector_of_enums(); if (colors) { TEST_EQ(colors->size(), 2); TEST_EQ(colors->Get(0), Color_Blue); TEST_EQ(colors->Get(1), Color_Green); } // Since Flatbuffers uses explicit mechanisms to override the default // compiler alignment, double check that the compiler indeed obeys them: // (Test consists of a short and byte): TEST_EQ(flatbuffers::AlignOf(), 2UL); TEST_EQ(sizeof(Test), 4UL); const flatbuffers::Vector *tests_array[] = { monster->test4(), monster->test5(), }; for (size_t i = 0; i < sizeof(tests_array) / sizeof(tests_array[0]); ++i) { auto tests = tests_array[i]; TEST_NOTNULL(tests); auto test_0 = tests->Get(0); auto test_1 = tests->Get(1); TEST_EQ(test_0->a(), 10); TEST_EQ(test_0->b(), 20); TEST_EQ(test_1->a(), 30); TEST_EQ(test_1->b(), 40); for (auto it = tests->begin(); it != tests->end(); ++it) { TEST_EQ(it->a() == 10 || it->a() == 30, true); // Just testing iterators. } } // Checking for presence of fields: TEST_EQ(flatbuffers::IsFieldPresent(monster, Monster::VT_HP), true); TEST_EQ(flatbuffers::IsFieldPresent(monster, Monster::VT_MANA), false); // Obtaining a buffer from a root: TEST_EQ(GetBufferStartFromRootPointer(monster), flatbuf); } // Change a FlatBuffer in-place, after it has been constructed. void MutateFlatBuffersTest(uint8_t *flatbuf, std::size_t length) { // Get non-const pointer to root. auto monster = GetMutableMonster(flatbuf); // Each of these tests mutates, then tests, then set back to the original, // so we can test that the buffer in the end still passes our original test. auto hp_ok = monster->mutate_hp(10); TEST_EQ(hp_ok, true); // Field was present. TEST_EQ(monster->hp(), 10); // Mutate to default value auto hp_ok_default = monster->mutate_hp(100); TEST_EQ(hp_ok_default, true); // Field was present. TEST_EQ(monster->hp(), 100); // Test that mutate to default above keeps field valid for further mutations auto hp_ok_2 = monster->mutate_hp(20); TEST_EQ(hp_ok_2, true); TEST_EQ(monster->hp(), 20); monster->mutate_hp(80); // Monster originally at 150 mana (default value) auto mana_default_ok = monster->mutate_mana(150); // Mutate to default value. TEST_EQ(mana_default_ok, true); // Mutation should succeed, because default value. TEST_EQ(monster->mana(), 150); auto mana_ok = monster->mutate_mana(10); TEST_EQ(mana_ok, false); // Field was NOT present, because default value. TEST_EQ(monster->mana(), 150); // Mutate structs. auto pos = monster->mutable_pos(); auto &test3 = pos->mutable_test3(); // Struct inside a struct. test3.mutate_a(50); // Struct fields never fail. TEST_EQ(test3.a(), 50); test3.mutate_a(10); // Mutate vectors. auto inventory = monster->mutable_inventory(); inventory->Mutate(9, 100); TEST_EQ(inventory->Get(9), 100); inventory->Mutate(9, 9); auto tables = monster->mutable_testarrayoftables(); auto first = tables->GetMutableObject(0); TEST_EQ(first->hp(), 1000); first->mutate_hp(0); TEST_EQ(first->hp(), 0); first->mutate_hp(1000); // Test for each loop over mutable entries for (auto item : *tables) { TEST_EQ(item->hp(), 1000); item->mutate_hp(0); TEST_EQ(item->hp(), 0); item->mutate_hp(1000); break; // one iteration is enough, just testing compilation } // Mutate via LookupByKey TEST_NOTNULL(tables->MutableLookupByKey("Barney")); TEST_EQ(static_cast(nullptr), tables->MutableLookupByKey("DoesntExist")); TEST_EQ(tables->MutableLookupByKey("Barney")->hp(), 1000); TEST_EQ(tables->MutableLookupByKey("Barney")->mutate_hp(0), true); TEST_EQ(tables->LookupByKey("Barney")->hp(), 0); TEST_EQ(tables->MutableLookupByKey("Barney")->mutate_hp(1000), true); // Run the verifier and the regular test to make sure we didn't trample on // anything. AccessFlatBufferTest(flatbuf, length); } // Unpack a FlatBuffer into objects. void ObjectFlatBuffersTest(uint8_t *flatbuf) { // Optional: we can specify resolver and rehasher functions to turn hashed // strings into object pointers and back, to implement remote references // and such. auto resolver = flatbuffers::resolver_function_t( [](void **pointer_adr, flatbuffers::hash_value_t hash) { (void)pointer_adr; (void)hash; // Don't actually do anything, leave variable null. }); auto rehasher = flatbuffers::rehasher_function_t( [](void *pointer) -> flatbuffers::hash_value_t { (void)pointer; return 0; }); // Turn a buffer into C++ objects. auto monster1 = UnPackMonster(flatbuf, &resolver); // Re-serialize the data. flatbuffers::FlatBufferBuilder fbb1; fbb1.Finish(CreateMonster(fbb1, monster1.get(), &rehasher), MonsterIdentifier()); // Unpack again, and re-serialize again. auto monster2 = UnPackMonster(fbb1.GetBufferPointer(), &resolver); flatbuffers::FlatBufferBuilder fbb2; fbb2.Finish(CreateMonster(fbb2, monster2.get(), &rehasher), MonsterIdentifier()); // Now we've gone full round-trip, the two buffers should match. const auto len1 = fbb1.GetSize(); const auto len2 = fbb2.GetSize(); TEST_EQ(len1, len2); TEST_EQ(memcmp(fbb1.GetBufferPointer(), fbb2.GetBufferPointer(), len1), 0); // Test it with the original buffer test to make sure all data survived. AccessFlatBufferTest(fbb2.GetBufferPointer(), len2, false); // Test accessing fields, similar to AccessFlatBufferTest above. CheckMonsterObject(monster2.get()); // Test object copy. MonsterT monster3 = *monster2; flatbuffers::FlatBufferBuilder fbb3; fbb3.Finish(CreateMonster(fbb3, &monster3, &rehasher), MonsterIdentifier()); const auto len3 = fbb3.GetSize(); TEST_EQ(len2, len3); TEST_EQ(memcmp(fbb2.GetBufferPointer(), fbb3.GetBufferPointer(), len2), 0); // Delete monster1 and monster2, then test accessing fields in monster3. monster1.reset(); monster2.reset(); CheckMonsterObject(&monster3); } // Utility function to check a Monster object. void CheckMonsterObject(MonsterT *monster2) { TEST_EQ(monster2->hp, 80); TEST_EQ(monster2->mana, 150); // default TEST_EQ_STR(monster2->name.c_str(), "MyMonster"); auto &pos = monster2->pos; TEST_NOTNULL(pos); TEST_EQ(pos->z(), 3); TEST_EQ(pos->test3().a(), 10); TEST_EQ(pos->test3().b(), 20); auto &inventory = monster2->inventory; TEST_EQ(inventory.size(), 10UL); unsigned char inv_data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; for (auto it = inventory.begin(); it != inventory.end(); ++it) TEST_EQ(*it, inv_data[it - inventory.begin()]); TEST_EQ(monster2->color, Color_Blue); auto monster3 = monster2->test.AsMonster(); TEST_NOTNULL(monster3); TEST_EQ_STR(monster3->name.c_str(), "Fred"); auto &vecofstrings = monster2->testarrayofstring; TEST_EQ(vecofstrings.size(), 4U); TEST_EQ_STR(vecofstrings[0].c_str(), "bob"); TEST_EQ_STR(vecofstrings[1].c_str(), "fred"); auto &vecofstrings2 = monster2->testarrayofstring2; TEST_EQ(vecofstrings2.size(), 2U); TEST_EQ_STR(vecofstrings2[0].c_str(), "jane"); TEST_EQ_STR(vecofstrings2[1].c_str(), "mary"); auto &vecoftables = monster2->testarrayoftables; TEST_EQ(vecoftables.size(), 3U); TEST_EQ_STR(vecoftables[0]->name.c_str(), "Barney"); TEST_EQ(vecoftables[0]->hp, 1000); TEST_EQ_STR(vecoftables[1]->name.c_str(), "Fred"); TEST_EQ_STR(vecoftables[2]->name.c_str(), "Wilma"); auto &tests = monster2->test4; TEST_EQ(tests[0].a(), 10); TEST_EQ(tests[0].b(), 20); TEST_EQ(tests[1].a(), 30); TEST_EQ(tests[1].b(), 40); } // Prefix a FlatBuffer with a size field. void SizePrefixedTest() { // Create size prefixed buffer. flatbuffers::FlatBufferBuilder fbb; FinishSizePrefixedMonsterBuffer( fbb, CreateMonster(fbb, nullptr, 200, 300, fbb.CreateString("bob"))); // Verify it. flatbuffers::Verifier verifier(fbb.GetBufferPointer(), fbb.GetSize()); TEST_EQ(VerifySizePrefixedMonsterBuffer(verifier), true); // The prefixed size doesn't include itself, so substract the size of the // prefix TEST_EQ(GetPrefixedSize(fbb.GetBufferPointer()), fbb.GetSize() - sizeof(uoffset_t)); // Getting the buffer length does include the prefix size, so it should be the // full lenght. TEST_EQ(GetSizePrefixedBufferLength(fbb.GetBufferPointer()), fbb.GetSize()); // Access it. auto m = GetSizePrefixedMonster(fbb.GetBufferPointer()); TEST_EQ(m->mana(), 200); TEST_EQ(m->hp(), 300); TEST_EQ_STR(m->name()->c_str(), "bob"); { // Verify that passing a larger size is OK, but not a smaller flatbuffers::Verifier verifier_larger(fbb.GetBufferPointer(), fbb.GetSize() + 10); TEST_EQ(VerifySizePrefixedMonsterBuffer(verifier_larger), true); flatbuffers::Verifier verifier_smaller(fbb.GetBufferPointer(), fbb.GetSize() - 10); TEST_EQ(VerifySizePrefixedMonsterBuffer(verifier_smaller), false); } } void TestMonsterExtraFloats(const std::string &tests_data_path) { #if defined(FLATBUFFERS_HAS_NEW_STRTOD) && (FLATBUFFERS_HAS_NEW_STRTOD > 0) TEST_EQ(is_quiet_nan(1.0), false); TEST_EQ(is_quiet_nan(infinity_d), false); TEST_EQ(is_quiet_nan(-infinity_f), false); TEST_EQ(is_quiet_nan(std::numeric_limits::quiet_NaN()), true); TEST_EQ(is_quiet_nan(std::numeric_limits::quiet_NaN()), true); using namespace flatbuffers; using namespace MyGame; // Load FlatBuffer schema (.fbs) from disk. std::string schemafile; TEST_EQ(LoadFile((tests_data_path + "monster_extra.fbs").c_str(), false, &schemafile), true); // Parse schema first, so we can use it to parse the data after. Parser parser; auto include_test_path = ConCatPathFileName(tests_data_path, "include_test"); const char *include_directories[] = { tests_data_path.c_str(), include_test_path.c_str(), nullptr }; TEST_EQ(parser.Parse(schemafile.c_str(), include_directories), true); // Create empty extra and store to json. parser.opts.output_default_scalars_in_json = true; parser.opts.output_enum_identifiers = true; FlatBufferBuilder builder; const auto def_root = MonsterExtraBuilder(builder).Finish(); FinishMonsterExtraBuffer(builder, def_root); const auto def_obj = builder.GetBufferPointer(); const auto def_extra = GetMonsterExtra(def_obj); TEST_NOTNULL(def_extra); TEST_EQ(is_quiet_nan(def_extra->f0()), true); TEST_EQ(is_quiet_nan(def_extra->f1()), true); TEST_EQ(def_extra->f2(), +infinity_f); TEST_EQ(def_extra->f3(), -infinity_f); TEST_EQ(is_quiet_nan(def_extra->d0()), true); TEST_EQ(is_quiet_nan(def_extra->d1()), true); TEST_EQ(def_extra->d2(), +infinity_d); TEST_EQ(def_extra->d3(), -infinity_d); std::string jsongen; auto result = GenText(parser, def_obj, &jsongen); TEST_NULL(result); // Check expected default values. TEST_EQ(std::string::npos != jsongen.find("f0: nan"), true); TEST_EQ(std::string::npos != jsongen.find("f1: nan"), true); TEST_EQ(std::string::npos != jsongen.find("f2: inf"), true); TEST_EQ(std::string::npos != jsongen.find("f3: -inf"), true); TEST_EQ(std::string::npos != jsongen.find("d0: nan"), true); TEST_EQ(std::string::npos != jsongen.find("d1: nan"), true); TEST_EQ(std::string::npos != jsongen.find("d2: inf"), true); TEST_EQ(std::string::npos != jsongen.find("d3: -inf"), true); // Parse 'mosterdata_extra.json'. const auto extra_base = tests_data_path + "monsterdata_extra"; jsongen = ""; TEST_EQ(LoadFile((extra_base + ".json").c_str(), false, &jsongen), true); TEST_EQ(parser.Parse(jsongen.c_str()), true); const auto test_file = parser.builder_.GetBufferPointer(); const auto test_size = parser.builder_.GetSize(); Verifier verifier(test_file, test_size); TEST_ASSERT(VerifyMonsterExtraBuffer(verifier)); const auto extra = GetMonsterExtra(test_file); TEST_NOTNULL(extra); TEST_EQ(is_quiet_nan(extra->f0()), true); TEST_EQ(is_quiet_nan(extra->f1()), true); TEST_EQ(extra->f2(), +infinity_f); TEST_EQ(extra->f3(), -infinity_f); TEST_EQ(is_quiet_nan(extra->d0()), true); TEST_EQ(extra->d1(), +infinity_d); TEST_EQ(extra->d2(), -infinity_d); TEST_EQ(is_quiet_nan(extra->d3()), true); TEST_NOTNULL(extra->fvec()); TEST_EQ(extra->fvec()->size(), 4); TEST_EQ(extra->fvec()->Get(0), 1.0f); TEST_EQ(extra->fvec()->Get(1), -infinity_f); TEST_EQ(extra->fvec()->Get(2), +infinity_f); TEST_EQ(is_quiet_nan(extra->fvec()->Get(3)), true); TEST_NOTNULL(extra->dvec()); TEST_EQ(extra->dvec()->size(), 4); TEST_EQ(extra->dvec()->Get(0), 2.0); TEST_EQ(extra->dvec()->Get(1), +infinity_d); TEST_EQ(extra->dvec()->Get(2), -infinity_d); TEST_EQ(is_quiet_nan(extra->dvec()->Get(3)), true); #endif } void EnumNamesTest() { TEST_EQ_STR("Red", EnumNameColor(Color_Red)); TEST_EQ_STR("Green", EnumNameColor(Color_Green)); TEST_EQ_STR("Blue", EnumNameColor(Color_Blue)); // Check that Color to string don't crash while decode a mixture of Colors. // 1) Example::Color enum is enum with unfixed underlying type. // 2) Valid enum range: [0; 2^(ceil(log2(Color_ANY))) - 1]. // Consequence: A value is out of this range will lead to UB (since C++17). // For details see C++17 standard or explanation on the SO: // stackoverflow.com/questions/18195312/what-happens-if-you-static-cast-invalid-value-to-enum-class TEST_EQ_STR("", EnumNameColor(static_cast(0))); TEST_EQ_STR("", EnumNameColor(static_cast(Color_ANY - 1))); TEST_EQ_STR("", EnumNameColor(static_cast(Color_ANY + 1))); } void TypeAliasesTest() { flatbuffers::FlatBufferBuilder builder; builder.Finish(CreateTypeAliases( builder, flatbuffers::numeric_limits::min(), flatbuffers::numeric_limits::max(), flatbuffers::numeric_limits::min(), flatbuffers::numeric_limits::max(), flatbuffers::numeric_limits::min(), flatbuffers::numeric_limits::max(), flatbuffers::numeric_limits::min(), flatbuffers::numeric_limits::max(), 2.3f, 2.3)); auto p = builder.GetBufferPointer(); auto ta = flatbuffers::GetRoot(p); TEST_EQ(ta->i8(), flatbuffers::numeric_limits::min()); TEST_EQ(ta->u8(), flatbuffers::numeric_limits::max()); TEST_EQ(ta->i16(), flatbuffers::numeric_limits::min()); TEST_EQ(ta->u16(), flatbuffers::numeric_limits::max()); TEST_EQ(ta->i32(), flatbuffers::numeric_limits::min()); TEST_EQ(ta->u32(), flatbuffers::numeric_limits::max()); TEST_EQ(ta->i64(), flatbuffers::numeric_limits::min()); TEST_EQ(ta->u64(), flatbuffers::numeric_limits::max()); TEST_EQ(ta->f32(), 2.3f); TEST_EQ(ta->f64(), 2.3); using namespace flatbuffers; // is_same static_assert(is_samei8()), int8_t>::value, "invalid type"); static_assert(is_samei16()), int16_t>::value, "invalid type"); static_assert(is_samei32()), int32_t>::value, "invalid type"); static_assert(is_samei64()), int64_t>::value, "invalid type"); static_assert(is_sameu8()), uint8_t>::value, "invalid type"); static_assert(is_sameu16()), uint16_t>::value, "invalid type"); static_assert(is_sameu32()), uint32_t>::value, "invalid type"); static_assert(is_sameu64()), uint64_t>::value, "invalid type"); static_assert(is_samef32()), float>::value, "invalid type"); static_assert(is_samef64()), double>::value, "invalid type"); } // example of parsing text straight into a buffer, and generating // text back from it: void ParseAndGenerateTextTest(const std::string &tests_data_path, bool binary) { // load FlatBuffer schema (.fbs) and JSON from disk std::string schemafile; std::string jsonfile; TEST_EQ(flatbuffers::LoadFile( (tests_data_path + "monster_test." + (binary ? "bfbs" : "fbs")) .c_str(), binary, &schemafile), true); TEST_EQ(flatbuffers::LoadFile( (tests_data_path + "monsterdata_test.golden").c_str(), false, &jsonfile), true); auto include_test_path = flatbuffers::ConCatPathFileName(tests_data_path, "include_test"); const char *include_directories[] = { tests_data_path.c_str(), include_test_path.c_str(), nullptr }; // parse schema first, so we can use it to parse the data after flatbuffers::Parser parser; if (binary) { flatbuffers::Verifier verifier( reinterpret_cast(schemafile.c_str()), schemafile.size()); TEST_EQ(reflection::VerifySchemaBuffer(verifier), true); // auto schema = reflection::GetSchema(schemafile.c_str()); TEST_EQ(parser.Deserialize( reinterpret_cast(schemafile.c_str()), schemafile.size()), true); } else { TEST_EQ(parser.Parse(schemafile.c_str(), include_directories), true); } TEST_EQ(parser.ParseJson(jsonfile.c_str()), true); // here, parser.builder_ contains a binary buffer that is the parsed data. // First, verify it, just in case: flatbuffers::Verifier verifier(parser.builder_.GetBufferPointer(), parser.builder_.GetSize()); TEST_EQ(VerifyMonsterBuffer(verifier), true); AccessFlatBufferTest(parser.builder_.GetBufferPointer(), parser.builder_.GetSize(), false); // to ensure it is correct, we now generate text back from the binary, // and compare the two: std::string jsongen; auto result = GenText(parser, parser.builder_.GetBufferPointer(), &jsongen); TEST_NULL(result); TEST_EQ_STR(jsongen.c_str(), jsonfile.c_str()); // We can also do the above using the convenient Registry that knows about // a set of file_identifiers mapped to schemas. flatbuffers::Registry registry; // Make sure schemas can find their includes. registry.AddIncludeDirectory(tests_data_path.c_str()); registry.AddIncludeDirectory(include_test_path.c_str()); // Call this with many schemas if possible. registry.Register(MonsterIdentifier(), (tests_data_path + "monster_test.fbs").c_str()); // Now we got this set up, we can parse by just specifying the identifier, // the correct schema will be loaded on the fly: auto buf = registry.TextToFlatBuffer(jsonfile.c_str(), MonsterIdentifier()); // If this fails, check registry.lasterror_. TEST_NOTNULL(buf.data()); // Test the buffer, to be sure: AccessFlatBufferTest(buf.data(), buf.size(), false); // We can use the registry to turn this back into text, in this case it // will get the file_identifier from the binary: std::string text; auto ok = registry.FlatBufferToText(buf.data(), buf.size(), &text); // If this fails, check registry.lasterror_. TEST_EQ(ok, true); TEST_EQ_STR(text.c_str(), jsonfile.c_str()); // Generate text for UTF-8 strings without escapes. std::string jsonfile_utf8; TEST_EQ(flatbuffers::LoadFile((tests_data_path + "unicode_test.json").c_str(), false, &jsonfile_utf8), true); TEST_EQ(parser.Parse(jsonfile_utf8.c_str(), include_directories), true); // To ensure it is correct, generate utf-8 text back from the binary. std::string jsongen_utf8; // request natural printing for utf-8 strings parser.opts.natural_utf8 = true; parser.opts.strict_json = true; TEST_NULL(GenText(parser, parser.builder_.GetBufferPointer(), &jsongen_utf8)); TEST_EQ_STR(jsongen_utf8.c_str(), jsonfile_utf8.c_str()); } void UnPackTo(const uint8_t *flatbuf) { // Get a monster that has a name and no enemy auto orig_monster = GetMonster(flatbuf); TEST_EQ_STR(orig_monster->name()->c_str(), "MyMonster"); TEST_ASSERT(orig_monster->enemy() == nullptr); // Create an enemy MonsterT *enemy = new MonsterT(); enemy->name = "Enemy"; // And create another monster owning the enemy, MonsterT mon; mon.name = "I'm monster 1"; mon.enemy.reset(enemy); TEST_ASSERT(mon.enemy != nullptr); // Assert that all the Monster objects are correct. TEST_EQ_STR(mon.name.c_str(), "I'm monster 1"); TEST_EQ_STR(enemy->name.c_str(), "Enemy"); TEST_EQ_STR(mon.enemy->name.c_str(), "Enemy"); // Now unpack monster ("MyMonster") into monster orig_monster->UnPackTo(&mon); // Monster name should be from monster TEST_EQ_STR(mon.name.c_str(), "MyMonster"); // The monster shouldn't have any enemies, because monster didn't. TEST_ASSERT(mon.enemy == nullptr); } } // namespace tests } // namespace flatbuffers