MediaWiki REL1_34
LuaInterpreterTest.php
Go to the documentation of this file.
1<?php
2
3abstract class Scribunto_LuaInterpreterTest extends PHPUnit\Framework\TestCase {
4 use MediaWikiCoversValidator;
5 use PHPUnit4And6Compat;
6
10 abstract protected function newInterpreter( $opts = [] );
11
12 protected function setUp() {
13 parent::setUp();
14 try {
15 $this->newInterpreter();
17 $this->markTestSkipped( "interpreter not available" );
18 }
19 }
20
21 protected function getBusyLoop( $interpreter ) {
22 $chunk = $interpreter->loadString( '
23 local args = {...}
24 local x, i
25 local s = string.rep("x", 1000000)
26 local n = args[1]
27 local e = args[2] and os.clock() + args[2] or nil
28 for i = 1, n do
29 x = x or string.find(s, "y", 1, true)
30 if e and os.clock() >= e then break end
31 end',
32 'busy' );
33 return $chunk;
34 }
35
37 public function testRoundtrip( /*...*/ ) {
38 $args = func_get_args();
39 $args = $this->normalizeOrder( $args );
40 $interpreter = $this->newInterpreter();
41 $passthru = $interpreter->loadString( 'return ...', 'passthru' );
42 $ret = $interpreter->callFunction( $passthru, ...$args );
43 $ret = $this->normalizeOrder( $ret );
44 $this->assertSame( $args, $ret );
45 }
46
48 public function testDoubleRoundtrip( /* ... */ ) {
49 $args = func_get_args();
50 $args = $this->normalizeOrder( $args );
51
52 $interpreter = $this->newInterpreter();
53 $interpreter->registerLibrary( 'test',
54 [ 'passthru' => [ $this, 'passthru' ] ] );
55 $doublePassthru = $interpreter->loadString(
56 'return test.passthru(...)', 'doublePassthru' );
57
58 $finalArgs = $args;
59 array_unshift( $finalArgs, $doublePassthru );
60 $ret = $interpreter->callFunction( ...$finalArgs );
61 $ret = $this->normalizeOrder( $ret );
62 $this->assertSame( $args, $ret );
63 }
64
69 public function testRoundtripNAN() {
70 $interpreter = $this->newInterpreter();
71
72 $passthru = $interpreter->loadString( 'return ...', 'passthru' );
73 $ret = $interpreter->callFunction( $passthru, NAN );
74 $this->assertTrue( is_nan( $ret[0] ), 'NaN was not passed through' );
75
76 $interpreter->registerLibrary( 'test',
77 [ 'passthru' => [ $this, 'passthru' ] ] );
78 $doublePassthru = $interpreter->loadString(
79 'return test.passthru(...)', 'doublePassthru' );
80 $ret = $interpreter->callFunction( $doublePassthru, NAN );
81 $this->assertTrue( is_nan( $ret[0] ), 'NaN was not double passed through' );
82 }
83
84 private function normalizeOrder( $a ) {
85 ksort( $a );
86 foreach ( $a as &$value ) {
87 if ( is_array( $value ) ) {
88 $value = $this->normalizeOrder( $value );
89 }
90 }
91 return $a;
92 }
93
94 public function passthru( /* ... */ ) {
95 $args = func_get_args();
96 return $args;
97 }
98
99 public function provideRoundtrip() {
100 return [
101 [ 1 ],
102 [ true ],
103 [ false ],
104 [ 'hello' ],
105 [ implode( '', array_map( 'chr', range( 0, 255 ) ) ) ],
106 [ 1, 2, 3 ],
107 [ [] ],
108 [ [ 0 => 'foo', 1 => 'bar' ] ],
109 [ [ 1 => 'foo', 2 => 'bar' ] ],
110 [ [ 'x' => 'foo', 'y' => 'bar', 'z' => [] ] ],
111 [ INF ],
112 [ -INF ],
113 [ 'ok', null, 'ok' ],
114 [ null, 'ok' ],
115 [ 'ok', null ],
116 [ null ],
117 ];
118 }
119
120 public function testTimeLimit() {
121 if ( php_uname( 's' ) === 'Darwin' ) {
122 $this->markTestSkipped( "Darwin is lacking POSIX timer, skipping CPU time limiting test." );
123 }
124
125 $interpreter = $this->newInterpreter( [ 'cpuLimit' => 1 ] );
126 $chunk = $this->getBusyLoop( $interpreter );
127 try {
128 $interpreter->callFunction(
129 $chunk,
130 1e9, // Arbitrary large quantity of work for the loop
131 2 // Early termination condition: 1 second CPU limit plus 1 second "fudge factor"
132 );
133 $this->fail( "Expected ScribuntoException was not thrown" );
134 } catch ( ScribuntoException $ex ) {
135 $this->assertSame( 'scribunto-common-timeout', $ex->messageName );
136 }
137 }
138
139 public function testTestMemoryLimit() {
140 $interpreter = $this->newInterpreter( [ 'memoryLimit' => 20 * 1e6 ] );
141 $chunk = $interpreter->loadString( '
142 t = {}
143 for i = 1, 10 do
144 t[#t + 1] = string.rep("x" .. i, 1000000)
145 end
146 ',
147 'memoryLimit' );
148 try {
149 $interpreter->callFunction( $chunk );
150 $this->fail( "Expected ScribuntoException was not thrown" );
151 } catch ( ScribuntoException $ex ) {
152 $this->assertSame( 'scribunto-lua-error', $ex->messageName );
153 $this->assertSame( 'not enough memory', $ex->messageArgs[1] );
154 }
155 }
156
157 public function testWrapPHPFunction() {
158 $interpreter = $this->newInterpreter();
159 $func = $interpreter->wrapPhpFunction( function ( $n ) {
160 return [ 42, $n ];
161 } );
162 $res = $interpreter->callFunction( $func, 'From PHP' );
163 $this->assertEquals( [ 42, 'From PHP' ], $res );
164
165 $chunk = $interpreter->loadString( '
166 f = ...
167 return f( "From Lua" )
168 ',
169 'wrappedPhpFunction' );
170 $res = $interpreter->callFunction( $chunk, $func );
171 $this->assertEquals( [ 42, 'From Lua' ], $res );
172 }
173
175 $interpreter = $this->newInterpreter();
176 $test1Called = false;
177 $test2Called = false;
178
179 // Like a first call to Scribunto_LuaEngine::registerInterface()
180 $interpreter->registerLibrary( 'mw_interface', [
181 'foo' => function ( $v ) use ( &$test1Called ) {
182 $test1Called = $v;
183 },
184 ] );
185 $interpreter->callFunction(
186 $interpreter->loadString( 'test1 = mw_interface; mw_interface = nil', 'test' )
187 );
188 // Like a second call to Scribunto_LuaEngine::registerInterface()
189 $interpreter->registerLibrary( 'mw_interface', [
190 'foo' => function ( $v ) use ( &$test2Called ) {
191 $test2Called = $v;
192 },
193 ] );
194 $interpreter->callFunction(
195 $interpreter->loadString( 'test2 = mw_interface; mw_interface = nil', 'test' )
196 );
197 // Call both of the interfaces registered above.
198 $interpreter->callFunction(
199 $interpreter->loadString( 'test1.foo( "first" ); test2.foo( "second" )', 'test' )
200 );
201 $this->assertSame( 'first', $test1Called, 'test1.foo was called with "first"' );
202 $this->assertSame( 'second', $test2Called, 'test2.foo was called with "second"' );
203 }
204
205}
if( $line===false) $args
Definition cdb.php:64
An exception class which represents an error in the script.
Definition Common.php:136
testRoundtrip()
@dataProvider provideRoundtrip
testRoundtripNAN()
This cannot be done in testRoundtrip and testDoubleRoundtrip, because assertSame( NAN,...
testDoubleRoundtrip()
@dataProvider provideRoundtrip
return true
Definition router.php:94