getEngine()->getParser()->getOptions()->setExpensiveParserFunctionLimit( 10 ); // Some of the tests need this $interpreter = $this->getEngine()->getInterpreter(); $interpreter->callFunction( $interpreter->loadString( 'mw.makeProtectedEnvFuncsForTest = mw.makeProtectedEnvFuncs', 'fortest' ) ); } function getTestModules() { return parent::getTestModules() + array( 'CommonTests' => __DIR__ . '/CommonTests.lua', 'CommonTests-data' => __DIR__ . '/CommonTests-data.lua', 'CommonTests-data-fail1' => __DIR__ . '/CommonTests-data-fail1.lua', 'CommonTests-data-fail2' => __DIR__ . '/CommonTests-data-fail2.lua', 'CommonTests-data-fail3' => __DIR__ . '/CommonTests-data-fail3.lua', 'CommonTests-data-fail4' => __DIR__ . '/CommonTests-data-fail4.lua', 'CommonTests-data-fail5' => __DIR__ . '/CommonTests-data-fail5.lua', ); } function testNoLeakedGlobals() { $interpreter = $this->getEngine()->getInterpreter(); list( $actualGlobals ) = $interpreter->callFunction( $interpreter->loadString( 'local t = {} for k in pairs( _G ) do t[#t+1] = k end return t', 'getglobals' ) ); $leakedGlobals = array_diff( $actualGlobals, self::$allowedGlobals ); $this->assertEquals( 0, count( $leakedGlobals ), 'The following globals are leaked: ' . join( ' ', $leakedGlobals ) ); } function testModuleStringExtend() { $engine = $this->getEngine(); $interpreter = $engine->getInterpreter(); $interpreter->callFunction( $interpreter->loadString( 'string.testModuleStringExtend = "ok"', 'extendstring' ) ); $ret = $interpreter->callFunction( $interpreter->loadString( 'return ("").testModuleStringExtend', 'teststring1' ) ); $this->assertSame( array( 'ok' ), $ret, 'string can be extended' ); $this->extraModules['Module:testModuleStringExtend'] = ' return { test = function() return ("").testModuleStringExtend end } '; $module = $engine->fetchModuleFromParser( Title::makeTitle( NS_MODULE, 'testModuleStringExtend' ) ); $ext = $module->execute(); $ret = $interpreter->callFunction( $ext['test'] ); $this->assertSame( array( 'ok' ), $ret, 'string extension can be used from module' ); $this->extraModules['Module:testModuleStringExtend2'] = ' return { test = function() string.testModuleStringExtend = "fail" return ("").testModuleStringExtend end } '; $module = $engine->fetchModuleFromParser( Title::makeTitle( NS_MODULE, 'testModuleStringExtend2' ) ); $ext = $module->execute(); $ret = $interpreter->callFunction( $ext['test'] ); $this->assertSame( array( 'ok' ), $ret, 'string extension cannot be modified from module' ); $ret = $interpreter->callFunction( $interpreter->loadString( 'return string.testModuleStringExtend', 'teststring2' ) ); $this->assertSame( array( 'ok' ), $ret, 'string extension cannot be modified from module' ); $ret = $engine->runConsole( array( 'prevQuestions' => array(), 'question' => '=("").testModuleStringExtend', 'content' => 'return {}', 'title' => Title::makeTitle( NS_MODULE, 'dummy' ), ) ); $this->assertSame( 'ok', $ret['return'], 'string extension can be used from console' ); $ret = $engine->runConsole( array( 'prevQuestions' => array( 'string.fail = "fail"' ), 'question' => '=("").fail', 'content' => 'return {}', 'title' => Title::makeTitle( NS_MODULE, 'dummy' ), ) ); $this->assertSame( 'nil', $ret['return'], 'string cannot be extended from console' ); $ret = $engine->runConsole( array( 'prevQuestions' => array( 'string.testModuleStringExtend = "fail"' ), 'question' => '=("").testModuleStringExtend', 'content' => 'return {}', 'title' => Title::makeTitle( NS_MODULE, 'dummy' ), ) ); $this->assertSame( 'ok', $ret['return'], 'string extension cannot be modified from console' ); $ret = $interpreter->callFunction( $interpreter->loadString( 'return string.testModuleStringExtend', 'teststring3' ) ); $this->assertSame( array( 'ok' ), $ret, 'string extension cannot be modified from console' ); $interpreter->callFunction( $interpreter->loadString( 'string.testModuleStringExtend = nil', 'unextendstring' ) ); } function testLoadDataLoadedOnce() { $engine = $this->getEngine(); $interpreter = $engine->getInterpreter(); $frame = $engine->getParser()->getPreprocessor()->newFrame(); $loadcount = 0; $interpreter->callFunction( $interpreter->loadString( 'mw.markLoaded = ...', 'fortest' ), $interpreter->wrapPHPFunction( function () use (&$loadcount) { $loadcount++; } ) ); $this->extraModules['Module:TestLoadDataLoadedOnce-data'] = ' mw.markLoaded() return {} '; $this->extraModules['Module:TestLoadDataLoadedOnce'] = ' local data = mw.loadData( "Module:TestLoadDataLoadedOnce-data" ) return { foo = function() end, bar = function() return tostring( package.loaded["Module:TestLoadDataLoadedOnce-data"] ) end, } '; // Make sure data module isn't parsed twice. Simulate several {{#invoke:}}s $title = Title::makeTitle( NS_MODULE, 'TestLoadDataLoadedOnce' ); for ( $i = 0; $i < 10; $i++ ) { $module = $engine->fetchModuleFromParser( $title ); $module->invoke( 'foo', $frame->newChild() ); } $this->assertSame( 1, $loadcount, 'data module was loaded more than once' ); // Make sure data module isn't in package.loaded $this->assertSame( 'nil', $module->invoke( 'bar', $frame ), 'data module was stored in module\'s package.loaded' ); $this->assertSame( array( 'nil' ), $interpreter->callFunction( $interpreter->loadString( 'return tostring( package.loaded["Module:TestLoadDataLoadedOnce-data"] )', 'getLoaded' ) ), 'data module was stored in top level\'s package.loaded' ); } function testFrames() { $engine = $this->getEngine(); $ret = $engine->runConsole( array( 'prevQuestions' => array(), 'question' => '=mw.getCurrentFrame()', 'content' => 'return {}', 'title' => Title::makeTitle( NS_MODULE, 'dummy' ), ) ); $this->assertSame( 'table', $ret['return'], 'frames can be used in the console' ); $ret = $engine->runConsole( array( 'prevQuestions' => array(), 'question' => '=mw.getCurrentFrame():newChild{}', 'content' => 'return {}', 'title' => Title::makeTitle( NS_MODULE, 'dummy' ), ) ); $this->assertSame( 'table', $ret['return'], 'child frames can be created' ); $ret = $engine->runConsole( array( 'prevQuestions' => array( 'f = mw.getCurrentFrame():newChild{ args = { "ok" } }', 'f2 = f:newChild{ args = {} }' ), 'question' => '=f2:getParent().args[1], f2:getParent():getParent()', 'content' => 'return {}', 'title' => Title::makeTitle( NS_MODULE, 'dummy' ), ) ); $this->assertSame( "ok\ttable", $ret['return'], 'child frames have correct parents' ); } function testCallParserFunction() { global $wgContLang; $engine = $this->getEngine(); $parser = $engine->getParser(); $args = array( 'prevQuestions' => array(), 'content' => 'return {}', 'title' => Title::makeTitle( NS_MODULE, 'dummy' ), ); // Test argument calling conventions $ret = $engine->runConsole( array( 'question' => '=mw.getCurrentFrame():callParserFunction{ name = "urlencode", args = { "x x", "wiki" } }', ) + $args ); $this->assertSame( "x_x", $ret['return'], 'callParserFunction works for {{urlencode:x x|wiki}} (named args w/table)' ); $ret = $engine->runConsole( array( 'question' => '=mw.getCurrentFrame():callParserFunction{ name = "urlencode", args = "x x" }', ) + $args ); $this->assertSame( "x+x", $ret['return'], 'callParserFunction works for {{urlencode:x x}} (named args w/scalar)' ); $ret = $engine->runConsole( array( 'question' => '=mw.getCurrentFrame():callParserFunction( "urlencode", { "x x", "wiki" } )', ) + $args ); $this->assertSame( "x_x", $ret['return'], 'callParserFunction works for {{urlencode:x x|wiki}} (positional args w/table)' ); $ret = $engine->runConsole( array( 'question' => '=mw.getCurrentFrame():callParserFunction( "urlencode", "x x", "wiki" )', ) + $args ); $this->assertSame( "x_x", $ret['return'], 'callParserFunction works for {{urlencode:x x|wiki}} (positional args w/scalars)' ); $ret = $engine->runConsole( array( 'question' => '=mw.getCurrentFrame():callParserFunction{ name = "urlencode:x x", args = { "wiki" } }', ) + $args ); $this->assertSame( "x_x", $ret['return'], 'callParserFunction works for {{urlencode:x x|wiki}} (colon in name, named args w/table)' ); $ret = $engine->runConsole( array( 'question' => '=mw.getCurrentFrame():callParserFunction{ name = "urlencode:x x", args = "wiki" }', ) + $args ); $this->assertSame( "x_x", $ret['return'], 'callParserFunction works for {{urlencode:x x|wiki}} (colon in name, named args w/scalar)' ); $ret = $engine->runConsole( array( 'question' => '=mw.getCurrentFrame():callParserFunction( "urlencode:x x", { "wiki" } )', ) + $args ); $this->assertSame( "x_x", $ret['return'], 'callParserFunction works for {{urlencode:x x|wiki}} (colon in name, positional args w/table)' ); $ret = $engine->runConsole( array( 'question' => '=mw.getCurrentFrame():callParserFunction( "urlencode:x x", "wiki" )', ) + $args ); $this->assertSame( "x_x", $ret['return'], 'callParserFunction works for {{urlencode:x x|wiki}} (colon in name, positional args w/scalars)' ); // Test named args to the parser function $ret = $engine->runConsole( array( 'question' => '=mw.getCurrentFrame():callParserFunction( "#tag:pre", { "foo", style = "margin-left: 1.6em" } )', ) + $args ); $this->assertSame( '
foo
', $parser->mStripState->unstripBoth( $ret['return'] ), 'callParserFunction works for {{#tag:pre|foo|style=margin-left: 1.6em}}' ); // Test extensionTag $ret = $engine->runConsole( array( 'question' => '=mw.getCurrentFrame():extensionTag( "pre", "foo", { style = "margin-left: 1.6em" } )', ) + $args ); $this->assertSame( '
foo
', $parser->mStripState->unstripBoth( $ret['return'] ), 'extensionTag works for {{#tag:pre|foo|style=margin-left: 1.6em}}' ); $ret = $engine->runConsole( array( 'question' => '=mw.getCurrentFrame():extensionTag{ name = "pre", content = "foo", args = { style = "margin-left: 1.6em" } }', ) + $args ); $this->assertSame( '
foo
', $parser->mStripState->unstripBoth( $ret['return'] ), 'extensionTag works for {{#tag:pre|foo|style=margin-left: 1.6em}}' ); // Test calling a non-existent function try { $ret = $engine->runConsole( array( 'question' => '=mw.getCurrentFrame():callParserFunction{ name = "thisDoesNotExist", arg1 = "" }', ) + $args ); $this->fail( "Expected LuaError not thrown for nonexistent parser function" ); } catch ( Scribunto_LuaError $err ) { $this->assertSame( 'Lua error: callParserFunction: function "thisDoesNotExist" was not found.', $err->getMessage(), 'callParserFunction correctly errors for nonexistent function' ); } } }