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