--- source: src/main.rs expression: compiled input_file: test-data/lua5.4-tests/api.lua --- if T == nil { (Message || print)('\n >>> testC not active: skipping API tests <<<\n'); return } local debug = require("debug") local pack = table.pack local MEMERRMSG = "not enough memory" global fn tcheck(t1, t2) { assert(t1.n == (t2.n || #t2) + 1); for i = 2, t1.n { assert(t1[(i)] == t2[(i - 1)]); } } local fn checkerr(msg, f, ...) { local stat, err = pcall(f, ...) assert(!stat && string.find(err, msg)); } print('testing C API'); a = T.testC("pushvalue R; return 1") assert(a == debug.getregistry()); assert(T.testC("settop 10; absindex -1; return 1") == 10); assert(T.testC("settop 5; absindex -5; return 1") == 1); assert(T.testC("settop 10; absindex 1; return 1") == 1); assert(T.testC("settop 10; absindex R; return 1") < -10); a = T.d2s(12458954321123.0) assert(a == string.pack("d", 12458954321123.0)); assert(T.s2d(a) == 12458954321123.0); a, b, c = T.testC("pushnum 1; pushnum 2; pushnum 3; return 2") assert(a == 2 && b == 3 && !c); f = T.makeCfunc("pushnum 1; pushnum 2; pushnum 3; return 2") a, b, c = f() assert(a == 2 && b == 3 && !c); a, b, c = T.testC("pushbool 1; pushbool 2; pushbool 0; return 3") assert(a == b && a == true && c == false); a, b, c = T.testC("pushbool 0; pushbool 10; pushnil;\ tobool -3; tobool -3; tobool -3; return 3") assert(a == false && b == true && c == false); a, b, c = T.testC("gettop; return 2", 10, 20, 30, 40) assert(a == 40 && b == 5 && !c); t = pack(T.testC("settop 5; return *", 2, 3)) tcheck(t, { n = 4, 2, 3 }); t = pack(T.testC("settop 0; settop 15; return 10", 3, 1, 23)) assert(t.n == 10 && t[(1)] == nil && t[(10)] == nil); t = pack(T.testC("remove -2; return *", 2, 3, 4)) tcheck(t, { n = 2, 2, 4 }); t = pack(T.testC("insert -1; return *", 2, 3)) tcheck(t, { n = 2, 2, 3 }); t = pack(T.testC("insert 3; return *", 2, 3, 4, 5)) tcheck(t, { n = 4, 2, 5, 3, 4 }); t = pack(T.testC("replace 2; return *", 2, 3, 4, 5)) tcheck(t, { n = 3, 5, 3, 4 }); t = pack(T.testC("replace -2; return *", 2, 3, 4, 5)) tcheck(t, { n = 3, 2, 3, 5 }); t = pack(T.testC("remove 3; return *", 2, 3, 4, 5)) tcheck(t, { n = 3, 2, 4, 5 }); t = pack(T.testC("copy 3 4; return *", 2, 3, 4, 5)) tcheck(t, { n = 4, 2, 3, 3, 5 }); t = pack(T.testC("copy -3 -1; return *", 2, 3, 4, 5)) tcheck(t, { n = 4, 2, 3, 4, 3 }); { local t = { 10, 20, 30, 40, 50, 60 } for i = -6, 6 { local s = string.format("rotate 2 %d; return 7", i) local t1 = pack(T.testC(s, 10, 20, 30, 40, 50, 60)) tcheck(t1, t); table.insert(t, 1, table.remove(t)); } t = pack(T.testC("rotate -2 1; return *", 10, 20, 30, 40)) tcheck(t, { 10, 20, 40, 30 }); t = pack(T.testC("rotate -2 -1; return *", 10, 20, 30, 40)) tcheck(t, { 10, 20, 40, 30 }); t = pack(T.testC("rotate -1 0; return *", 10, 20, 30, 40)) tcheck(t, { 10, 20, 30, 40 }); t = pack(T.testC("rotate -1 1; return *", 10, 20, 30, 40)) tcheck(t, { 10, 20, 30, 40 }); t = pack(T.testC("rotate 5 -1; return *", 10, 20, 30, 40)) tcheck(t, { 10, 20, 30, 40 }); } T.testC(` warningC "#This shold be a" warningC " single " warning "warning" warningC "#This should be " warning "another one" `); { local f = T.makeCfunc([[ getglobal error pushstring bola pcall 1 1 1 # call 'error' with given handler pushstatus return 2 # return error message and status ]]) local msg, st = f(string.upper) assert(st == "ERRRUN" && msg == "BOLA"); local msg, st = f(string.len) assert(st == "ERRRUN" && msg == 4); } t = pack(T.testC("insert 3; pushvalue 3; remove 3; pushvalue 2; remove 2; \ insert 2; pushvalue 1; remove 1; insert 1; \ insert -2; pushvalue -2; remove -3; return *", 2, 3, 4, 5, 10, 40, 90)) tcheck(t, { n = 7, 2, 3, 4, 5, 10, 40, 90 }); t = pack(T.testC("concat 5; return *", "alo", 2, 3, "joao", 12)) tcheck(t, { n = 1, "alo23joao12" }); t = pack(T.testC("call 2,-1; return *", fn (a, b) { return 1, 2, 3, 4, a, b }, "alo", "joao")) tcheck(t, { n = 6, 1, 2, 3, 4, "alo", "joao" }); { local a = {} for i = 1, 1000 { a[(i)] = true } a[(999)] = 10 local b = T.testC(`pcall 1 -1 0; pop 1; tostring -1; return 1`, table.unpack, a) assert(b == "10"); } _G.a = 14 _G.b = "a31" local a = { T.testC([[ getglobal a; getglobal b; getglobal b; setglobal a; return * ]]) } assert(a[(2)] == 14 && a[(3)] == "a31" && a[(4)] == nil && _G.a == "a31"); assert(T.testC("pushnum 10; pushnum 20; arith /; return 1") == 0.5); assert(T.testC("pushnum 10; pushnum 20; arith -; return 1") == -10); assert(T.testC("pushnum 10; pushnum -20; arith *; return 1") == -200); assert(T.testC("pushnum 10; pushnum 3; arith ^; return 1") == 1000); assert(T.testC("pushnum 10; pushstring 20; arith /; return 1") == 0.5); assert(T.testC("pushstring 10; pushnum 20; arith -; return 1") == -10); assert(T.testC("pushstring 10; pushstring -20; arith *; return 1") == -200); assert(T.testC("pushstring 10; pushstring 3; arith ^; return 1") == 1000); assert(T.testC("arith /; return 1", 2, 0) == 10.0 / 0); a = T.testC("pushnum 10; pushint 3; arith \\; return 1") assert(a == 3.0 && math.type(a) == "float"); a = T.testC("pushint 10; pushint 3; arith \\; return 1") assert(a == 3 && math.type(a) == "integer"); a = assert(T.testC("pushint 10; pushint 3; arith +; return 1")) assert(a == 13 && math.type(a) == "integer"); a = assert(T.testC("pushnum 10; pushint 3; arith +; return 1")) assert(a == 13 && math.type(a) == "float"); a, b, c = T.testC(`pushnum 1; pushstring 10; arith _; pushstring 5; return 3`) assert(a == 1 && b == -10 && c == "5"); mt = { __add = fn (a, b) { return setmetatable({ a[(1)] + b[(1)] }, mt) }, __mod = fn (a, b) { return setmetatable({ a[(1)] % b[(1)] }, mt) }, __unm = fn (a) { return setmetatable({ a[(1)] * 2 }, mt) } } a, b, c = setmetatable({ 4 }, mt), setmetatable({ 8 }, mt), setmetatable({ -3 }, mt) x, y, z = T.testC("arith +; return 2", 10, a, b) assert(x == 10 && y[(1)] == 12 && z == nil); assert(T.testC("arith %; return 1", a, c)[(1)] == 4 % -3); assert(T.testC("arith _; arith +; arith %; return 1", b, a, c)[(1)] == 8 % (4 + (-3) * 2)); checkerr("divide by zero", T.testC, "arith \\", 10, 0); checkerr("%%0", T.testC, "arith %", 10, 0); assert(T.testC("compare LT 2 5, return 1", 3, 2, 2, 4, 2, 2)); assert(T.testC("compare LE 2 5, return 1", 3, 2, 2, 4, 2, 2)); assert(!T.testC("compare LT 3 4, return 1", 3, 2, 2, 4, 2, 2)); assert(T.testC("compare LE 3 4, return 1", 3, 2, 2, 4, 2, 2)); assert(T.testC("compare LT 5 2, return 1", 4, 2, 2, 3, 2, 2)); assert(!T.testC("compare LT 2 -3, return 1", "4", "2", "2", "3", "2", "2")); assert(!T.testC("compare LT -3 2, return 1", "3", "2", "2", "4", "2", "2")); assert(!T.testC("compare LT 1 4, return 1")); assert(!T.testC("compare LE 9 1, return 1")); assert(!T.testC("compare EQ 9 9, return 1")); local b = { __lt = fn (a, b) { return a[(1)] < b[(1)] } } local a1, a3, a4 = setmetatable({ 1 }, b), setmetatable({ 3 }, b), setmetatable({ 4 }, b) assert(T.testC("compare LT 2 5, return 1", a3, 2, 2, a4, 2, 2)); assert(T.testC("compare LE 2 5, return 1", a3, 2, 2, a4, 2, 2)); assert(T.testC("compare LT 5 -6, return 1", a4, 2, 2, a3, 2, 2)); a, b = T.testC("compare LT 5 -6, return 2", a1, 2, 2, a3, 2, 20) assert(a == 20 && b == false); a, b = T.testC("compare LE 5 -6, return 2", a1, 2, 2, a3, 2, 20) assert(a == 20 && b == false); a, b = T.testC("compare LE 5 -6, return 2", a1, 2, 2, a1, 2, 20) assert(a == 20 && b == true); { local mt = { __lt = fn (a, b) { return a[(1)] < b[(1)] }, __le = fn (a, b) { return a[(1)] <= b[(1)] }, __eq = fn (a, b) { return a[(1)] == b[(1)] } } local fn O(x) { return setmetatable({ x }, mt) } local a, b = T.testC("compare LT 2 3; pushint 10; return 2", O(1), O(2)) assert(a == true && b == 10); local a, b = T.testC("compare LE 2 3; pushint 10; return 2", O(3), O(2)) assert(a == false && b == 10); local a, b = T.testC("compare EQ 2 3; pushint 10; return 2", O(3), O(3)) assert(a == true && b == 10); } local t = setmetatable({ x = 20 }, { __len = fn (t) { return t.x } }) a, b, c = T.testC(` len 2; Llen 2; objsize 2; return 3 `, t) assert(a == 20 && b == 20 && c == 0); t.x = "234" t[(1)] = 20 a, b, c = T.testC(` len 2; Llen 2; objsize 2; return 3 `, t) assert(a == "234" && b == 234 && c == 1); t.x = print t[(1)] = 20 a, c = T.testC(` len 2; objsize 2; return 2 `, t) assert(a == print && c == 1); a = setmetatable({ x = "u" }, { __concat = fn (a, b) { return a.x .. '.' .. b.x } }) x, y = T.testC(` pushnum 5 pushvalue 2; pushvalue 2; concat 2; pushvalue -2; return 2; `, a, a) assert(x == a .. a && y == 5); assert(T.testC("concat 0; return 1") == ""); assert(T.testC("concat 1; return 1", "xuxu") == "xuxu"); global fn B(x) { return x && 1 || 0 } global fn count(x, n) { n = n || 2 local prog = ` isnumber %d; isstring %d; isfunction %d; iscfunction %d; istable %d; isuserdata %d; isnil %d; isnull %d; return 8 ` prog = string.format(prog, n, n, n, n, n, n, n, n) local a, b, c, d, e, f, g, h = T.testC(prog, x) return B(a) + B(b) + B(c) + B(d) + B(e) + B(f) + B(g) + (100 * B(h)) } assert(count(3) == 2); assert(count('alo') == 1); assert(count('32') == 2); assert(count({}) == 1); assert(count(print) == 2); assert(count(fn () { }) == 1); assert(count(nil) == 1); assert(count(io.stdin) == 1); assert(count(nil, 15) == 100); global fn to(s, x, n) { n = n || 2 return T.testC(string.format("%s %d; return 1", s, n), x) } local null = T.pushuserdata(0) local hfunc = string.gmatch("", "") assert(debug.getupvalue(hfunc, 1)); assert(to("tostring", {}) == nil); assert(to("tostring", "alo") == "alo"); assert(to("tostring", 12) == "12"); assert(to("tostring", 12, 3) == nil); assert(to("objsize", {}) == 0); assert(to("objsize", { 1, 2, 3 }) == 3); assert(to("objsize", "alo\0\0a") == 6); assert(to("objsize", T.newuserdata(0)) == 0); assert(to("objsize", T.newuserdata(101)) == 101); assert(to("objsize", 124) == 0); assert(to("objsize", true) == 0); assert(to("tonumber", {}) == 0); assert(to("tonumber", "12") == 12); assert(to("tonumber", "s2") == 0); assert(to("tonumber", 1, 20) == 0); assert(to("topointer", 10) == null); assert(to("topointer", true) == null); assert(to("topointer", nil) == null); assert(to("topointer", "abc") != null); assert(to("topointer", string.rep("x", 10)) == to("topointer", string.rep("x", 10))); { local s1 = string.rep("x", 300) local s2 = string.rep("x", 300) assert(to("topointer", s1) != to("topointer", s2)); } assert(to("topointer", T.pushuserdata(20)) != null); assert(to("topointer", io.read) != null); assert(to("topointer", hfunc) != null); assert(to("topointer", fn () { }) != null); assert(to("topointer", io.stdin) != null); assert(to("func2num", 20) == 0); assert(to("func2num", T.pushuserdata(10)) == 0); assert(to("func2num", io.read) != 0); assert(to("func2num", hfunc) != 0); a = to("tocfunction", math.deg) assert(a(3) == math.deg(3) && a == math.deg); print("testing panic function"); { assert(T.checkpanic("pushstring hi; error") == "hi"); assert(T.checkpanic("pushstring hi; error;", `checkstack 5 XX pushstring ' alo' pushstring ' mundo' concat 3`) == "hi alo mundo"); assert(T.checkpanic("loadstring 4") == "bad argument #4 (string expected, got no value)"); T.totalmem(T.totalmem() + 10000); assert(T.checkpanic("newuserdata 20000") == MEMERRMSG); T.totalmem(0); if !_soft { local msg = T.checkpanic([[ pushstring "function f() f() end" loadstring -1; call 0 0 getglobal f; call 0 0 ]]) assert(string.find(msg, "stack overflow")); } assert(T.checkpanic(` pushstring "return {__close = function () Y = 'ho'; end}" newtable loadstring -2 call 0 1 setmetatable -2 toclose -1 pushstring "hi" error `, ` getglobal Y concat 2 # concat original error with global Y `) == "hiho"); } if !_soft { print("testing stack overflow"); collectgarbage("stop"); checkerr("XXXX", T.testC, "checkstack 1000023 XXXX"); checkerr("^stack overflow$", T.testC, "checkstack 1000023 ''"); local s = string.rep("pushnil;checkstack 1 XX;", 1000000) checkerr("overflow", T.testC, s); collectgarbage("restart"); print('+'); } local lim = _soft && 500 || 12000 local prog = { "checkstack " .. (lim * 2 + 100) .. "msg", "newtable" } for i = 1, lim { prog[(#prog + 1)] = "pushnum " .. i prog[(#prog + 1)] = "pushnum " .. i * 10 } prog[(#prog + 1)] = "rawgeti R 2" prog[(#prog + 1)] = "insert " .. -(2 * lim + 2) for i = 1, lim { prog[(#prog + 1)] = "settable " .. -(2 * (lim - i + 1) + 1) } prog[(#prog + 1)] = "return 2" prog = table.concat(prog, ";") local g, t = T.testC(prog) assert(g == _G); for i = 1, lim { assert(t[(i)] == i * 10); t[(i)] = undef } assert(next(t) == nil); prog, g, t = nil a = T.testC(` loadstring 2; pcall 0 1 0; pushvalue 3; insert -2; pcall 1 1 0; pcall 0 0 0; return 1 `, "x=150", fn (a) { assert(a == nil); return 3 }) assert(type(a) == 'string' && x == 150); global fn check3(p, ...) { local arg = { ... } assert(#arg == 3); assert(string.find(arg[(3)], p)); } check3(":1:", T.testC("loadstring 2; return *", "x=")); check3("%.", T.testC("loadfile 2; return *", ".")); check3("xxxx", T.testC("loadfile 2; return *", "xxxx")); global fn checkerrnopro(code, msg) { local th = coroutine.create(fn () { }) local stt, err = pcall(T.testC, th, code) assert(!stt && string.find(err, msg)); } if !_soft { collectgarbage("stop"); checkerrnopro("pushnum 3; call 0 0", "attempt to call"); print("testing stack overflow in unprotected thread"); global fn f() { f(); } checkerrnopro("getglobal 'f'; call 0 0;", "stack overflow"); collectgarbage("restart"); } print("+"); { local a = {} local a1 = T.testC("rawsetp 2 1; return 1", a, 20) assert(a == a1); assert(a[(T.pushuserdata(1))] == 20); local a1, res = T.testC("rawgetp -1 1; return 2", a) assert(a == a1 && res == 20); } { local a = {} a[(a)] = 10 local prog = "gettable -1; return *" local res = { T.testC(prog, a) } assert(#res == 2 && res[(1)] == prog && res[(2)] == 10); local prog = "settable -2; return *" local res = { T.testC(prog, a, 20) } assert(a[(a)] == 20); assert(#res == 1 && res[(1)] == prog); a[(a)] = 10 local prog = "rawget -1; return *" local res = { T.testC(prog, a) } assert(#res == 2 && res[(1)] == prog && res[(2)] == 10); local prog = "rawset -2; return *" local res = { T.testC(prog, a, 20) } assert(a[(a)] == 20); assert(#res == 1 && res[(1)] == prog); local prog = "rawset -1; return *" local res = { T.testC(prog, 30, a) } assert(a[(30)] == a); assert(#res == 1 && res[(1)] == prog); local prog = "settable -1; return *" local res = { T.testC(prog, 40, a) } assert(a[(40)] == a); assert(#res == 1 && res[(1)] == prog); local prog = "rawseti -1 100; return *" local res = { T.testC(prog, a) } assert(a[(100)] == a); assert(#res == 1 && res[(1)] == prog); local prog = "seti -1 200; return *" local res = { T.testC(prog, a) } assert(a[(200)] == a); assert(#res == 1 && res[(1)] == prog); } a = { x = 0, y = 12 } x, y = T.testC("gettable 2; pushvalue 4; gettable 2; return 2", a, 3, "y", 4, "x") assert(x == 0 && y == 12); T.testC("settable -5", a, 3, 4, "x", 15); assert(a.x == 15); a[(a)] = print x = T.testC("gettable 2; return 1", a) assert(x == print); T.testC("settable 2", a, "x"); assert(a[(a)] == "x"); b = setmetatable({ p = a }, {}) getmetatable(b).__index = fn (t, i) { return t.p[(i)] } k, x = T.testC("gettable 3, return 2", 4, b, 20, 35, "x") assert(x == 15 && k == 35); k = T.testC("getfield 2 y, return 1", b) assert(k == 12); getmetatable(b).__index = fn (t, i) { return a[(i)] } getmetatable(b).__newindex = fn (t, i, v) { a[(i)] = v } y = T.testC("insert 2; gettable -5; return 1", 2, 3, 4, "y", b) assert(y == 12); k = T.testC("settable -5, return 1", b, 3, 4, "x", 16) assert(a.x == 16 && k == 4); a[(b)] = 'xuxu' y = T.testC("gettable 2, return 1", b) assert(y == 'xuxu'); T.testC("settable 2", b, 19); assert(a[(b)] == 19); { local t = { _012345678901234567890123456789012345678901234567890123456789 = 32 } local a = T.testC(` getfield 2 _012345678901234567890123456789012345678901234567890123456789 return 1 `, t) assert(a == 32); local a = T.testC(` pushnum 33 setglobal _012345678901234567890123456789012345678901234567890123456789 `) assert(_012345678901234567890123456789012345678901234567890123456789 == 33); _012345678901234567890123456789012345678901234567890123456789 = nil } a = {} t = pack(T.testC("next; return *", a, nil)) tcheck(t, { n = 1, a }); a = { a = 3 } t = pack(T.testC("next; return *", a, nil)) tcheck(t, { n = 3, a, 'a', 3 }); t = pack(T.testC("next; pop 1; next; return *", a, nil)) tcheck(t, { n = 1, a }); { local A = T.testC([[ pushnum 10; pushnum 20; pushcclosure 2; return 1]]) t, b, c = A(`pushvalue U0; pushvalue U1; pushvalue U2; return 3`) assert(b == 10 && c == 20 && type(t) == 'table'); a, b = A(`tostring U3; tonumber U4; return 2`) assert(a == nil && b == 0); A(`pushnum 100; pushnum 200; replace U2; replace U1`); b, c = A(`pushvalue U1; pushvalue U2; return 2`) assert(b == 100 && c == 200); A(`replace U2; replace U1`, { x = 1 }, { x = 2 }); b, c = A(`pushvalue U1; pushvalue U2; return 2`) assert(b.x == 1 && c.x == 2); T.checkmemory(); } assert(T.testC([[isnull U1; return 1]]) == true); assert(T.testC([[isnull U100; return 1]]) == true); assert(T.testC([[pushvalue U1; return 1]]) == nil); local f = T.testC([[ pushnum 10; pushnum 20; pushcclosure 2; return 1]]) assert(T.upvalue(f, 1) == 10 && T.upvalue(f, 2) == 20 && T.upvalue(f, 3) == nil); T.upvalue(f, 2, "xuxu"); assert(T.upvalue(f, 2) == "xuxu"); { local A = "checkstack 300 msg;" .. string.rep("pushnum 10;", 255) .. "pushcclosure 255; return 1" A = T.testC(A) for i = 1, 255 { assert(A(("pushvalue U%d; return 1")::format(i)) == 10); } assert(A("isnull U256; return 1")); assert(!A("isnil U256; return 1")); } checkerr("got number", debug.setuservalue, 3, {}); checkerr("got nil", debug.setuservalue, nil, {}); checkerr("got light userdata", debug.setuservalue, T.pushuserdata(1), {}); local b = T.newuserdata(0, 10) for i = 1, 10 { local v, p = debug.getuservalue(b, i) assert(v == nil && p); } { local v, p = debug.getuservalue(b, -2) assert(v == nil && !p); local v, p = debug.getuservalue(b, 11) assert(v == nil && !p); } local t = { true, false, 4.56, print, {}, b, "XYZ" } for k, v with ipairs(t) { debug.setuservalue(b, v, k); } for k, v with ipairs(t) { local v1, p = debug.getuservalue(b, k) assert(v1 == v && p); } assert(!debug.getuservalue(4)); debug.setuservalue(b, fn () { return 10 }, 10); collectgarbage(); assert(debug.getuservalue(b, 10)() == 10); debug.setuservalue(b, 134); collectgarbage(); assert(debug.getuservalue(b) == 134); { local oldmode = collectgarbage("incremental") T.gcstate("atomic"); assert(T.gccolor(b) == "black"); debug.setuservalue(b, { x = 100 }); T.gcstate("pause"); assert(debug.getuservalue(b).x == 100); collectgarbage(oldmode); } for i = 1, 1000 { local bb = T.newuserdata(0, 1) debug.setuservalue(bb, b); b = bb } collectgarbage(); for i = 1, 1000 { b = debug.getuservalue(b) } assert(debug.getuservalue(b).x == 100); b = nil local i = T.ref({}) T.unref(i); assert(T.ref({}) == i); Arr = {} Lim = 100 for i = 1, Lim { Arr[(i)] = T.ref({}) } assert(T.ref(nil) == -1 && T.getref(-1) == nil); T.unref(-1); T.unref(-1); for i = 1, Lim { T.unref(Arr[(i)]); } global fn printlocks() { local f = T.makeCfunc("gettable R; return 1") local n = f("n") print("n", n); for i = 0, n { print(i, f(i)); } } for i = 1, Lim { Arr[(i)] = T.ref({}) } for i = 1, Lim, 2 { T.unref(Arr[(i)]); } assert(type(T.getref(Arr[(2)])) == 'table'); assert(T.getref(-1) == nil); a = T.ref({}) collectgarbage(); assert(type(T.getref(a)) == 'table'); tt = {} cl = { n = 0 } A = nil B = nil local F F = fn (x) { local udval = T.udataval(x) table.insert(cl, udval); local d = T.newuserdata(100) d = nil assert(debug.getmetatable(x).__gc == F); assert(load("table.insert({}, {})"))(); assert(!collectgarbage()); local dummy = {} if A != nil { assert(type(A) == "userdata"); assert(T.udataval(A) == B); debug.getmetatable(A); } A = x B = udval return 1, 2, 3 } tt.__gc = F { collectgarbage(); collectgarbage(); local x = collectgarbage("count") local a = T.newuserdata(5001) assert(T.testC("objsize 2; return 1", a) == 5001); assert(collectgarbage("count") >= x + 4); a = nil collectgarbage(); assert(collectgarbage("count") <= x + 1); x = collectgarbage("count") collectgarbage("stop"); for i = 1, 1000 { T.newuserdata(0); } assert(collectgarbage("count") > x + 10); collectgarbage(); assert(collectgarbage("count") <= x + 1); collectgarbage(); x = collectgarbage("count") collectgarbage("stop"); a = { __gc = fn () { } } for i = 1, 1000 { debug.setmetatable(T.newuserdata(0), a); } assert(collectgarbage("count") >= x + 10); collectgarbage(); assert(collectgarbage("count") >= x + 10); collectgarbage(); assert(collectgarbage("count") <= x + 1); collectgarbage("restart"); } collectgarbage("stop"); a = T.newuserdata(0) debug.setmetatable(a, tt); na = T.udataval(a) b = T.newuserdata(0) debug.setmetatable(b, tt); nb = T.udataval(b) c = T.newuserdata(0) debug.setmetatable(c, tt); nc = T.udataval(c) x = T.newuserdata(4) y = T.newuserdata(0) checkerr("FILE%* expected, got userdata", io.input, a); checkerr("FILE%* expected, got userdata", io.input, x); assert(debug.getmetatable(x) == nil && debug.getmetatable(y) == nil); d = T.ref(a) e = T.ref(b) f = T.ref(c) t = { T.getref(d), T.getref(e), T.getref(f) } assert(t[(1)] == a && t[(2)] == b && t[(3)] == c); t = nil a = nil c = nil T.unref(e); T.unref(f); collectgarbage(); assert(#cl == 1 && cl[(1)] == nc); x = T.getref(d) assert(type(x) == 'userdata' && debug.getmetatable(x) == tt); x = nil tt.b = b tt = nil A = nil b = nil T.unref(d); n5 = T.newuserdata(0) debug.setmetatable(n5, { __gc = F }); n5 = T.udataval(n5) collectgarbage(); assert(#cl == 4); assert(cl[(2)] == n5 && cl[(3)] == nb && cl[(4)] == na); collectgarbage("restart"); a, na = {}, {} for i = 30, 1, -1 { a[(i)] = T.newuserdata(0) debug.setmetatable(a[(i)], { __gc = F }); na[(i)] = T.udataval(a[(i)]) } cl = {} a = nil collectgarbage(); assert(#cl == 30); for i = 1, 30 { assert(cl[(i)] == na[(i)]); } na = nil for i = 2, Lim, 2 { T.unref(Arr[(i)]); } x = T.newuserdata(41) debug.setmetatable(x, { __gc = F }); assert(T.testC("objsize 2; return 1", x) == 41); cl = {} a = { x = 1 } x = T.udataval(x) collectgarbage(); assert(#cl == 0); for n with pairs(a) { a[(n)] = undef } collectgarbage(); assert(#cl == 1 && cl[(1)] == x); assert(T.testC("compare EQ 2 4; return 1", print, 1, print, 20)); assert(T.testC("compare EQ 3 2; return 1", 'alo', "alo")); assert(T.testC("compare EQ 2 3; return 1", nil, nil)); assert(!T.testC("compare EQ 2 3; return 1", {}, {})); assert(!T.testC("compare EQ 2 3; return 1")); assert(!T.testC("compare EQ 2 3; return 1", 3)); { local map = {} local t = { __eq = fn (a, b) { return map[(a)] == map[(b)] } } local fn f(x) { local u = T.newuserdata(0) debug.setmetatable(u, t); map[(u)] = x return u } assert(f(10) == f(10)); assert(f(10) != f(11)); assert(T.testC("compare EQ 2 3; return 1", f(10), f(10))); assert(!T.testC("compare EQ 2 3; return 1", f(10), f(20))); t.__eq = nil assert(f(10) != f(10)); } print('+'); _G.t = {} T.sethook(` # set a line hook after 3 count hooks sethook 4 0 ' getglobal t; pushvalue -3; append -2 pushvalue -2; append -2 '`, "c", 3); local a = 1 a = 1 a = 1 a = 1 a = 1 debug.sethook(); t = _G.t assert(t[(1)] == "line"); line = t[(2)] assert(t[(3)] == "line" && t[(4)] == line + 1); assert(t[(5)] == "line" && t[(6)] == line + 2); assert(t[(7)] == nil); { warn("@off"); collectgarbage("stop"); local a = {} for i = 1, 20 { a[(i)] = T.newuserdata(i) } for i = 1, 20, 2 { debug.setmetatable(a[(i)], { __gc = fn (x) { error("@expected error in gc"); } }); } for i = 2, 20, 2 { debug.setmetatable(a[(i)], { __gc = fn (x) { load("A=A+1")(); } }); } a = nil _G.A = 0 collectgarbage(); assert(A == 10); collectgarbage("restart"); warn("@on"); } { local a = {} local lim = 30 for i = 0, lim { a[(i)] = T.pushuserdata(i) } for i = 0, lim { assert(T.udataval(a[(i)]) == i); } for i = 0, lim { assert(T.pushuserdata(i) == a[(i)]); } for i = 0, lim { a[(a[(i)])] = i } for i = 0, lim { a[(T.pushuserdata(i))] = i } assert(type(tostring(a[(1)])) == "string"); } T.closestate(T.newstate()); L1 = T.newstate() assert(L1); assert(T.doremote(L1, "X='a'; return 'a'") == 'a'); assert(#pack(T.doremote(L1, "function f () return 'alo', 3 end; f()")) == 0); a, b = T.doremote(L1, "return f()") assert(a == 'alo' && b == '3'); T.doremote(L1, "_ERRORMESSAGE = nil"); a, _, b = T.doremote(L1, "return sin(1)") assert(a == nil && b == 2); a, b, c = T.doremote(L1, "return a+") assert(a == nil && c == 3 && type(b) == "string"); T.loadlib(L1); a, b, c = T.doremote(L1, ` string = require'string' a = require'_G'; assert(a == _G and require("_G") == a) io = require'io'; assert(type(io.read) == "function") assert(require("io") == io) a = require'table'; assert(type(a.insert) == "function") a = require'debug'; assert(type(a.getlocal) == "function") a = require'math'; assert(type(a.sin) == "function") return string.sub('okinama', 1, 2) `) assert(a == "ok"); T.closestate(L1); L1 = T.newstate() T.loadlib(L1); T.doremote(L1, "a = {}"); T.testC(L1, `getglobal "a"; pushstring "x"; pushint 1; settable -3`); assert(T.doremote(L1, "return a.x") == "1"); T.closestate(L1); L1 = nil print('+'); print("testing to-be-closed variables"); { local openresource = {} local fn newresource() { local x = setmetatable({ 10 }, { __close = fn (y) { assert(openresource[(#openresource)] == y); openresource[(#openresource)] = nil y[(1)] = y[(1)] + 1 } }) openresource[(#openresource + 1)] = x return x } local a, b = T.testC(` call 0 1 # create resource pushnil toclose -2 # mark call result to be closed toclose -1 # mark nil to be closed (will be ignored) return 2 `, newresource) assert(a[(1)] == 11 && b == nil); assert(#openresource == 0); local a = { T.testC(` call 0 1 # create resource toclose 2 # mark it to be closed return 2 `, newresource) } assert(type(a[(1)]) == "string" && a[(2)][(1)] == 11); assert(#openresource == 0); local a, b = pcall(T.makeCfunc([[ call 0 1 # create resource toclose -1 # mark it to be closed error # resource is the error object ]]), newresource) assert(a == false && b[(1)] == 11); assert(#openresource == 0); local a, b = pcall(T.makeCfunc([[ newtable # create non-closable object toclose -1 # mark it to be closed (should raise an error) abort # will not be executed ]])) assert(a == false && string.find(b, "non%-closable value")); local fn check(n) { assert(#openresource == n); } _ENV.xxx = true local a = T.testC(` pushvalue 2 # stack: S, NR, CH, NR call 0 1 # create resource; stack: S, NR, CH, R toclose -1 # mark it to be closed pushvalue 2 # stack: S, NR, CH, R, NR call 0 1 # create another resource; stack: S, NR, CH, R, R toclose -1 # mark it to be closed pushvalue 3 # stack: S, NR, CH, R, R, CH pushint 2 # there should be two open resources call 1 0 # stack: S, NR, CH, R, R closeslot -1 # close second resource pushvalue 3 # stack: S, NR, CH, R, R, CH pushint 1 # there should be one open resource call 1 0 # stack: S, NR, CH, R, R closeslot 4 setglobal "xxx" # previous op. erased the slot pop 1 # pop other resource from the stack pushint * return 1 # return stack size `, newresource, check) assert(a == 3 && _ENV.xxx == nil); local a = T.testC(` pushvalue 2 # stack: S, NR, CH, NR call 0 1 # create resource; stack: S, NR, CH, R toclose -1 # mark it to be closed pushvalue 2 # stack: S, NR, CH, R, NR call 0 1 # create another resource; stack: S, NR, CH, R, R toclose -1 # mark it to be closed pushvalue 3 # stack: S, NR, CH, R, R, CH pushint 2 # there should be two open resources call 1 0 # stack: S, NR, CH, R, R pop 1 # pop second resource pushvalue 3 # stack: S, NR, CH, R, CH pushint 1 # there should be one open resource call 1 0 # stack: S, NR, CH, R pop 1 # pop other resource from the stack pushvalue 3 # stack: S, NR, CH, CH pushint 0 # there should be no open resources call 1 0 # stack: S, NR, CH pushint * return 1 # return stack size `, newresource, check) assert(a == 3); local a, b = pcall(T.makeCfunc([[ pushint 32 toclose -1 ]])) assert(!a && string.find(b, "(C temporary)")); } print("memory-allocation errors"); checkerr("block too big", T.newuserdata, math.maxinteger); collectgarbage(); local f = load("local a={}; for i=1,100000 do a[i]=i end") T.alloccount(10); checkerr(MEMERRMSG, f); T.alloccount(); global fn testbytes(s, f) { collectgarbage(); local M = T.totalmem() local oldM = M local a, b = nil while true { collectgarbage(); collectgarbage(); T.totalmem(M); a, b = T.testC("pcall 0 1 0; pushstatus; return 2", f) T.totalmem(0); if a && b == "OK" { break } if b != "OK" && b != MEMERRMSG { error(a, 0); } M = M + 7 } print(string.format("minimum memory for %s: %d bytes", s, M - oldM)); return a } global fn testalloc(s, f) { collectgarbage(); local M = 0 local a, b = nil while true { collectgarbage(); collectgarbage(); T.alloccount(M); a, b = T.testC("pcall 0 1 0; pushstatus; return 2", f) T.alloccount(); if a && b == "OK" { break } if b != "OK" && b != MEMERRMSG { error(a, 0); } M = M + 1 } print(string.format("minimum allocations for %s: %d allocations", s, M)); return a } local fn testamem(s, f) { testalloc(s, f); return testbytes(s, f) } b = testamem("doing nothing", fn () { return 10 }) assert(b == 10); testamem("state creation", fn () { local st = T.newstate() if st { T.closestate(st); } return st }); testamem("empty-table creation", fn () { return {} }); testamem("string creation", fn () { return "XXX" .. "YYY" }); testamem("coroutine creation", fn () { return coroutine.create(print) }); testamem("to-be-closed variables", fn () { local flag { local x = setmetatable({}, { __close = fn () { flag = true } }) flag = false local x = {} getmetatable(x).__close(x); } return flag }); mt = T.testC("rawgeti R 1; return 1") assert(type(mt) == "thread" && coroutine.running() == mt); global fn expand(n, s) { if n == 0 { return "" } local e = string.rep("=", n) return string.format("T.doonnewstack([%s[ %s;\n collectgarbage(); %s]%s])\n", e, s, expand(n - 1, s), e) } G = 0 collectgarbage(); a = collectgarbage("count") load(expand(20, "G=G+1"))(); assert(G == 20); collectgarbage(); testamem("running code on new thread", fn () { return T.doonnewstack("x=1") == 0 }); testamem("loadstring", fn () { return load("x=1") }); local testprog = ` local function foo () return end local t = {"x"} a = "aaa" for i = 1, #t do a=a..t[i] end return true ` _G.a = nil local t = os.tmpname() local f = assert(io.open(t, "w")) f::write(testprog); f::close(); testamem("dofile", fn () { local a = loadfile(t) return a && a() }); assert(os.remove(t)); assert(_G.a == "aaax"); testamem("gsub", fn () { local a, b = string.gsub("alo alo", "(a)", fn (x) { return x .. 'b' }) return (a == 'ablo ablo') }); testamem("dump/undump", fn () { local a = load(testprog) local b = a && string.dump(a) a = b && load(b) return a && a() }); local t = os.tmpname() testamem("file creation", fn () { local f = assert(io.open(t, 'w')) assert(!io.open("nomenaoexistente")); io.close(f); return !loadfile('nomenaoexistente') }); assert(os.remove(t)); testamem("table creation", fn () { local a, lim = {}, 10 for i = 1, lim { a[(i)] = i a[(i .. 'a')] = {} } return (type(a[(lim .. 'a')]) == 'table' && a[(lim)] == lim) }); testamem("constructors", fn () { local a = { 10, 20, 30, 40, 50, a = 1, b = 2, c = 3, d = 4, e = 5 } return (type(a) == 'table' && a.e == 5) }); local a = 1 close = nil testamem("closure creation", fn () { global fn close(b) { return fn (x) { return b + x } } return (close(2)(4) == 6) }); testamem("using coroutines", fn () { local a = coroutine.wrap(fn () { coroutine.yield(string.rep("a", 10)); return {} }) assert(string.len(a()) == 10); return a() }); { local lim = 100 local a = {} for i = 1, lim { a[(i)] = "01234567890123456789" } testamem("auxiliary buffer", fn () { return (#table.concat(a, ",") == 20 * lim + lim - 1) }); } testamem("growing stack", fn () { local fn foo(n) { if n == 0 { return 1 } else { return 1 + foo(n - 1) } } return foo(100) }); { local res = T.testC(`rawcheckstack 500000; return 1`) assert(res == false); local L = T.newstate() T.alloccount(0); res = T.testC(L, `rawcheckstack 5000; return 1`) T.alloccount(); T.closestate(L); assert(res == false); } { local L = T.newstate() T.alloccount(0); T.closestate(L); T.alloccount(); } { local L = T.newstate() T.loadlib(L); local res = (T.doremote(L, ` _ENV = require"_G" local T = require"T" local a = {} for i = 1, 1000 do a[i] = 'i' .. i end -- grow string table local stsize, stuse = T.querystr() assert(stuse > 1000) local function foo (n) if n > 0 then foo(n - 1) end end foo(180) -- grow stack local _, stksize = T.stacklevel() assert(stksize > 180) a = nil T.alloccount(0) collectgarbage() T.alloccount() -- stack and string table could not be reallocated, -- so they kept their sizes (without errors) assert(select(2, T.stacklevel()) == stksize) assert(T.querystr() == stsize) return 'ok' `)) assert(res == 'ok'); T.closestate(L); } print('+'); local fn gsub(a, b, c) { a, b = T.testC("gsub 2 3 4; gettop; return 2", a, b, c) assert(b == 5); return a } assert(gsub("alo.alo.uhuh.", ".", "//") == "alo//alo//uhuh//"); assert(gsub("alo.alo.uhuh.", "alo", "//") == "//.//.uhuh."); assert(gsub("", "alo", "//") == ""); assert(gsub("...", ".", "/.") == "/././."); assert(gsub("...", "...", "") == ""); local mt_xuxu, res, top = T.testC("newmetatable xuxu; gettop; return 3") assert(type(mt_xuxu) == "table" && res && top == 3); local d, res, top = T.testC("newmetatable xuxu; gettop; return 3") assert(mt_xuxu == d && !res && top == 3); d, res, top = T.testC("newmetatable xuxu1; gettop; return 3") assert(mt_xuxu != d && res && top == 3); x = T.newuserdata(0) y = T.newuserdata(0) T.testC("pushstring xuxu; gettable R; setmetatable 2", x); assert(getmetatable(x) == mt_xuxu); local res1, res2, top = T.testC(`testudata -1 xuxu testudata 2 xuxu gettop return 3`, x) assert(res1 && res2 && top == 4); res1, res2, top = T.testC(`testudata -1 xuxu1 testudata 2 xuxu1 gettop return 3`, x) assert(!res1 && !res2 && top == 4); res1, res2, top = T.testC(`testudata -1 xuxu2 testudata 2 xuxu2 gettop return 3`, x) assert(!res1 && !res2 && top == 4); res1, res2, top = T.testC(`testudata -1 xuxu testudata 2 xuxu gettop return 3`, y) assert(!res1 && !res2 && top == 4); { local r = debug.getregistry() assert(r.xuxu == mt_xuxu && r.xuxu1 == d); r.xuxu = nil r.xuxu1 = nil } print('OK');