MediaWiki REL1_34
LuaStandaloneInterpreterTest.php
Go to the documentation of this file.
1<?php
2
3if ( !wfIsCLI() ) {
4 exit;
5}
6
7require_once __DIR__ . '/../LuaCommon/LuaInterpreterTest.php';
8
9use Wikimedia\TestingAccessWrapper;
10
17 public $stdOpts = [
18 'errorFile' => null,
19 'luaPath' => null,
20 'memoryLimit' => 50000000,
21 'cpuLimit' => 30,
22 ];
23
24 private function getVsize( $pid ) {
25 $size = wfShellExec( wfEscapeShellArg( 'ps', '-p', $pid, '-o', 'vsz', '--no-headers' ) );
26 return trim( $size ) * 1024;
27 }
28
29 protected function newInterpreter( $opts = [] ) {
30 $opts = $opts + $this->stdOpts;
31 $engine = new Scribunto_LuaStandaloneEngine( $this->stdOpts );
32 return new Scribunto_LuaStandaloneInterpreter( $engine, $opts );
33 }
34
35 public function testIOErrorExit() {
36 $interpreter = $this->newInterpreter();
37 try {
38 $interpreter->testquit();
39 $this->fail( 'Expected exception not thrown' );
40 } catch ( ScribuntoException $ex ) {
41 $this->assertSame( 'scribunto-luastandalone-exited', $ex->getMessageName() );
42 $this->assertSame( [ '[UNKNOWN]', 42 ], $ex->messageArgs );
43 }
44 }
45
46 public function testIOErrorSignal() {
47 $interpreter = $this->newInterpreter();
48 try {
49 proc_terminate( $interpreter->proc, 15 );
50 // Some dummy protocol interaction to make it see the interpreter went away
51 $interpreter->loadString( 'return ...', 'test' );
52 $this->fail( 'Expected exception not thrown' );
53 } catch ( ScribuntoException $ex ) {
54 $this->assertSame( 'scribunto-luastandalone-signal', $ex->getMessageName() );
55 $this->assertSame( [ '[UNKNOWN]', 15 ], $ex->messageArgs );
56 }
57 }
58
59 public function testGetStatus() {
60 $startTime = microtime( true );
61 if ( php_uname( 's' ) !== 'Linux' ) {
62 $this->markTestSkipped( "getStatus() not supported on platforms other than Linux" );
63 return;
64 }
65 $interpreter = $this->newInterpreter();
66 $engine = TestingAccessWrapper::newFromObject( $interpreter->engine );
67 $status = $interpreter->getStatus();
68 $pid = $status['pid'];
69 $this->assertInternalType( 'integer', $status['pid'] );
70 $initialVsize = $this->getVsize( $pid );
71 $this->assertGreaterThan( 0, $initialVsize, 'Initial vsize' );
72
73 $chunk = $this->getBusyLoop( $interpreter );
74
75 while ( microtime( true ) - $startTime < 1 ) {
76 $interpreter->callFunction( $chunk, 100 );
77 }
78 $status = $interpreter->getStatus();
79 $vsize = $this->getVsize( $pid );
80 $time = $status['time'] / $engine->getClockTick();
81 $this->assertGreaterThan( 0.1, $time, 'getStatus() time usage' );
82 $this->assertLessThan( 1.5, $time, 'getStatus() time usage' );
83 $this->assertEquals( $vsize, $status['vsize'], 'vsize', $vsize * 0.1 );
84 }
85
89 public function testPhpToLuaArrayKeyConversion( $array, $expect ) {
90 $interpreter = $this->newInterpreter();
91
92 $ret = $interpreter->callFunction(
93 $interpreter->loadString(
94 'local t, r = ..., {}; for k, v in pairs( t ) do r[v] = type(k) end return r', 'test'
95 ),
96 $array
97 );
98 ksort( $ret[0], SORT_STRING );
99 $this->assertSame( $expect, $ret[0] );
100 }
101
102 public static function providePhpToLuaArrayKeyConversion() {
103 if ( PHP_INT_MAX > 9007199254740992 ) {
104 $a = [
105 '9007199254740992' => 'max', '9007199254740993' => 'max+1',
106 '-9007199254740992' => 'min', '-9007199254740993' => 'min-1',
107 ];
108 } else {
109 $a = [
110 '2147483647' => 'max', '2147483648' => 'max+1',
111 '-2147483648' => 'min', '-2147483649' => 'min-1',
112 ];
113 }
114
115 return [
116 'simple integers' => [
117 [ -10 => 'minus ten', 0 => 'zero', 10 => 'ten' ],
118 [ 'minus ten' => 'number', 'ten' => 'number', 'zero' => 'number' ],
119 ],
120 'maximal values' => [
121 $a,
122 [ 'max' => 'number', 'max+1' => 'string', 'min' => 'number', 'min-1' => 'string' ],
123 ],
124 ];
125 }
126
130 public function testLuaToPhpArrayKeyConversion( $lua, $expect ) {
131 if ( $expect instanceof Exception ) {
132 $this->setExpectedException( Scribunto_LuaError::class, $expect->getMessage() );
133 }
134
135 $interpreter = $this->newInterpreter();
136 $ret = $interpreter->callFunction(
137 $interpreter->loadString( "return { $lua }", 'test' )
138 );
139 if ( $expect instanceof Exception ) {
140 $this->fail( 'Expected exception not thrown' );
141 }
142 ksort( $ret[0], SORT_STRING );
143 $this->assertSame( $expect, $ret[0] );
144 }
145
146 public static function provideLuaToPhpArrayKeyConversion() {
147 if ( PHP_INT_MAX > 9007199254740992 ) {
148 $max = '9223372036854774784';
149 $max2 = '9223372036854775808';
150 $min = '-9223372036854775808';
151 $min2 = '-9223372036854775809';
152 } else {
153 $max = '2147483647';
154 $max2 = '2147483648';
155 $min = '-2147483648';
156 $min2 = '-2147483649';
157 }
158
159 return [
160 'simple integers' => [
161 '[-10] = "minus ten", [0] = "zero", [10] = "ten"',
162 [ -10 => 'minus ten', 0 => 'zero', 10 => 'ten' ],
163 ],
164 'stringified integers' => [
165 '["-10"] = "minus ten", ["0"] = "zero", ["10"] = "ten"',
166 [ -10 => 'minus ten', 0 => 'zero', 10 => 'ten' ],
167 ],
168 'maximal integers' => [
169 "['$max'] = 'near max', ['$max2'] = 'max+1', ['$min'] = 'min', ['$min2'] = 'min-1'",
170 [ $min => 'min', $min2 => 'min-1', $max => 'near max', $max2 => 'max+1' ],
171 ],
172 'collision (0)' => [
173 '[0] = "number zero", ["0"] = "string zero"',
174 new Exception( 'Collision for array key 0 when passing data from Lua to PHP.' ),
175 ],
176 'collision (float)' => [
177 '[1.5] = "number 1.5", ["1.5"] = "string 1.5"',
178 new Exception( 'Collision for array key 1.5 when passing data from Lua to PHP.' ),
179 ],
180 'collision (inf)' => [
181 '[1/0] = "number inf", ["inf"] = "string inf"',
182 new Exception( 'Collision for array key inf when passing data from Lua to PHP.' ),
183 ],
184 ];
185 }
186
187 public function testFreeFunctions() {
188 $interpreter = $this->newInterpreter();
189
190 // Test #1: Make sure freeing actually works
191 $ret = $interpreter->callFunction(
192 $interpreter->loadString( 'return function() return "testFreeFunction #1" end', 'test' )
193 );
194 $id = $ret[0]->id;
195 $interpreter->cleanupLuaChunks();
196 $this->assertEquals(
197 [ 'testFreeFunction #1' ], $interpreter->callFunction( $ret[0] ),
198 'Test that function #1 was not freed while a reference exists'
199 );
200 $ret = null;
201 $interpreter->cleanupLuaChunks();
202 $testfunc = new Scribunto_LuaStandaloneInterpreterFunction( $interpreter->id, $id );
203 try {
204 $interpreter->callFunction( $testfunc );
205 $this->fail( "Expected exception because function #1 should have been freed" );
206 } catch ( Scribunto_LuaError $e ) {
207 $this->assertEquals(
208 "function id $id does not exist", $e->messageArgs[1],
209 'Testing for expected error when calling a freed function #1'
210 );
211 }
212
213 // Test #2: Make sure constructing a new copy of the function works
214 $ret = $interpreter->callFunction(
215 $interpreter->loadString( 'return function() return "testFreeFunction #2" end', 'test' )
216 );
217 $id = $ret[0]->id;
218 $func = new Scribunto_LuaStandaloneInterpreterFunction( $interpreter->id, $id );
219 $ret = null;
220 $interpreter->cleanupLuaChunks();
221 $this->assertEquals(
222 [ 'testFreeFunction #2' ], $interpreter->callFunction( $func ),
223 'Test that function #2 was not freed while a reference exists'
224 );
225 $func = null;
226 $interpreter->cleanupLuaChunks();
227 $testfunc = new Scribunto_LuaStandaloneInterpreterFunction( $interpreter->id, $id );
228 try {
229 $interpreter->callFunction( $testfunc );
230 $this->fail( "Expected exception because function #2 should have been freed" );
231 } catch ( Scribunto_LuaError $e ) {
232 $this->assertEquals(
233 "function id $id does not exist", $e->messageArgs[1],
234 'Testing for expected error when calling a freed function #2'
235 );
236 }
237
238 // Test #3: Make sure cloning works
239 $ret = $interpreter->callFunction(
240 $interpreter->loadString( 'return function() return "testFreeFunction #3" end', 'test' )
241 );
242 $id = $ret[0]->id;
243 $func = clone $ret[0];
244 $ret = null;
245 $interpreter->cleanupLuaChunks();
246 $this->assertEquals(
247 [ 'testFreeFunction #3' ], $interpreter->callFunction( $func ),
248 'Test that function #3 was not freed while a reference exists'
249 );
250 $func = null;
251 $interpreter->cleanupLuaChunks();
252 $testfunc = new Scribunto_LuaStandaloneInterpreterFunction( $interpreter->id, $id );
253 try {
254 $interpreter->callFunction( $testfunc );
255 $this->fail( "Expected exception because function #3 should have been freed" );
256 } catch ( Scribunto_LuaError $e ) {
257 $this->assertEquals(
258 "function id $id does not exist", $e->messageArgs[1],
259 'Testing for expected error when calling a freed function #3'
260 );
261 }
262 }
263}
wfEscapeShellArg(... $args)
Version of escapeshellarg() that works better on Windows.
wfShellExec( $cmd, &$retval=null, $environ=[], $limits=[], $options=[])
Execute a shell command, with time and memory limits mirrored from the PHP configuration if supported...
wfIsCLI()
Check if we are running from the commandline.
An exception class which represents an error in the script.
Definition Common.php:136
@group Lua @group LuaStandalone @covers Scribunto_LuaStandaloneInterpreter
testLuaToPhpArrayKeyConversion( $lua, $expect)
@dataProvider provideLuaToPhpArrayKeyConversion
testPhpToLuaArrayKeyConversion( $array, $expect)
@dataProvider providePhpToLuaArrayKeyConversion