mirror of
https://github.com/lua/lua
synced 2025-02-16 05:04:08 +03:00
![Roberto Ierusalimschy](/assets/img/avatar_default.png)
The implicit variable 'state' in a generic 'for' is marked as a to-be-closed variable, so that the state will be closed as soon as the loop ends, no matter how. Taking advantage of this new facility, the call 'io.lines(filename)' now returns the open file as a second result. Therefore, an iteraction like 'for l in io.lines(name)...' will close the file even when the loop ends with a break or an error.
417 lines
9.3 KiB
Lua
417 lines
9.3 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 (now just a generic test)
|
|
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 fiels 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)
|
|
|
|
|
|
print"testing to-be-closed variables"
|
|
|
|
local function stack(n) n = ((n == 0) or stack(n - 1)) end
|
|
|
|
|
|
do
|
|
local a = {}
|
|
do
|
|
local scoped x = setmetatable({"x"}, {__close = function (self)
|
|
a[#a + 1] = self[1] end})
|
|
local scoped y = function (x) assert(x == nil); a[#a + 1] = "y" end
|
|
a[#a + 1] = "in"
|
|
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 function closescope () stack(10); X = true end
|
|
|
|
-- closing functions do not corrupt returning values
|
|
local function foo (x)
|
|
local scoped _ = 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 scoped _ = closescope
|
|
local y = 15
|
|
return y
|
|
end
|
|
|
|
assert(foo() == 15 and X == true)
|
|
|
|
X = false
|
|
foo = function ()
|
|
local scoped x = closescope
|
|
return x
|
|
end
|
|
|
|
assert(foo() == closescope and X == true)
|
|
|
|
end
|
|
|
|
|
|
do
|
|
-- to-be-closed variables must be closed in tail calls
|
|
local X, Y
|
|
local function foo ()
|
|
local scoped _ = function () Y = 10 end
|
|
assert(X == 20 and Y == nil)
|
|
return 1,2,3
|
|
end
|
|
|
|
local function bar ()
|
|
local scoped _ = function () X = 20 end
|
|
return foo()
|
|
end
|
|
|
|
local a, b, c, d = bar()
|
|
assert(a == 1 and b == 2 and c == 3 and X == 20 and Y == 10 and d == nil)
|
|
end
|
|
|
|
do -- errors in __close
|
|
local log = {}
|
|
local function foo (err)
|
|
local scoped x = function (msg) log[#log + 1] = msg; error(1) end
|
|
local scoped x1 = function (msg) log[#log + 1] = msg; end
|
|
local scoped gc = function () collectgarbage() end
|
|
local scoped y = function (msg) log[#log + 1] = msg; error(2) end
|
|
local scoped z = function (msg) log[#log + 1] = msg or 10; error(3) end
|
|
if err then error(4) end
|
|
end
|
|
local stat, msg = pcall(foo, false)
|
|
assert(msg == 1)
|
|
assert(log[1] == 10 and log[2] == 3 and log[3] == 2 and log[4] == 2
|
|
and #log == 4)
|
|
|
|
log = {}
|
|
local stat, msg = pcall(foo, true)
|
|
assert(msg == 1)
|
|
assert(log[1] == 4 and log[2] == 3 and log[3] == 2 and log[4] == 2
|
|
and #log == 4)
|
|
end
|
|
|
|
if rawget(_G, "T") then
|
|
-- memory error inside closing function
|
|
local function foo ()
|
|
local scoped y = function () T.alloccount() end
|
|
local scoped x = setmetatable({}, {__close = function ()
|
|
T.alloccount(0); local x = {} -- force a memory error
|
|
end})
|
|
error("a") -- 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 == "not enough memory")
|
|
|
|
local function close (msg)
|
|
T.alloccount()
|
|
assert(msg == "not enough memory")
|
|
end
|
|
|
|
-- set a memory limit and return a closing function to remove the limit
|
|
local function enter (count)
|
|
stack(10) -- reserve some stack space
|
|
T.alloccount(count)
|
|
return close
|
|
end
|
|
|
|
local function test ()
|
|
local scoped x = 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")
|
|
|
|
-- now use metamethod for closing
|
|
close = setmetatable({}, {__close = function ()
|
|
T.alloccount()
|
|
end})
|
|
|
|
-- repeat test with extra closing upvalues
|
|
local function test ()
|
|
local scoped xxx = function (msg)
|
|
assert(msg == "not enough memory");
|
|
error(1000) -- raise another error
|
|
end
|
|
local scoped xx = function (msg)
|
|
assert(msg == "not enough memory");
|
|
end
|
|
local scoped x = 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 == 1000)
|
|
|
|
end
|
|
|
|
|
|
-- to-be-closed variables in coroutines
|
|
do
|
|
-- an error in a coroutine closes variables
|
|
local x = false
|
|
local y = false
|
|
local co = coroutine.create(function ()
|
|
local scoped xv = function () x = true end
|
|
do
|
|
local scoped yv = 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 a, b = coroutine.resume(co)
|
|
assert(a and b == 100 and not x and not y)
|
|
a, b = coroutine.resume(co)
|
|
assert(a and b == 200 and not x and y)
|
|
a, b = coroutine.resume(co)
|
|
assert(not a and b == 23 and x and y)
|
|
end
|
|
|
|
-- a suspended coroutine should not close its variables when collected
|
|
local co
|
|
co = coroutine.wrap(function()
|
|
local scoped x = function () os.exit(false) end -- should not run
|
|
co = nil
|
|
coroutine.yield()
|
|
end)
|
|
co() -- start coroutine
|
|
assert(co == nil) -- eventually it will be collected
|
|
|
|
|
|
-- 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,
|
|
function () -- closing function
|
|
numopen = numopen - 1
|
|
end
|
|
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)
|
|
|
|
-- repeat test with '__open' metamethod instead of a function
|
|
local function open (x)
|
|
numopen = numopen + 1
|
|
return
|
|
function (t) -- iteraction function
|
|
t[1] = t[1] - 1
|
|
if t[1] > 0 then return t[1] end
|
|
end,
|
|
setmetatable({x}, {__close = function () numopen = numopen - 1 end})
|
|
end
|
|
|
|
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)
|
|
end
|
|
|
|
print('OK')
|
|
|
|
return 5,f
|
|
|
|
end -- }
|
|
|