1package.path = string.format("../lua/?.lua;./?.lua;%s",package.path) 2local compat = require("flatbuffers.compat") 3 4local performBenchmarkTests = false 5 6if #arg > 1 then 7 print("usage: lua luatests [benchmark]"); 8 return 9elseif #arg > 0 then 10 if(arg[1] == "benchmark") then 11 performBenchmarkTests = true 12 end 13end 14 15local function checkReadBuffer(buf, offset, sizePrefix) 16 offset = offset or 0 17 18 if type(buf) == "string" then 19 buf = flatbuffers.binaryArray.New(buf) 20 end 21 22 if sizePrefix then 23 local size = flatbuffers.N.Int32:Unpack(buf, offset) 24 assert(size == buf.size - offset - 4) 25 offset = offset + flatbuffers.N.Int32.bytewidth 26 end 27 28 local mon = monster.GetRootAsMonster(buf, offset) 29 assert(mon:Hp() == 80, "Monster Hp is not 80") 30 assert(mon:Mana() == 150, "Monster Mana is not 150") 31 assert(mon:Name() == "MyMonster", "Monster Name is not MyMonster") 32 assert(mon:Testbool() == true) 33 34 local vec = assert(mon:Pos(), "Monster Position is nil") 35 assert(vec:X() == 1.0) 36 assert(vec:Y() == 2.0) 37 assert(vec:Z() == 3.0) 38 assert(vec:Test1() == 3.0) 39 assert(vec:Test2() == 2) 40 41 local t = require("MyGame.Example.Test").New() 42 t = assert(vec:Test3(t)) 43 44 assert(t:A() == 5) 45 assert(t:B() == 6) 46 47 local ut = require("MyGame.Example.Any") 48 assert(mon:TestType() == ut.Monster) 49 50 local table2 = mon:Test() 51 assert(getmetatable(table2) == "flatbuffers.view.mt") 52 53 local mon2 = monster.New() 54 mon2:Init(table2.bytes, table2.pos) 55 56 assert(mon2:Name() == "Fred") 57 58 assert(mon:InventoryLength() == 5) 59 local invsum = 0 60 for i=1,mon:InventoryLength() do 61 local v = mon:Inventory(i) 62 invsum = invsum + v 63 end 64 assert(invsum == 10) 65 66 for i=1,5 do 67 assert(mon:VectorOfLongs(i) == 10^((i-1)*2)) 68 end 69 70 local dbls = { -1.7976931348623157e+308, 0, 1.7976931348623157e+308} 71 for i=1,mon:VectorOfDoublesLength() do 72 assert(mon:VectorOfDoubles(i) == dbls[i]) 73 end 74 75 assert(mon:Test4Length() == 2) 76 77 local test0 = mon:Test4(1) 78 local test1 = mon:Test4(2) 79 80 local v0 = test0:A() 81 local v1 = test0:B() 82 local v2 = test1:A() 83 local v3 = test1:B() 84 85 local sumtest12 = v0 + v1 + v2 + v3 86 assert(sumtest12 == 100) 87 88 assert(mon:TestarrayofstringLength() == 2) 89 assert(mon:Testarrayofstring(1) == "test1") 90 assert(mon:Testarrayofstring(2) == "test2") 91 92 assert(mon:TestarrayoftablesLength() == 0) 93 assert(mon:TestnestedflatbufferLength() == 0) 94 assert(mon:Testempty() == nil) 95end 96 97local function generateMonster(sizePrefix, b) 98 if b then b:Clear() end 99 b = b or flatbuffers.Builder(0) 100 local str = b:CreateString("MyMonster") 101 local test1 = b:CreateString("test1") 102 local test2 = b:CreateString("test2") 103 local fred = b:CreateString("Fred") 104 105 monster.StartInventoryVector(b, 5) 106 b:PrependByte(4) 107 b:PrependByte(3) 108 b:PrependByte(2) 109 b:PrependByte(1) 110 b:PrependByte(0) 111 local inv = b:EndVector(5) 112 113 monster.Start(b) 114 monster.AddName(b, fred) 115 local mon2 = monster.End(b) 116 117 monster.StartTest4Vector(b, 2) 118 test.CreateTest(b, 10, 20) 119 test.CreateTest(b, 30, 40) 120 local test4 = b:EndVector(2) 121 122 monster.StartTestarrayofstringVector(b, 2) 123 b:PrependUOffsetTRelative(test2) 124 b:PrependUOffsetTRelative(test1) 125 local testArrayOfString = b:EndVector(2) 126 127 monster.StartVectorOfLongsVector(b, 5) 128 b:PrependInt64(100000000) 129 b:PrependInt64(1000000) 130 b:PrependInt64(10000) 131 b:PrependInt64(100) 132 b:PrependInt64(1) 133 local vectorOfLongs = b:EndVector(5) 134 135 monster.StartVectorOfDoublesVector(b, 3) 136 b:PrependFloat64(1.7976931348623157e+308) 137 b:PrependFloat64(0) 138 b:PrependFloat64(-1.7976931348623157e+308) 139 local vectorOfDoubles = b:EndVector(3) 140 141 monster.Start(b) 142 local pos = vec3.CreateVec3(b, 1.0, 2.0, 3.0, 3.0, 2, 5, 6) 143 monster.AddPos(b, pos) 144 145 monster.AddHp(b, 80) 146 monster.AddName(b, str) 147 monster.AddInventory(b, inv) 148 monster.AddTestType(b, 1) 149 monster.AddTest(b, mon2) 150 monster.AddTest4(b, test4) 151 monster.AddTestbool(b, true) 152 monster.AddTestbool(b, false) 153 monster.AddTestbool(b, null) 154 monster.AddTestbool(b,"true") 155 monster.AddTestarrayofstring(b, testArrayOfString) 156 monster.AddVectorOfLongs(b, vectorOfLongs) 157 monster.AddVectorOfDoubles(b, vectorOfDoubles) 158 local mon = monster.End(b) 159 160 if sizePrefix then 161 b:FinishSizePrefixed(mon) 162 else 163 b:Finish(mon) 164 end 165 return b:Output(true), b:Head() 166end 167 168local function sizePrefix(sizePrefix) 169 local buf,offset = generateMonster(sizePrefix) 170 checkReadBuffer(buf, offset, sizePrefix) 171end 172 173local function fbbClear() 174 -- Generate a builder that will be 'cleared' and reused to create two different objects. 175 local fbb = flatbuffers.Builder(0) 176 177 -- First use the builder to read the normal monster data and verify it works 178 local buf, offset = generateMonster(false, fbb) 179 checkReadBuffer(buf, offset, false) 180 181 -- Then clear the builder to be used again 182 fbb:Clear() 183 184 -- Storage for the built monsters 185 local monsters = {} 186 local lastBuf 187 188 -- Make another builder that will be use identically to the 'cleared' one so outputs can be compared. Build both the 189 -- Cleared builder and new builder in the exact same way, so we can compare their results 190 for i, builder in ipairs({fbb, flatbuffers.Builder(0)}) do 191 local strOffset = builder:CreateString("Hi there") 192 monster.Start(builder) 193 monster.AddPos(builder, vec3.CreateVec3(builder, 3.0, 2.0, 1.0, 17.0, 3, 100, 123)) 194 monster.AddName(builder, strOffset) 195 monster.AddMana(builder, 123) 196 builder:Finish(monster.End(builder)) 197 local buf = builder:Output(false) 198 if not lastBuf then 199 lastBuf = buf 200 else 201 -- the output, sized-buffer should be identical 202 assert(lastBuf == buf, "Monster output buffers are not identical") 203 end 204 monsters[i] = monster.GetRootAsMonster(flatbuffers.binaryArray.New(buf), 0) 205 end 206 207 -- Check that all the fields for the generated monsters are as we expect 208 for i, monster in ipairs(monsters) do 209 assert(monster:Name() == "Hi there", "Monster Name is not 'Hi There' for monster "..i) 210 -- HP is default to 100 in the schema, but we change it in generateMonster to 80, so this is a good test to 211 -- see if the cleared builder really clears the data. 212 assert(monster:Hp() == 100, "HP doesn't equal the default value for monster "..i) 213 assert(monster:Mana() == 123, "Monster Mana is not '123' for monster "..i) 214 assert(monster:Pos():X() == 3.0, "Monster vec3.X is not '3' for monster "..i) 215 end 216end 217 218local function testCanonicalData() 219 local f = assert(io.open('monsterdata_test.mon', 'rb')) 220 local wireData = f:read("*a") 221 f:close() 222 checkReadBuffer(wireData) 223end 224 225local function testCreateEmptyString() 226 local b = flatbuffers.Builder(0) 227 local str = b:CreateString("") 228 monster.Start(b) 229 monster.AddName(b, str) 230 b:Finish(monster.End(b)) 231 local s = b:Output() 232 local data = flatbuffers.binaryArray.New(s) 233 local mon = monster.GetRootAsMonster(data, 0) 234 assert(mon:Name() == "") 235end 236 237local function benchmarkMakeMonster(count, reuseBuilder) 238 local fbb = reuseBuilder and flatbuffers.Builder(0) 239 local length = #(generateMonster(false, fbb)) 240 241 local s = os.clock() 242 for i=1,count do 243 generateMonster(false, fbb) 244 end 245 local e = os.clock() 246 247 local dur = (e - s) 248 local rate = count / (dur * 1000) 249 local data = (length * count) / (1024 * 1024) 250 local dataRate = data / dur 251 252 print(string.format('built %d %d-byte flatbuffers in %.2fsec: %.2f/msec, %.2fMB/sec', 253 count, length, dur, rate, dataRate)) 254end 255 256local function benchmarkReadBuffer(count) 257 local f = assert(io.open('monsterdata_test.mon', 'rb')) 258 local buf = f:read("*a") 259 f:close() 260 261 local s = os.clock() 262 for i=1,count do 263 checkReadBuffer(buf) 264 end 265 local e = os.clock() 266 267 local dur = (e - s) 268 local rate = count / (dur * 1000) 269 local data = (#buf * count) / (1024 * 1024) 270 local dataRate = data / dur 271 272 print(string.format('traversed %d %d-byte flatbuffers in %.2fsec: %.2f/msec, %.2fMB/sec', 273 count, #buf, dur, rate, dataRate)) 274end 275 276local function getRootAs_canAcceptString() 277 local f = assert(io.open('monsterdata_test.mon', 'rb')) 278 local wireData = f:read("*a") 279 f:close() 280 assert(type(wireData) == "string", "Data is not a string"); 281 local mon = monster.GetRootAsMonster(wireData, 0) 282 assert(mon:Hp() == 80, "Monster Hp is not 80") 283end 284 285local function testAccessByteVectorAsString() 286 local f = assert(io.open('monsterdata_test.mon', 'rb')) 287 local wireData = f:read("*a") 288 f:close() 289 local mon = monster.GetRootAsMonster(wireData, 0) 290 -- the data of byte array Inventory is [0, 1, 2, 3, 4] 291 local s = mon:InventoryAsString(1, 3) 292 assert(#s == 3) 293 for i = 1, #s do 294 assert(string.byte(s, i) == i - 1) 295 end 296 297 local s = mon:InventoryAsString(2, 5) 298 assert(#s == 4) 299 for i = 1, #s do 300 assert(string.byte(s, i) == i) 301 end 302 303 local s = mon:InventoryAsString(5, 5) 304 assert(#s == 1) 305 assert(string.byte(s, 1) == 4) 306 307 local s = mon:InventoryAsString(2) 308 assert(#s == 4) 309 for i = 1, #s do 310 assert(string.byte(s, i) == i) 311 end 312 313 local s = mon:InventoryAsString() 314 assert(#s == 5) 315 for i = 1, #s do 316 assert(string.byte(s, i) == i - 1) 317 end 318end 319 320local tests = 321{ 322 { 323 f = sizePrefix, 324 d = "Test size prefix", 325 args = {{true}, {false}} 326 }, 327 { 328 f = fbbClear, 329 d = "FlatBufferBuilder Clear", 330 }, 331 { 332 f = testCanonicalData, 333 d = "Tests Canonical flatbuffer file included in repo" 334 }, 335 { 336 f = testCreateEmptyString, 337 d = "Avoid infinite loop when creating empty string" 338 }, 339 { 340 f = getRootAs_canAcceptString, 341 d = "Tests that GetRootAs<type>() generated methods accept strings" 342 }, 343 { 344 f = testAccessByteVectorAsString, 345 d = "Access byte vector as string" 346 }, 347} 348 349local benchmarks = 350{ 351 { 352 f = benchmarkMakeMonster, 353 d = "Benchmark making monsters", 354 args = { 355 {100}, 356 {1000}, 357 {10000}, 358 {10000, true} 359 } 360 }, 361 { 362 f = benchmarkReadBuffer, 363 d = "Benchmark reading monsters", 364 args = { 365 {100}, 366 {1000}, 367 {10000}, 368 -- uncomment following to run 1 million to compare. 369 -- Took ~141 seconds on my machine 370 --{1000000}, 371 } 372 }, 373} 374 375local result, err = xpcall(function() 376 flatbuffers = assert(require("flatbuffers")) 377 monster = assert(require("MyGame.Example.Monster")) 378 test = assert(require("MyGame.Example.Test")) 379 vec3 = assert(require("MyGame.Example.Vec3")) 380 381 local function buildArgList(tbl) 382 local s = "" 383 for _,item in ipairs(tbl) do 384 s = s .. tostring(item) .. "," 385 end 386 return s:sub(1,-2) 387 end 388 389 if performBenchmarkTests then 390 for _,benchmark in ipairs(benchmarks) do 391 table.insert(tests, benchmark) 392 end 393 end 394 395 local testsPassed, testsFailed = 0,0 396 for _,test in ipairs(tests) do 397 local allargs = test.args or {{}} 398 for _,args in ipairs(allargs) do 399 local results, err = xpcall(test.f,debug.traceback, table.unpack(args)) 400 if results then 401 testsPassed = testsPassed + 1 402 else 403 testsFailed = testsFailed + 1 404 print(string.format(" Test [%s](%s) failed: \n\t%s", 405 test.d or "", 406 buildArgList(args), 407 err)) 408 end 409 end 410 end 411 412 local totalTests = testsPassed + testsFailed 413 print(string.format("# of test passed: %d / %d (%.2f%%)", 414 testsPassed, 415 totalTests, 416 totalTests ~= 0 417 and 100 * (testsPassed / totalTests) 418 or 0) 419 ) 420 421 return 0 422end, debug.traceback) 423 424if not result then 425 print("Unable to run tests due to test framework error: ",err) 426end 427 428os.exit(result and 0 or -1) 429