Austin Schuh | e89fa2d | 2019-08-14 20:24:23 -0700 | [diff] [blame] | 1 | package.path = string.format("../lua/?.lua;./?.lua;%s",package.path) |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame^] | 2 | local compat = require("flatbuffers.compat") |
| 3 | |
| 4 | local performBenchmarkTests = false |
| 5 | |
| 6 | if #arg > 1 then |
| 7 | print("usage: lua luatests [benchmark]"); |
| 8 | return |
| 9 | elseif #arg > 0 then |
| 10 | if(arg[1] == "benchmark") then |
| 11 | performBenchmarkTests = true |
| 12 | end |
| 13 | end |
Austin Schuh | e89fa2d | 2019-08-14 20:24:23 -0700 | [diff] [blame] | 14 | |
| 15 | local 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) |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame^] | 24 | assert(size == buf.size - offset - 4) |
Austin Schuh | e89fa2d | 2019-08-14 20:24:23 -0700 | [diff] [blame] | 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") |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame^] | 32 | assert(mon:Testbool() == true) |
Austin Schuh | e89fa2d | 2019-08-14 20:24:23 -0700 | [diff] [blame] | 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) |
| 95 | end |
| 96 | |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 97 | local function generateMonster(sizePrefix, b) |
| 98 | if b then b:Clear() end |
| 99 | b = b or flatbuffers.Builder(0) |
Austin Schuh | e89fa2d | 2019-08-14 20:24:23 -0700 | [diff] [blame] | 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() |
| 166 | end |
| 167 | |
| 168 | local function sizePrefix(sizePrefix) |
| 169 | local buf,offset = generateMonster(sizePrefix) |
| 170 | checkReadBuffer(buf, offset, sizePrefix) |
| 171 | end |
| 172 | |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 173 | local 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 |
| 216 | end |
| 217 | |
Austin Schuh | e89fa2d | 2019-08-14 20:24:23 -0700 | [diff] [blame] | 218 | local function testCanonicalData() |
| 219 | local f = assert(io.open('monsterdata_test.mon', 'rb')) |
| 220 | local wireData = f:read("*a") |
| 221 | f:close() |
| 222 | checkReadBuffer(wireData) |
| 223 | end |
| 224 | |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame^] | 225 | local 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() == "") |
| 235 | end |
| 236 | |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 237 | local function benchmarkMakeMonster(count, reuseBuilder) |
| 238 | local fbb = reuseBuilder and flatbuffers.Builder(0) |
| 239 | local length = #(generateMonster(false, fbb)) |
| 240 | |
Austin Schuh | e89fa2d | 2019-08-14 20:24:23 -0700 | [diff] [blame] | 241 | local s = os.clock() |
| 242 | for i=1,count do |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 243 | generateMonster(false, fbb) |
Austin Schuh | e89fa2d | 2019-08-14 20:24:23 -0700 | [diff] [blame] | 244 | end |
| 245 | local e = os.clock() |
Austin Schuh | e89fa2d | 2019-08-14 20:24:23 -0700 | [diff] [blame] | 246 | |
Austin Schuh | e89fa2d | 2019-08-14 20:24:23 -0700 | [diff] [blame] | 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)) |
| 254 | end |
| 255 | |
| 256 | local 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)) |
| 274 | end |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame^] | 275 | |
| 276 | local 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") |
| 283 | end |
Austin Schuh | e89fa2d | 2019-08-14 20:24:23 -0700 | [diff] [blame] | 284 | |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame^] | 285 | local 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 |
| 318 | end |
| 319 | |
Austin Schuh | e89fa2d | 2019-08-14 20:24:23 -0700 | [diff] [blame] | 320 | local tests = |
| 321 | { |
| 322 | { |
| 323 | f = sizePrefix, |
| 324 | d = "Test size prefix", |
| 325 | args = {{true}, {false}} |
| 326 | }, |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 327 | { |
| 328 | f = fbbClear, |
| 329 | d = "FlatBufferBuilder Clear", |
| 330 | }, |
Austin Schuh | e89fa2d | 2019-08-14 20:24:23 -0700 | [diff] [blame] | 331 | { |
| 332 | f = testCanonicalData, |
| 333 | d = "Tests Canonical flatbuffer file included in repo" |
| 334 | }, |
| 335 | { |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame^] | 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 | |
| 349 | local benchmarks = |
| 350 | { |
| 351 | { |
Austin Schuh | e89fa2d | 2019-08-14 20:24:23 -0700 | [diff] [blame] | 352 | f = benchmarkMakeMonster, |
| 353 | d = "Benchmark making monsters", |
| 354 | args = { |
| 355 | {100}, |
| 356 | {1000}, |
| 357 | {10000}, |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 358 | {10000, true} |
Austin Schuh | e89fa2d | 2019-08-14 20:24:23 -0700 | [diff] [blame] | 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 |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 370 | --{1000000}, |
Austin Schuh | e89fa2d | 2019-08-14 20:24:23 -0700 | [diff] [blame] | 371 | } |
| 372 | }, |
| 373 | } |
| 374 | |
| 375 | local 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 | |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame^] | 389 | if performBenchmarkTests then |
| 390 | for _,benchmark in ipairs(benchmarks) do |
| 391 | table.insert(tests, benchmark) |
| 392 | end |
| 393 | end |
| 394 | |
Austin Schuh | e89fa2d | 2019-08-14 20:24:23 -0700 | [diff] [blame] | 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 |
| 422 | end, debug.traceback) |
| 423 | |
| 424 | if not result then |
| 425 | print("Unable to run tests due to test framework error: ",err) |
| 426 | end |
| 427 | |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame^] | 428 | os.exit(result and 0 or -1) |