...

Text file src/github.com/google/flatbuffers/tests/reflection_test.cpp

Documentation: github.com/google/flatbuffers/tests

     1#include "reflection_test.h"
     2
     3#include "arrays_test_generated.h"
     4#include "flatbuffers/minireflect.h"
     5#include "flatbuffers/reflection.h"
     6#include "flatbuffers/reflection_generated.h"
     7#include "flatbuffers/verifier.h"
     8#include "monster_test.h"
     9#include "monster_test_generated.h"
    10#include "test_assert.h"
    11
    12namespace flatbuffers {
    13namespace tests {
    14
    15using namespace MyGame::Example;
    16
    17void ReflectionTest(const std::string &tests_data_path, uint8_t *flatbuf,
    18                    size_t length) {
    19  // Load a binary schema.
    20  std::string bfbsfile;
    21  TEST_EQ(flatbuffers::LoadFile((tests_data_path + "monster_test.bfbs").c_str(),
    22                                true, &bfbsfile),
    23          true);
    24
    25  // Verify it, just in case:
    26  flatbuffers::Verifier verifier(
    27      reinterpret_cast<const uint8_t *>(bfbsfile.c_str()), bfbsfile.length());
    28  TEST_EQ(reflection::VerifySchemaBuffer(verifier), true);
    29
    30  // Make sure the schema is what we expect it to be.
    31  auto &schema = *reflection::GetSchema(bfbsfile.c_str());
    32  auto root_table = schema.root_table();
    33
    34  // Check the declaration files.
    35  TEST_EQ_STR(root_table->name()->c_str(), "MyGame.Example.Monster");
    36  TEST_EQ_STR(root_table->declaration_file()->c_str(), "//monster_test.fbs");
    37  TEST_EQ_STR(
    38      schema.objects()->LookupByKey("TableA")->declaration_file()->c_str(),
    39      "//include_test/include_test1.fbs");
    40  TEST_EQ_STR(schema.objects()
    41                  ->LookupByKey("MyGame.OtherNameSpace.Unused")
    42                  ->declaration_file()
    43                  ->c_str(),
    44              "//include_test/sub/include_test2.fbs");
    45  TEST_EQ_STR(schema.enums()
    46                  ->LookupByKey("MyGame.OtherNameSpace.FromInclude")
    47                  ->declaration_file()
    48                  ->c_str(),
    49              "//include_test/sub/include_test2.fbs");
    50
    51  // Check scheam filenames and their includes.
    52  TEST_EQ(schema.fbs_files()->size(), 3);
    53
    54  const auto fbs0 = schema.fbs_files()->Get(0);
    55  TEST_EQ_STR(fbs0->filename()->c_str(), "//include_test/include_test1.fbs");
    56  const auto fbs0_includes = fbs0->included_filenames();
    57  TEST_EQ(fbs0_includes->size(), 2);
    58
    59  // TODO(caspern): Should we force or disallow inclusion of self?
    60  TEST_EQ_STR(fbs0_includes->Get(0)->c_str(),
    61              "//include_test/include_test1.fbs");
    62  TEST_EQ_STR(fbs0_includes->Get(1)->c_str(),
    63              "//include_test/sub/include_test2.fbs");
    64
    65  const auto fbs1 = schema.fbs_files()->Get(1);
    66  TEST_EQ_STR(fbs1->filename()->c_str(),
    67              "//include_test/sub/include_test2.fbs");
    68  const auto fbs1_includes = fbs1->included_filenames();
    69  TEST_EQ(fbs1_includes->size(), 2);
    70  TEST_EQ_STR(fbs1_includes->Get(0)->c_str(),
    71              "//include_test/include_test1.fbs");
    72  TEST_EQ_STR(fbs1_includes->Get(1)->c_str(),
    73              "//include_test/sub/include_test2.fbs");
    74
    75  const auto fbs2 = schema.fbs_files()->Get(2);
    76  TEST_EQ_STR(fbs2->filename()->c_str(), "//monster_test.fbs");
    77  const auto fbs2_includes = fbs2->included_filenames();
    78  TEST_EQ(fbs2_includes->size(), 1);
    79  TEST_EQ_STR(fbs2_includes->Get(0)->c_str(),
    80              "//include_test/include_test1.fbs");
    81
    82  // Check Root table fields
    83  auto fields = root_table->fields();
    84  auto hp_field_ptr = fields->LookupByKey("hp");
    85  TEST_NOTNULL(hp_field_ptr);
    86  auto &hp_field = *hp_field_ptr;
    87  TEST_EQ_STR(hp_field.name()->c_str(), "hp");
    88  TEST_EQ(hp_field.id(), 2);
    89  TEST_EQ(hp_field.type()->base_type(), reflection::Short);
    90
    91  auto friendly_field_ptr = fields->LookupByKey("friendly");
    92  TEST_NOTNULL(friendly_field_ptr);
    93  TEST_NOTNULL(friendly_field_ptr->attributes());
    94  TEST_NOTNULL(friendly_field_ptr->attributes()->LookupByKey("priority"));
    95
    96  // Make sure the table index is what we expect it to be.
    97  auto pos_field_ptr = fields->LookupByKey("pos");
    98  TEST_NOTNULL(pos_field_ptr);
    99  TEST_EQ(pos_field_ptr->type()->base_type(), reflection::Obj);
   100  auto pos_table_ptr = schema.objects()->Get(pos_field_ptr->type()->index());
   101  TEST_NOTNULL(pos_table_ptr);
   102  TEST_EQ_STR(pos_table_ptr->name()->c_str(), "MyGame.Example.Vec3");
   103
   104  // Test nullability of fields: hp is a 0-default scalar, pos is a struct =>
   105  // optional, and name is a required string => not optional.
   106  TEST_EQ(hp_field.optional(), false);
   107  TEST_EQ(pos_field_ptr->optional(), true);
   108  TEST_EQ(fields->LookupByKey("name")->optional(), false);
   109
   110  // Now use it to dynamically access a buffer.
   111  auto &root = *flatbuffers::GetAnyRoot(flatbuf);
   112
   113  // Verify the buffer first using reflection based verification
   114  TEST_EQ(flatbuffers::Verify(schema, *schema.root_table(), flatbuf, length),
   115          true);
   116
   117  auto hp = flatbuffers::GetFieldI<uint16_t>(root, hp_field);
   118  TEST_EQ(hp, 80);
   119
   120  // Rather than needing to know the type, we can also get the value of
   121  // any field as an int64_t/double/string, regardless of what it actually is.
   122  auto hp_int64 = flatbuffers::GetAnyFieldI(root, hp_field);
   123  TEST_EQ(hp_int64, 80);
   124  auto hp_double = flatbuffers::GetAnyFieldF(root, hp_field);
   125  TEST_EQ(hp_double, 80.0);
   126  auto hp_string = flatbuffers::GetAnyFieldS(root, hp_field, &schema);
   127  TEST_EQ_STR(hp_string.c_str(), "80");
   128
   129  // Get struct field through reflection
   130  auto pos_struct = flatbuffers::GetFieldStruct(root, *pos_field_ptr);
   131  TEST_NOTNULL(pos_struct);
   132  TEST_EQ(flatbuffers::GetAnyFieldF(*pos_struct,
   133                                    *pos_table_ptr->fields()->LookupByKey("z")),
   134          3.0f);
   135
   136  auto test3_field = pos_table_ptr->fields()->LookupByKey("test3");
   137  auto test3_struct = flatbuffers::GetFieldStruct(*pos_struct, *test3_field);
   138  TEST_NOTNULL(test3_struct);
   139  auto test3_object = schema.objects()->Get(test3_field->type()->index());
   140
   141  TEST_EQ(flatbuffers::GetAnyFieldF(*test3_struct,
   142                                    *test3_object->fields()->LookupByKey("a")),
   143          10);
   144
   145  // We can also modify it.
   146  flatbuffers::SetField<uint16_t>(&root, hp_field, 200);
   147  hp = flatbuffers::GetFieldI<uint16_t>(root, hp_field);
   148  TEST_EQ(hp, 200);
   149
   150  // We can also set fields generically:
   151  flatbuffers::SetAnyFieldI(&root, hp_field, 300);
   152  hp_int64 = flatbuffers::GetAnyFieldI(root, hp_field);
   153  TEST_EQ(hp_int64, 300);
   154  flatbuffers::SetAnyFieldF(&root, hp_field, 300.5);
   155  hp_int64 = flatbuffers::GetAnyFieldI(root, hp_field);
   156  TEST_EQ(hp_int64, 300);
   157  flatbuffers::SetAnyFieldS(&root, hp_field, "300");
   158  hp_int64 = flatbuffers::GetAnyFieldI(root, hp_field);
   159  TEST_EQ(hp_int64, 300);
   160
   161  // Test buffer is valid after the modifications
   162  TEST_EQ(flatbuffers::Verify(schema, *schema.root_table(), flatbuf, length),
   163          true);
   164
   165  // Reset it, for further tests.
   166  flatbuffers::SetField<uint16_t>(&root, hp_field, 80);
   167
   168  // More advanced functionality: changing the size of items in-line!
   169  // First we put the FlatBuffer inside an std::vector.
   170  std::vector<uint8_t> resizingbuf(flatbuf, flatbuf + length);
   171  // Find the field we want to modify.
   172  auto &name_field = *fields->LookupByKey("name");
   173  // Get the root.
   174  // This time we wrap the result from GetAnyRoot in a smartpointer that
   175  // will keep rroot valid as resizingbuf resizes.
   176  auto rroot = flatbuffers::piv(flatbuffers::GetAnyRoot(resizingbuf.data()),
   177                                resizingbuf);
   178  SetString(schema, "totally new string", GetFieldS(**rroot, name_field),
   179            &resizingbuf);
   180  // Here resizingbuf has changed, but rroot is still valid.
   181  TEST_EQ_STR(GetFieldS(**rroot, name_field)->c_str(), "totally new string");
   182  // Now lets extend a vector by 100 elements (10 -> 110).
   183  auto &inventory_field = *fields->LookupByKey("inventory");
   184  auto rinventory = flatbuffers::piv(
   185      flatbuffers::GetFieldV<uint8_t>(**rroot, inventory_field), resizingbuf);
   186  flatbuffers::ResizeVector<uint8_t>(schema, 110, 50, *rinventory,
   187                                     &resizingbuf);
   188  // rinventory still valid, so lets read from it.
   189  TEST_EQ(rinventory->Get(10), 50);
   190
   191  // For reflection uses not covered already, there is a more powerful way:
   192  // we can simply generate whatever object we want to add/modify in a
   193  // FlatBuffer of its own, then add that to an existing FlatBuffer:
   194  // As an example, let's add a string to an array of strings.
   195  // First, find our field:
   196  auto &testarrayofstring_field = *fields->LookupByKey("testarrayofstring");
   197  // Find the vector value:
   198  auto rtestarrayofstring = flatbuffers::piv(
   199      flatbuffers::GetFieldV<flatbuffers::Offset<flatbuffers::String>>(
   200          **rroot, testarrayofstring_field),
   201      resizingbuf);
   202  // It's a vector of 2 strings, to which we add one more, initialized to
   203  // offset 0.
   204  flatbuffers::ResizeVector<flatbuffers::Offset<flatbuffers::String>>(
   205      schema, 3, 0, *rtestarrayofstring, &resizingbuf);
   206  // Here we just create a buffer that contans a single string, but this
   207  // could also be any complex set of tables and other values.
   208  flatbuffers::FlatBufferBuilder stringfbb;
   209  stringfbb.Finish(stringfbb.CreateString("hank"));
   210  // Add the contents of it to our existing FlatBuffer.
   211  // We do this last, so the pointer doesn't get invalidated (since it is
   212  // at the end of the buffer):
   213  auto string_ptr = flatbuffers::AddFlatBuffer(
   214      resizingbuf, stringfbb.GetBufferPointer(), stringfbb.GetSize());
   215  // Finally, set the new value in the vector.
   216  rtestarrayofstring->MutateOffset(2, string_ptr);
   217  TEST_EQ_STR(rtestarrayofstring->Get(0)->c_str(), "bob");
   218  TEST_EQ_STR(rtestarrayofstring->Get(2)->c_str(), "hank");
   219  // Test integrity of all resize operations above.
   220  flatbuffers::Verifier resize_verifier(
   221      reinterpret_cast<const uint8_t *>(resizingbuf.data()),
   222      resizingbuf.size());
   223  TEST_EQ(VerifyMonsterBuffer(resize_verifier), true);
   224
   225  // Test buffer is valid using reflection as well
   226  TEST_EQ(flatbuffers::Verify(schema, *schema.root_table(), resizingbuf.data(),
   227                              resizingbuf.size()),
   228          true);
   229
   230  // As an additional test, also set it on the name field.
   231  // Note: unlike the name change above, this just overwrites the offset,
   232  // rather than changing the string in-place.
   233  SetFieldT(*rroot, name_field, string_ptr);
   234  TEST_EQ_STR(GetFieldS(**rroot, name_field)->c_str(), "hank");
   235
   236  // Using reflection, rather than mutating binary FlatBuffers, we can also copy
   237  // tables and other things out of other FlatBuffers into a FlatBufferBuilder,
   238  // either part or whole.
   239  flatbuffers::FlatBufferBuilder fbb;
   240  auto root_offset = flatbuffers::CopyTable(
   241      fbb, schema, *root_table, *flatbuffers::GetAnyRoot(flatbuf), true);
   242  fbb.Finish(root_offset, MonsterIdentifier());
   243  // Test that it was copied correctly:
   244  AccessFlatBufferTest(fbb.GetBufferPointer(), fbb.GetSize());
   245
   246  // Test buffer is valid using reflection as well
   247  TEST_EQ(flatbuffers::Verify(schema, *schema.root_table(),
   248                              fbb.GetBufferPointer(), fbb.GetSize()),
   249          true);
   250}
   251
   252void MiniReflectFlatBuffersTest(uint8_t *flatbuf) {
   253  auto s =
   254      flatbuffers::FlatBufferToString(flatbuf, Monster::MiniReflectTypeTable());
   255  TEST_EQ_STR(
   256      s.c_str(),
   257      "{ "
   258      "pos: { x: 1.0, y: 2.0, z: 3.0, test1: 0.0, test2: Red, test3: "
   259      "{ a: 10, b: 20 } }, "
   260      "hp: 80, "
   261      "name: \"MyMonster\", "
   262      "inventory: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ], "
   263      "test_type: Monster, "
   264      "test: { name: \"Fred\" }, "
   265      "test4: [ { a: 10, b: 20 }, { a: 30, b: 40 } ], "
   266      "testarrayofstring: [ \"bob\", \"fred\", \"bob\", \"fred\" ], "
   267      "testarrayoftables: [ { hp: 1000, name: \"Barney\" }, { name: \"Fred\" "
   268      "}, "
   269      "{ name: \"Wilma\" } ], "
   270      // TODO(wvo): should really print this nested buffer correctly.
   271      "testnestedflatbuffer: [ 124, 0, 0, 0, 77, 79, 78, 83, 0, 0, 114, 0, 16, "
   272      "0, 0, 0, 4, 0, 6, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "
   273      "0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "
   274      "0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "
   275      "0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "
   276      "0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 114, 0, 0, 0, 0, 0, 0, 0, "
   277      "8, 0, 0, 0, 0, 0, 192, 127, 13, 0, 0, 0, 78, 101, 115, 116, 101, 100, "
   278      "77, 111, 110, 115, 116, 101, 114, 0, 0, 0 ], "
   279      "testarrayofstring2: [ \"jane\", \"mary\" ], "
   280      "testarrayofsortedstruct: [ { id: 0, distance: 0 }, "
   281      "{ id: 2, distance: 20 }, { id: 3, distance: 30 }, "
   282      "{ id: 4, distance: 40 } ], "
   283      "flex: [ 210, 4, 5, 2 ], "
   284      "test5: [ { a: 10, b: 20 }, { a: 30, b: 40 } ], "
   285      "vector_of_enums: [ Blue, Green ], "
   286      "scalar_key_sorted_tables: [ { id: \"miss\" } ], "
   287      "nan_default: nan "
   288      "}");
   289
   290  Test test(16, 32);
   291  Vec3 vec(1, 2, 3, 1.5, Color_Red, test);
   292  flatbuffers::FlatBufferBuilder vec_builder;
   293  vec_builder.Finish(vec_builder.CreateStruct(vec));
   294  auto vec_buffer = vec_builder.Release();
   295  auto vec_str = flatbuffers::FlatBufferToString(vec_buffer.data(),
   296                                                 Vec3::MiniReflectTypeTable());
   297  TEST_EQ_STR(vec_str.c_str(),
   298              "{ x: 1.0, y: 2.0, z: 3.0, test1: 1.5, test2: Red, test3: { a: "
   299              "16, b: 32 } }");
   300}
   301
   302void MiniReflectFixedLengthArrayTest() {
   303  // VS10 does not support typed enums, exclude from tests
   304#if !defined(_MSC_VER) || _MSC_VER >= 1700
   305  flatbuffers::FlatBufferBuilder fbb;
   306  MyGame::Example::ArrayStruct aStruct(2, 12, 1);
   307  auto aTable = MyGame::Example::CreateArrayTable(fbb, &aStruct);
   308  fbb.Finish(aTable);
   309
   310  auto flatbuf = fbb.Release();
   311  auto s = flatbuffers::FlatBufferToString(
   312      flatbuf.data(), MyGame::Example::ArrayTableTypeTable());
   313  TEST_EQ_STR(
   314      "{ "
   315      "a: { a: 2.0, "
   316      "b: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], "
   317      "c: 12, "
   318      "d: [ { a: [ 0, 0 ], b: A, c: [ A, A ], d: [ 0, 0 ] }, "
   319      "{ a: [ 0, 0 ], b: A, c: [ A, A ], d: [ 0, 0 ] } ], "
   320      "e: 1, f: [ 0, 0 ] } "
   321      "}",
   322      s.c_str());
   323#endif
   324}
   325
   326}  // namespace tests
   327}  // namespace flatbuffers

View as plain text