summaryrefslogtreecommitdiff
path: root/tests/test.cpp
blob: f860897536946062570739c658811bcd0acfb5d9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
/*
 * Copyright 2014 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "flatbuffers/flatbuffers.h"
#include "flatbuffers/idl.h"
#include "flatbuffers/util.h"

#include "monster_test_generated.h"

#include <random>

using namespace MyGame::Example;

#ifdef __ANDROID__
  #include <android/log.h>
  #define TEST_OUTPUT_LINE(...) \
    __android_log_print(ANDROID_LOG_INFO, "FlatBuffers", __VA_ARGS__)
#else
  #define TEST_OUTPUT_LINE(...) \
    { printf(__VA_ARGS__); printf("\n"); }
#endif

int testing_fails = 0;

template<typename T, typename U>
void TestEq(T expval, U val, const char *exp, const char *file, int line) {
  if (expval != val) {
    auto expval_str = flatbuffers::NumToString(expval);
    auto val_str = flatbuffers::NumToString(val);
    TEST_OUTPUT_LINE("TEST FAILED: %s:%d, %s (%s) != %s", file, line,
                     exp, expval_str.c_str(), val_str.c_str());
    assert(0);
    testing_fails++;
  }
}

#define TEST_EQ(exp, val) TestEq(exp,         val,   #exp, __FILE__, __LINE__)
#define TEST_NOTNULL(exp) TestEq(exp == NULL, false, #exp, __FILE__, __LINE__)

// Include simple random number generator to ensure results will be the
// same cross platform.
// http://en.wikipedia.org/wiki/Park%E2%80%93Miller_random_number_generator
uint32_t lcg_seed = 48271;
uint32_t lcg_rand() {
    return lcg_seed = ((uint64_t)lcg_seed * 279470273UL) % 4294967291UL;
}
void lcg_reset() { lcg_seed = 48271; }

// example of how to build up a serialized buffer algorithmically:
std::string CreateFlatBufferTest() {
  flatbuffers::FlatBufferBuilder builder;

  auto vec = Vec3(1, 2, 3, 0, 0, Test(10, 20));

  auto name = builder.CreateString("MyMonster");

  unsigned char inv_data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  auto inventory = builder.CreateVector(inv_data, 10);

  Test tests[] = { Test(10, 20), Test(30, 40) };
  auto testv = builder.CreateVectorOfStructs(tests, 2);

  // create monster with very few fields set:
  // (same functionality as CreateMonster below, but sets fields manually)
  MonsterBuilder mb(builder);
  mb.add_hp(20);
  auto mloc2 = mb.Finish();

  // Create an array of strings:
  flatbuffers::Offset<flatbuffers::String> strings[2];
  strings[0] = builder.CreateString("bob");
  strings[1] = builder.CreateString("fred");
  auto vecofstrings = builder.CreateVector(strings, 2);

  // Create an array of tables:
  auto vecoftables = builder.CreateVector(&mloc2, 1);

  // shortcut for creating monster with all fields set:
  auto mloc = CreateMonster(builder, &vec, 150, 80, name, inventory, Color_Blue,
                            Any_Monster, mloc2.Union(), // Store a union.
                            testv, vecofstrings, vecoftables, 0);

  builder.Finish(mloc);

  #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

  // return the buffer for the caller to use.
  return std::string(reinterpret_cast<const char *>(builder.GetBufferPointer()),
                     builder.GetSize());
}

//  example of accessing a buffer loaded in memory:
void AccessFlatBufferTest(const std::string &flatbuf) {

  // First, verify the buffers integrity (optional)
  flatbuffers::Verifier verifier(
    reinterpret_cast<const uint8_t *>(flatbuf.c_str()),
    flatbuf.length());
  TEST_EQ(VerifyMonsterBuffer(verifier), true);

  // Access the buffer from the root.
  auto monster = GetMonster(flatbuf.c_str());

  TEST_EQ(monster->hp(), 80);
  TEST_EQ(monster->mana(), 150);  // default
  TEST_EQ(strcmp(monster->name()->c_str(), "MyMonster"), 0);
  // 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_NOTNULL(inventory);
  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()]);

  // 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(monster2->hp(), 20);

  // Example of accessing a vector of strings:
  auto vecofstrings = monster->testarrayofstring();
  TEST_EQ(vecofstrings->Length(), 2U);
  TEST_EQ(strcmp(vecofstrings->Get(0)->c_str(), "bob"), 0);
  TEST_EQ(strcmp(vecofstrings->Get(1)->c_str(), "fred"), 0);

  // Example of accessing a vector of tables:
  auto vecoftables = monster->testarrayoftables();
  TEST_EQ(vecoftables->Length(), 1U);
  for (auto it = vecoftables->begin(); it != vecoftables->end(); ++it)
    TEST_EQ(it->hp(), 20);

  // 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);

  auto tests = monster->test4();
  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.
  }

}

// example of parsing text straight into a buffer, and generating
// text back from it:
void ParseAndGenerateTextTest() {

  // load FlatBuffer schema (.fbs) and JSON from disk
  std::string schemafile;
  std::string jsonfile;
  TEST_EQ(flatbuffers::LoadFile(
    "tests/monster_test.fbs", false, &schemafile), true);
  TEST_EQ(flatbuffers::LoadFile(
    "tests/monsterdata_test.golden", false, &jsonfile), true);

  // parse schema first, so we can use it to parse the data after
  flatbuffers::Parser parser;
  TEST_EQ(parser.Parse(schemafile.c_str()), true);
  TEST_EQ(parser.Parse(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);

  // to ensure it is correct, we now generate text back from the binary,
  // and compare the two:
  std::string jsongen;
  flatbuffers::GeneratorOptions opts;
  GenerateText(parser, parser.builder_.GetBufferPointer(), opts, &jsongen);

  if (jsongen != jsonfile) {
    printf("%s----------------\n%s", jsongen.c_str(), jsonfile.c_str());
    TEST_NOTNULL(NULL);
  }
}

template<typename T> void CompareTableFieldValue(flatbuffers::Table *table,
                                                 flatbuffers::voffset_t voffset,
                                                 T val) {
  T read = table->GetField(voffset, static_cast<T>(0));
  TEST_EQ(read, val);
}

// Low level stress/fuzz test: serialize/deserialize a variety of
// different kinds of data in different combinations
void FuzzTest1() {

  // Values we're testing against: chosen to ensure no bits get chopped
  // off anywhere, and also be different from eachother.
  const uint8_t  bool_val   = true;
  const int8_t   char_val   = -127;  // 0x81
  const uint8_t  uchar_val  = 0xFF;
  const int16_t  short_val  = -32222; // 0x8222;
  const uint16_t ushort_val = 0xFEEE;
  const int32_t  int_val    = 0x83333333;
  const uint32_t uint_val   = 0xFDDDDDDD;
  const int64_t  long_val   = 0x8444444444444444;
  const uint64_t ulong_val  = 0xFCCCCCCCCCCCCCCC;
  const float    float_val  = 3.14159f;
  const double   double_val = 3.14159265359;

  const int test_values_max = 11;
  const flatbuffers::voffset_t fields_per_object = 4;
  const int num_fuzz_objects = 10000;  // The higher, the more thorough :)

  flatbuffers::FlatBufferBuilder builder;

  lcg_reset();  // Keep it deterministic.

  flatbuffers::uoffset_t objects[num_fuzz_objects];

  // Generate num_fuzz_objects random objects each consisting of
  // fields_per_object fields, each of a random type.
  for (int i = 0; i < num_fuzz_objects; i++) {
    auto start = builder.StartTable();
    for (flatbuffers::voffset_t f = 0; f < fields_per_object; f++) {
      int choice = lcg_rand() % test_values_max;
      auto off = flatbuffers::FieldIndexToOffset(f);
      switch (choice) {
        case 0:  builder.AddElement<uint8_t >(off, bool_val,   0); break;
        case 1:  builder.AddElement<int8_t  >(off, char_val,   0); break;
        case 2:  builder.AddElement<uint8_t >(off, uchar_val,  0); break;
        case 3:  builder.AddElement<int16_t >(off, short_val,  0); break;
        case 4:  builder.AddElement<uint16_t>(off, ushort_val, 0); break;
        case 5:  builder.AddElement<int32_t >(off, int_val,    0); break;
        case 6:  builder.AddElement<uint32_t>(off, uint_val,   0); break;
        case 7:  builder.AddElement<int64_t >(off, long_val,   0); break;
        case 8:  builder.AddElement<uint64_t>(off, ulong_val,  0); break;
        case 9:  builder.AddElement<float   >(off, float_val,  0); break;
        case 10: builder.AddElement<double  >(off, double_val, 0); break;
      }
    }
    objects[i] = builder.EndTable(start, fields_per_object);
  }
  builder.PreAlign<flatbuffers::largest_scalar_t>(0);  // Align whole buffer.

  lcg_reset();  // Reset.

  uint8_t *eob = builder.GetBufferPointer() + builder.GetSize();

  // Test that all objects we generated are readable and return the
  // expected values. We generate random objects in the same order
  // so this is deterministic.
  for (int i = 0; i < num_fuzz_objects; i++) {
    auto table = reinterpret_cast<flatbuffers::Table *>(eob - objects[i]);
    for (flatbuffers::voffset_t f = 0; f < fields_per_object; f++) {
      int choice = lcg_rand() % test_values_max;
      flatbuffers::voffset_t off = flatbuffers::FieldIndexToOffset(f);
      switch (choice) {
        case 0:  CompareTableFieldValue(table, off, bool_val  ); break;
        case 1:  CompareTableFieldValue(table, off, char_val  ); break;
        case 2:  CompareTableFieldValue(table, off, uchar_val ); break;
        case 3:  CompareTableFieldValue(table, off, short_val ); break;
        case 4:  CompareTableFieldValue(table, off, ushort_val); break;
        case 5:  CompareTableFieldValue(table, off, int_val   ); break;
        case 6:  CompareTableFieldValue(table, off, uint_val  ); break;
        case 7:  CompareTableFieldValue(table, off, long_val  ); break;
        case 8:  CompareTableFieldValue(table, off, ulong_val ); break;
        case 9:  CompareTableFieldValue(table, off, float_val ); break;
        case 10: CompareTableFieldValue(table, off, double_val); break;
      }
    }
  }
}

// High level stress/fuzz test: generate a big schema and
// matching json data in random combinations, then parse both,
// generate json back from the binary, and compare with the original.
void FuzzTest2() {
  lcg_reset();  // Keep it deterministic.

  const int num_definitions = 30;
  const int num_struct_definitions = 5;  // Subset of num_definitions.
  const int fields_per_definition = 15;
  const int instances_per_definition = 5;

  std::string schema = "namespace test;\n\n";

  struct RndDef {
    std::string instances[instances_per_definition];
  };

  RndDef definitions[num_definitions];

  // We are going to generate num_definitions, the first
  // num_struct_definitions will be structs, the rest tables. For each
  // generate random fields, some of which may be struct/table types
  // referring to previously generated structs/tables.
  // Simultanenously, we generate instances_per_definition JSON data
  // definitions, which will have identical structure to the schema
  // being generated. We generate multiple instances such that when creating
  // hierarchy, we get some variety by picking one randomly.
  for (int definition = 0; definition < num_definitions; definition++) {
    // Since we're generating schema & and corresponding data in tandem,
    // this convenience function adds strings to both at once.
    auto AddToSchemaAndInstances = [&](const char *schema_add,
                                       const char *instance_add) {
      schema += schema_add;
      for (int i = 0; i < instances_per_definition; i++)
        definitions[definition].instances[i] += instance_add;
    };
    // Generate a default type if we can't generate something else.
    auto Dummy = [&]() { AddToSchemaAndInstances("byte", "1"); };

    std::string definition_name = "D" + flatbuffers::NumToString(definition);

    bool is_struct = definition < num_struct_definitions;

    AddToSchemaAndInstances(
      ((is_struct ? "struct " : "table ") + definition_name + " {\n").c_str(),
      "{\n");

    for (int field = 0; field < fields_per_definition; field++) {
      std::string field_name = "f" + flatbuffers::NumToString(field);
      AddToSchemaAndInstances(("  " + field_name + ":").c_str(),
                              (field_name + ": ").c_str());
      // Pick random type:
      int base_type = lcg_rand() % (flatbuffers::BASE_TYPE_UNION + 1);
      switch (base_type) {
        case flatbuffers::BASE_TYPE_STRING:
          if (is_struct) {
            Dummy();  // No strings in structs,
          } else {
            AddToSchemaAndInstances("string", "\"hi\"");
          }
          break;
        case flatbuffers::BASE_TYPE_NONE:
        case flatbuffers::BASE_TYPE_UTYPE:
        case flatbuffers::BASE_TYPE_STRUCT:
        case flatbuffers::BASE_TYPE_UNION:
        case flatbuffers::BASE_TYPE_VECTOR:
          if (definition) {
            // Pick a random previous definition and random data instance of
            // that definition.
            int defref = lcg_rand() % definition;
            int instance = lcg_rand() % instances_per_definition;
            AddToSchemaAndInstances(
              ("D" + flatbuffers::NumToString(defref)).c_str(),
              definitions[defref].instances[instance].c_str());
          } else {
            // If this is the first definition, we have no definition we can
            // refer to.
            Dummy();
          }
          break;
        default:
          // All the scalar types.
          AddToSchemaAndInstances(
            flatbuffers::kTypeNames[base_type],
            flatbuffers::NumToString(lcg_rand() % 128).c_str());
      }
      AddToSchemaAndInstances(
        ";\n",
        field == fields_per_definition - 1 ? "\n" : ",\n");
    }
    AddToSchemaAndInstances("}\n\n", "}");
  }

  schema += "root_type D" + flatbuffers::NumToString(num_definitions - 1);
  schema += ";\n";

  flatbuffers::Parser parser;

  // Will not compare against the original if we don't write defaults
  parser.builder_.ForceDefaults(true);

  // Parse the schema, parse the generated data, then generate text back
  // from the binary and compare against the original.
  TEST_EQ(parser.Parse(schema.c_str()), true);

  const std::string &json =
    definitions[num_definitions - 1].instances[0] + "\n";

  TEST_EQ(parser.Parse(json.c_str()), true);

  std::string jsongen;
  flatbuffers::GeneratorOptions opts;
  opts.indent_step = 0;
  GenerateText(parser, parser.builder_.GetBufferPointer(), opts, &jsongen);

  if (jsongen != json) {
    // These strings are larger than a megabyte, so we show the bytes around
    // the first bytes that are different rather than the whole string.
    size_t len = std::min(json.length(), jsongen.length());
    for (size_t i = 0; i < len; i++) {
      if (json[i] != jsongen[i]) {
        i -= std::min(static_cast<size_t>(10), i); // show some context;
        size_t end = std::min(len, i + 20);
        for (; i < end; i++)
          printf("at %d: found \"%c\", expected \"%c\"\n",
               static_cast<int>(i), jsongen[i], json[i]);
        break;
      }
    }
    TEST_NOTNULL(NULL);
  }

  printf("%dk schema tested with %dk of json\n",
         static_cast<int>(schema.length() / 1024),
         static_cast<int>(json.length() / 1024));
}

// Test that parser errors are actually generated.
void TestError(const char *src, const char *error_substr) {
  flatbuffers::Parser parser;
  TEST_EQ(parser.Parse(src), false);  // Must signal error
  // Must be the error we're expecting
  TEST_NOTNULL(strstr(parser.error_.c_str(), error_substr));
}

// Test that parsing errors occur as we'd expect.
// Also useful for coverage, making sure these paths are run.
void ErrorTest() {
  // In order they appear in idl_parser.cpp
  TestError("table X { Y:byte; } root_type X; { Y: 999 }", "bit field");
  TestError(".0", "floating point");
  TestError("\"\0", "illegal");
  TestError("\"\\q", "escape code");
  TestError("table ///", "documentation");
  TestError("@", "illegal");
  TestError("table 1", "expecting");
  TestError("table X { Y:[[int]]; }", "nested vector");
  TestError("union Z { X } table X { Y:[Z]; }", "vector of union");
  TestError("table X { Y:1; }", "illegal type");
  TestError("table X { Y:int; Y:int; }", "field already");
  TestError("struct X { Y:string; }", "only scalar");
  TestError("struct X { Y:int (deprecated); }", "deprecate");
  TestError("union Z { X } table X { Y:Z; } root_type X; { Y: {",
            "missing type field");
  TestError("union Z { X } table X { Y:Z; } root_type X; { Y_type: 99, Y: {",
            "type id");
  TestError("table X { Y:int; } root_type X; { Z:", "unknown field");
  TestError("struct X { Y:int; Z:int; } table W { V:X; } root_type W; "
            "{ V:{ Y:1 } }", "incomplete");
  TestError("enum E:byte { A } table X { Y:E; } root_type X; { Y:U }",
            "unknown enum value");
  TestError("table X { Y:byte; } root_type X; { Y:; }", "starting");
  TestError("enum X:byte { Y } enum X {", "enum already");
  TestError("enum X:float {}", "underlying");
  TestError("enum X:byte { Y, Y }", "value already");
  TestError("enum X:byte { Y=2, Z=1 }", "ascending");
  TestError("enum X:byte (bit_flags) { Y=8 }", "bit flag out");
  TestError("table X { Y:int; } table X {", "datatype already");
  TestError("struct X (force_align: 7) { Y:int; }", "force_align");
  TestError("{}", "no root");
  TestError("table X { Y:byte; } root_type X; { Y:1 } { Y:1 }", "one json");
  TestError("root_type X;", "unknown root");
  TestError("struct X { Y:int; } root_type X;", "a table");
  TestError("union X { Y }", "referenced");
  TestError("union Z { X } struct X { Y:int; }", "only tables");
}

// Additional parser testing not covered elsewhere.
void ScientificTest() {
  flatbuffers::Parser parser;

  // Simple schema.
  TEST_EQ(parser.Parse("table X { Y:float; } root_type X;"), true);

  // Test scientific notation numbers.
  TEST_EQ(parser.Parse("{ Y:0.0314159e+2 }"), true);
  auto root = flatbuffers::GetRoot<float>(parser.builder_.GetBufferPointer());
  // root will point to the table, which is a 32bit vtable offset followed
  // by a float:
  TEST_EQ(fabs(root[1] - 3.14159) < 0.001, true);
}

void EnumStringsTest() {
  flatbuffers::Parser parser1;
  TEST_EQ(parser1.Parse("enum E:byte { A, B, C } table T { F:[E]; }"
                        "root_type T;"
                        "{ F:[ A, B, \"C\", \"A B C\" ] }"), true);
  flatbuffers::Parser parser2;
  TEST_EQ(parser2.Parse("enum E:byte { A, B, C } table T { F:[int]; }"
                        "root_type T;"
                        "{ F:[ \"E.C\", \"E.A E.B E.C\" ] }"), true);
}



int main(int /*argc*/, const char * /*argv*/[]) {
  // Run our various test suites:

  auto flatbuf = CreateFlatBufferTest();
  AccessFlatBufferTest(flatbuf);

  #ifndef __ANDROID__  // requires file access
  ParseAndGenerateTextTest();
  #endif

  FuzzTest1();
  FuzzTest2();

  ErrorTest();
  ScientificTest();
  EnumStringsTest();

  if (!testing_fails) {
    TEST_OUTPUT_LINE("ALL TESTS PASSED");
    return 0;
  } else {
    TEST_OUTPUT_LINE("%d FAILED TESTS", testing_fails);
    return 1;
  }
}