mirror of
https://github.com/lua/lua.git
synced 2025-01-14 05:43:00 +08:00
bfcf06d91a
To allow their use in memory tests, some functions in 'ltests.c' should never allocate memory. To avoid this allocation, the library registers the strings used for status codes, and keeps the variable '_WARN' always defined (with false instead of nil).
737 lines
17 KiB
Lua
737 lines
17 KiB
Lua
-- $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 == false)
|
|
warn("@normal")
|
|
end
|
|
end
|
|
|
|
|
|
local function checkwarn (msg)
|
|
if T then
|
|
assert(string.find(_WARN, msg))
|
|
_WARN = false -- 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 -- }
|
|
|