| -- $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 *toclose x = setmetatable({"x"}, {__close = function (self) |
| a[#a + 1] = self[1] end}) |
| local *toclose 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 *toclose _ = 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 *toclose _ = closescope |
| local y = 15 |
| return y |
| end |
| |
| assert(foo() == 15 and X == true) |
| |
| X = false |
| foo = function () |
| local *toclose x = closescope |
| return x |
| end |
| |
| assert(foo() == closescope and X == true) |
| |
| end |
| |
| |
| do |
| -- calls cannot be tail in the scope of to-be-closed variables |
| local X, Y |
| local function foo () |
| local *toclose _ = function () Y = 10 end |
| assert(X == true and Y == nil) -- 'X' not closed yet |
| return 1,2,3 |
| end |
| |
| local function bar () |
| local *toclose _ = 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 |
| |
| |
| do -- errors in __close |
| local log = {} |
| local function foo (err) |
| local *toclose x = function (msg) log[#log + 1] = msg; error(1) end |
| local *toclose x1 = function (msg) log[#log + 1] = msg; end |
| local *toclose gc = function () collectgarbage() end |
| local *toclose y = function (msg) log[#log + 1] = msg; error(2) end |
| local *toclose 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 |
| |
| |
| do |
| |
| -- errors due to non-closable values |
| local function foo () |
| local *toclose x = 34 |
| end |
| local stat, msg = pcall(foo) |
| assert(not stat and string.find(msg, "variable 'x'")) |
| |
| |
| -- with other errors, non-closable values are ignored |
| local function foo () |
| local *toclose x = 34 |
| local *toclose y = function () error(32) end |
| end |
| local stat, msg = pcall(foo) |
| assert(not stat and msg == 32) |
| |
| end |
| |
| |
| if rawget(_G, "T") then |
| |
| -- memory error inside closing function |
| local function foo () |
| local *toclose y = function () T.alloccount() end |
| local *toclose 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 *toclose x = 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 *toclose xxx = function (msg) |
| assert(msg == "not enough memory"); |
| error(1000) -- raise another error |
| end |
| local *toclose xx = function (msg) |
| assert(msg == "not enough memory"); |
| end |
| local *toclose 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) |
| |
| |
| 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 |
| 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 *toclose xv = function () x = true end |
| do |
| local *toclose 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 *toclose 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, |
| nil, -- state |
| nil, -- control variable |
| 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 |
| local state = setmetatable({x}, |
| {__close = function () numopen = numopen - 1 end}) |
| return |
| function (t) -- iteraction function |
| t[1] = t[1] - 1 |
| if t[1] > 0 then return t[1] end |
| end, |
| state, |
| nil, |
| state -- to-be-closed |
| 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 -- } |
| |