mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Scribunto
synced 2024-12-01 19:36:15 +00:00
7f94d88733
I252ec046 noticeably broke things by adding a dependency on the pcntl functions, which tend not to be present under Apache. It also subtly broke exit handling by using proc_close()'s return value, which PHP mangles in such a way that we can't tell the difference between an actual XCPU kill and exit( SIGXCPU ). This one wasn't noticed because the pcntl functions interpret everything proc_close() is going to return as a signal kill and we didn't test the 'exited' code path. I'm not sure what was going on in I57cdf8aa since it provides no details about what it was trying to fix, but that would have broken signal handling in the other way: Ibf5f4656 worked because proc_open() on Linux executes the command by passing it to /bin/sh -c, and that shell is going to turn any signal that kills Lua (e.g. the SIGXCPU) into an exit status of 128+signum. To avoid proc_close()'s broken return value while also avoiding the race, we can loop on proc_get_status() until $status['running'] is false. To have signals that kill Lua actually be interpreted as signals, we have two options: add an "exec" in front of the command so proc_open()'s /bin/sh -c is execed away, or detect shell-style signal reporting and convert it. We may as well do both. Bug: T128048 Change-Id: I8a62e1660fe1694e9ba5de77d01960c1ab4580aa
160 lines
5.4 KiB
PHP
160 lines
5.4 KiB
PHP
<?php
|
|
|
|
if ( PHP_SAPI !== 'cli' ) {
|
|
exit;
|
|
}
|
|
|
|
require_once __DIR__ . '/../LuaCommon/LuaInterpreterTest.php';
|
|
|
|
/**
|
|
* @group Lua
|
|
* @group LuaStandalone
|
|
*/
|
|
// @codingStandardsIgnoreLine Squiz.Classes.ValidClassName.NotCamelCaps
|
|
class Scribunto_LuaStandaloneInterpreterTest extends Scribunto_LuaInterpreterTest {
|
|
public $stdOpts = array(
|
|
'errorFile' => null,
|
|
'luaPath' => null,
|
|
'memoryLimit' => 50000000,
|
|
'cpuLimit' => 30,
|
|
);
|
|
|
|
private function getVsize( $pid ) {
|
|
$size = wfShellExec( wfEscapeShellArg( 'ps', '-p', $pid, '-o', 'vsz', '--no-headers' ) );
|
|
return $size * 1024;
|
|
}
|
|
|
|
protected function newInterpreter( $opts = array() ) {
|
|
$opts = $opts + $this->stdOpts;
|
|
$engine = new Scribunto_LuaStandaloneEngine( $this->stdOpts );
|
|
return new Scribunto_LuaStandaloneInterpreter( $engine, $opts );
|
|
}
|
|
|
|
public function testIOErrorExit() {
|
|
$interpreter = $this->newInterpreter();
|
|
try {
|
|
$interpreter->testquit();
|
|
$this->fail( 'Expected exception not thrown' );
|
|
} catch ( ScribuntoException $ex ) {
|
|
$this->assertSame( 'scribunto-luastandalone-exited', $ex->getMessageName() );
|
|
$this->assertSame( [ '[UNKNOWN]', 42 ], $ex->messageArgs );
|
|
}
|
|
}
|
|
|
|
public function testIOErrorSignal() {
|
|
$interpreter = $this->newInterpreter();
|
|
try {
|
|
proc_terminate( $interpreter->proc, 15 );
|
|
// Some dummy protocol interaction to make it see the interpreter went away
|
|
$interpreter->loadString( 'return ...', 'test' );
|
|
$this->fail( 'Expected exception not thrown' );
|
|
} catch ( ScribuntoException $ex ) {
|
|
$this->assertSame( 'scribunto-luastandalone-signal', $ex->getMessageName() );
|
|
$this->assertSame( [ '[UNKNOWN]', 15 ], $ex->messageArgs );
|
|
}
|
|
}
|
|
|
|
public function testGetStatus() {
|
|
$startTime = microtime( true );
|
|
if ( php_uname( 's' ) !== 'Linux' ) {
|
|
$this->markTestSkipped( "getStatus() not supported on platforms other than Linux" );
|
|
return;
|
|
}
|
|
$interpreter = $this->newInterpreter();
|
|
$status = $interpreter->getStatus();
|
|
$pid = $status['pid'];
|
|
$this->assertInternalType( 'integer', $status['pid'] );
|
|
$initialVsize = $this->getVsize( $pid );
|
|
$this->assertGreaterThan( 0, $initialVsize, 'Initial vsize' );
|
|
|
|
$chunk = $this->getBusyLoop( $interpreter );
|
|
|
|
while ( microtime( true ) - $startTime < 1 ) {
|
|
$interpreter->callFunction( $chunk, 100 );
|
|
}
|
|
$status = $interpreter->getStatus();
|
|
$vsize = $this->getVsize( $pid );
|
|
$time = $status['time'] / $interpreter->engine->getClockTick();
|
|
$this->assertGreaterThan( 0.1, $time, 'getStatus() time usage' );
|
|
$this->assertLessThan( 1.5, $time, 'getStatus() time usage' );
|
|
$this->assertEquals( $vsize, $status['vsize'], 'vsize', $vsize * 0.1 );
|
|
}
|
|
|
|
public function testFreeFunctions() {
|
|
$interpreter = $this->newInterpreter();
|
|
|
|
// Test #1: Make sure freeing actually works
|
|
$ret = $interpreter->callFunction(
|
|
$interpreter->loadString( 'return function() return "testFreeFunction #1" end', 'test' )
|
|
);
|
|
$id = $ret[0]->id;
|
|
$interpreter->cleanupLuaChunks();
|
|
$this->assertEquals(
|
|
array( 'testFreeFunction #1' ), $interpreter->callFunction( $ret[0] ),
|
|
'Test that function #1 was not freed while a reference exists'
|
|
);
|
|
$ret = null;
|
|
$interpreter->cleanupLuaChunks();
|
|
$testfunc = new Scribunto_LuaStandaloneInterpreterFunction( $interpreter->id, $id );
|
|
try {
|
|
$interpreter->callFunction( $testfunc );
|
|
$this->fail( "Expected exception because function #1 should have been freed" );
|
|
} catch ( Scribunto_LuaError $e ) {
|
|
$this->assertEquals(
|
|
"function id $id does not exist", $e->messageArgs[1],
|
|
'Testing for expected error when calling a freed function #1'
|
|
);
|
|
}
|
|
|
|
// Test #2: Make sure constructing a new copy of the function works
|
|
$ret = $interpreter->callFunction(
|
|
$interpreter->loadString( 'return function() return "testFreeFunction #2" end', 'test' )
|
|
);
|
|
$id = $ret[0]->id;
|
|
$func = new Scribunto_LuaStandaloneInterpreterFunction( $interpreter->id, $id );
|
|
$ret = null;
|
|
$interpreter->cleanupLuaChunks();
|
|
$this->assertEquals(
|
|
array( 'testFreeFunction #2' ), $interpreter->callFunction( $func ),
|
|
'Test that function #2 was not freed while a reference exists'
|
|
);
|
|
$func = null;
|
|
$interpreter->cleanupLuaChunks();
|
|
$testfunc = new Scribunto_LuaStandaloneInterpreterFunction( $interpreter->id, $id );
|
|
try {
|
|
$interpreter->callFunction( $testfunc );
|
|
$this->fail( "Expected exception because function #2 should have been freed" );
|
|
} catch ( Scribunto_LuaError $e ) {
|
|
$this->assertEquals(
|
|
"function id $id does not exist", $e->messageArgs[1],
|
|
'Testing for expected error when calling a freed function #2'
|
|
);
|
|
}
|
|
|
|
// Test #3: Make sure cloning works
|
|
$ret = $interpreter->callFunction(
|
|
$interpreter->loadString( 'return function() return "testFreeFunction #3" end', 'test' )
|
|
);
|
|
$id = $ret[0]->id;
|
|
$func = clone $ret[0];
|
|
$ret = null;
|
|
$interpreter->cleanupLuaChunks();
|
|
$this->assertEquals(
|
|
array( 'testFreeFunction #3' ), $interpreter->callFunction( $func ),
|
|
'Test that function #3 was not freed while a reference exists'
|
|
);
|
|
$func = null;
|
|
$interpreter->cleanupLuaChunks();
|
|
$testfunc = new Scribunto_LuaStandaloneInterpreterFunction( $interpreter->id, $id );
|
|
try {
|
|
$interpreter->callFunction( $testfunc );
|
|
$this->fail( "Expected exception because function #3 should have been freed" );
|
|
} catch ( Scribunto_LuaError $e ) {
|
|
$this->assertEquals(
|
|
"function id $id does not exist", $e->messageArgs[1],
|
|
'Testing for expected error when calling a freed function #3'
|
|
);
|
|
}
|
|
}
|
|
}
|