| #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 |