Initial commit of the FlatBuffers code.

Change-Id: I4c9f0f722490b374257adb3fec63e44ae93da920
Tested: using VS2010 / Xcode / gcc on Linux.
This commit is contained in:
Wouter van Oortmerssen
2014-01-27 16:52:49 -08:00
parent c1b43e22b0
commit 26a30738a4
102 changed files with 12647 additions and 0 deletions

21
tests/JavaTest.bat Executable file
View File

@@ -0,0 +1,21 @@
@echo off
rem Copyright 2014 Google Inc. All rights reserved.
rem
rem Licensed under the Apache License, Version 2.0 (the "License");
rem you may not use this file except in compliance with the License.
rem You may obtain a copy of the License at
rem
rem http://www.apache.org/licenses/LICENSE-2.0
rem
rem Unless required by applicable law or agreed to in writing, software
rem distributed under the License is distributed on an "AS IS" BASIS,
rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
rem See the License for the specific language governing permissions and
rem limitations under the License.
rem Compile then run the Java test.
set batch_file_dir=%~d0%~p0
javac -classpath %batch_file_dir%\..\java;%batch_file_dir% JavaTest.java
java -classpath %batch_file_dir%\..\java;%batch_file_dir% JavaTest

145
tests/JavaTest.java Executable file
View File

@@ -0,0 +1,145 @@
/*
* 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.
*/
import java.io.*;
import java.nio.ByteBuffer;
import MyGame.Example.*;
import flatbuffers.FlatBufferBuilder;
class JavaTest {
public static void main(String[] args) {
// First, let's test reading a FlatBuffer generated by C++ code:
// This file was generated from monsterdata_test.json
byte[] data = null;
File file = new File("monsterdata_test_wire.bin");
RandomAccessFile f = null;
try {
f = new RandomAccessFile(file, "r");
data = new byte[(int)f.length()];
f.readFully(data);
f.close();
} catch(java.io.IOException e) {
System.out.println("FlatBuffers test: couldn't read file");
return;
}
// Now test it:
ByteBuffer bb = ByteBuffer.wrap(data);
TestBuffer(bb, 0);
// Second, let's create a FlatBuffer from scratch in Java, and test it also.
// We set up the same values as monsterdata.json:
FlatBufferBuilder fbb = new FlatBufferBuilder(1024);
int str = fbb.createString("MyMonster");
Monster.startInventoryVector(fbb, 5);
for (byte i = 4; i >=0; i--) fbb.addByte(i);
int inv = fbb.endVector();
Monster.startMonster(fbb);
Monster.addHp(fbb, (short)20);
int mon2 = Monster.endMonster(fbb);
Monster.startTest4Vector(fbb, 2);
Test.createTest(fbb, (short)10, (byte)20);
Test.createTest(fbb, (short)30, (byte)40);
int test4 = fbb.endVector();
Monster.startMonster(fbb);
Monster.addPos(fbb, Vec3.createVec3(fbb, 1.0f, 2.0f, 3.0f, 3.0,
(byte)4, (short)5, (byte)6));
Monster.addHp(fbb, (short)80);
Monster.addName(fbb, str);
Monster.addInventory(fbb, inv);
Monster.addTestType(fbb, (byte)1);
Monster.addTest(fbb, mon2);
Monster.addTest4(fbb, test4);
int mon = Monster.endMonster(fbb);
fbb.finish(mon);
// Write the result to a file for debugging purposes:
// Note that the binaries are not necessarily identical, since the JSON
// parser may serialize in a slightly different order than the above
// Java code. They are functionally equivalent though.
try {
DataOutputStream os = new DataOutputStream(new FileOutputStream(
"monsterdata_java_wire.bin"));
os.write(fbb.dataBuffer().array(), fbb.dataStart(), fbb.offset());
os.close();
} catch(java.io.IOException e) {
System.out.println("FlatBuffers test: couldn't write file");
return;
}
// Test it:
TestBuffer(fbb.dataBuffer(), fbb.dataStart());
System.out.println("FlatBuffers test: completed successfully");
}
static void TestBuffer(ByteBuffer bb, int start) {
Monster monster = Monster.getRootAsMonster(bb, start);
TestEq(monster.hp(), (short)80);
TestEq(monster.mana(), (short)150); // default
TestEq(monster.name(), "MyMonster");
// monster.friendly() // can't access, deprecated
Vec3 pos = monster.pos();
TestEq(pos.x(), 1.0f);
TestEq(pos.y(), 2.0f);
TestEq(pos.z(), 3.0f);
TestEq(pos.test1(), 3.0);
TestEq(pos.test2(), (byte)4);
Test t = pos.test3();
TestEq(t.a(), (short)5);
TestEq(t.b(), (byte)6);
TestEq(monster.testType(), (byte)Any.Monster);
Monster monster2 = new Monster();
TestEq(monster.test(monster2) != null, true);
TestEq(monster2.hp(), (short)20);
TestEq(monster.inventoryLength(), 5);
int invsum = 0;
for (int i = 0; i < monster.inventoryLength(); i++)
invsum += monster.inventory(i);
TestEq(invsum, 10);
Test test_0 = monster.test4(0);
Test test_1 = monster.test4(1);
TestEq(monster.test4Length(), 2);
TestEq(test_0.a() + test_0.b() + test_1.a() + test_1.b(), 100);
}
static <T> void TestEq(T a, T b) {
if (!a.equals(b)) {
System.out.println("" + a.getClass().getName() + " " + b.getClass().getName());
System.out.println("FlatBuffers test FAILED: \'" + a + "\' != \'" + b + "\'");
assert false;
System.exit(1);
}
}
}

14
tests/MyGame/Example/Any.java Executable file
View File

@@ -0,0 +1,14 @@
// automatically generated, do not modify
package MyGame.Example;
import java.nio.*;
import java.lang.*;
import java.util.*;
import flatbuffers.*;
public class Any {
public static final byte NONE = 0;
public static final byte Monster = 1;
};

15
tests/MyGame/Example/Color.java Executable file
View File

@@ -0,0 +1,15 @@
// automatically generated, do not modify
package MyGame.Example;
import java.nio.*;
import java.lang.*;
import java.util.*;
import flatbuffers.*;
public class Color {
public static final byte Red = 0;
public static final byte Green = 1;
public static final byte Blue = 2;
};

View File

@@ -0,0 +1,42 @@
// automatically generated, do not modify
package MyGame.Example;
import java.nio.*;
import java.lang.*;
import java.util.*;
import flatbuffers.*;
public class Monster extends Table {
public static Monster getRootAsMonster(ByteBuffer _bb, int offset) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (new Monster()).__init(_bb.getInt(offset) + offset, _bb); }
public Monster __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; return this; }
public Vec3 pos() { return pos(new Vec3()); }
public Vec3 pos(Vec3 obj) { int o = __offset(4); return o != 0 ? obj.__init(o + bb_pos, bb) : null; }
public short mana() { int o = __offset(6); return o != 0 ? bb.getShort(o + bb_pos) : 150; }
public short hp() { int o = __offset(8); return o != 0 ? bb.getShort(o + bb_pos) : 100; }
public String name() { int o = __offset(10); return o != 0 ? __string(o) : null; }
public byte inventory(int j) { int o = __offset(14); return o != 0 ? bb.get(__vector(o) + j * 1) : 0; }
public int inventoryLength() { int o = __offset(14); return o != 0 ? __vector_len(o) : 0; }
/// an example documentation comment: this will end up in the generated code multiline too
public byte color() { int o = __offset(16); return o != 0 ? bb.get(o + bb_pos) : 2; }
public byte testType() { int o = __offset(18); return o != 0 ? bb.get(o + bb_pos) : 0; }
public Table test(Table obj) { int o = __offset(20); return o != 0 ? __union(obj, o) : null; }
public Test test4(int j) { return test4(new Test(), j); }
public Test test4(Test obj, int j) { int o = __offset(22); return o != 0 ? obj.__init(__vector(o) + j * 4, bb) : null; }
public int test4Length() { int o = __offset(22); return o != 0 ? __vector_len(o) : 0; }
public static void startMonster(FlatBufferBuilder builder) { builder.startObject(10); }
public static void addPos(FlatBufferBuilder builder, int pos) { builder.addStruct(0, pos, 0); }
public static void addMana(FlatBufferBuilder builder, short mana) { builder.addShort(1, mana, 150); }
public static void addHp(FlatBufferBuilder builder, short hp) { builder.addShort(2, hp, 100); }
public static void addName(FlatBufferBuilder builder, int name) { builder.addOffset(3, name, 0); }
public static void addInventory(FlatBufferBuilder builder, int inventory) { builder.addOffset(5, inventory, 0); }
public static void startInventoryVector(FlatBufferBuilder builder, int numElems) { builder.startVector(4, numElems); }
public static void addColor(FlatBufferBuilder builder, byte color) { builder.addByte(6, color, 2); }
public static void addTestType(FlatBufferBuilder builder, byte testType) { builder.addByte(7, testType, 0); }
public static void addTest(FlatBufferBuilder builder, int test) { builder.addOffset(8, test, 0); }
public static void addTest4(FlatBufferBuilder builder, int test4) { builder.addOffset(9, test4, 0); }
public static void startTest4Vector(FlatBufferBuilder builder, int numElems) { builder.startVector(4, numElems); }
public static int endMonster(FlatBufferBuilder builder) { return builder.endObject(); }
};

23
tests/MyGame/Example/Test.java Executable file
View File

@@ -0,0 +1,23 @@
// automatically generated, do not modify
package MyGame.Example;
import java.nio.*;
import java.lang.*;
import java.util.*;
import flatbuffers.*;
public class Test extends Struct {
public Test __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; return this; }
public short a() { return bb.getShort(bb_pos + 0); }
public byte b() { return bb.get(bb_pos + 2); }
public static int createTest(FlatBufferBuilder builder, short a, byte b) {
builder.prep(2, 0);
builder.pad(1);
builder.putByte(b);
builder.putShort(a);
return builder.offset();
}
};

37
tests/MyGame/Example/Vec3.java Executable file
View File

@@ -0,0 +1,37 @@
// automatically generated, do not modify
package MyGame.Example;
import java.nio.*;
import java.lang.*;
import java.util.*;
import flatbuffers.*;
public class Vec3 extends Struct {
public Vec3 __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; return this; }
public float x() { return bb.getFloat(bb_pos + 0); }
public float y() { return bb.getFloat(bb_pos + 4); }
public float z() { return bb.getFloat(bb_pos + 8); }
public double test1() { return bb.getDouble(bb_pos + 16); }
public byte test2() { return bb.get(bb_pos + 24); }
public Test test3() { return test3(new Test()); }
public Test test3(Test obj) { return obj.__init(bb_pos + 26, bb); }
public static int createVec3(FlatBufferBuilder builder, float x, float y, float z, double test1, byte test2, short Test_a, byte Test_b) {
builder.prep(16, 0);
builder.pad(2);
builder.prep(2, 0);
builder.pad(1);
builder.putByte(Test_b);
builder.putShort(Test_a);
builder.pad(1);
builder.putByte(test2);
builder.putDouble(test1);
builder.pad(4);
builder.putFloat(z);
builder.putFloat(y);
builder.putFloat(x);
return builder.offset();
}
};

34
tests/monster_test.fbs Executable file
View File

@@ -0,0 +1,34 @@
// example IDL file
namespace MyGame.Example;
enum Color:byte { Red = 0, Green, Blue = 2 }
union Any { Monster } // TODO: add more elements
struct Test { a:short; b:byte; }
struct Vec3 (force_align: 16) {
x:float;
y:float;
z:float;
test1:double;
test2:byte;
test3:Test;
}
table Monster {
pos:Vec3;
mana:short = 150;
hp:short = 100;
name:string;
friendly:bool = false (deprecated, priority: 1);
inventory:[ubyte];
/// an example documentation comment: this will end up in the generated code
/// multiline too
color:Color = Blue;
test:Any;
test4:[Test];
}
root_type Monster;

123
tests/monster_test_generated.h Executable file
View File

@@ -0,0 +1,123 @@
// automatically generated, do not modify
#include "flatbuffers/flatbuffers.h"
namespace MyGame {
namespace Example {
enum {
Color_Red = 0,
Color_Green = 1,
Color_Blue = 2,
};
inline const char **EnumNamesColor() {
static const char *names[] = { "Red", "Green", "Blue", nullptr };
return names;
}
inline const char *EnumNameColor(int e) { return EnumNamesColor()[e]; }
enum {
Any_NONE = 0,
Any_Monster = 1,
};
inline const char **EnumNamesAny() {
static const char *names[] = { "NONE", "Monster", nullptr };
return names;
}
inline const char *EnumNameAny(int e) { return EnumNamesAny()[e]; }
struct Test;
struct Vec3;
struct Monster;
MANUALLY_ALIGNED_STRUCT(2) Test {
private:
int16_t a_;
int8_t b_;
int8_t __padding0;
public:
Test(int16_t a, int8_t b)
: a_(flatbuffers::EndianScalar(a)), b_(flatbuffers::EndianScalar(b)), __padding0(0) {}
int16_t a() const { return flatbuffers::EndianScalar(a_); }
int8_t b() const { return flatbuffers::EndianScalar(b_); }
};
STRUCT_END(Test, 4);
MANUALLY_ALIGNED_STRUCT(16) Vec3 {
private:
float x_;
float y_;
float z_;
int32_t __padding0;
double test1_;
int8_t test2_;
int8_t __padding1;
Test test3_;
int16_t __padding2;
public:
Vec3(float x, float y, float z, double test1, int8_t test2, const Test &test3)
: x_(flatbuffers::EndianScalar(x)), y_(flatbuffers::EndianScalar(y)), z_(flatbuffers::EndianScalar(z)), __padding0(0), test1_(flatbuffers::EndianScalar(test1)), test2_(flatbuffers::EndianScalar(test2)), __padding1(0), test3_(test3), __padding2(0) {}
float x() const { return flatbuffers::EndianScalar(x_); }
float y() const { return flatbuffers::EndianScalar(y_); }
float z() const { return flatbuffers::EndianScalar(z_); }
double test1() const { return flatbuffers::EndianScalar(test1_); }
int8_t test2() const { return flatbuffers::EndianScalar(test2_); }
const Test &test3() const { return test3_; }
};
STRUCT_END(Vec3, 32);
struct Monster : private flatbuffers::Table {
const Vec3 *pos() const { return GetStruct<const Vec3 *>(4); }
int16_t mana() const { return GetField<int16_t>(6, 150); }
int16_t hp() const { return GetField<int16_t>(8, 100); }
const flatbuffers::String *name() const { return GetPointer<const flatbuffers::String *>(10); }
const flatbuffers::Vector<uint8_t> *inventory() const { return GetPointer<const flatbuffers::Vector<uint8_t> *>(14); }
/// an example documentation comment: this will end up in the generated code multiline too
int8_t color() const { return GetField<int8_t>(16, 2); }
uint8_t test_type() const { return GetField<uint8_t>(18, 0); }
const void *test() const { return GetPointer<const void *>(20); }
const flatbuffers::Vector<const Test *> *test4() const { return GetPointer<const flatbuffers::Vector<const Test *> *>(22); }
};
struct MonsterBuilder {
flatbuffers::FlatBufferBuilder &fbb_;
flatbuffers::uoffset_t start_;
void add_pos(const Vec3 *pos) { fbb_.AddStruct(4, pos); }
void add_mana(int16_t mana) { fbb_.AddElement<int16_t>(6, mana, 150); }
void add_hp(int16_t hp) { fbb_.AddElement<int16_t>(8, hp, 100); }
void add_name(flatbuffers::Offset<flatbuffers::String> name) { fbb_.AddOffset(10, name); }
void add_inventory(flatbuffers::Offset<flatbuffers::Vector<uint8_t>> inventory) { fbb_.AddOffset(14, inventory); }
void add_color(int8_t color) { fbb_.AddElement<int8_t>(16, color, 2); }
void add_test_type(uint8_t test_type) { fbb_.AddElement<uint8_t>(18, test_type, 0); }
void add_test(flatbuffers::Offset<void> test) { fbb_.AddOffset(20, test); }
void add_test4(flatbuffers::Offset<flatbuffers::Vector<const Test *>> test4) { fbb_.AddOffset(22, test4); }
MonsterBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); }
flatbuffers::Offset<Monster> Finish() { return flatbuffers::Offset<Monster>(fbb_.EndTable(start_, 10)); }
};
inline flatbuffers::Offset<Monster> CreateMonster(flatbuffers::FlatBufferBuilder &_fbb, const Vec3 *pos, int16_t mana, int16_t hp, flatbuffers::Offset<flatbuffers::String> name, flatbuffers::Offset<flatbuffers::Vector<uint8_t>> inventory, int8_t color, uint8_t test_type, flatbuffers::Offset<void> test, flatbuffers::Offset<flatbuffers::Vector<const Test *>> test4) {
MonsterBuilder builder_(_fbb);
builder_.add_test4(test4);
builder_.add_test(test);
builder_.add_inventory(inventory);
builder_.add_name(name);
builder_.add_pos(pos);
builder_.add_hp(hp);
builder_.add_mana(mana);
builder_.add_test_type(test_type);
builder_.add_color(color);
return builder_.Finish();
}
inline const Monster *GetMonster(const void *buf) { return flatbuffers::GetRoot<Monster>(buf); }
}; // namespace MyGame
}; // namespace Example

36
tests/monsterdata_test.json Executable file
View File

@@ -0,0 +1,36 @@
{
pos: {
x: 1,
y: 2,
z: 3,
test1: 3,
test2: 4,
test3: {
a: 5,
b: 6
}
},
hp: 80,
name: "MyMonster",
inventory: [
0,
1,
2,
3,
4
],
test_type: 1,
test: {
hp: 20
},
test4: [
{
a: 10,
b: 20
},
{
a: 30,
b: 40
}
]
}

BIN
tests/monsterdata_test_wire.bin Executable file

Binary file not shown.

471
tests/test.cpp Normal file
View File

@@ -0,0 +1,471 @@
/*
* 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
bool 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, 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();
// 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);
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) {
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 (flatbuffers::uoffset_t i = 0; i < inventory->Length(); i++)
TEST_EQ(inventory->Get(i), inv_data[i]);
// 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);
// 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>(), static_cast<size_t>(2));
TEST_EQ(sizeof(Test), static_cast<size_t>(4));
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);
}
// 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.json", 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.
// to ensure it is correct, we now generate text back from the binary,
// and compare the two:
std::string jsongen;
GenerateText(parser, parser.builder_.GetBufferPointer(), 2, &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,
int 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 int 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 (int 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: 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 (int 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;
GenerateText(parser, parser.builder_.GetBufferPointer(), 0, &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("table X { Y:byte; } root_type X; { Y:U }", "valid enum");
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("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");
}
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();
if (!testing_fails) {
TEST_OUTPUT_LINE("ALL TESTS PASSED");
return 0;
} else {
TEST_OUTPUT_LINE("%d FAILED TESTS", testing_fails);
return 1;
}
}