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