326 lines
14 KiB
C++
326 lines
14 KiB
C++
#include "reflection_test.h"
|
|
#include "arrays_test_generated.h"
|
|
#include "flatbuffers/minireflect.h"
|
|
#include "flatbuffers/reflection.h"
|
|
#include "flatbuffers/reflection_generated.h"
|
|
#include "flatbuffers/verifier.h"
|
|
#include "test_assert.h"
|
|
#include "monster_test.h"
|
|
#include "monster_test_generated.h"
|
|
|
|
namespace flatbuffers {
|
|
namespace tests {
|
|
|
|
using namespace MyGame::Example;
|
|
|
|
void ReflectionTest(const std::string& tests_data_path, uint8_t *flatbuf, size_t length) {
|
|
// Load a binary schema.
|
|
std::string bfbsfile;
|
|
TEST_EQ(flatbuffers::LoadFile((tests_data_path + "monster_test.bfbs").c_str(),
|
|
true, &bfbsfile),
|
|
true);
|
|
|
|
// Verify it, just in case:
|
|
flatbuffers::Verifier verifier(
|
|
reinterpret_cast<const uint8_t *>(bfbsfile.c_str()), bfbsfile.length());
|
|
TEST_EQ(reflection::VerifySchemaBuffer(verifier), true);
|
|
|
|
// Make sure the schema is what we expect it to be.
|
|
auto &schema = *reflection::GetSchema(bfbsfile.c_str());
|
|
auto root_table = schema.root_table();
|
|
|
|
// Check the declaration files.
|
|
TEST_EQ_STR(root_table->name()->c_str(), "MyGame.Example.Monster");
|
|
TEST_EQ_STR(root_table->declaration_file()->c_str(), "//monster_test.fbs");
|
|
TEST_EQ_STR(
|
|
schema.objects()->LookupByKey("TableA")->declaration_file()->c_str(),
|
|
"//include_test/include_test1.fbs");
|
|
TEST_EQ_STR(schema.objects()
|
|
->LookupByKey("MyGame.OtherNameSpace.Unused")
|
|
->declaration_file()
|
|
->c_str(),
|
|
"//include_test/sub/include_test2.fbs");
|
|
TEST_EQ_STR(schema.enums()
|
|
->LookupByKey("MyGame.OtherNameSpace.FromInclude")
|
|
->declaration_file()
|
|
->c_str(),
|
|
"//include_test/sub/include_test2.fbs");
|
|
|
|
// Check scheam filenames and their includes.
|
|
TEST_EQ(schema.fbs_files()->size(), 3);
|
|
|
|
const auto fbs0 = schema.fbs_files()->Get(0);
|
|
TEST_EQ_STR(fbs0->filename()->c_str(), "//include_test/include_test1.fbs");
|
|
const auto fbs0_includes = fbs0->included_filenames();
|
|
TEST_EQ(fbs0_includes->size(), 2);
|
|
|
|
// TODO(caspern): Should we force or disallow inclusion of self?
|
|
TEST_EQ_STR(fbs0_includes->Get(0)->c_str(),
|
|
"//include_test/include_test1.fbs");
|
|
TEST_EQ_STR(fbs0_includes->Get(1)->c_str(),
|
|
"//include_test/sub/include_test2.fbs");
|
|
|
|
const auto fbs1 = schema.fbs_files()->Get(1);
|
|
TEST_EQ_STR(fbs1->filename()->c_str(),
|
|
"//include_test/sub/include_test2.fbs");
|
|
const auto fbs1_includes = fbs1->included_filenames();
|
|
TEST_EQ(fbs1_includes->size(), 2);
|
|
TEST_EQ_STR(fbs1_includes->Get(0)->c_str(),
|
|
"//include_test/include_test1.fbs");
|
|
TEST_EQ_STR(fbs1_includes->Get(1)->c_str(),
|
|
"//include_test/sub/include_test2.fbs");
|
|
|
|
const auto fbs2 = schema.fbs_files()->Get(2);
|
|
TEST_EQ_STR(fbs2->filename()->c_str(), "//monster_test.fbs");
|
|
const auto fbs2_includes = fbs2->included_filenames();
|
|
TEST_EQ(fbs2_includes->size(), 1);
|
|
TEST_EQ_STR(fbs2_includes->Get(0)->c_str(),
|
|
"//include_test/include_test1.fbs");
|
|
|
|
// Check Root table fields
|
|
auto fields = root_table->fields();
|
|
auto hp_field_ptr = fields->LookupByKey("hp");
|
|
TEST_NOTNULL(hp_field_ptr);
|
|
auto &hp_field = *hp_field_ptr;
|
|
TEST_EQ_STR(hp_field.name()->c_str(), "hp");
|
|
TEST_EQ(hp_field.id(), 2);
|
|
TEST_EQ(hp_field.type()->base_type(), reflection::Short);
|
|
|
|
auto friendly_field_ptr = fields->LookupByKey("friendly");
|
|
TEST_NOTNULL(friendly_field_ptr);
|
|
TEST_NOTNULL(friendly_field_ptr->attributes());
|
|
TEST_NOTNULL(friendly_field_ptr->attributes()->LookupByKey("priority"));
|
|
|
|
// Make sure the table index is what we expect it to be.
|
|
auto pos_field_ptr = fields->LookupByKey("pos");
|
|
TEST_NOTNULL(pos_field_ptr);
|
|
TEST_EQ(pos_field_ptr->type()->base_type(), reflection::Obj);
|
|
auto pos_table_ptr = schema.objects()->Get(pos_field_ptr->type()->index());
|
|
TEST_NOTNULL(pos_table_ptr);
|
|
TEST_EQ_STR(pos_table_ptr->name()->c_str(), "MyGame.Example.Vec3");
|
|
|
|
// Test nullability of fields: hp is a 0-default scalar, pos is a struct =>
|
|
// optional, and name is a required string => not optional.
|
|
TEST_EQ(hp_field.optional(), false);
|
|
TEST_EQ(pos_field_ptr->optional(), true);
|
|
TEST_EQ(fields->LookupByKey("name")->optional(), false);
|
|
|
|
// Now use it to dynamically access a buffer.
|
|
auto &root = *flatbuffers::GetAnyRoot(flatbuf);
|
|
|
|
// Verify the buffer first using reflection based verification
|
|
TEST_EQ(flatbuffers::Verify(schema, *schema.root_table(), flatbuf, length),
|
|
true);
|
|
|
|
auto hp = flatbuffers::GetFieldI<uint16_t>(root, hp_field);
|
|
TEST_EQ(hp, 80);
|
|
|
|
// Rather than needing to know the type, we can also get the value of
|
|
// any field as an int64_t/double/string, regardless of what it actually is.
|
|
auto hp_int64 = flatbuffers::GetAnyFieldI(root, hp_field);
|
|
TEST_EQ(hp_int64, 80);
|
|
auto hp_double = flatbuffers::GetAnyFieldF(root, hp_field);
|
|
TEST_EQ(hp_double, 80.0);
|
|
auto hp_string = flatbuffers::GetAnyFieldS(root, hp_field, &schema);
|
|
TEST_EQ_STR(hp_string.c_str(), "80");
|
|
|
|
// Get struct field through reflection
|
|
auto pos_struct = flatbuffers::GetFieldStruct(root, *pos_field_ptr);
|
|
TEST_NOTNULL(pos_struct);
|
|
TEST_EQ(flatbuffers::GetAnyFieldF(*pos_struct,
|
|
*pos_table_ptr->fields()->LookupByKey("z")),
|
|
3.0f);
|
|
|
|
auto test3_field = pos_table_ptr->fields()->LookupByKey("test3");
|
|
auto test3_struct = flatbuffers::GetFieldStruct(*pos_struct, *test3_field);
|
|
TEST_NOTNULL(test3_struct);
|
|
auto test3_object = schema.objects()->Get(test3_field->type()->index());
|
|
|
|
TEST_EQ(flatbuffers::GetAnyFieldF(*test3_struct,
|
|
*test3_object->fields()->LookupByKey("a")),
|
|
10);
|
|
|
|
// We can also modify it.
|
|
flatbuffers::SetField<uint16_t>(&root, hp_field, 200);
|
|
hp = flatbuffers::GetFieldI<uint16_t>(root, hp_field);
|
|
TEST_EQ(hp, 200);
|
|
|
|
// We can also set fields generically:
|
|
flatbuffers::SetAnyFieldI(&root, hp_field, 300);
|
|
hp_int64 = flatbuffers::GetAnyFieldI(root, hp_field);
|
|
TEST_EQ(hp_int64, 300);
|
|
flatbuffers::SetAnyFieldF(&root, hp_field, 300.5);
|
|
hp_int64 = flatbuffers::GetAnyFieldI(root, hp_field);
|
|
TEST_EQ(hp_int64, 300);
|
|
flatbuffers::SetAnyFieldS(&root, hp_field, "300");
|
|
hp_int64 = flatbuffers::GetAnyFieldI(root, hp_field);
|
|
TEST_EQ(hp_int64, 300);
|
|
|
|
// Test buffer is valid after the modifications
|
|
TEST_EQ(flatbuffers::Verify(schema, *schema.root_table(), flatbuf, length),
|
|
true);
|
|
|
|
// Reset it, for further tests.
|
|
flatbuffers::SetField<uint16_t>(&root, hp_field, 80);
|
|
|
|
// More advanced functionality: changing the size of items in-line!
|
|
// First we put the FlatBuffer inside an std::vector.
|
|
std::vector<uint8_t> resizingbuf(flatbuf, flatbuf + length);
|
|
// Find the field we want to modify.
|
|
auto &name_field = *fields->LookupByKey("name");
|
|
// Get the root.
|
|
// This time we wrap the result from GetAnyRoot in a smartpointer that
|
|
// will keep rroot valid as resizingbuf resizes.
|
|
auto rroot = flatbuffers::piv(flatbuffers::GetAnyRoot(resizingbuf.data()),
|
|
resizingbuf);
|
|
SetString(schema, "totally new string", GetFieldS(**rroot, name_field),
|
|
&resizingbuf);
|
|
// Here resizingbuf has changed, but rroot is still valid.
|
|
TEST_EQ_STR(GetFieldS(**rroot, name_field)->c_str(), "totally new string");
|
|
// Now lets extend a vector by 100 elements (10 -> 110).
|
|
auto &inventory_field = *fields->LookupByKey("inventory");
|
|
auto rinventory = flatbuffers::piv(
|
|
flatbuffers::GetFieldV<uint8_t>(**rroot, inventory_field), resizingbuf);
|
|
flatbuffers::ResizeVector<uint8_t>(schema, 110, 50, *rinventory,
|
|
&resizingbuf);
|
|
// rinventory still valid, so lets read from it.
|
|
TEST_EQ(rinventory->Get(10), 50);
|
|
|
|
// For reflection uses not covered already, there is a more powerful way:
|
|
// we can simply generate whatever object we want to add/modify in a
|
|
// FlatBuffer of its own, then add that to an existing FlatBuffer:
|
|
// As an example, let's add a string to an array of strings.
|
|
// First, find our field:
|
|
auto &testarrayofstring_field = *fields->LookupByKey("testarrayofstring");
|
|
// Find the vector value:
|
|
auto rtestarrayofstring = flatbuffers::piv(
|
|
flatbuffers::GetFieldV<flatbuffers::Offset<flatbuffers::String>>(
|
|
**rroot, testarrayofstring_field),
|
|
resizingbuf);
|
|
// It's a vector of 2 strings, to which we add one more, initialized to
|
|
// offset 0.
|
|
flatbuffers::ResizeVector<flatbuffers::Offset<flatbuffers::String>>(
|
|
schema, 3, 0, *rtestarrayofstring, &resizingbuf);
|
|
// Here we just create a buffer that contans a single string, but this
|
|
// could also be any complex set of tables and other values.
|
|
flatbuffers::FlatBufferBuilder stringfbb;
|
|
stringfbb.Finish(stringfbb.CreateString("hank"));
|
|
// Add the contents of it to our existing FlatBuffer.
|
|
// We do this last, so the pointer doesn't get invalidated (since it is
|
|
// at the end of the buffer):
|
|
auto string_ptr = flatbuffers::AddFlatBuffer(
|
|
resizingbuf, stringfbb.GetBufferPointer(), stringfbb.GetSize());
|
|
// Finally, set the new value in the vector.
|
|
rtestarrayofstring->MutateOffset(2, string_ptr);
|
|
TEST_EQ_STR(rtestarrayofstring->Get(0)->c_str(), "bob");
|
|
TEST_EQ_STR(rtestarrayofstring->Get(2)->c_str(), "hank");
|
|
// Test integrity of all resize operations above.
|
|
flatbuffers::Verifier resize_verifier(
|
|
reinterpret_cast<const uint8_t *>(resizingbuf.data()),
|
|
resizingbuf.size());
|
|
TEST_EQ(VerifyMonsterBuffer(resize_verifier), true);
|
|
|
|
// Test buffer is valid using reflection as well
|
|
TEST_EQ(flatbuffers::Verify(schema, *schema.root_table(), resizingbuf.data(),
|
|
resizingbuf.size()),
|
|
true);
|
|
|
|
// As an additional test, also set it on the name field.
|
|
// Note: unlike the name change above, this just overwrites the offset,
|
|
// rather than changing the string in-place.
|
|
SetFieldT(*rroot, name_field, string_ptr);
|
|
TEST_EQ_STR(GetFieldS(**rroot, name_field)->c_str(), "hank");
|
|
|
|
// Using reflection, rather than mutating binary FlatBuffers, we can also copy
|
|
// tables and other things out of other FlatBuffers into a FlatBufferBuilder,
|
|
// either part or whole.
|
|
flatbuffers::FlatBufferBuilder fbb;
|
|
auto root_offset = flatbuffers::CopyTable(
|
|
fbb, schema, *root_table, *flatbuffers::GetAnyRoot(flatbuf), true);
|
|
fbb.Finish(root_offset, MonsterIdentifier());
|
|
// Test that it was copied correctly:
|
|
AccessFlatBufferTest(fbb.GetBufferPointer(), fbb.GetSize());
|
|
|
|
// Test buffer is valid using reflection as well
|
|
TEST_EQ(flatbuffers::Verify(schema, *schema.root_table(),
|
|
fbb.GetBufferPointer(), fbb.GetSize()),
|
|
true);
|
|
}
|
|
|
|
void MiniReflectFlatBuffersTest(uint8_t *flatbuf) {
|
|
auto s =
|
|
flatbuffers::FlatBufferToString(flatbuf, Monster::MiniReflectTypeTable());
|
|
TEST_EQ_STR(
|
|
s.c_str(),
|
|
"{ "
|
|
"pos: { x: 1.0, y: 2.0, z: 3.0, test1: 0.0, test2: Red, test3: "
|
|
"{ a: 10, b: 20 } }, "
|
|
"hp: 80, "
|
|
"name: \"MyMonster\", "
|
|
"inventory: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ], "
|
|
"test_type: Monster, "
|
|
"test: { name: \"Fred\" }, "
|
|
"test4: [ { a: 10, b: 20 }, { a: 30, b: 40 } ], "
|
|
"testarrayofstring: [ \"bob\", \"fred\", \"bob\", \"fred\" ], "
|
|
"testarrayoftables: [ { hp: 1000, name: \"Barney\" }, { name: \"Fred\" "
|
|
"}, "
|
|
"{ name: \"Wilma\" } ], "
|
|
// TODO(wvo): should really print this nested buffer correctly.
|
|
"testnestedflatbuffer: [ 124, 0, 0, 0, 77, 79, 78, 83, 0, 0, 114, 0, 16, "
|
|
"0, 0, 0, 4, 0, 6, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "
|
|
"0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "
|
|
"0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "
|
|
"0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "
|
|
"0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 114, 0, 0, 0, 0, 0, 0, 0, "
|
|
"8, 0, 0, 0, 0, 0, 192, 127, 13, 0, 0, 0, 78, 101, 115, 116, 101, 100, "
|
|
"77, 111, 110, 115, 116, 101, 114, 0, 0, 0 ], "
|
|
"testarrayofstring2: [ \"jane\", \"mary\" ], "
|
|
"testarrayofsortedstruct: [ { id: 0, distance: 0 }, "
|
|
"{ id: 2, distance: 20 }, { id: 3, distance: 30 }, "
|
|
"{ id: 4, distance: 40 } ], "
|
|
"flex: [ 210, 4, 5, 2 ], "
|
|
"test5: [ { a: 10, b: 20 }, { a: 30, b: 40 } ], "
|
|
"vector_of_enums: [ Blue, Green ], "
|
|
"scalar_key_sorted_tables: [ { id: \"miss\" } ], "
|
|
"nan_default: nan "
|
|
"}");
|
|
|
|
Test test(16, 32);
|
|
Vec3 vec(1, 2, 3, 1.5, Color_Red, test);
|
|
flatbuffers::FlatBufferBuilder vec_builder;
|
|
vec_builder.Finish(vec_builder.CreateStruct(vec));
|
|
auto vec_buffer = vec_builder.Release();
|
|
auto vec_str = flatbuffers::FlatBufferToString(vec_buffer.data(),
|
|
Vec3::MiniReflectTypeTable());
|
|
TEST_EQ_STR(vec_str.c_str(),
|
|
"{ x: 1.0, y: 2.0, z: 3.0, test1: 1.5, test2: Red, test3: { a: "
|
|
"16, b: 32 } }");
|
|
}
|
|
|
|
void MiniReflectFixedLengthArrayTest() {
|
|
// VS10 does not support typed enums, exclude from tests
|
|
#if !defined(_MSC_VER) || _MSC_VER >= 1700
|
|
flatbuffers::FlatBufferBuilder fbb;
|
|
MyGame::Example::ArrayStruct aStruct(2, 12, 1);
|
|
auto aTable = MyGame::Example::CreateArrayTable(fbb, &aStruct);
|
|
fbb.Finish(aTable);
|
|
|
|
auto flatbuf = fbb.Release();
|
|
auto s = flatbuffers::FlatBufferToString(
|
|
flatbuf.data(), MyGame::Example::ArrayTableTypeTable());
|
|
TEST_EQ_STR(
|
|
"{ "
|
|
"a: { a: 2.0, "
|
|
"b: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], "
|
|
"c: 12, "
|
|
"d: [ { a: [ 0, 0 ], b: A, c: [ A, A ], d: [ 0, 0 ] }, "
|
|
"{ a: [ 0, 0 ], b: A, c: [ A, A ], d: [ 0, 0 ] } ], "
|
|
"e: 1, f: [ 0, 0 ] } "
|
|
"}",
|
|
s.c_str());
|
|
#endif
|
|
}
|
|
|
|
} // namespace tests
|
|
} // namespace flatbuffers
|