diff options
| author | 2025-12-07 12:44:18 +0100 | |
|---|---|---|
| committer | 2025-12-07 12:44:18 +0100 | |
| commit | 6a335e2b7d47afdb5e37034a095aaa26a34d7ee1 (patch) | |
| tree | b7aae9d679df13ce04f695670e80a8e7a73361c9 /test/external/lua-5.4.0-tests/locals.lua | |
| parent | bfc6b66078ab3ec543b8355ad22df1c423bf9a8c (diff) | |
Add lua 5.4.0 test suite
Diffstat (limited to 'test/external/lua-5.4.0-tests/locals.lua')
| -rw-r--r-- | test/external/lua-5.4.0-tests/locals.lua | 736 |
1 files changed, 736 insertions, 0 deletions
diff --git a/test/external/lua-5.4.0-tests/locals.lua b/test/external/lua-5.4.0-tests/locals.lua new file mode 100644 index 0000000..0e5e0c7 --- /dev/null +++ b/test/external/lua-5.4.0-tests/locals.lua @@ -0,0 +1,736 @@ +-- $Id: testes/locals.lua $ +-- See Copyright Notice in file all.lua + +print('testing local variables and environments') + +local debug = require"debug" + + +-- bug in 5.1: + +local function f(x) x = nil; return x end +assert(f(10) == nil) + +local function f() local x; return x end +assert(f(10) == nil) + +local function f(x) x = nil; local y; return x, y end +assert(f(10) == nil and select(2, f(20)) == nil) + +do + local i = 10 + do local i = 100; assert(i==100) end + do local i = 1000; assert(i==1000) end + assert(i == 10) + if i ~= 10 then + local i = 20 + else + local i = 30 + assert(i == 30) + end +end + + + +f = nil + +local f +x = 1 + +a = nil +load('local a = {}')() +assert(a == nil) + +function f (a) + local _1, _2, _3, _4, _5 + local _6, _7, _8, _9, _10 + local x = 3 + local b = a + local c,d = a,b + if (d == b) then + local x = 'q' + x = b + assert(x == 2) + else + assert(nil) + end + assert(x == 3) + local f = 10 +end + +local b=10 +local a; repeat local b; a,b=1,2; assert(a+1==b); until a+b==3 + + +assert(x == 1) + +f(2) +assert(type(f) == 'function') + + +local function getenv (f) + local a,b = debug.getupvalue(f, 1) + assert(a == '_ENV') + return b +end + +-- test for global table of loaded chunks +assert(getenv(load"a=3") == _G) +local c = {}; local f = load("a = 3", nil, nil, c) +assert(getenv(f) == c) +assert(c.a == nil) +f() +assert(c.a == 3) + +-- old test for limits for special instructions +do + local i = 2 + local p = 4 -- p == 2^i + repeat + for j=-3,3 do + assert(load(string.format([[local a=%s; + a=a+%s; + assert(a ==2^%s)]], j, p-j, i), '')) () + assert(load(string.format([[local a=%s; + a=a-%s; + assert(a==-2^%s)]], -j, p-j, i), '')) () + assert(load(string.format([[local a,b=0,%s; + a=b-%s; + assert(a==-2^%s)]], -j, p-j, i), '')) () + end + p = 2 * p; i = i + 1 + until p <= 0 +end + +print'+' + + +if rawget(_G, "T") then + -- testing clearing of dead elements from tables + collectgarbage("stop") -- stop GC + local a = {[{}] = 4, [3] = 0, alo = 1, + a1234567890123456789012345678901234567890 = 10} + + local t = T.querytab(a) + + for k,_ in pairs(a) do a[k] = undef end + collectgarbage() -- restore GC and collect dead fields in 'a' + for i=0,t-1 do + local k = querytab(a, i) + assert(k == nil or type(k) == 'number' or k == 'alo') + end + + -- testing allocation errors during table insertions + local a = {} + local function additems () + a.x = true; a.y = true; a.z = true + a[1] = true + a[2] = true + end + for i = 1, math.huge do + T.alloccount(i) + local st, msg = pcall(additems) + T.alloccount() + local count = 0 + for k, v in pairs(a) do + assert(a[k] == v) + count = count + 1 + end + if st then assert(count == 5); break end + end +end + + +-- testing lexical environments + +assert(_ENV == _G) + +do +local dummy +local _ENV = (function (...) return ... end)(_G, dummy) -- { + +do local _ENV = {assert=assert}; assert(true) end +mt = {_G = _G} +local foo,x +A = false -- "declare" A +do local _ENV = mt + function foo (x) + A = x + do local _ENV = _G; A = 1000 end + return function (x) return A .. x end + end +end +assert(getenv(foo) == mt) +x = foo('hi'); assert(mt.A == 'hi' and A == 1000) +assert(x('*') == mt.A .. '*') + +do local _ENV = {assert=assert, A=10}; + do local _ENV = {assert=assert, A=20}; + assert(A==20);x=A + end + assert(A==10 and x==20) +end +assert(x==20) + + +do -- constants + local a<const>, b, c<const> = 10, 20, 30 + b = a + c + b -- 'b' is not constant + assert(a == 10 and b == 60 and c == 30) + local function checkro (name, code) + local st, msg = load(code) + local gab = string.format("attempt to assign to const variable '%s'", name) + assert(not st and string.find(msg, gab)) + end + checkro("y", "local x, y <const>, z = 10, 20, 30; x = 11; y = 12") + checkro("x", "local x <const>, y, z <const> = 10, 20, 30; x = 11") + checkro("z", "local x <const>, y, z <const> = 10, 20, 30; y = 10; z = 11") + + checkro("z", [[ + local a, z <const>, b = 10; + function foo() a = 20; z = 32; end + ]]) + + checkro("var1", [[ + local a, var1 <const> = 10; + function foo() a = 20; z = function () var1 = 12; end end + ]]) +end + + +print"testing to-be-closed variables" + +local function stack(n) n = ((n == 0) or stack(n - 1)) end + +local function func2close (f, x, y) + local obj = setmetatable({}, {__close = f}) + if x then + return x, obj, y + else + return obj + end +end + + +do + local a = {} + do + local b <close> = false -- not to be closed + local x <close> = setmetatable({"x"}, {__close = function (self) + a[#a + 1] = self[1] end}) + local w, y <close>, z = func2close(function (self, err) + assert(err == nil); a[#a + 1] = "y" + end, 10, 20) + local c <close> = nil -- not to be closed + a[#a + 1] = "in" + assert(w == 10 and z == 20) + end + a[#a + 1] = "out" + assert(a[1] == "in" and a[2] == "y" and a[3] == "x" and a[4] == "out") +end + +do + local X = false + + local x, closescope = func2close(function () stack(10); X = true end, 100) + assert(x == 100); x = 101; -- 'x' is not read-only + + -- closing functions do not corrupt returning values + local function foo (x) + local _ <close> = closescope + return x, X, 23 + end + + local a, b, c = foo(1.5) + assert(a == 1.5 and b == false and c == 23 and X == true) + + X = false + foo = function (x) + local _<close> = closescope + local y = 15 + return y + end + + assert(foo() == 15 and X == true) + + X = false + foo = function () + local x <close> = closescope + return x + end + + assert(foo() == closescope and X == true) + +end + + +-- testing to-be-closed x compile-time constants +-- (there were some bugs here in Lua 5.4-rc3, due to a confusion +-- between compile levels and stack levels of variables) +do + local flag = false + local x = setmetatable({}, + {__close = function() assert(flag == false); flag = true end}) + local y <const> = nil + local z <const> = nil + do + local a <close> = x + end + assert(flag) -- 'x' must be closed here +end + +do + -- similar problem, but with implicit close in for loops + local flag = false + local x = setmetatable({}, + {__close = function () assert(flag == false); flag = true end}) + -- return an empty iterator, nil, nil, and 'x' to be closed + local function a () + return (function () return nil end), nil, nil, x + end + local v <const> = 1 + local w <const> = 1 + local x <const> = 1 + local y <const> = 1 + local z <const> = 1 + for k in a() do + a = k + end -- ending the loop must close 'x' + assert(flag) -- 'x' must be closed here +end + + + +do + -- calls cannot be tail in the scope of to-be-closed variables + local X, Y + local function foo () + local _ <close> = func2close(function () Y = 10 end) + assert(X == true and Y == nil) -- 'X' not closed yet + return 1,2,3 + end + + local function bar () + local _ <close> = func2close(function () X = false end) + X = true + do + return foo() -- not a tail call! + end + end + + local a, b, c, d = bar() + assert(a == 1 and b == 2 and c == 3 and X == false and Y == 10 and d == nil) +end + + +-- auxiliary functions for testing warnings in '__close' +local function prepwarn () + if not T then -- no test library? + warn("@off") -- do not show (lots of) warnings + else + warn("@store") -- to test the warnings + end +end + + +local function endwarn () + if not T then + warn("@on") -- back to normal + else + assert(_WARN == nil) + warn("@normal") + end +end + + +local function checkwarn (msg) + if T then + assert(string.find(_WARN, msg)) + _WARN = nil -- reset variable to check next warning + end +end + +warn("@on") + +do print("testing errors in __close") + + prepwarn() + + -- original error is in __close + local function foo () + + local x <close> = + func2close(function (self, msg) + assert(string.find(msg, "@z")) + error("@x") + end) + + local x1 <close> = + func2close(function (self, msg) + checkwarn("@y") + assert(string.find(msg, "@z")) + end) + + local gc <close> = func2close(function () collectgarbage() end) + + local y <close> = + func2close(function (self, msg) + assert(string.find(msg, "@z")) -- first error in 'z' + checkwarn("@z") -- second error in 'z' generated a warning + error("@y") + end) + + local first = true + local z <close> = + -- 'z' close is called twice + func2close(function (self, msg) + if first then + assert(msg == nil) + first = false + else + assert(string.find(msg, "@z")) -- own error + end + error("@z") + end) + + return 200 + end + + local stat, msg = pcall(foo, false) + assert(string.find(msg, "@z")) + checkwarn("@x") + + + -- original error not in __close + local function foo () + + local x <close> = + func2close(function (self, msg) + assert(msg == 4) + end) + + local x1 <close> = + func2close(function (self, msg) + checkwarn("@y") + assert(msg == 4) + error("@x1") + end) + + local gc <close> = func2close(function () collectgarbage() end) + + local y <close> = + func2close(function (self, msg) + assert(msg == 4) -- error in body + checkwarn("@z") + error("@y") + end) + + local first = true + local z <close> = + func2close(function (self, msg) + -- 'z' close is called once + assert(first and msg == 4) + first = false + error("@z") + end) + + error(4) -- original error + end + + local stat, msg = pcall(foo, true) + assert(msg == 4) + checkwarn("@x1") -- last error + + -- error leaving a block + local function foo (...) + do + local x1 <close> = + func2close(function () + checkwarn("@X") + error("@Y") + end) + + local x123 <close> = + func2close(function () + error("@X") + end) + end + os.exit(false) -- should not run + end + + local st, msg = xpcall(foo, debug.traceback) + assert(string.match(msg, "^[^ ]* @X")) + assert(string.find(msg, "in metamethod 'close'")) + checkwarn("@Y") + + -- error in toclose in vararg function + local function foo (...) + local x123 <close> = func2close(function () error("@x123") end) + end + + local st, msg = xpcall(foo, debug.traceback) + assert(string.match(msg, "^[^ ]* @x123")) + assert(string.find(msg, "in metamethod 'close'")) + checkwarn("@x123") -- from second call to close 'x123' + + endwarn() +end + + +do -- errors due to non-closable values + local function foo () + local x <close> = {} + os.exit(false) -- should not run + end + local stat, msg = pcall(foo) + assert(not stat and + string.find(msg, "variable 'x' got a non%-closable value")) + + local function foo () + local xyz <close> = setmetatable({}, {__close = print}) + getmetatable(xyz).__close = nil -- remove metamethod + end + local stat, msg = pcall(foo) + assert(not stat and + string.find(msg, "attempt to close non%-closable variable 'xyz'")) +end + + +if rawget(_G, "T") then + + warn("@off") + + -- memory error inside closing function + local function foo () + local y <close> = func2close(function () T.alloccount() end) + local x <close> = setmetatable({}, {__close = function () + T.alloccount(0); local x = {} -- force a memory error + end}) + error(1000) -- common error inside the function's body + end + + stack(5) -- ensure a minimal number of CI structures + + -- despite memory error, 'y' will be executed and + -- memory limit will be lifted + local _, msg = pcall(foo) + assert(msg == 1000) + + local close = func2close(function (self, msg) + T.alloccount() + assert(msg == "not enough memory") + end) + + -- set a memory limit and return a closing object to remove the limit + local function enter (count) + stack(10) -- reserve some stack space + T.alloccount(count) + return close + end + + local function test () + local x <close> = enter(0) -- set a memory limit + -- creation of previous upvalue will raise a memory error + assert(false) -- should not run + end + + local _, msg = pcall(test) + assert(msg == "not enough memory") + + -- now use metamethod for closing + close = setmetatable({}, {__close = function () + T.alloccount() + end}) + + -- repeat test with extra closing upvalues + local function test () + local xxx <close> = func2close(function (self, msg) + assert(msg == "not enough memory"); + error(1000) -- raise another error + end) + local xx <close> = func2close(function (self, msg) + assert(msg == "not enough memory"); + end) + local x <close> = enter(0) -- set a memory limit + -- creation of previous upvalue will raise a memory error + os.exit(false) -- should not run + end + + local _, msg = pcall(test) + assert(msg == "not enough memory") -- reported error is the first one + + do -- testing 'toclose' in C string buffer + collectgarbage() + local s = string.rep('a', 10000) -- large string + local m = T.totalmem() + collectgarbage("stop") + s = string.upper(s) -- allocate buffer + new string (10K each) + -- ensure buffer was deallocated + assert(T.totalmem() - m <= 11000) + collectgarbage("restart") + end + + do -- now some tests for freeing buffer in case of errors + local lim = 10000 -- some size larger than the static buffer + local extra = 2000 -- some extra memory (for callinfo, etc.) + + local s = string.rep("a", lim) + + -- concat this table needs two buffer resizes (one for each 's') + local a = {s, s} + + collectgarbage() + + m = T.totalmem() + collectgarbage("stop") + + -- error in the first buffer allocation + T. totalmem(m + extra) + assert(not pcall(table.concat, a)) + -- first buffer was not even allocated + assert(T.totalmem() - m <= extra) + + -- error in the second buffer allocation + T. totalmem(m + lim + extra) + assert(not pcall(table.concat, a)) + -- first buffer was released by 'toclose' + assert(T.totalmem() - m <= extra) + + -- error in creation of final string + T.totalmem(m + 2 * lim + extra) + assert(not pcall(table.concat, a)) + -- second buffer was released by 'toclose' + assert(T.totalmem() - m <= extra) + + -- userdata, upvalue, buffer, buffer, final string + T.totalmem(m + 4*lim + extra) + assert(#table.concat(a) == 2*lim) + + T.totalmem(0) -- remove memory limit + collectgarbage("restart") + + print'+' + end + + warn("@on") +end + + +print "to-be-closed variables in coroutines" + +do + -- an error in a wrapped coroutine closes variables + local x = false + local y = false + local co = coroutine.wrap(function () + local xv <close> = func2close(function () x = true end) + do + local yv <close> = func2close(function () y = true end) + coroutine.yield(100) -- yield doesn't close variable + end + coroutine.yield(200) -- yield doesn't close variable + error(23) -- error does + end) + + local b = co() + assert(b == 100 and not x and not y) + b = co() + assert(b == 200 and not x and y) + local a, b = pcall(co) + assert(not a and b == 23 and x and y) +end + + +do + prepwarn() + + -- error in a wrapped coroutine raising errors when closing a variable + local x = 0 + local co = coroutine.wrap(function () + local xx <close> = func2close(function () x = x + 1; error("@YYY") end) + local xv <close> = func2close(function () x = x + 1; error("@XXX") end) + coroutine.yield(100) + error(200) + end) + assert(co() == 100); assert(x == 0) + local st, msg = pcall(co); assert(x == 2) + assert(not st and msg == 200) -- should get first error raised + checkwarn("@YYY") + + local x = 0 + local y = 0 + co = coroutine.wrap(function () + local xx <close> = func2close(function () y = y + 1; error("YYY") end) + local xv <close> = func2close(function () x = x + 1; error("XXX") end) + coroutine.yield(100) + return 200 + end) + assert(co() == 100); assert(x == 0) + local st, msg = pcall(co) + assert(x == 2 and y == 1) -- first close is called twice + -- should get first error raised + assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX")) + checkwarn("YYY") + + endwarn() +end + + +-- a suspended coroutine should not close its variables when collected +local co +co = coroutine.wrap(function() + -- should not run + local x <close> = func2close(function () os.exit(false) end) + co = nil + coroutine.yield() +end) +co() -- start coroutine +assert(co == nil) -- eventually it will be collected +collectgarbage() + + +-- to-be-closed variables in generic for loops +do + local numopen = 0 + local function open (x) + numopen = numopen + 1 + return + function () -- iteraction function + x = x - 1 + if x > 0 then return x end + end, + nil, -- state + nil, -- control variable + func2close(function () numopen = numopen - 1 end) -- closing function + end + + local s = 0 + for i in open(10) do + s = s + i + end + assert(s == 45 and numopen == 0) + + local s = 0 + for i in open(10) do + if i < 5 then break end + s = s + i + end + assert(s == 35 and numopen == 0) + + local s = 0 + for i in open(10) do + for j in open(10) do + if i + j < 5 then goto endloop end + s = s + i + end + end + ::endloop:: + assert(s == 375 and numopen == 0) +end + +print('OK') + +return 5,f + +end -- } + |