MediaWiki  1.34.0
CommonTest.php
Go to the documentation of this file.
1 <?php
2 
13  protected static $moduleName = 'CommonTests';
14 
15  private static $allowedGlobals = [
16  // Functions
17  'assert',
18  'error',
19  'getfenv',
20  'getmetatable',
21  'ipairs',
22  'next',
23  'pairs',
24  'pcall',
25  'rawequal',
26  'rawget',
27  'rawset',
28  'require',
29  'select',
30  'setfenv',
31  'setmetatable',
32  'tonumber',
33  'tostring',
34  'type',
35  'unpack',
36  'xpcall',
37 
38  // Packages
39  '_G',
40  'debug',
41  'math',
42  'mw',
43  'os',
44  'package',
45  'string',
46  'table',
47 
48  // Misc
49  '_VERSION',
50  ];
51 
52  protected function setUp() {
53  parent::setUp();
54 
55  // Register libraries for self::testPHPLibrary()
56  $this->mergeMwGlobalArrayValue( 'wgHooks', [
57  'ScribuntoExternalLibraries' => [
58  function ( $engine, &$libs ) {
59  $libs += [
60  'CommonTestsLib' => [
61  'class' => 'Scribunto_LuaCommonTestsLibrary',
62  'deferLoad' => true,
63  ],
64  'CommonTestsFailLib' => [
65  'class' => 'Scribunto_LuaCommonTestsFailLibrary',
66  'deferLoad' => true,
67  ],
68  ];
69  }
70  ]
71  ] );
72 
73  // Note this depends on every iteration of the data provider running with a clean parser
74  $this->getEngine()->getParser()->getOptions()->setExpensiveParserFunctionLimit( 10 );
75 
76  // Some of the tests need this
77  $interpreter = $this->getEngine()->getInterpreter();
78  $interpreter->callFunction( $interpreter->loadString(
79  'mw.makeProtectedEnvFuncsForTest = mw.makeProtectedEnvFuncs', 'fortest'
80  ) );
81  }
82 
83  protected function getTestModules() {
84  return parent::getTestModules() + [
85  'CommonTests' => __DIR__ . '/CommonTests.lua',
86  'CommonTests-data' => __DIR__ . '/CommonTests-data.lua',
87  'CommonTests-data-fail1' => __DIR__ . '/CommonTests-data-fail1.lua',
88  'CommonTests-data-fail2' => __DIR__ . '/CommonTests-data-fail2.lua',
89  'CommonTests-data-fail3' => __DIR__ . '/CommonTests-data-fail3.lua',
90  'CommonTests-data-fail4' => __DIR__ . '/CommonTests-data-fail4.lua',
91  'CommonTests-data-fail5' => __DIR__ . '/CommonTests-data-fail5.lua',
92  ];
93  }
94 
95  public function testNoLeakedGlobals() {
96  $interpreter = $this->getEngine()->getInterpreter();
97 
98  list( $actualGlobals ) = $interpreter->callFunction(
99  $interpreter->loadString(
100  'local t = {} for k in pairs( _G ) do t[#t+1] = k end return t',
101  'getglobals'
102  )
103  );
104 
105  $leakedGlobals = array_diff( $actualGlobals, self::$allowedGlobals );
106  $this->assertEquals( 0, count( $leakedGlobals ),
107  'The following globals are leaked: ' . implode( ' ', $leakedGlobals )
108  );
109  }
110 
111  public function testPHPLibrary() {
112  $engine = $this->getEngine();
113  $frame = $engine->getParser()->getPreprocessor()->newFrame();
114 
115  $title = Title::makeTitle( NS_MODULE, 'TestInfoPassViaPHPLibrary' );
116  $this->extraModules[$title->getFullText()] = '
117  local p = {}
118 
119  function p.test()
120  local lib = require( "CommonTestsLib" )
121  return table.concat( { lib.test() }, "; " )
122  end
123 
124  function p.setVal( frame )
125  local lib = require( "CommonTestsLib" )
126  lib.val = frame.args[1]
127  lib.foobar.val = frame.args[1]
128  end
129 
130  function p.getVal()
131  local lib = require( "CommonTestsLib" )
132  return tostring( lib.val ), tostring( lib.foobar.val )
133  end
134 
135  function p.getSetVal( frame )
136  p.setVal( frame )
137  return p.getVal()
138  end
139 
140  function p.checkPackage()
141  local ret = {}
142  ret[1] = package.loaded["CommonTestsLib"] == nil
143  require( "CommonTestsLib" )
144  ret[2] = package.loaded["CommonTestsLib"] ~= nil
145  return ret[1], ret[2]
146  end
147 
148  function p.libSetVal( frame )
149  local lib = require( "CommonTestsLib" )
150  return lib.setVal( frame )
151  end
152 
153  function p.libGetVal()
154  local lib = require( "CommonTestsLib" )
155  return lib.getVal()
156  end
157 
158  return p
159  ';
160 
161  # Test loading
162  $module = $engine->fetchModuleFromParser( $title );
163  $ret = $module->invoke( 'test', $frame->newChild() );
164  $this->assertSame( 'Test option; Test function', $ret,
165  'Library can be loaded and called' );
166 
167  # Test package.loaded
168  $module = $engine->fetchModuleFromParser( $title );
169  $ret = $module->invoke( 'checkPackage', $frame->newChild() );
170  $this->assertSame( 'truetrue', $ret,
171  'package.loaded is right on the first call' );
172  $ret = $module->invoke( 'checkPackage', $frame->newChild() );
173  $this->assertSame( 'truetrue', $ret,
174  'package.loaded is right on the second call' );
175 
176  # Test caching for require
177  $args = $engine->getParser()->getPreprocessor()->newPartNodeArray( [ 1 => 'cached' ] );
178  $ret = $module->invoke( 'getSetVal', $frame->newChild( $args ) );
179  $this->assertSame( 'cachedcached', $ret,
180  'same loaded table is returned by multiple require calls' );
181 
182  # Test no data communication between invokes
183  $module = $engine->fetchModuleFromParser( $title );
184  $args = $engine->getParser()->getPreprocessor()->newPartNodeArray( [ 1 => 'fail' ] );
185  $module->invoke( 'setVal', $frame->newChild( $args ) );
186  $ret = $module->invoke( 'getVal', $frame->newChild() );
187  $this->assertSame( 'nilnope', $ret,
188  'same loaded table is not shared between invokes' );
189 
190  # Test that the library isn't being recreated between invokes
191  $module = $engine->fetchModuleFromParser( $title );
192  $ret = $module->invoke( 'libGetVal', $frame->newChild() );
193  $this->assertSame( 'nil', $ret, 'sanity check' );
194  $args = $engine->getParser()->getPreprocessor()->newPartNodeArray( [ 1 => 'ok' ] );
195  $module->invoke( 'libSetVal', $frame->newChild( $args ) );
196 
197  $module = $engine->fetchModuleFromParser( $title );
198  $ret = $module->invoke( 'libGetVal', $frame->newChild() );
199  $this->assertSame( 'ok', $ret,
200  'library is not recreated between invokes' );
201  }
202 
203  public function testModuleStringExtend() {
204  $engine = $this->getEngine();
205  $interpreter = $engine->getInterpreter();
206 
207  $interpreter->callFunction(
208  $interpreter->loadString( 'string.testModuleStringExtend = "ok"', 'extendstring' )
209  );
210  $ret = $interpreter->callFunction(
211  $interpreter->loadString( 'return ("").testModuleStringExtend', 'teststring1' )
212  );
213  $this->assertSame( [ 'ok' ], $ret, 'string can be extended' );
214 
215  $this->extraModules['Module:testModuleStringExtend'] = '
216  return {
217  test = function() return ("").testModuleStringExtend end
218  }
219  ';
220  $module = $engine->fetchModuleFromParser(
221  Title::makeTitle( NS_MODULE, 'testModuleStringExtend' )
222  );
223  $ret = $interpreter->callFunction(
224  $engine->executeModule( $module->getInitChunk(), 'test', null )
225  );
226  $this->assertSame( [ 'ok' ], $ret, 'string extension can be used from module' );
227 
228  $this->extraModules['Module:testModuleStringExtend2'] = '
229  return {
230  test = function()
231  string.testModuleStringExtend = "fail"
232  return ("").testModuleStringExtend
233  end
234  }
235  ';
236  $module = $engine->fetchModuleFromParser(
237  Title::makeTitle( NS_MODULE, 'testModuleStringExtend2' )
238  );
239  $ret = $interpreter->callFunction(
240  $engine->executeModule( $module->getInitChunk(), 'test', null )
241  );
242  $this->assertSame( [ 'ok' ], $ret, 'string extension cannot be modified from module' );
243  $ret = $interpreter->callFunction(
244  $interpreter->loadString( 'return string.testModuleStringExtend', 'teststring2' )
245  );
246  $this->assertSame( [ 'ok' ], $ret, 'string extension cannot be modified from module' );
247 
248  $ret = $engine->runConsole( [
249  'prevQuestions' => [],
250  'question' => '=("").testModuleStringExtend',
251  'content' => 'return {}',
252  'title' => Title::makeTitle( NS_MODULE, 'dummy' ),
253  ] );
254  $this->assertSame( 'ok', $ret['return'], 'string extension can be used from console' );
255 
256  $ret = $engine->runConsole( [
257  'prevQuestions' => [ 'string.fail = "fail"' ],
258  'question' => '=("").fail',
259  'content' => 'return {}',
260  'title' => Title::makeTitle( NS_MODULE, 'dummy' ),
261  ] );
262  $this->assertSame( 'nil', $ret['return'], 'string cannot be extended from console' );
263 
264  $ret = $engine->runConsole( [
265  'prevQuestions' => [ 'string.testModuleStringExtend = "fail"' ],
266  'question' => '=("").testModuleStringExtend',
267  'content' => 'return {}',
268  'title' => Title::makeTitle( NS_MODULE, 'dummy' ),
269  ] );
270  $this->assertSame( 'ok', $ret['return'], 'string extension cannot be modified from console' );
271  $ret = $interpreter->callFunction(
272  $interpreter->loadString( 'return string.testModuleStringExtend', 'teststring3' )
273  );
274  $this->assertSame( [ 'ok' ], $ret, 'string extension cannot be modified from console' );
275 
276  $interpreter->callFunction(
277  $interpreter->loadString( 'string.testModuleStringExtend = nil', 'unextendstring' )
278  );
279  }
280 
281  public function testLoadDataLoadedOnce() {
282  $engine = $this->getEngine();
283  $interpreter = $engine->getInterpreter();
284  $frame = $engine->getParser()->getPreprocessor()->newFrame();
285 
286  $loadcount = 0;
287  $interpreter->callFunction(
288  $interpreter->loadString( 'mw.markLoaded = ...', 'fortest' ),
289  $interpreter->wrapPHPFunction( function () use ( &$loadcount ) {
290  $loadcount++;
291  } )
292  );
293  $this->extraModules['Module:TestLoadDataLoadedOnce-data'] = '
294  mw.markLoaded()
295  return {}
296  ';
297  $this->extraModules['Module:TestLoadDataLoadedOnce'] = '
298  local data = mw.loadData( "Module:TestLoadDataLoadedOnce-data" )
299  return {
300  foo = function() end,
301  bar = function()
302  return tostring( package.loaded["Module:TestLoadDataLoadedOnce-data"] )
303  end,
304  }
305  ';
306 
307  // Make sure data module isn't parsed twice. Simulate several {{#invoke:}}s
308  $title = Title::makeTitle( NS_MODULE, 'TestLoadDataLoadedOnce' );
309  for ( $i = 0; $i < 10; $i++ ) {
310  $module = $engine->fetchModuleFromParser( $title );
311  $module->invoke( 'foo', $frame->newChild() );
312  }
313  $this->assertSame( 1, $loadcount, 'data module was loaded more than once' );
314 
315  // Make sure data module isn't in package.loaded
316  $this->assertSame( 'nil', $module->invoke( 'bar', $frame ),
317  'data module was stored in module\'s package.loaded'
318  );
319  $this->assertSame( [ 'nil' ],
320  $interpreter->callFunction( $interpreter->loadString(
321  'return tostring( package.loaded["Module:TestLoadDataLoadedOnce-data"] )', 'getLoaded'
322  ) ),
323  'data module was stored in top level\'s package.loaded'
324  );
325  }
326 
327  public function testFrames() {
328  $engine = $this->getEngine();
329 
330  $ret = $engine->runConsole( [
331  'prevQuestions' => [],
332  'question' => '=mw.getCurrentFrame()',
333  'content' => 'return {}',
334  'title' => Title::makeTitle( NS_MODULE, 'dummy' ),
335  ] );
336  $this->assertSame( 'table', $ret['return'], 'frames can be used in the console' );
337 
338  $ret = $engine->runConsole( [
339  'prevQuestions' => [],
340  'question' => '=mw.getCurrentFrame():newChild{}',
341  'content' => 'return {}',
342  'title' => Title::makeTitle( NS_MODULE, 'dummy' ),
343  ] );
344  $this->assertSame( 'table', $ret['return'], 'child frames can be created' );
345 
346  $ret = $engine->runConsole( [
347  'prevQuestions' => [
348  'f = mw.getCurrentFrame():newChild{ args = { "ok" } }',
349  'f2 = f:newChild{ args = {} }'
350  ],
351  'question' => '=f2:getParent().args[1], f2:getParent():getParent()',
352  'content' => 'return {}',
353  'title' => Title::makeTitle( NS_MODULE, 'dummy' ),
354  ] );
355  $this->assertSame( "ok\ttable", $ret['return'], 'child frames have correct parents' );
356  }
357 
358  public function testCallParserFunction() {
359  $engine = $this->getEngine();
360  $parser = $engine->getParser();
361 
362  $args = [
363  'prevQuestions' => [],
364  'content' => 'return {}',
365  'title' => Title::makeTitle( NS_MODULE, 'dummy' ),
366  ];
367 
368  // Test argument calling conventions
369  $ret = $engine->runConsole( [
370  'question' => '=mw.getCurrentFrame():callParserFunction{
371  name = "urlencode", args = { "x x", "wiki" }
372  }',
373  ] + $args );
374  $this->assertSame( "x_x", $ret['return'],
375  'callParserFunction works for {{urlencode:x x|wiki}} (named args w/table)'
376  );
377 
378  $ret = $engine->runConsole( [
379  'question' => '=mw.getCurrentFrame():callParserFunction{
380  name = "urlencode", args = "x x"
381  }',
382  ] + $args );
383  $this->assertSame( "x+x", $ret['return'],
384  'callParserFunction works for {{urlencode:x x}} (named args w/scalar)'
385  );
386 
387  $ret = $engine->runConsole( [
388  'question' => '=mw.getCurrentFrame():callParserFunction( "urlencode", { "x x", "wiki" } )',
389  ] + $args );
390  $this->assertSame( "x_x", $ret['return'],
391  'callParserFunction works for {{urlencode:x x|wiki}} (positional args w/table)'
392  );
393 
394  $ret = $engine->runConsole( [
395  'question' => '=mw.getCurrentFrame():callParserFunction( "urlencode", "x x", "wiki" )',
396  ] + $args );
397  $this->assertSame( "x_x", $ret['return'],
398  'callParserFunction works for {{urlencode:x x|wiki}} (positional args w/scalars)'
399  );
400 
401  $ret = $engine->runConsole( [
402  'question' => '=mw.getCurrentFrame():callParserFunction{
403  name = "urlencode:x x", args = { "wiki" }
404  }',
405  ] + $args );
406  $this->assertSame( "x_x", $ret['return'],
407  'callParserFunction works for {{urlencode:x x|wiki}} (colon in name, named args w/table)'
408  );
409 
410  $ret = $engine->runConsole( [
411  'question' => '=mw.getCurrentFrame():callParserFunction{
412  name = "urlencode:x x", args = "wiki"
413  }',
414  ] + $args );
415  $this->assertSame( "x_x", $ret['return'],
416  'callParserFunction works for {{urlencode:x x|wiki}} (colon in name, named args w/scalar)'
417  );
418 
419  $ret = $engine->runConsole( [
420  'question' => '=mw.getCurrentFrame():callParserFunction( "urlencode:x x", { "wiki" } )',
421  ] + $args );
422  $this->assertSame( "x_x", $ret['return'],
423  'callParserFunction works for {{urlencode:x x|wiki}} (colon in name, positional args w/table)'
424  );
425 
426  $ret = $engine->runConsole( [
427  'question' => '=mw.getCurrentFrame():callParserFunction( "urlencode:x x", "wiki" )',
428  ] + $args );
429  $this->assertSame( "x_x", $ret['return'],
430  'callParserFunction works for {{urlencode:x x|wiki}} (colon in name, positional args w/scalars)'
431  );
432 
433  // Test named args to the parser function
434  $ret = $engine->runConsole( [
435  'question' => '=mw.getCurrentFrame():callParserFunction( "#tag:pre",
436  { "foo", style = "margin-left: 1.6em" }
437  )',
438  ] + $args );
439  $this->assertSame(
440  '<pre style="margin-left: 1.6em">foo</pre>',
441  $parser->mStripState->unstripBoth( $ret['return'] ),
442  'callParserFunction works for {{#tag:pre|foo|style=margin-left: 1.6em}}'
443  );
444 
445  // Test extensionTag
446  $ret = $engine->runConsole( [
447  'question' => '=mw.getCurrentFrame():extensionTag( "pre", "foo",
448  { style = "margin-left: 1.6em" }
449  )',
450  ] + $args );
451  $this->assertSame(
452  '<pre style="margin-left: 1.6em">foo</pre>',
453  $parser->mStripState->unstripBoth( $ret['return'] ),
454  'extensionTag works for {{#tag:pre|foo|style=margin-left: 1.6em}}'
455  );
456 
457  $ret = $engine->runConsole( [
458  'question' => '=mw.getCurrentFrame():extensionTag{ name = "pre", content = "foo",
459  args = { style = "margin-left: 1.6em" }
460  }',
461  ] + $args );
462  $this->assertSame(
463  '<pre style="margin-left: 1.6em">foo</pre>',
464  $parser->mStripState->unstripBoth( $ret['return'] ),
465  'extensionTag works for {{#tag:pre|foo|style=margin-left: 1.6em}}'
466  );
467 
468  // Test calling a non-existent function
469  try {
470  $ret = $engine->runConsole( [
471  'question' => '=mw.getCurrentFrame():callParserFunction{
472  name = "thisDoesNotExist", args = { "" }
473  }',
474  ] + $args );
475  $this->fail( "Expected LuaError not thrown for nonexistent parser function" );
476  } catch ( Scribunto_LuaError $err ) {
477  $this->assertSame(
478  'Lua error: callParserFunction: function "thisDoesNotExist" was not found.',
479  $err->getMessage(),
480  'callParserFunction correctly errors for nonexistent function'
481  );
482  }
483  }
484 
485  public function testBug62291() {
486  $engine = $this->getEngine();
487  $frame = $engine->getParser()->getPreprocessor()->newFrame();
488 
489  $this->extraModules['Module:Bug62291'] = '
490  local p = {}
491  function p.foo()
492  return table.concat( {
493  math.random(), math.random(), math.random(), math.random(), math.random()
494  }, ", " )
495  end
496  function p.bar()
497  local t = {}
498  t[1] = p.foo()
499  t[2] = mw.getCurrentFrame():preprocess( "{{#invoke:Bug62291|bar2}}" )
500  t[3] = p.foo()
501  return table.concat( t, "; " )
502  end
503  function p.bar2()
504  return "bar2 called"
505  end
506  return p
507  ';
508 
509  $title = Title::makeTitle( NS_MODULE, 'Bug62291' );
510  $module = $engine->fetchModuleFromParser( $title );
511 
512  // Make sure multiple invokes return the same text
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' );
516 
517  // Make sure a recursive invoke doesn't reset the PRNG
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' );
522 
523  // But a second invoke does
524  $r2 = $module->invoke( 'bar', $frame->newChild() );
525  $this->assertSame( $r1, $r2,
526  'Multiple invokes with recursive invoke returned different sets of random numbers' );
527  }
528 
529  public function testOsDateTimeTTLs() {
530  $engine = $this->getEngine();
531  $pp = $engine->getParser()->getPreprocessor();
532 
533  $this->extraModules['Module:DateTime'] = '
534  local p = {}
535  function p.day()
536  return os.date( "%d" )
537  end
538  function p.AMPM()
539  return os.date( "%p" )
540  end
541  function p.hour()
542  return os.date( "%H" )
543  end
544  function p.minute()
545  return os.date( "%M" )
546  end
547  function p.second()
548  return os.date( "%S" )
549  end
550  function p.table()
551  return os.date( "*t" )
552  end
553  function p.tablesec()
554  return os.date( "*t" ).sec
555  end
556  function p.time()
557  return os.time()
558  end
559  function p.specificDateAndTime()
560  return os.date("%S", os.time{year = 2013, month = 1, day = 1})
561  end
562  return p
563  ';
564 
565  $title = Title::makeTitle( NS_MODULE, 'DateTime' );
566  $module = $engine->fetchModuleFromParser( $title );
567 
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' );
573 
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' );
579 
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' );
585 
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' );
591 
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' );
596 
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' );
601 
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' );
606 
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' );
611 
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' );
616  }
617 
621  public function testVolatileCaching( $func ) {
622  $engine = $this->getEngine();
623  $parser = $engine->getParser();
624  $pp = $parser->getPreprocessor();
625 
626  $count = 0;
627  $parser->setHook( 'scribuntocount', function ( $str, $argv, $parser, $frame ) use ( &$count ) {
628  $frame->setVolatile();
629  return ++$count;
630  } );
631 
632  $this->extraModules['Template:ScribuntoTestVolatileCaching'] = '<scribuntocount/>';
633  $this->extraModules['Module:TestVolatileCaching'] = '
634  return {
635  preprocess = function ( frame )
636  return frame:preprocess( "<scribuntocount/>" )
637  end,
638  extensionTag = function ( frame )
639  return frame:extensionTag( "scribuntocount" )
640  end,
641  expandTemplate = function ( frame )
642  return frame:expandTemplate{ title = "ScribuntoTestVolatileCaching" }
643  end,
644  }
645  ';
646 
647  $frame = $pp->newFrame();
648  $count = 0;
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" );
654  }
655 
656  public function provideVolatileCaching() {
657  return [
658  [ 'preprocess' ],
659  [ 'extensionTag' ],
660  [ 'expandTemplate' ],
661  ];
662  }
663 
665  $engine = $this->getEngine();
666  $parser = $engine->getParser();
667  $pp = $parser->getPreprocessor();
668 
669  $this->extraModules['Module:Bug65687'] = '
670  return {
671  test = function ( frame )
672  return mw.loadData( "Module:Bug65687-LD" )[1]
673  end
674  }
675  ';
676  $this->extraModules['Module:Bug65687-LD'] = 'return { mw.getCurrentFrame().args[1] or "ok" }';
677 
678  $frame = $pp->newFrame();
679  $text = $frame->expand( $pp->preprocessToObj( "{{#invoke:Bug65687|test|foo}}" ) );
680  $text = $parser->mStripState->unstripBoth( $text );
681  $this->assertEquals( 'ok', $text, 'mw.loadData allowed access to frame args' );
682  }
683 
685  $engine = $this->getEngine();
686  $parser = $engine->getParser();
687  $pp = $parser->getPreprocessor();
688 
689  $this->extraModules['Module:Bug67498-directly'] = '
690  local f = mw.getCurrentFrame()
691  local f2 = f and f.args[1] or "<none>"
692 
693  return {
694  test = function ( frame )
695  return ( f and f.args[1] or "<none>" ) .. " " .. f2
696  end
697  }
698  ';
699  $this->extraModules['Module:Bug67498-statically'] = '
700  local M = require( "Module:Bug67498-directly" )
701  return {
702  test = function ( frame )
703  return M.test( frame )
704  end
705  }
706  ';
707  $this->extraModules['Module:Bug67498-dynamically'] = '
708  return {
709  test = function ( frame )
710  local M = require( "Module:Bug67498-directly" )
711  return M.test( frame )
712  end
713  }
714  ';
715 
716  foreach ( [ 'directly', 'statically', 'dynamically' ] as $how ) {
717  $frame = $pp->newFrame();
718  $text = $frame->expand( $pp->preprocessToObj(
719  "{{#invoke:Bug67498-$how|test|foo}} -- {{#invoke:Bug67498-$how|test|bar}}"
720  ) );
721  $text = $parser->mStripState->unstripBoth( $text );
722  $text = explode( ' -- ', $text );
723  $this->assertEquals( 'foo foo', $text[0],
724  "mw.getCurrentFrame() failed from a module loaded $how"
725  );
726  $this->assertEquals( 'bar bar', $text[1],
727  "mw.getCurrentFrame() cached the frame from a module loaded $how"
728  );
729  }
730  }
731 
732  public function testNonUtf8Errors() {
733  $engine = $this->getEngine();
734  $parser = $engine->getParser();
735 
736  $this->extraModules['Module:T208689'] = '
737  local p = {}
738 
739  p["foo\255bar"] = function ()
740  error( "error\255bar" )
741  end
742 
743  p.foo = function ()
744  p["foo\255bar"]()
745  end
746 
747  return p
748  ';
749 
750  // As via the API
751  try {
752  $engine->runConsole( [
753  'prevQuestions' => [],
754  'question' => 'p.foo()',
755  'title' => Title::newFromText( 'Module:T208689' ),
756  'content' => $this->extraModules['Module:T208689'],
757  ] );
758  $this->fail( 'Expected exception not thrown' );
759  } catch ( ScribuntoException $e ) {
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' );
762  }
763 
764  // Via the parser
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' );
771  }
772  }
773 }
774 
776  public function register() {
777  $lib = [
778  'test' => [ $this, 'test' ],
779  ];
780  $opts = [
781  'test' => 'Test option',
782  ];
783 
784  return $this->getEngine()->registerInterface( __DIR__ . '/CommonTests-lib.lua', $lib, $opts );
785  }
786 
787  public function test() {
788  return [ 'Test function' ];
789  }
790 }
791 
793  public function __construct() {
794  throw new MWException( 'deferLoad library that is never required was loaded anyway' );
795  }
796 
797  public function register() {
798  }
799 }
Scribunto_LuaCommonTestsLibrary
Definition: CommonTest.php:775
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:316
Scribunto_LuaCommonTest\testNonUtf8Errors
testNonUtf8Errors()
Definition: CommonTest.php:732
Scribunto_LuaError
Definition: LuaCommon.php:992
Scribunto_LuaCommonTest\testVolatileCaching
testVolatileCaching( $func)
@dataProvider provideVolatileCaching
Definition: CommonTest.php:621
Scribunto_LuaCommonTestsFailLibrary\__construct
__construct()
Definition: CommonTest.php:793
Scribunto_LuaCommonTest\testCallParserFunction
testCallParserFunction()
Definition: CommonTest.php:358
Scribunto_LuaCommonTest\testLoadDataLoadedOnce
testLoadDataLoadedOnce()
Definition: CommonTest.php:281
Scribunto_LuaCommonTest\testFrames
testFrames()
Definition: CommonTest.php:327
Scribunto_LuaCommonTest\$allowedGlobals
static $allowedGlobals
Definition: CommonTest.php:15
Scribunto_LuaLibraryBase\getEngine
getEngine()
Get the engine.
Definition: LibraryBase.php:56
Scribunto_LuaCommonTest
@covers ScribuntoEngineBase @covers Scribunto_LuaEngine @covers Scribunto_LuaStandaloneEngine @covers...
Definition: CommonTest.php:12
MWException
MediaWiki exception.
Definition: MWException.php:26
Scribunto_LuaCommonTestsLibrary\test
test()
Definition: CommonTest.php:787
NS_MODULE
const NS_MODULE
Definition: Scribunto.constants.php:5
$title
$title
Definition: testCompression.php:34
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:586
Scribunto_LuaCommonTest\testBug62291
testBug62291()
Definition: CommonTest.php:485
ScribuntoException\getScriptTraceHtml
getScriptTraceHtml( $options=[])
Get the backtrace as HTML, or false if there is none available.
Definition: Common.php:206
Scribunto_LuaEngineTestBase
This is the subclass for Lua library tests.
Definition: LuaEngineTestBase.php:12
Scribunto_LuaCommonTestsFailLibrary
Definition: CommonTest.php:792
Scribunto_LuaCommonTest\testOsDateTimeTTLs
testOsDateTimeTTLs()
Definition: CommonTest.php:529
Scribunto_LuaCommonTest\testGetCurrentFrameAtModuleScope
testGetCurrentFrameAtModuleScope()
Definition: CommonTest.php:684
Scribunto_LuaLibraryBase
This class provides some basic services that Lua libraries will probably need.
Definition: LibraryBase.php:27
Scribunto_LuaEngineTestBase\$engine
$engine
Definition: LuaEngineTestBase.php:17
Scribunto_LuaCommonTest\testNoLeakedGlobals
testNoLeakedGlobals()
Definition: CommonTest.php:95
Scribunto_LuaCommonTest\getTestModules
getTestModules()
Definition: CommonTest.php:83
ScribuntoException
An exception class which represents an error in the script.
Definition: Common.php:136
Scribunto_LuaCommonTest\$moduleName
static $moduleName
Definition: CommonTest.php:13
$args
if( $line===false) $args
Definition: cdb.php:64
Scribunto_LuaCommonTest\provideVolatileCaching
provideVolatileCaching()
Definition: CommonTest.php:656
Scribunto_LuaCommonTest\testModuleStringExtend
testModuleStringExtend()
Definition: CommonTest.php:203
Scribunto_LuaCommonTest\testGetCurrentFrameAndMWLoadData
testGetCurrentFrameAndMWLoadData()
Definition: CommonTest.php:664
getEngine
getEngine()
Definition: LuaEngineTestHelper.php:109
Scribunto_LuaCommonTest\testPHPLibrary
testPHPLibrary()
Definition: CommonTest.php:111
Scribunto_LuaCommonTest\setUp
setUp()
Definition: CommonTest.php:52