mediawiki-extensions-Scribunto/tests/engines/LuaCommon/CommonTests.lua
Tim Starling 41b93dd7e1 Fixed setfenv() across a tail call
Fixed the issue noticed during testing of da06273e, and which resulted
in satest.setfenv1() being disabled. It's not possible to protect
environments by iterating through every stack level, calling getfenv()
at each one, because if any of the stack levels is a tail call, an error
is raised.

Such a tail call was introduced in da06273e, which is why the test broke.

Instead, just protect the actual specified environments, not their
callers. The callers will have to protect themselves.

Change-Id: If39104010ff2663c1bae5105cc8d37e276532100
2012-04-24 12:33:06 +10:00

194 lines
4.7 KiB
Lua

-- Parts are based on lua-TestMore (Copyright © 2009-2012 François Perrad, MIT
-- license)
local test = {}
local function is_deeply (got, expected, name)
if type(got) ~= 'table' then
return false
elseif type(expected) ~= 'table' then
error("expected value isn't a table : " .. tostring(expected))
end
local msg1
local msg2
local function deep_eq (t1, t2, key_path)
if t1 == t2 then
return true
end
for k, v2 in pairs(t2) do
local v1 = t1[k]
if type(v1) == 'table' and type(v2) == 'table' then
local r = deep_eq(v1, v2, key_path .. "." .. tostring(k))
if not r then
return false
end
else
if v1 ~= v2 then
key_path = key_path .. "." .. tostring(k)
msg1 = " got" .. key_path .. ": " .. tostring(v1)
msg2 = "expected" .. key_path .. ": " .. tostring(v2)
return false
end
end
end
for k in pairs(t1) do
local v2 = t2[k]
if v2 == nil then
key_path = key_path .. "." .. tostring(k)
msg1 = " got" .. key_path .. ": " .. tostring(t1[k])
msg2 = "expected" .. key_path .. ": " .. tostring(v2)
return false
end
end
return true
end -- deep_eq
return deep_eq(got, expected, '')
end
function test.getTests( engine )
return {
{ 'clone1', true },
{ 'clone2', true },
{ 'clone3', true },
{ 'clone4', true },
{ 'setfenv1', { error = '%s cannot set the global %s' } },
{ 'setfenv2', { error = '%s cannot set an environment %s' } },
{ 'setfenv3', { error = '%s cannot set the requested environment%s' } },
{ 'setfenv4', true },
{ 'setfenv5', true },
{ 'setfenv6', { error = '%s cannot be called on a protected function' } },
{ 'setfenv7', { error = '%s can only be called with a function%s' } },
{ 'getfenv1', true },
{ 'getfenv2', { error = '%s cannot get the global environment' } },
{ 'getfenv3', { error = '%Sno function environment for tail call %s' } },
}
end
function test.clone1()
local x = 1
local y = mw.clone( x )
assert( x == y )
return true
end
function test.clone2()
local x = { 'a' }
local y = mw.clone( x )
assert( x ~= y )
assert( is_deeply( y, x ) )
y[2] = 'b'
assert( not is_deeply( y, x ) )
return true
end
function test.clone3()
local mt = { __add = function() end }
local x = {}
setmetatable( x, mt )
local y = mw.clone( x )
assert( getmetatable( x ) ~= getmetatable( y ) )
assert( is_deeply( getmetatable( y ), getmetatable( x ) ) )
return true
end
function test.clone4()
local x = {}
x.x = x
local y = mw.clone( x )
assert( x ~= y )
assert( y == y.x )
return true
end
function test.setfenv1()
setfenv( 0, {} )
end
function test.setfenv2()
setfenv( 1000, {} )
end
function test.setfenv3()
local function jailbreak()
setfenv( 2, {} )
end
local new_setfenv, new_getfenv = mw.makeProtectedEnvFuncs( { [_G] = true }, {} )
setfenv( jailbreak, {setfenv = new_setfenv} )
jailbreak()
end
function test.setfenv4()
-- Set an unprotected environment at a higher stack level than a protected
-- environment. It's assumed that any higher-level environment will protect
-- itself with its own setfenv wrapper, so this succeeds.
local function level3()
local function level2()
local env = {setfenv = setfenv}
local function level1()
setfenv( 3, {} )
end
setfenv( level1, env )()
end
local protected = {mw = mw}
protected.setfenv, protected.getfenv = mw.makeProtectedEnvFuncs(
{[protected] = true}, {} )
setfenv( level2, protected )()
end
local unprotected = {setfenv = setfenv, getfenv = getfenv, mw = mw}
setfenv( level3, unprotected )()
assert( getfenv( level3 ) ~= unprotected )
return true
end
function test.setfenv5()
local function allowed()
(function() setfenv( 2, {} ) end )()
end
local new_setfenv, new_getfenv = mw.makeProtectedEnvFuncs( { [_G] = true }, {} )
setfenv( allowed, {setfenv = new_setfenv} )()
return true
end
function test.setfenv6()
local function target() end
local function jailbreak()
setfenv( target, {} )
end
local new_setfenv, new_getfenv = mw.makeProtectedEnvFuncs( {}, { [target] = true } )
setfenv( jailbreak, {setfenv = new_setfenv} )()
end
function test.setfenv7()
setfenv( {}, {} )
end
function test.setfenv8()
setfenv( 2, {} )
end
function test.getfenv1()
assert( getfenv( 1 ) == _G )
return true
end
function test.getfenv2()
getfenv( 0 )
end
function test.getfenv3()
local function foo()
return getfenv( 2 )
end
local function bar()
return foo()
end
bar()
end
return test