blob: 9f9268951947b22e97bebccc27ae699eedd3e0d1 [file] [log] [blame]
Austin Schuh2dd86a92022-09-14 21:19:23 -07001#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
11namespace flatbuffers {
12namespace tests {
13
14using namespace MyGame::Example;
15
16void 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
250void 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 Kuszmaul3b15b0c2022-11-08 14:03:16 -0800269 "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 Schuh2dd86a92022-09-14 21:19:23 -0700277 "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 Kuszmaul3b15b0c2022-11-08 14:03:16 -0800284 "scalar_key_sorted_tables: [ { id: \"miss\" } ], "
285 "nan_default: nan "
Austin Schuh2dd86a92022-09-14 21:19:23 -0700286 "}");
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
300void 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 Kuszmaul3b15b0c2022-11-08 14:03:16 -0800325} // namespace flatbuffers