blob: 1a70f5ff42b2134b4666be72e004b1a4da0abda3 [file] [log] [blame]
Austin Schuhe89fa2d2019-08-14 20:24:23 -07001package.path = string.format("../lua/?.lua;./?.lua;%s",package.path)
James Kuszmaul8e62b022022-03-22 09:33:25 -07002local 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
Austin Schuhe89fa2d2019-08-14 20:24:23 -070014
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)
James Kuszmaul8e62b022022-03-22 09:33:25 -070024 assert(size == buf.size - offset - 4)
Austin Schuhe89fa2d2019-08-14 20:24:23 -070025 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 Kuszmaul8e62b022022-03-22 09:33:25 -070032 assert(mon:Testbool() == true)
Austin Schuhe89fa2d2019-08-14 20:24:23 -070033
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
Austin Schuh272c6132020-11-14 16:37:52 -080097local function generateMonster(sizePrefix, b)
98 if b then b:Clear() end
99 b = b or flatbuffers.Builder(0)
Austin Schuhe89fa2d2019-08-14 20:24:23 -0700100 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
Austin Schuh272c6132020-11-14 16:37:52 -0800173local 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
Austin Schuhe89fa2d2019-08-14 20:24:23 -0700218local 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
James Kuszmaul8e62b022022-03-22 09:33:25 -0700225local 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
Austin Schuh272c6132020-11-14 16:37:52 -0800237local function benchmarkMakeMonster(count, reuseBuilder)
238 local fbb = reuseBuilder and flatbuffers.Builder(0)
239 local length = #(generateMonster(false, fbb))
240
Austin Schuhe89fa2d2019-08-14 20:24:23 -0700241 local s = os.clock()
242 for i=1,count do
Austin Schuh272c6132020-11-14 16:37:52 -0800243 generateMonster(false, fbb)
Austin Schuhe89fa2d2019-08-14 20:24:23 -0700244 end
245 local e = os.clock()
Austin Schuhe89fa2d2019-08-14 20:24:23 -0700246
Austin Schuhe89fa2d2019-08-14 20:24:23 -0700247 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
James Kuszmaul8e62b022022-03-22 09:33:25 -0700275
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
Austin Schuhe89fa2d2019-08-14 20:24:23 -0700284
James Kuszmaul8e62b022022-03-22 09:33:25 -0700285local 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
Austin Schuhe89fa2d2019-08-14 20:24:23 -0700320local tests =
321{
322 {
323 f = sizePrefix,
324 d = "Test size prefix",
325 args = {{true}, {false}}
326 },
Austin Schuh272c6132020-11-14 16:37:52 -0800327 {
328 f = fbbClear,
329 d = "FlatBufferBuilder Clear",
330 },
Austin Schuhe89fa2d2019-08-14 20:24:23 -0700331 {
332 f = testCanonicalData,
333 d = "Tests Canonical flatbuffer file included in repo"
334 },
335 {
James Kuszmaul8e62b022022-03-22 09:33:25 -0700336 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 {
Austin Schuhe89fa2d2019-08-14 20:24:23 -0700352 f = benchmarkMakeMonster,
353 d = "Benchmark making monsters",
354 args = {
355 {100},
356 {1000},
357 {10000},
Austin Schuh272c6132020-11-14 16:37:52 -0800358 {10000, true}
Austin Schuhe89fa2d2019-08-14 20:24:23 -0700359 }
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 Schuh272c6132020-11-14 16:37:52 -0800370 --{1000000},
Austin Schuhe89fa2d2019-08-14 20:24:23 -0700371 }
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
James Kuszmaul8e62b022022-03-22 09:33:25 -0700389 if performBenchmarkTests then
390 for _,benchmark in ipairs(benchmarks) do
391 table.insert(tests, benchmark)
392 end
393 end
394
Austin Schuhe89fa2d2019-08-14 20:24:23 -0700395 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
James Kuszmaul8e62b022022-03-22 09:33:25 -0700428os.exit(result and 0 or -1)