| #include "monster_test.h" | |
| #include <limits> | |
| #include <vector> | |
| #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<float>::infinity(); | |
| static const auto infinity_d = std::numeric_limits<double>::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<uint8_t>({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<unsigned char>( | |
| // 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<Test>( | |
| 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<Monster> 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<flatbuffers::Offset<flatbuffers::String>>( | |
| 4, | |
| [](size_t i, flatbuffers::FlatBufferBuilder* b) | |
| -> flatbuffers::Offset<flatbuffers::String> { | |
| static const char* names[] = {"bob", "fred", "bob", "fred"}; | |
| return b->CreateSharedString(names[i]); | |
| }, | |
| &builder); | |
| // Creating vectors of strings in one convenient call. | |
| std::vector<std::string> 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<const char*> names3; | |
| names3.push_back("foo"); | |
| names3.push_back("bar"); | |
| builder.CreateVectorOfStrings(names3); // Also an accepted type | |
| #ifdef FLATBUFFERS_HAS_STRING_VIEW | |
| std::vector<flatbuffers::string_view> 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<std::string> | |
| builder.CreateVectorOfStrings({"hello", "world"}); | |
| // Create many vectors of strings | |
| std::vector<std::string> 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<Ability> 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<Stat> 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<uint8_t, Color>(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<const char*>(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<uint8_t> flex_reuse_tracker; | |
| verifier.SetFlexReuseTracker(&flex_reuse_tracker); | |
| TEST_EQ(VerifyMonsterBuffer(verifier), true); | |
| // clang-format off | |
| #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE | |
| std::vector<uint8_t> 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<unsigned char> 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<const Monster*>(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")); | |
| // Verify the same objects are returned for char*-based and string-based | |
| // lookups. | |
| TEST_EQ(vecoftables->LookupByKey("Barney"), | |
| vecoftables->LookupByKey(std::string("Barney"))); | |
| TEST_EQ(vecoftables->LookupByKey("Fred"), | |
| vecoftables->LookupByKey(std::string("Fred"))); | |
| TEST_EQ(vecoftables->LookupByKey("Wilma"), | |
| vecoftables->LookupByKey(std::string("Wilma"))); | |
| #ifdef FLATBUFFERS_HAS_STRING_VIEW | |
| // Tests for LookupByKey with a key that is a truncated | |
| // version of a longer, invalid key. | |
| const std::string invalid_key = "Barney123"; | |
| std::string_view valid_truncated_key = invalid_key; | |
| valid_truncated_key.remove_suffix(3); // "Barney" | |
| TEST_NOTNULL(vecoftables->LookupByKey(valid_truncated_key)); | |
| TEST_EQ(vecoftables->LookupByKey("Barney"), | |
| vecoftables->LookupByKey(valid_truncated_key)); | |
| // Tests for LookupByKey with a key that is a truncated | |
| // version of a longer, valid key. | |
| const std::string valid_key = "Barney"; | |
| std::string_view invalid_truncated_key = valid_key; | |
| invalid_truncated_key.remove_suffix(3); // "Bar" | |
| TEST_NULL(vecoftables->LookupByKey(invalid_truncated_key)); | |
| #endif // FLATBUFFERS_HAS_STRING_VIEW | |
| // 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<const Ability*>(nullptr), vecofstructs->LookupByKey(5)); | |
| } | |
| if (auto vec_of_stat = monster->scalar_key_sorted_tables()) { | |
| auto stat_0 = vec_of_stat->LookupByKey(static_cast<uint16_t>(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<Test>(), 2UL); | |
| TEST_EQ(sizeof(Test), 4UL); | |
| const flatbuffers::Vector<const Test*>* 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<Monster*>(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<float>::quiet_NaN()), true); | |
| TEST_EQ(is_quiet_nan(std::numeric_limits<double>::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<Color>(0))); | |
| TEST_EQ_STR("", EnumNameColor(static_cast<Color>(Color_ANY - 1))); | |
| TEST_EQ_STR("", EnumNameColor(static_cast<Color>(Color_ANY + 1))); | |
| } | |
| void TypeAliasesTest() { | |
| flatbuffers::FlatBufferBuilder builder; | |
| builder.Finish(CreateTypeAliases( | |
| builder, flatbuffers::numeric_limits<int8_t>::min(), | |
| flatbuffers::numeric_limits<uint8_t>::max(), | |
| flatbuffers::numeric_limits<int16_t>::min(), | |
| flatbuffers::numeric_limits<uint16_t>::max(), | |
| flatbuffers::numeric_limits<int32_t>::min(), | |
| flatbuffers::numeric_limits<uint32_t>::max(), | |
| flatbuffers::numeric_limits<int64_t>::min(), | |
| flatbuffers::numeric_limits<uint64_t>::max(), 2.3f, 2.3)); | |
| auto p = builder.GetBufferPointer(); | |
| auto ta = flatbuffers::GetRoot<TypeAliases>(p); | |
| TEST_EQ(ta->i8(), flatbuffers::numeric_limits<int8_t>::min()); | |
| TEST_EQ(ta->u8(), flatbuffers::numeric_limits<uint8_t>::max()); | |
| TEST_EQ(ta->i16(), flatbuffers::numeric_limits<int16_t>::min()); | |
| TEST_EQ(ta->u16(), flatbuffers::numeric_limits<uint16_t>::max()); | |
| TEST_EQ(ta->i32(), flatbuffers::numeric_limits<int32_t>::min()); | |
| TEST_EQ(ta->u32(), flatbuffers::numeric_limits<uint32_t>::max()); | |
| TEST_EQ(ta->i64(), flatbuffers::numeric_limits<int64_t>::min()); | |
| TEST_EQ(ta->u64(), flatbuffers::numeric_limits<uint64_t>::max()); | |
| TEST_EQ(ta->f32(), 2.3f); | |
| TEST_EQ(ta->f64(), 2.3); | |
| using namespace flatbuffers; // is_same | |
| static_assert(is_same<decltype(ta->i8()), int8_t>::value, "invalid type"); | |
| static_assert(is_same<decltype(ta->i16()), int16_t>::value, "invalid type"); | |
| static_assert(is_same<decltype(ta->i32()), int32_t>::value, "invalid type"); | |
| static_assert(is_same<decltype(ta->i64()), int64_t>::value, "invalid type"); | |
| static_assert(is_same<decltype(ta->u8()), uint8_t>::value, "invalid type"); | |
| static_assert(is_same<decltype(ta->u16()), uint16_t>::value, "invalid type"); | |
| static_assert(is_same<decltype(ta->u32()), uint32_t>::value, "invalid type"); | |
| static_assert(is_same<decltype(ta->u64()), uint64_t>::value, "invalid type"); | |
| static_assert(is_same<decltype(ta->f32()), float>::value, "invalid type"); | |
| static_assert(is_same<decltype(ta->f64()), 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<const uint8_t*>(schemafile.c_str()), | |
| schemafile.size()); | |
| TEST_EQ(reflection::VerifySchemaBuffer(verifier), true); | |
| // auto schema = reflection::GetSchema(schemafile.c_str()); | |
| TEST_EQ( | |
| parser.Deserialize(reinterpret_cast<const uint8_t*>(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 |