96 $interpreter = $this->
getEngine()->getInterpreter();
98 list( $actualGlobals ) = $interpreter->callFunction(
99 $interpreter->loadString(
100 'local t = {} for k in pairs( _G ) do t[#t+1] = k end return t',
105 $leakedGlobals = array_diff( $actualGlobals, self::$allowedGlobals );
106 $this->assertEquals( 0, count( $leakedGlobals ),
107 'The following globals are leaked: ' . implode(
' ', $leakedGlobals )
205 $interpreter =
$engine->getInterpreter();
207 $interpreter->callFunction(
208 $interpreter->loadString(
'string.testModuleStringExtend = "ok"',
'extendstring' )
210 $ret = $interpreter->callFunction(
211 $interpreter->loadString(
'return ("").testModuleStringExtend',
'teststring1' )
213 $this->assertSame( [
'ok' ], $ret,
'string can be extended' );
215 $this->extraModules[
'Module:testModuleStringExtend'] =
'
217 test = function() return ("").testModuleStringExtend end
220 $module =
$engine->fetchModuleFromParser(
221 Title::makeTitle(
NS_MODULE,
'testModuleStringExtend' )
223 $ret = $interpreter->callFunction(
224 $engine->executeModule( $module->getInitChunk(),
'test',
null )
226 $this->assertSame( [
'ok' ], $ret,
'string extension can be used from module' );
228 $this->extraModules[
'Module:testModuleStringExtend2'] =
'
231 string.testModuleStringExtend = "fail"
232 return ("").testModuleStringExtend
236 $module =
$engine->fetchModuleFromParser(
237 Title::makeTitle(
NS_MODULE,
'testModuleStringExtend2' )
239 $ret = $interpreter->callFunction(
240 $engine->executeModule( $module->getInitChunk(),
'test',
null )
242 $this->assertSame( [
'ok' ], $ret,
'string extension cannot be modified from module' );
243 $ret = $interpreter->callFunction(
244 $interpreter->loadString(
'return string.testModuleStringExtend',
'teststring2' )
246 $this->assertSame( [
'ok' ], $ret,
'string extension cannot be modified from module' );
249 'prevQuestions' => [],
250 'question' =>
'=("").testModuleStringExtend',
251 'content' =>
'return {}',
252 'title' => Title::makeTitle(
NS_MODULE,
'dummy' ),
254 $this->assertSame(
'ok', $ret[
'return'],
'string extension can be used from console' );
257 'prevQuestions' => [
'string.fail = "fail"' ],
258 'question' =>
'=("").fail',
259 'content' =>
'return {}',
260 'title' => Title::makeTitle(
NS_MODULE,
'dummy' ),
262 $this->assertSame(
'nil', $ret[
'return'],
'string cannot be extended from console' );
265 'prevQuestions' => [
'string.testModuleStringExtend = "fail"' ],
266 'question' =>
'=("").testModuleStringExtend',
267 'content' =>
'return {}',
268 'title' => Title::makeTitle(
NS_MODULE,
'dummy' ),
270 $this->assertSame(
'ok', $ret[
'return'],
'string extension cannot be modified from console' );
271 $ret = $interpreter->callFunction(
272 $interpreter->loadString(
'return string.testModuleStringExtend',
'teststring3' )
274 $this->assertSame( [
'ok' ], $ret,
'string extension cannot be modified from console' );
276 $interpreter->callFunction(
277 $interpreter->loadString(
'string.testModuleStringExtend = nil',
'unextendstring' )
283 $interpreter =
$engine->getInterpreter();
284 $frame =
$engine->getParser()->getPreprocessor()->newFrame();
287 $interpreter->callFunction(
288 $interpreter->loadString(
'mw.markLoaded = ...',
'fortest' ),
289 $interpreter->wrapPHPFunction( function () use ( &$loadcount ) {
293 $this->extraModules[
'Module:TestLoadDataLoadedOnce-data'] =
'
297 $this->extraModules[
'Module:TestLoadDataLoadedOnce'] =
'
298 local data = mw.loadData( "Module:TestLoadDataLoadedOnce-data" )
300 foo = function() end,
302 return tostring( package.loaded["Module:TestLoadDataLoadedOnce-data"] )
309 for ( $i = 0; $i < 10; $i++ ) {
311 $module->invoke(
'foo', $frame->newChild() );
313 $this->assertSame( 1, $loadcount,
'data module was loaded more than once' );
316 $this->assertSame(
'nil', $module->invoke(
'bar', $frame ),
317 'data module was stored in module\'s package.loaded'
319 $this->assertSame( [
'nil' ],
320 $interpreter->callFunction( $interpreter->loadString(
321 'return tostring( package.loaded["Module:TestLoadDataLoadedOnce-data"] )',
'getLoaded'
323 'data module was stored in top level\'s package.loaded'
487 $frame =
$engine->getParser()->getPreprocessor()->newFrame();
489 $this->extraModules[
'Module:Bug62291'] =
'
492 return table.concat( {
493 math.random(), math.random(), math.random(), math.random(), math.random()
499 t[2] = mw.getCurrentFrame():preprocess( "{{#invoke:Bug62291|bar2}}" )
501 return table.concat( t, "; " )
513 $r1 = $module->invoke(
'foo', $frame->newChild() );
514 $r2 = $module->invoke(
'foo', $frame->newChild() );
515 $this->assertSame( $r1, $r2,
'Multiple invokes returned different sets of random numbers' );
518 $r1 = $module->invoke(
'bar', $frame->newChild() );
519 $r = explode(
'; ', $r1 );
520 $this->assertNotSame( $r[0], $r[2],
'Recursive invoke reset PRNG' );
521 $this->assertSame(
'bar2 called', $r[1],
'Sanity check failed' );
524 $r2 = $module->invoke(
'bar', $frame->newChild() );
525 $this->assertSame( $r1, $r2,
526 'Multiple invokes with recursive invoke returned different sets of random numbers' );
531 $pp =
$engine->getParser()->getPreprocessor();
533 $this->extraModules[
'Module:DateTime'] =
'
536 return os.date( "%d" )
539 return os.date( "%p" )
542 return os.date( "%H" )
545 return os.date( "%M" )
548 return os.date( "%S" )
551 return os.date( "*t" )
553 function p.tablesec()
554 return os.date( "*t" ).sec
559 function p.specificDateAndTime()
560 return os.date("%S", os.time{year = 2013, month = 1, day = 1})
568 $frame = $pp->newFrame();
569 $module->invoke(
'day', $frame );
570 $this->assertNotNull( $frame->getTTL(),
'TTL must be set when day is requested' );
571 $this->assertLessThanOrEqual( 86400, $frame->getTTL(),
572 'TTL must not exceed 1 day when day is requested' );
574 $frame = $pp->newFrame();
575 $module->invoke(
'AMPM', $frame );
576 $this->assertNotNull( $frame->getTTL(),
'TTL must be set when AM/PM is requested' );
577 $this->assertLessThanOrEqual( 43200, $frame->getTTL(),
578 'TTL must not exceed 12 hours when AM/PM is requested' );
580 $frame = $pp->newFrame();
581 $module->invoke(
'hour', $frame );
582 $this->assertNotNull( $frame->getTTL(),
'TTL must be set when hour is requested' );
583 $this->assertLessThanOrEqual( 3600, $frame->getTTL(),
584 'TTL must not exceed 1 hour when hours are requested' );
586 $frame = $pp->newFrame();
587 $module->invoke(
'minute', $frame );
588 $this->assertNotNull( $frame->getTTL(),
'TTL must be set when minutes are requested' );
589 $this->assertLessThanOrEqual( 60, $frame->getTTL(),
590 'TTL must not exceed 1 minute when minutes are requested' );
592 $frame = $pp->newFrame();
593 $module->invoke(
'second', $frame );
594 $this->assertEquals( 1, $frame->getTTL(),
595 'TTL must be equal to 1 second when seconds are requested' );
597 $frame = $pp->newFrame();
598 $module->invoke(
'table', $frame );
599 $this->assertNull( $frame->getTTL(),
600 'TTL must not be set when os.date( "*t" ) is called but no values are looked at' );
602 $frame = $pp->newFrame();
603 $module->invoke(
'tablesec', $frame );
604 $this->assertEquals( 1, $frame->getTTL(),
605 'TTL must be equal to 1 second when seconds are requested from a table' );
607 $frame = $pp->newFrame();
608 $module->invoke(
'time', $frame );
609 $this->assertEquals( 1, $frame->getTTL(),
610 'TTL must be equal to 1 second when os.time() is called' );
612 $frame = $pp->newFrame();
613 $module->invoke(
'specificDateAndTime', $frame );
614 $this->assertNull( $frame->getTTL(),
615 'TTL must not be set when os.date() or os.time() are called with a specific time' );
623 $parser =
$engine->getParser();
624 $pp = $parser->getPreprocessor();
627 $parser->setHook(
'scribuntocount',
function ( $str, $argv, $parser, $frame ) use ( &$count ) {
628 $frame->setVolatile();
632 $this->extraModules[
'Template:ScribuntoTestVolatileCaching'] =
'<scribuntocount/>';
633 $this->extraModules[
'Module:TestVolatileCaching'] =
'
635 preprocess = function ( frame )
636 return frame:preprocess( "<scribuntocount/>" )
638 extensionTag = function ( frame )
639 return frame:extensionTag( "scribuntocount" )
641 expandTemplate = function ( frame )
642 return frame:expandTemplate{ title = "ScribuntoTestVolatileCaching" }
647 $frame = $pp->newFrame();
649 $wikitext =
"{{#invoke:TestVolatileCaching|$func}}";
650 $text = $frame->expand( $pp->preprocessToObj(
"$wikitext $wikitext" ) );
651 $text = $parser->mStripState->unstripBoth( $text );
652 $this->assertTrue( $frame->isVolatile(),
"Frame is marked volatile" );
653 $this->assertEquals(
'1 2', $text,
"Volatile wikitext was not cached" );
734 $parser =
$engine->getParser();
736 $this->extraModules[
'Module:T208689'] =
'
739 p["foo\255bar"] = function ()
740 error( "error\255bar" )
753 'prevQuestions' => [],
754 'question' =>
'p.foo()',
755 'title' => Title::newFromText(
'Module:T208689' ),
756 'content' => $this->extraModules[
'Module:T208689'],
758 $this->fail(
'Expected exception not thrown' );
760 $this->assertTrue( mb_check_encoding( $e->getMessage(),
'UTF-8' ),
'Message is UTF-8' );
761 $this->assertTrue( mb_check_encoding( $e->
getScriptTraceHtml(),
'UTF-8' ),
'Message is UTF-8' );
765 $text = $parser->recursiveTagParseFully(
'{{#invoke:T208689|foo}}' );
766 $this->assertTrue( mb_check_encoding( $text,
'UTF-8' ),
'Parser output is UTF-8' );
767 $vars = $parser->getOutput()->getJsConfigVars();
768 $this->assertArrayHasKey(
'ScribuntoErrors', $vars );
769 foreach ( $vars[
'ScribuntoErrors'] as $err ) {
770 $this->assertTrue( mb_check_encoding( $err,
'UTF-8' ),
'JS config vars are UTF-8' );