Skip to content

[WIP] moved signal handling tests to AbstractLoopTest #46

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/ExtEventLoop.php
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ private function unsubscribeStreamEvent($stream, $flag)
*/
private function createTimerCallback()
{
$this->timerCallback = function ($_, $_, $timer) {
$this->timerCallback = function ($_, $__, $timer) {
call_user_func($timer->getCallback(), $timer);

if (!$timer->isPeriodic() && $this->isTimerActive($timer)) {
Expand Down
120 changes: 120 additions & 0 deletions tests/AbstractLoopTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace React\Tests\EventLoop;

use React\EventLoop\LoopInterface;

abstract class AbstractLoopTest extends TestCase
{
/**
Expand All @@ -11,9 +13,18 @@ abstract class AbstractLoopTest extends TestCase

public function setUp()
{
parent::setUp();
$this->loop = $this->createLoop();
}

protected function tearDown()
{
parent::tearDown();
if (strncmp($this->getName(false), 'testSignal', 10) === 0 && extension_loaded('pcntl')) {
$this->resetSignalHandlers();
}
}

abstract public function createLoop();

public function createStream()
Expand Down Expand Up @@ -495,4 +506,113 @@ private function assertRunFasterThan($maxInterval)

$this->assertLessThan($maxInterval, $interval);
}

public function signalProvider()
{
return [
['SIGUSR1', SIGUSR1],
['SIGHUP', SIGHUP],
['SIGTERM', SIGTERM],
];
}

private $_signalHandled = false;

/**
* Test signal interrupt when no stream is attached to the loop
* @dataProvider signalProvider
*/
public function testSignalInterruptNoStream($sigName, $signal)
{
if (!extension_loaded('pcntl')) {
$this->markTestSkipped('"pcntl" extension is required to run this test.');
}

// dispatch signal handler once before signal is sent and once after
$this->loop->addTimer(0.01, function() { pcntl_signal_dispatch(); });
$this->loop->addTimer(0.03, function() { pcntl_signal_dispatch(); });
if (defined('HHVM_VERSION')) {
// hhvm startup is slow so we need to add another handler much later
$this->loop->addTimer(0.5, function() { pcntl_signal_dispatch(); });
}

$this->setUpSignalHandler($signal);

// spawn external process to send signal to current process id
$this->forkSendSignal($signal);
$this->loop->run();
$this->assertTrue($this->_signalHandled);
}

/**
* Test signal interrupt when a stream is attached to the loop
* @dataProvider signalProvider
*/
public function testSignalInterruptWithStream($sigName, $signal)
{
if (!extension_loaded('pcntl')) {
$this->markTestSkipped('"pcntl" extension is required to run this test.');
}

// dispatch signal handler every 10ms
$this->loop->addPeriodicTimer(0.01, function() { pcntl_signal_dispatch(); });

// add stream to the loop
list($writeStream, $readStream) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
$this->loop->addReadStream($readStream, function($stream, $loop) {
/** @var $loop LoopInterface */
$read = fgets($stream);
if ($read === "end loop\n") {
$loop->stop();
}
});
$this->loop->addTimer(0.05, function() use ($writeStream) {
fwrite($writeStream, "end loop\n");
});

$this->setUpSignalHandler($signal);

// spawn external process to send signal to current process id
$this->forkSendSignal($signal);

$this->loop->run();

$this->assertTrue($this->_signalHandled);
}

/**
* add signal handler for signal
*/
protected function setUpSignalHandler($signal)
{
$this->_signalHandled = false;
$this->assertTrue(pcntl_signal($signal, function() { $this->_signalHandled = true; }));
}

/**
* reset all signal handlers to default
*/
protected function resetSignalHandlers()
{
foreach($this->signalProvider() as $signal) {
pcntl_signal($signal[1], SIG_DFL);
}
}

/**
* fork child process to send signal to current process id
*/
protected function forkSendSignal($signal)
{
$currentPid = posix_getpid();
$childPid = pcntl_fork();
if ($childPid == -1) {
$this->fail("Failed to fork child process!");
} else if ($childPid === 0) {
// this is executed in the child process
usleep(20000);
posix_kill($currentPid, $signal);
die();
}
}
}
118 changes: 0 additions & 118 deletions tests/StreamSelectLoopTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,10 @@

namespace React\Tests\EventLoop;

use React\EventLoop\LoopInterface;
use React\EventLoop\StreamSelectLoop;

class StreamSelectLoopTest extends AbstractLoopTest
{
protected function tearDown()
{
parent::tearDown();
if (strncmp($this->getName(false), 'testSignal', 10) === 0 && extension_loaded('pcntl')) {
$this->resetSignalHandlers();
}
}

public function createLoop()
{
return new StreamSelectLoop();
Expand All @@ -36,113 +27,4 @@ public function testStreamSelectTimeoutEmulation()

$this->assertGreaterThan(0.04, $interval);
}

public function signalProvider()
{
return [
['SIGUSR1', SIGUSR1],
['SIGHUP', SIGHUP],
['SIGTERM', SIGTERM],
];
}

private $_signalHandled = false;

/**
* Test signal interrupt when no stream is attached to the loop
* @dataProvider signalProvider
*/
public function testSignalInterruptNoStream($sigName, $signal)
{
if (!extension_loaded('pcntl')) {
$this->markTestSkipped('"pcntl" extension is required to run this test.');
}

// dispatch signal handler once before signal is sent and once after
$this->loop->addTimer(0.01, function() { pcntl_signal_dispatch(); });
$this->loop->addTimer(0.03, function() { pcntl_signal_dispatch(); });
if (defined('HHVM_VERSION')) {
// hhvm startup is slow so we need to add another handler much later
$this->loop->addTimer(0.5, function() { pcntl_signal_dispatch(); });
}

$this->setUpSignalHandler($signal);

// spawn external process to send signal to current process id
$this->forkSendSignal($signal);
$this->loop->run();
$this->assertTrue($this->_signalHandled);
}

/**
* Test signal interrupt when a stream is attached to the loop
* @dataProvider signalProvider
*/
public function testSignalInterruptWithStream($sigName, $signal)
{
if (!extension_loaded('pcntl')) {
$this->markTestSkipped('"pcntl" extension is required to run this test.');
}

// dispatch signal handler every 10ms
$this->loop->addPeriodicTimer(0.01, function() { pcntl_signal_dispatch(); });

// add stream to the loop
list($writeStream, $readStream) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
$this->loop->addReadStream($readStream, function($stream, $loop) {
/** @var $loop LoopInterface */
$read = fgets($stream);
if ($read === "end loop\n") {
$loop->stop();
}
});
$this->loop->addTimer(0.05, function() use ($writeStream) {
fwrite($writeStream, "end loop\n");
});

$this->setUpSignalHandler($signal);

// spawn external process to send signal to current process id
$this->forkSendSignal($signal);

$this->loop->run();

$this->assertTrue($this->_signalHandled);
}

/**
* add signal handler for signal
*/
protected function setUpSignalHandler($signal)
{
$this->_signalHandled = false;
$this->assertTrue(pcntl_signal($signal, function() { $this->_signalHandled = true; }));
}

/**
* reset all signal handlers to default
*/
protected function resetSignalHandlers()
{
foreach($this->signalProvider() as $signal) {
pcntl_signal($signal[1], SIG_DFL);
}
}

/**
* fork child process to send signal to current process id
*/
protected function forkSendSignal($signal)
{
$currentPid = posix_getpid();
$childPid = pcntl_fork();
if ($childPid == -1) {
$this->fail("Failed to fork child process!");
} else if ($childPid === 0) {
// this is executed in the child process
usleep(20000);
posix_kill($currentPid, $signal);
die();
}
}
}