package.path = string.format("../lua/?.lua;./?.lua;%s",package.path) local compat = require("flatbuffers.compat") local performBenchmarkTests = false if #arg > 1 then print("usage: lua luatests [benchmark]"); return elseif #arg > 0 then if(arg[1] == "benchmark") then performBenchmarkTests = true end end local function checkReadBuffer(buf, offset, sizePrefix) offset = offset or 0 if type(buf) == "string" then buf = flatbuffers.binaryArray.New(buf) end if sizePrefix then local size = flatbuffers.N.Int32:Unpack(buf, offset) assert(size == buf.size - offset - 4) offset = offset + flatbuffers.N.Int32.bytewidth end local mon = monster.GetRootAsMonster(buf, offset) assert(mon:Hp() == 80, "Monster Hp is not 80") assert(mon:Mana() == 150, "Monster Mana is not 150") assert(mon:Name() == "MyMonster", "Monster Name is not MyMonster") assert(mon:Testbool() == true) local vec = assert(mon:Pos(), "Monster Position is nil") assert(vec:X() == 1.0) assert(vec:Y() == 2.0) assert(vec:Z() == 3.0) assert(vec:Test1() == 3.0) assert(vec:Test2() == 2) local t = require("MyGame.Example.Test").New() t = assert(vec:Test3(t)) assert(t:A() == 5) assert(t:B() == 6) local ut = require("MyGame.Example.Any") assert(mon:TestType() == ut.Monster) local table2 = mon:Test() assert(getmetatable(table2) == "flatbuffers.view.mt") local mon2 = monster.New() mon2:Init(table2.bytes, table2.pos) assert(mon2:Name() == "Fred") assert(mon:InventoryLength() == 5) local invsum = 0 for i=1,mon:InventoryLength() do local v = mon:Inventory(i) invsum = invsum + v end assert(invsum == 10) for i=1,5 do assert(mon:VectorOfLongs(i) == 10^((i-1)*2)) end local dbls = { -1.7976931348623157e+308, 0, 1.7976931348623157e+308} for i=1,mon:VectorOfDoublesLength() do assert(mon:VectorOfDoubles(i) == dbls[i]) end assert(mon:Test4Length() == 2) local test0 = mon:Test4(1) local test1 = mon:Test4(2) local v0 = test0:A() local v1 = test0:B() local v2 = test1:A() local v3 = test1:B() local sumtest12 = v0 + v1 + v2 + v3 assert(sumtest12 == 100) assert(mon:TestarrayofstringLength() == 2) assert(mon:Testarrayofstring(1) == "test1") assert(mon:Testarrayofstring(2) == "test2") assert(mon:TestarrayoftablesLength() == 0) assert(mon:TestnestedflatbufferLength() == 0) assert(mon:Testempty() == nil) end local function generateMonster(sizePrefix, b) if b then b:Clear() end b = b or flatbuffers.Builder(0) local str = b:CreateString("MyMonster") local test1 = b:CreateString("test1") local test2 = b:CreateString("test2") local fred = b:CreateString("Fred") monster.StartInventoryVector(b, 5) b:PrependByte(4) b:PrependByte(3) b:PrependByte(2) b:PrependByte(1) b:PrependByte(0) local inv = b:EndVector(5) monster.Start(b) monster.AddName(b, fred) local mon2 = monster.End(b) monster.StartTest4Vector(b, 2) test.CreateTest(b, 10, 20) test.CreateTest(b, 30, 40) local test4 = b:EndVector(2) monster.StartTestarrayofstringVector(b, 2) b:PrependUOffsetTRelative(test2) b:PrependUOffsetTRelative(test1) local testArrayOfString = b:EndVector(2) monster.StartVectorOfLongsVector(b, 5) b:PrependInt64(100000000) b:PrependInt64(1000000) b:PrependInt64(10000) b:PrependInt64(100) b:PrependInt64(1) local vectorOfLongs = b:EndVector(5) monster.StartVectorOfDoublesVector(b, 3) b:PrependFloat64(1.7976931348623157e+308) b:PrependFloat64(0) b:PrependFloat64(-1.7976931348623157e+308) local vectorOfDoubles = b:EndVector(3) monster.Start(b) local pos = vec3.CreateVec3(b, 1.0, 2.0, 3.0, 3.0, 2, 5, 6) monster.AddPos(b, pos) monster.AddHp(b, 80) monster.AddName(b, str) monster.AddInventory(b, inv) monster.AddTestType(b, 1) monster.AddTest(b, mon2) monster.AddTest4(b, test4) monster.AddTestbool(b, true) monster.AddTestbool(b, false) monster.AddTestbool(b, null) monster.AddTestbool(b,"true") monster.AddTestarrayofstring(b, testArrayOfString) monster.AddVectorOfLongs(b, vectorOfLongs) monster.AddVectorOfDoubles(b, vectorOfDoubles) local mon = monster.End(b) if sizePrefix then b:FinishSizePrefixed(mon) else b:Finish(mon) end return b:Output(true), b:Head() end local function sizePrefix(sizePrefix) local buf,offset = generateMonster(sizePrefix) checkReadBuffer(buf, offset, sizePrefix) end local function fbbClear() -- Generate a builder that will be 'cleared' and reused to create two different objects. local fbb = flatbuffers.Builder(0) -- First use the builder to read the normal monster data and verify it works local buf, offset = generateMonster(false, fbb) checkReadBuffer(buf, offset, false) -- Then clear the builder to be used again fbb:Clear() -- Storage for the built monsters local monsters = {} local lastBuf -- Make another builder that will be use identically to the 'cleared' one so outputs can be compared. Build both the -- Cleared builder and new builder in the exact same way, so we can compare their results for i, builder in ipairs({fbb, flatbuffers.Builder(0)}) do local strOffset = builder:CreateString("Hi there") monster.Start(builder) monster.AddPos(builder, vec3.CreateVec3(builder, 3.0, 2.0, 1.0, 17.0, 3, 100, 123)) monster.AddName(builder, strOffset) monster.AddMana(builder, 123) builder:Finish(monster.End(builder)) local buf = builder:Output(false) if not lastBuf then lastBuf = buf else -- the output, sized-buffer should be identical assert(lastBuf == buf, "Monster output buffers are not identical") end monsters[i] = monster.GetRootAsMonster(flatbuffers.binaryArray.New(buf), 0) end -- Check that all the fields for the generated monsters are as we expect for i, monster in ipairs(monsters) do assert(monster:Name() == "Hi there", "Monster Name is not 'Hi There' for monster "..i) -- HP is default to 100 in the schema, but we change it in generateMonster to 80, so this is a good test to -- see if the cleared builder really clears the data. assert(monster:Hp() == 100, "HP doesn't equal the default value for monster "..i) assert(monster:Mana() == 123, "Monster Mana is not '123' for monster "..i) assert(monster:Pos():X() == 3.0, "Monster vec3.X is not '3' for monster "..i) end end local function testCanonicalData() local f = assert(io.open('monsterdata_test.mon', 'rb')) local wireData = f:read("*a") f:close() checkReadBuffer(wireData) end local function testCreateEmptyString() local b = flatbuffers.Builder(0) local str = b:CreateString("") monster.Start(b) monster.AddName(b, str) b:Finish(monster.End(b)) local s = b:Output() local data = flatbuffers.binaryArray.New(s) local mon = monster.GetRootAsMonster(data, 0) assert(mon:Name() == "") end local function benchmarkMakeMonster(count, reuseBuilder) local fbb = reuseBuilder and flatbuffers.Builder(0) local length = #(generateMonster(false, fbb)) local s = os.clock() for i=1,count do generateMonster(false, fbb) end local e = os.clock() local dur = (e - s) local rate = count / (dur * 1000) local data = (length * count) / (1024 * 1024) local dataRate = data / dur print(string.format('built %d %d-byte flatbuffers in %.2fsec: %.2f/msec, %.2fMB/sec', count, length, dur, rate, dataRate)) end local function benchmarkReadBuffer(count) local f = assert(io.open('monsterdata_test.mon', 'rb')) local buf = f:read("*a") f:close() local s = os.clock() for i=1,count do checkReadBuffer(buf) end local e = os.clock() local dur = (e - s) local rate = count / (dur * 1000) local data = (#buf * count) / (1024 * 1024) local dataRate = data / dur print(string.format('traversed %d %d-byte flatbuffers in %.2fsec: %.2f/msec, %.2fMB/sec', count, #buf, dur, rate, dataRate)) end local function getRootAs_canAcceptString() local f = assert(io.open('monsterdata_test.mon', 'rb')) local wireData = f:read("*a") f:close() assert(type(wireData) == "string", "Data is not a string"); local mon = monster.GetRootAsMonster(wireData, 0) assert(mon:Hp() == 80, "Monster Hp is not 80") end local function testAccessByteVectorAsString() local f = assert(io.open('monsterdata_test.mon', 'rb')) local wireData = f:read("*a") f:close() local mon = monster.GetRootAsMonster(wireData, 0) -- the data of byte array Inventory is [0, 1, 2, 3, 4] local s = mon:InventoryAsString(1, 3) assert(#s == 3) for i = 1, #s do assert(string.byte(s, i) == i - 1) end local s = mon:InventoryAsString(2, 5) assert(#s == 4) for i = 1, #s do assert(string.byte(s, i) == i) end local s = mon:InventoryAsString(5, 5) assert(#s == 1) assert(string.byte(s, 1) == 4) local s = mon:InventoryAsString(2) assert(#s == 4) for i = 1, #s do assert(string.byte(s, i) == i) end local s = mon:InventoryAsString() assert(#s == 5) for i = 1, #s do assert(string.byte(s, i) == i - 1) end end local tests = { { f = sizePrefix, d = "Test size prefix", args = {{true}, {false}} }, { f = fbbClear, d = "FlatBufferBuilder Clear", }, { f = testCanonicalData, d = "Tests Canonical flatbuffer file included in repo" }, { f = testCreateEmptyString, d = "Avoid infinite loop when creating empty string" }, { f = getRootAs_canAcceptString, d = "Tests that GetRootAs() generated methods accept strings" }, { f = testAccessByteVectorAsString, d = "Access byte vector as string" }, } local benchmarks = { { f = benchmarkMakeMonster, d = "Benchmark making monsters", args = { {100}, {1000}, {10000}, {10000, true} } }, { f = benchmarkReadBuffer, d = "Benchmark reading monsters", args = { {100}, {1000}, {10000}, -- uncomment following to run 1 million to compare. -- Took ~141 seconds on my machine --{1000000}, } }, } local result, err = xpcall(function() flatbuffers = assert(require("flatbuffers")) monster = assert(require("MyGame.Example.Monster")) test = assert(require("MyGame.Example.Test")) vec3 = assert(require("MyGame.Example.Vec3")) local function buildArgList(tbl) local s = "" for _,item in ipairs(tbl) do s = s .. tostring(item) .. "," end return s:sub(1,-2) end if performBenchmarkTests then for _,benchmark in ipairs(benchmarks) do table.insert(tests, benchmark) end end local testsPassed, testsFailed = 0,0 for _,test in ipairs(tests) do local allargs = test.args or {{}} for _,args in ipairs(allargs) do local results, err = xpcall(test.f,debug.traceback, table.unpack(args)) if results then testsPassed = testsPassed + 1 else testsFailed = testsFailed + 1 print(string.format(" Test [%s](%s) failed: \n\t%s", test.d or "", buildArgList(args), err)) end end end local totalTests = testsPassed + testsFailed print(string.format("# of test passed: %d / %d (%.2f%%)", testsPassed, totalTests, totalTests ~= 0 and 100 * (testsPassed / totalTests) or 0) ) return 0 end, debug.traceback) if not result then print("Unable to run tests due to test framework error: ",err) end os.exit(result and 0 or -1)