2016-05-24 10:37:25 +02:00
< ? php
2017-03-12 11:21:44 +01:00
namespace Amp\Test\Loop ;
2016-05-24 10:37:25 +02:00
2017-03-15 00:34:37 +01:00
use Amp\Coroutine ;
use Amp\Failure ;
2017-03-12 22:09:19 +01:00
use Amp\Loop ;
2017-03-10 21:31:57 +01:00
use Amp\Loop\Driver ;
2017-03-14 17:50:24 +01:00
use Amp\Loop\InvalidWatcherError ;
2017-03-10 21:31:57 +01:00
use Amp\Loop\UnsupportedFeatureException ;
2017-03-15 07:32:43 +01:00
use Amp\Success ;
2017-03-12 11:21:44 +01:00
use PHPUnit\Framework\TestCase ;
2017-03-12 21:02:26 +01:00
use React\Promise\RejectedPromise as RejectedReactPromise ;
2016-05-24 10:37:25 +02:00
2016-05-27 03:25:55 +02:00
if ( ! defined ( " SIGUSR1 " )) {
2017-03-10 21:31:57 +01:00
define ( " SIGUSR1 " , 30 );
2016-08-14 21:14:43 +02:00
}
if ( ! defined ( " SIGUSR2 " )) {
2017-03-10 21:31:57 +01:00
define ( " SIGUSR2 " , 31 );
2016-05-27 03:25:55 +02:00
}
2016-08-14 22:44:49 +02:00
if ( ! defined ( " PHP_INT_MIN " )) {
2017-03-10 21:31:57 +01:00
define ( " PHP_INT_MIN " , ~ PHP_INT_MAX );
2016-08-14 22:44:49 +02:00
}
2017-03-12 11:21:44 +01:00
abstract class DriverTest extends TestCase {
2016-06-03 17:01:21 +02:00
/**
* The DriverFactory to run this test on
*
2017-03-10 21:31:57 +01:00
* @ return callable
2016-06-03 17:01:21 +02:00
*/
2017-03-10 22:20:28 +01:00
abstract function getFactory () : callable ;
2016-06-03 17:01:21 +02:00
/** @var Driver */
public $loop ;
2017-03-10 21:31:57 +01:00
function setUp () {
$this -> loop = ( $this -> getFactory ())();
2017-03-12 22:09:19 +01:00
2016-06-03 17:01:21 +02:00
if ( ! $this -> loop instanceof Driver ) {
$this -> fail ( " Factory did not return a loop Driver " );
}
2017-03-12 22:09:19 +01:00
// Required for error handler to work
Loop :: set ( $this -> loop );
2016-06-03 17:01:21 +02:00
}
2017-03-10 21:31:57 +01:00
function start ( $cb ) {
2016-06-03 17:01:21 +02:00
$cb ( $this -> loop );
$this -> loop -> run ();
}
2017-03-10 21:31:57 +01:00
function testEmptyLoop () {
2017-03-14 00:52:57 +01:00
$this -> assertNull ( $this -> loop -> run ());
2016-06-03 17:01:21 +02:00
}
2017-03-10 21:31:57 +01:00
function testStopWorksEvenIfNotCurrentlyRunning () {
2017-03-14 00:52:57 +01:00
$this -> assertNull ( $this -> loop -> stop ());
2016-06-03 17:01:21 +02:00
}
// Note: The running nesting is important for being able to continue actually still running loops (i.e. running flag set, if the driver has one) inside register_shutdown_function() for example
2017-03-10 21:31:57 +01:00
function testLoopRunsCanBeConsecutiveAndNested () {
2016-06-03 17:01:21 +02:00
$this -> expectOutputString ( " 123456 " );
$this -> start ( function ( Driver $loop ) {
2016-06-07 01:53:28 +02:00
$loop -> stop ();
2017-03-10 21:31:57 +01:00
$loop -> defer ( function () use ( & $run ) {
2016-06-07 01:53:28 +02:00
echo $run = 1 ;
2016-06-03 17:01:21 +02:00
});
$loop -> run ();
2016-06-07 01:53:28 +02:00
if ( ! $run ) {
$this -> fail ( " A loop stop before a run must not impact that run " );
}
2017-03-10 21:31:57 +01:00
$loop -> defer ( function () use ( $loop ) {
2016-06-03 17:01:21 +02:00
$loop -> run ();
2017-03-27 07:05:55 +02:00
echo 5 ;
2017-03-10 21:31:57 +01:00
$loop -> defer ( function () use ( $loop ) {
2016-06-03 17:01:21 +02:00
echo 6 ;
$loop -> stop ();
2017-03-10 21:31:57 +01:00
$loop -> defer ( function () {
2016-06-07 01:53:28 +02:00
$this -> fail ( " A loop stopped at all levels must not execute further defers " );
2016-06-03 17:01:21 +02:00
});
});
2017-03-27 07:05:55 +02:00
$loop -> run ();
2016-06-03 17:01:21 +02:00
});
2017-03-10 21:31:57 +01:00
$loop -> defer ( function () use ( $loop ) {
2016-06-03 17:01:21 +02:00
echo 2 ;
2017-03-10 21:31:57 +01:00
$loop -> defer ( function () use ( $loop ) {
2017-03-27 07:05:55 +02:00
echo 4 ;
2016-06-03 17:01:21 +02:00
});
});
2017-03-10 21:31:57 +01:00
$loop -> defer ( function () use ( $loop ) {
2016-06-03 17:01:21 +02:00
echo 3 ;
});
});
}
2017-03-12 17:33:46 +01:00
/** This MUST NOT have a "test" prefix, otherwise it's executed as test and marked as risky. */
function checkForSignalCapability () {
2016-06-03 17:01:21 +02:00
try {
2017-03-15 00:34:37 +01:00
$watcher = $this -> loop -> onSignal ( SIGUSR1 , function () {
});
2016-06-03 17:01:21 +02:00
$this -> loop -> cancel ( $watcher );
} catch ( UnsupportedFeatureException $e ) {
$this -> markTestSkipped ( " The loop is not capable of handling signals properly. Skipping. " );
}
}
2017-03-10 21:31:57 +01:00
function testWatcherUnrefRerefRunResult () {
2016-06-03 17:01:21 +02:00
$invoked = false ;
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) use ( & $invoked ) {
$watcher = $loop -> defer ( function () use ( & $invoked ) {
2016-06-03 17:01:21 +02:00
$invoked = true ;
});
$loop -> unreference ( $watcher );
2016-06-07 01:53:28 +02:00
$loop -> unreference ( $watcher );
2016-06-03 17:01:21 +02:00
$loop -> reference ( $watcher );
});
$this -> assertTrue ( $invoked );
}
2017-03-10 21:31:57 +01:00
function testDeferWatcherUnrefRunResult () {
2017-03-14 00:52:57 +01:00
$invoked = false ;
$this -> start ( function ( Driver $loop ) use ( & $invoked ) {
$watcher = $loop -> defer ( function () use ( & $invoked ) {
$invoked = true ;
2016-06-03 17:01:21 +02:00
});
$loop -> unreference ( $watcher );
});
2017-03-14 00:52:57 +01:00
$this -> assertFalse ( $invoked );
2016-06-03 17:01:21 +02:00
}
2017-03-10 21:31:57 +01:00
function testOnceWatcherUnrefRunResult () {
2016-06-03 17:01:21 +02:00
$invoked = false ;
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) use ( & $invoked ) {
$watcher = $loop -> delay ( 2000 , function () use ( & $invoked ) {
2016-06-03 17:01:21 +02:00
$invoked = true ;
});
$loop -> unreference ( $watcher );
$loop -> unreference ( $watcher );
});
$this -> assertFalse ( $invoked );
}
2017-03-10 21:31:57 +01:00
function testRepeatWatcherUnrefRunResult () {
2016-06-03 17:01:21 +02:00
$invoked = false ;
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) use ( & $invoked ) {
$watcher = $loop -> repeat ( 2000 , function () use ( & $invoked ) {
2016-06-03 17:01:21 +02:00
$invoked = true ;
});
$loop -> unreference ( $watcher );
});
$this -> assertFalse ( $invoked );
}
2017-03-10 21:31:57 +01:00
function testOnReadableWatcherUnrefRunResult () {
2017-03-14 00:52:57 +01:00
$invoked = false ;
$this -> start ( function ( Driver $loop ) use ( & $invoked ) {
$watcher = $loop -> onReadable ( STDIN , function () use ( & $invoked ) {
$invoked = true ;
2016-06-03 17:01:21 +02:00
});
$loop -> unreference ( $watcher );
});
2017-03-14 00:52:57 +01:00
$this -> assertFalse ( $invoked );
2016-06-03 17:01:21 +02:00
}
2017-03-10 21:31:57 +01:00
function testOnWritableWatcherKeepAliveRunResult () {
2017-03-14 00:52:57 +01:00
$invoked = false ;
$this -> start ( function ( Driver $loop ) use ( & $invoked ) {
$watcher = $loop -> onWritable ( STDOUT , function () use ( & $invoked ) {
$invoked = true ;
2016-06-03 17:01:21 +02:00
});
$loop -> unreference ( $watcher );
});
2017-03-14 00:52:57 +01:00
$this -> assertFalse ( $invoked );
2016-06-03 17:01:21 +02:00
}
2017-03-10 21:31:57 +01:00
function testOnSignalWatcherKeepAliveRunResult () {
2017-03-14 22:47:54 +01:00
$this -> checkForSignalCapability ();
$invoked = false ;
$this -> start ( function ( Driver $loop ) use ( & $invoked ) {
2017-03-10 21:31:57 +01:00
$watcher = $loop -> onSignal ( SIGUSR1 , function () {
2016-06-03 17:01:21 +02:00
// empty
});
2017-03-14 22:47:54 +01:00
$watcher = $loop -> delay ( 100 , function () use ( & $invoked , $loop , $watcher ) {
$invoked = true ;
$loop -> unreference ( $watcher );
});
2016-06-03 17:01:21 +02:00
$loop -> unreference ( $watcher );
});
2017-03-14 22:47:54 +01:00
$this -> assertTrue ( $invoked );
2016-06-03 17:01:21 +02:00
}
2017-03-10 21:31:57 +01:00
function testUnreferencedDeferWatcherStillExecutes () {
2017-01-08 17:56:59 +01:00
$invoked = false ;
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) use ( & $invoked ) {
2017-01-08 17:56:59 +01:00
$watcher = $loop -> defer ( function () use ( & $invoked ) {
$invoked = true ;
});
$loop -> unreference ( $watcher );
$loop -> defer ( function () {
// just to keep loop running
});
});
$this -> assertTrue ( $invoked );
}
2017-03-10 21:31:57 +01:00
function testLoopDoesNotBlockOnNegativeTimerExpiration () {
2017-01-08 17:56:59 +01:00
$invoked = false ;
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) use ( & $invoked ) {
2017-01-08 17:56:59 +01:00
$loop -> delay ( 1 , function () use ( & $invoked ) {
$invoked = true ;
});
usleep ( 1000 * 10 );
});
$this -> assertTrue ( $invoked );
}
2017-03-10 21:31:57 +01:00
function testDisabledDeferReenableInSubsequentTick () {
2016-06-03 17:01:21 +02:00
$this -> expectOutputString ( " 123 " );
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) {
2016-06-03 17:01:21 +02:00
$watcherId = $loop -> defer ( function ( $watcherId ) {
echo 3 ;
});
$loop -> disable ( $watcherId );
$loop -> defer ( function () use ( $loop , $watcherId ) {
$loop -> enable ( $watcherId );
echo 2 ;
});
echo 1 ;
});
}
2017-03-10 21:31:57 +01:00
function provideRegistrationArgs () {
2016-06-03 17:01:21 +02:00
$args = [
2017-03-10 21:31:57 +01:00
[ " defer " , [ function () {
}]],
[ " delay " , [ 5 , function () {
}]],
[ " repeat " , [ 5 , function () {
}]],
[ " onWritable " , [ \STDOUT , function () {
}]],
[ " onReadable " , [ \STDIN , function () {
}]],
[ " onSignal " , [ \SIGUSR1 , function () {
}]],
2016-06-03 17:01:21 +02:00
];
return $args ;
}
2017-01-07 12:23:28 +01:00
2016-12-29 21:59:26 +01:00
/**
* @ requires PHP 7
* @ dataProvider provideRegistrationArgs
*/
2016-12-29 17:45:12 +01:00
function testWeakTypes ( $type , $args ) {
2016-12-29 18:16:36 +01:00
if ( $type == " onSignal " ) {
2017-03-12 17:33:46 +01:00
$this -> checkForSignalCapability ();
2016-12-29 18:16:36 +01:00
}
2016-12-29 17:45:12 +01:00
$this -> start ( function ( Driver $loop ) use ( $type , $args , & $invoked ) {
if ( $type == " onReadable " ) {
$ends = stream_socket_pair ( \stripos ( PHP_OS , " win " ) === 0 ? STREAM_PF_INET : STREAM_PF_UNIX , STREAM_SOCK_STREAM , STREAM_IPPROTO_IP );
fwrite ( $ends [ 0 ], " trigger readability watcher " );
$args = [ $ends [ 1 ]];
} else {
array_pop ( $args );
}
$expectedData = 20.75 ;
if ( substr ( $type , 0 , 2 ) == " on " ) {
2017-03-10 21:31:57 +01:00
$args [] = function ( $watcherId , $arg , int $data ) use ( $loop , & $invoked , $expectedData ) {
2016-12-29 17:45:12 +01:00
$invoked = true ;
$this -> assertSame (( int ) $expectedData , $data );
$loop -> unreference ( $watcherId );
};
} else {
2017-03-10 21:31:57 +01:00
$args [] = function ( $watcherId , int $data ) use ( $loop , & $invoked , $expectedData , $type ) {
2016-12-29 17:45:12 +01:00
$invoked = true ;
$this -> assertSame (( int ) $expectedData , $data );
if ( $type == " repeat " ) {
$loop -> unreference ( $watcherId );
}
};
}
$args [] = $expectedData ;
call_user_func_array ([ $loop , $type ], $args );
if ( $type == " onSignal " ) {
2017-03-10 21:31:57 +01:00
$loop -> defer ( function () {
2016-12-29 17:45:12 +01:00
\posix_kill ( \getmypid (), \SIGUSR1 );
});
}
});
$this -> assertTrue ( $invoked );
}
2016-06-07 01:53:28 +02:00
/** @dataProvider provideRegistrationArgs */
2017-03-10 21:31:57 +01:00
function testDisableWithConsecutiveCancel ( $type , $args ) {
2016-06-03 17:01:21 +02:00
if ( $type === " onSignal " ) {
2017-03-12 17:33:46 +01:00
$this -> checkForSignalCapability ();
2016-06-03 17:01:21 +02:00
}
2017-03-14 00:52:57 +01:00
$invoked = false ;
$this -> start ( function ( Driver $loop ) use ( & $invoked , $type , $args ) {
2016-06-03 17:01:21 +02:00
$func = [ $loop , $type ];
$watcherId = \call_user_func_array ( $func , $args );
$loop -> disable ( $watcherId );
2017-03-14 00:52:57 +01:00
$loop -> defer ( function () use ( & $invoked , $loop , $watcherId ) {
2016-06-03 17:01:21 +02:00
$loop -> cancel ( $watcherId );
2017-03-14 00:52:57 +01:00
$invoked = true ;
2016-06-03 17:01:21 +02:00
});
2017-03-14 00:52:57 +01:00
$this -> assertFalse ( $invoked );
2016-06-03 17:01:21 +02:00
});
2017-03-14 00:52:57 +01:00
$this -> assertTrue ( $invoked );
2016-06-03 17:01:21 +02:00
}
2016-06-07 01:53:28 +02:00
/** @dataProvider provideRegistrationArgs */
2017-03-10 21:31:57 +01:00
function testWatcherReferenceInfo ( $type , $args ) {
2016-06-03 17:01:21 +02:00
if ( $type === " onSignal " ) {
2017-03-12 17:33:46 +01:00
$this -> checkForSignalCapability ();
2016-06-03 17:01:21 +02:00
}
$loop = $this -> loop ;
$func = [ $loop , $type ];
if ( substr ( $type , 0 , 2 ) === " on " ) {
$type = " on_ " . lcfirst ( substr ( $type , 2 ));
}
// being referenced is the default
$watcherId1 = \call_user_func_array ( $func , $args );
2016-12-28 19:48:34 +01:00
$info = $loop -> getInfo ();
2016-06-03 17:01:21 +02:00
$expected = [ " enabled " => 1 , " disabled " => 0 ];
$this -> assertSame ( $expected , $info [ $type ]);
$expected = [ " referenced " => 1 , " unreferenced " => 0 ];
2017-03-10 22:20:28 +01:00
$this -> assertSame ( $expected , $info [ " enabled_watchers " ]);
2016-06-03 17:01:21 +02:00
// explicitly reference() even though it's the default setting
$argsCopy = $args ;
$watcherId2 = \call_user_func_array ( $func , $argsCopy );
$loop -> reference ( $watcherId2 );
$loop -> reference ( $watcherId2 );
2016-12-28 19:48:34 +01:00
$info = $loop -> getInfo ();
2016-06-03 17:01:21 +02:00
$expected = [ " enabled " => 2 , " disabled " => 0 ];
$this -> assertSame ( $expected , $info [ $type ]);
$expected = [ " referenced " => 2 , " unreferenced " => 0 ];
2017-03-10 22:20:28 +01:00
$this -> assertSame ( $expected , $info [ " enabled_watchers " ]);
2016-06-03 17:01:21 +02:00
// disabling a referenced watcher should decrement the referenced count
$loop -> disable ( $watcherId2 );
$loop -> disable ( $watcherId2 );
$loop -> disable ( $watcherId2 );
2016-12-28 19:48:34 +01:00
$info = $loop -> getInfo ();
2016-06-03 17:01:21 +02:00
$expected = [ " referenced " => 1 , " unreferenced " => 0 ];
2017-03-10 22:20:28 +01:00
$this -> assertSame ( $expected , $info [ " enabled_watchers " ]);
2016-06-03 17:01:21 +02:00
// enabling a referenced watcher should increment the referenced count
$loop -> enable ( $watcherId2 );
$loop -> enable ( $watcherId2 );
2016-12-28 19:48:34 +01:00
$info = $loop -> getInfo ();
2016-06-03 17:01:21 +02:00
$expected = [ " referenced " => 2 , " unreferenced " => 0 ];
2017-03-10 22:20:28 +01:00
$this -> assertSame ( $expected , $info [ " enabled_watchers " ]);
2016-06-03 17:01:21 +02:00
// cancelling an referenced watcher should decrement the referenced count
$loop -> cancel ( $watcherId2 );
2016-12-28 19:48:34 +01:00
$info = $loop -> getInfo ();
2016-06-03 17:01:21 +02:00
$expected = [ " referenced " => 1 , " unreferenced " => 0 ];
2017-03-10 22:20:28 +01:00
$this -> assertSame ( $expected , $info [ " enabled_watchers " ]);
2016-06-03 17:01:21 +02:00
// unreference() should just increment unreferenced count
$watcherId2 = \call_user_func_array ( $func , $args );
$loop -> unreference ( $watcherId2 );
2016-12-28 19:48:34 +01:00
$info = $loop -> getInfo ();
2016-06-03 17:01:21 +02:00
$expected = [ " enabled " => 2 , " disabled " => 0 ];
$this -> assertSame ( $expected , $info [ $type ]);
$expected = [ " referenced " => 1 , " unreferenced " => 1 ];
2017-03-10 22:20:28 +01:00
$this -> assertSame ( $expected , $info [ " enabled_watchers " ]);
2016-06-03 17:01:21 +02:00
$loop -> cancel ( $watcherId1 );
$loop -> cancel ( $watcherId2 );
}
2016-06-07 01:53:28 +02:00
/** @dataProvider provideRegistrationArgs */
2017-03-10 21:31:57 +01:00
function testWatcherRegistrationAndCancellationInfo ( $type , $args ) {
2016-06-03 17:01:21 +02:00
if ( $type === " onSignal " ) {
2017-03-12 17:33:46 +01:00
$this -> checkForSignalCapability ();
2016-06-03 17:01:21 +02:00
}
$loop = $this -> loop ;
$func = [ $loop , $type ];
if ( substr ( $type , 0 , 2 ) === " on " ) {
$type = " on_ " . lcfirst ( substr ( $type , 2 ));
}
$watcherId = \call_user_func_array ( $func , $args );
$this -> assertInternalType ( " string " , $watcherId );
2016-12-28 19:48:34 +01:00
$info = $loop -> getInfo ();
2016-06-03 17:01:21 +02:00
$expected = [ " enabled " => 1 , " disabled " => 0 ];
$this -> assertSame ( $expected , $info [ $type ]);
// invoke enable() on active watcher to ensure it has no side-effects
$loop -> enable ( $watcherId );
2016-12-28 19:48:34 +01:00
$info = $loop -> getInfo ();
2016-06-03 17:01:21 +02:00
$expected = [ " enabled " => 1 , " disabled " => 0 ];
$this -> assertSame ( $expected , $info [ $type ]);
// invoke disable() twice to ensure it has no side-effects
$loop -> disable ( $watcherId );
$loop -> disable ( $watcherId );
2016-12-28 19:48:34 +01:00
$info = $loop -> getInfo ();
2016-06-03 17:01:21 +02:00
$expected = [ " enabled " => 0 , " disabled " => 1 ];
$this -> assertSame ( $expected , $info [ $type ]);
$loop -> cancel ( $watcherId );
2016-12-28 19:48:34 +01:00
$info = $loop -> getInfo ();
2016-06-03 17:01:21 +02:00
$expected = [ " enabled " => 0 , " disabled " => 0 ];
$this -> assertSame ( $expected , $info [ $type ]);
$watcherId = \call_user_func_array ( $func , $args );
2016-12-28 19:48:34 +01:00
$info = $loop -> getInfo ();
2016-06-03 17:01:21 +02:00
$expected = [ " enabled " => 1 , " disabled " => 0 ];
$this -> assertSame ( $expected , $info [ $type ]);
$loop -> disable ( $watcherId );
2016-12-28 19:48:34 +01:00
$info = $loop -> getInfo ();
2016-06-03 17:01:21 +02:00
$expected = [ " enabled " => 0 , " disabled " => 1 ];
$this -> assertSame ( $expected , $info [ $type ]);
$loop -> enable ( $watcherId );
2016-12-28 19:48:34 +01:00
$info = $loop -> getInfo ();
2016-06-03 17:01:21 +02:00
$expected = [ " enabled " => 1 , " disabled " => 0 ];
$this -> assertSame ( $expected , $info [ $type ]);
$loop -> cancel ( $watcherId );
2016-12-28 19:48:34 +01:00
$info = $loop -> getInfo ();
2016-06-03 17:01:21 +02:00
$expected = [ " enabled " => 0 , " disabled " => 0 ];
$this -> assertSame ( $expected , $info [ $type ]);
2016-08-30 22:39:47 +02:00
$loop -> disable ( $watcherId );
2016-12-28 19:48:34 +01:00
$info = $loop -> getInfo ();
2016-08-30 22:39:47 +02:00
$expected = [ " enabled " => 0 , " disabled " => 0 ];
$this -> assertSame ( $expected , $info [ $type ]);
2016-06-03 17:01:21 +02:00
}
2016-06-07 01:53:28 +02:00
/** @dataProvider provideRegistrationArgs */
2017-03-10 21:31:57 +01:00
function testNoMemoryLeak ( $type , $args ) {
if ( $this -> getTestResultObject () -> getCollectCodeCoverageInformation ()) {
2016-12-29 17:45:12 +01:00
$this -> markTestSkipped ( " Cannot run this test with code coverage active [code coverage consumes memory which makes it impossible to rely on memory_get_usage()] " );
}
2016-08-15 15:08:09 +02:00
2016-06-07 19:45:15 +02:00
$runs = 2000 ;
2016-06-03 17:01:21 +02:00
if ( $type === " onSignal " ) {
2017-03-12 17:33:46 +01:00
$this -> checkForSignalCapability ();
2016-06-03 17:01:21 +02:00
}
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) use ( $type , $args , $runs ) {
2016-06-03 17:01:21 +02:00
$initialMem = memory_get_usage ();
$cb = function ( $runs ) use ( $loop , $type , $args ) {
$func = [ $loop , $type ];
for ( $watchers = [], $i = 0 ; $i < $runs ; $i ++ ) {
$watchers [] = \call_user_func_array ( $func , $args );
}
foreach ( $watchers as $watcher ) {
$loop -> cancel ( $watcher );
}
for ( $watchers = [], $i = 0 ; $i < $runs ; $i ++ ) {
$watchers [] = \call_user_func_array ( $func , $args );
}
foreach ( $watchers as $watcher ) {
$loop -> disable ( $watcher );
$loop -> cancel ( $watcher );
}
for ( $watchers = [], $i = 0 ; $i < $runs ; $i ++ ) {
$watchers [] = \call_user_func_array ( $func , $args );
}
if ( $type === " repeat " ) {
$loop -> delay ( $msInterval = 7 , function () use ( $loop , $watchers ) {
foreach ( $watchers as $watcher ) {
$loop -> cancel ( $watcher );
}
});
} elseif ( $type !== " defer " && $type !== " delay " ) {
$loop -> defer ( function () use ( $loop , $watchers ) {
foreach ( $watchers as $watcher ) {
$loop -> cancel ( $watcher );
}
});
}
$loop -> run ();
if ( $type === " defer " ) {
$loop -> defer ( $fn = function ( $watcherId , $i ) use ( & $fn , $loop ) {
if ( $i ) {
$loop -> defer ( $fn , -- $i );
}
}, $runs );
$loop -> run ();
}
if ( $type === " delay " ) {
$loop -> delay ( $msDelay = 0 , $fn = function ( $watcherId , $i ) use ( & $fn , $loop ) {
if ( $i ) {
$loop -> delay ( $msDelay = 0 , $fn , -- $i );
}
}, $runs );
$loop -> run ();
}
if ( $type === " repeat " ) {
$loop -> repeat ( $msDelay = 0 , $fn = function ( $watcherId , $i ) use ( & $fn , $loop ) {
$loop -> cancel ( $watcherId );
if ( $i ) {
$loop -> repeat ( $msDelay = 0 , $fn , -- $i );
}
}, $runs );
$loop -> run ();
}
if ( $type === " onWritable " ) {
2017-03-10 21:31:57 +01:00
$loop -> defer ( function ( $watcherId , $runs ) use ( $loop ) {
2016-06-03 17:01:21 +02:00
$fn = function ( $watcherId , $socket , $i ) use ( & $fn , $loop ) {
$loop -> cancel ( $watcherId );
if ( $socket ) {
fwrite ( $socket , " . " );
}
if ( $i ) {
// explicitly use *different* streams with *different* resource ids
$ends = stream_socket_pair ( \stripos ( PHP_OS , " win " ) === 0 ? STREAM_PF_INET : STREAM_PF_UNIX , STREAM_SOCK_STREAM , STREAM_IPPROTO_IP );
$loop -> onWritable ( $ends [ 0 ], $fn , -- $i );
2017-03-10 21:31:57 +01:00
$loop -> onReadable ( $ends [ 1 ], function ( $watcherId ) use ( $loop ) {
2016-06-03 17:01:21 +02:00
$loop -> cancel ( $watcherId );
});
}
};
$fn ( $watcherId , null , $runs );
}, $runs + 1 );
$loop -> run ();
}
if ( $type === " onSignal " ) {
2016-06-07 20:21:10 +02:00
$sendSignal = function () {
\posix_kill ( \getmypid (), \SIGUSR1 );
};
$loop -> onSignal ( \SIGUSR1 , $fn = function ( $watcherId , $signo , $i ) use ( & $fn , $loop , $sendSignal ) {
2016-06-03 17:01:21 +02:00
if ( $i ) {
2016-06-07 20:21:10 +02:00
$loop -> onSignal ( \SIGUSR1 , $fn , -- $i );
$loop -> defer ( $sendSignal );
2016-06-03 17:01:21 +02:00
}
2016-06-07 20:21:10 +02:00
$loop -> cancel ( $watcherId );
}, $runs );
$loop -> defer ( $sendSignal );
2016-06-03 17:01:21 +02:00
$loop -> run ();
}
};
$closureMem = memory_get_usage () - $initialMem ;
2016-06-07 19:45:15 +02:00
$cb ( $runs ); /* just to set up eventual structures inside loop without counting towards memory comparison */
2016-06-03 17:01:21 +02:00
gc_collect_cycles ();
$initialMem = memory_get_usage () - $closureMem ;
2016-06-07 19:45:15 +02:00
$cb ( $runs );
2016-06-03 17:01:21 +02:00
unset ( $cb );
gc_collect_cycles ();
$endMem = memory_get_usage ();
/* this is allowing some memory usage due to runtime caches etc., but nothing actually leaking */
2016-06-07 19:45:15 +02:00
$this -> assertLessThan ( $runs * 4 , $endMem - $initialMem ); // * 4, as 4 is minimal sizeof(void *)
2016-06-03 17:01:21 +02:00
});
}
2017-03-25 19:52:17 +01:00
/**
* The first number of each tuple indicates the tick in which the watcher is supposed to execute , the second digit
* indicates the order within the tick .
*/
2017-03-10 21:31:57 +01:00
function testExecutionOrderGuarantees () {
$this -> expectOutputString ( " 01 02 03 04 " . str_repeat ( " 05 " , 8 ) . " 10 11 12 " . str_repeat ( " 13 " , 4 ) . " 20 " . str_repeat ( " 21 " , 4 ) . " 30 40 41 " );
$this -> start ( function ( Driver $loop ) use ( & $ticks ) {
$f = function () use ( $loop ) {
2016-06-07 01:53:28 +02:00
$args = func_get_args ();
2017-03-10 21:31:57 +01:00
return function ( $watcherId ) use ( $loop , & $args ) {
2016-06-07 01:53:28 +02:00
if ( ! $args ) {
$this -> fail ( " Watcher callback called too often " );
}
$loop -> cancel ( $watcherId );
echo array_shift ( $args ) . array_shift ( $args ), " " ;
};
};
2017-03-25 19:52:17 +01:00
$loop -> onWritable ( STDOUT , $f ( 0 , 5 ));
$writ1 = $loop -> onWritable ( STDOUT , $f ( 0 , 5 ));
$writ2 = $loop -> onWritable ( STDOUT , $f ( 0 , 5 ));
2017-01-07 12:23:28 +01:00
2016-06-26 17:12:33 +02:00
$loop -> delay ( $msDelay = 0 , $f ( 0 , 5 ));
$del1 = $loop -> delay ( $msDelay = 0 , $f ( 0 , 5 ));
$del2 = $loop -> delay ( $msDelay = 0 , $f ( 0 , 5 ));
2016-06-07 01:53:28 +02:00
$del3 = $loop -> delay ( $msDelay = 0 , $f ());
2016-06-26 17:12:33 +02:00
$del4 = $loop -> delay ( $msDelay = 0 , $f ( 1 , 3 ));
$del5 = $loop -> delay ( $msDelay = 0 , $f ( 2 , 0 ));
2017-03-10 21:31:57 +01:00
$loop -> defer ( function () use ( $loop , $del5 ) {
2016-06-26 17:12:33 +02:00
$loop -> disable ( $del5 );
});
2016-06-07 01:53:28 +02:00
$loop -> cancel ( $del3 );
$loop -> disable ( $del1 );
$loop -> disable ( $del2 );
2017-03-25 19:52:17 +01:00
$writ3 = $loop -> onWritable ( STDOUT , $f ());
2016-06-07 01:53:28 +02:00
$loop -> cancel ( $writ3 );
$loop -> disable ( $writ1 );
$loop -> disable ( $writ2 );
$loop -> enable ( $writ1 );
2017-03-25 19:52:17 +01:00
$writ4 = $loop -> onWritable ( STDOUT , $f ( 1 , 3 ));
$loop -> onWritable ( STDOUT , $f ( 0 , 5 ));
2016-06-07 01:53:28 +02:00
$loop -> enable ( $writ2 );
$loop -> disable ( $writ4 );
2017-03-10 21:31:57 +01:00
$loop -> defer ( function () use ( $loop , $writ4 , $f ) {
2016-06-07 01:53:28 +02:00
$loop -> enable ( $writ4 );
2017-03-25 19:52:17 +01:00
$loop -> onWritable ( STDOUT , $f ( 1 , 3 ));
2016-06-07 01:53:28 +02:00
});
$loop -> enable ( $del1 );
2016-06-26 17:12:33 +02:00
$loop -> delay ( $msDelay = 0 , $f ( 0 , 5 ));
2016-06-07 01:53:28 +02:00
$loop -> enable ( $del2 );
$loop -> disable ( $del4 );
2017-03-10 21:31:57 +01:00
$loop -> defer ( function () use ( $loop , $del4 , $f ) {
2016-06-07 01:53:28 +02:00
$loop -> enable ( $del4 );
2017-03-25 19:52:17 +01:00
$loop -> onWritable ( STDOUT , $f ( 1 , 3 ));
2016-06-07 01:53:28 +02:00
});
2017-01-07 12:23:28 +01:00
2017-01-05 21:10:09 +01:00
$loop -> delay ( $msDelay = 1000 , $f ( 4 , 1 ));
$loop -> delay ( $msDelay = 600 , $f ( 3 , 0 ));
$loop -> delay ( $msDelay = 500 , $f ( 2 , 1 ));
$loop -> repeat ( $msDelay = 500 , $f ( 2 , 1 ));
$rep1 = $loop -> repeat ( $msDelay = 50 , $f ( 2 , 1 ));
2016-06-07 01:53:28 +02:00
$loop -> disable ( $rep1 );
2017-01-05 21:10:09 +01:00
$loop -> delay ( $msDelay = 500 , $f ( 2 , 1 ));
2016-06-07 01:53:28 +02:00
$loop -> enable ( $rep1 );
$loop -> defer ( $f ( 0 , 1 ));
$def1 = $loop -> defer ( $f ( 0 , 3 ));
$def2 = $loop -> defer ( $f ( 1 , 1 ));
$def3 = $loop -> defer ( $f ());
$loop -> defer ( $f ( 0 , 2 ));
$loop -> disable ( $def1 );
$loop -> cancel ( $def3 );
$loop -> enable ( $def1 );
2017-03-10 21:31:57 +01:00
$loop -> defer ( function () use ( $loop , $def2 , $del4 , $del5 , $f ) {
2016-06-07 01:53:28 +02:00
$tick = $f ( 0 , 4 );
$tick ( " invalid " );
$loop -> defer ( $f ( 1 , 0 ));
$loop -> enable ( $def2 );
$loop -> defer ( $f ( 1 , 2 ));
2017-03-10 21:31:57 +01:00
$loop -> defer ( function () use ( $loop , $del5 , $f ) {
2016-06-26 17:12:33 +02:00
$loop -> enable ( $del5 );
2017-03-10 21:31:57 +01:00
$loop -> defer ( function () use ( $loop , $f ) {
2017-01-05 21:10:09 +01:00
usleep ( 700000 ); // to have $msDelay == 500 and $msDelay == 600 run at the same tick (but not $msDelay == 150)
2016-06-26 17:12:33 +02:00
$loop -> defer ( function () use ( $loop , $f ) {
2017-01-05 21:10:09 +01:00
$loop -> defer ( $f ( 4 , 0 ));
2016-06-26 17:12:33 +02:00
});
});
});
2016-06-07 01:53:28 +02:00
});
$loop -> disable ( $def2 );
});
}
2017-03-10 21:31:57 +01:00
function testSignalExecutionOrder () {
2017-03-14 22:47:54 +01:00
$this -> checkForSignalCapability ();
2016-06-07 01:53:28 +02:00
2016-10-26 15:39:07 +02:00
$this -> expectOutputString ( " 122222 " );
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) {
$f = function ( $i ) use ( $loop ) {
return function ( $watcherId ) use ( $loop , $i ) {
2016-06-07 01:53:28 +02:00
$loop -> cancel ( $watcherId );
echo $i ;
};
};
$loop -> defer ( $f ( 1 ));
$loop -> onSignal ( SIGUSR1 , $f ( 2 ));
2016-10-26 15:39:07 +02:00
$sig1 = $loop -> onSignal ( SIGUSR1 , $f ( 2 ));
$sig2 = $loop -> onSignal ( SIGUSR1 , $f ( 2 ));
2016-06-07 01:53:28 +02:00
$sig3 = $loop -> onSignal ( SIGUSR1 , $f ( " FAIL - MUST NOT BE CALLED " ));
$loop -> disable ( $sig1 );
2016-10-26 15:39:07 +02:00
$loop -> onSignal ( SIGUSR1 , $f ( 2 ));
2016-06-07 01:53:28 +02:00
$loop -> disable ( $sig2 );
$loop -> enable ( $sig1 );
$loop -> cancel ( $sig3 );
2016-10-26 15:39:07 +02:00
$loop -> onSignal ( SIGUSR1 , $f ( 2 ));
2017-03-10 21:31:57 +01:00
$loop -> defer ( function () use ( $loop , $sig2 ) {
2016-06-07 01:53:28 +02:00
$loop -> enable ( $sig2 );
2017-03-10 21:31:57 +01:00
$loop -> defer ( function () use ( $loop ) {
2016-06-07 19:31:49 +02:00
\posix_kill ( \getmypid (), \SIGUSR1 );
2017-03-10 21:31:57 +01:00
$loop -> delay ( $msDelay = 10 , function () use ( $loop ) {
2016-06-07 19:31:49 +02:00
$loop -> stop ();
});
2016-06-07 01:53:28 +02:00
});
});
});
}
2017-03-14 17:50:24 +01:00
/** @expectedException \Amp\Loop\InvalidWatcherError */
2017-03-10 21:31:57 +01:00
function testExceptionOnEnableNonexistentWatcher () {
2016-12-23 01:43:09 +01:00
try {
$this -> loop -> enable ( " nonexistentWatcher " );
2017-03-14 17:50:24 +01:00
} catch ( InvalidWatcherError $e ) {
2016-12-23 01:43:09 +01:00
$this -> assertEquals ( " nonexistentWatcher " , $e -> getWatcherId ());
throw $e ;
}
2016-06-03 17:01:21 +02:00
}
2017-03-10 21:31:57 +01:00
function testSuccessOnDisableNonexistentWatcher () {
2016-06-03 17:01:21 +02:00
$this -> loop -> disable ( " nonexistentWatcher " );
2017-03-12 17:33:46 +01:00
// Otherwise risky, throwing fails the test
$this -> assertTrue ( true );
2016-06-03 17:01:21 +02:00
}
2017-03-10 21:31:57 +01:00
function testSuccessOnCancelNonexistentWatcher () {
2016-06-03 17:01:21 +02:00
$this -> loop -> cancel ( " nonexistentWatcher " );
2017-03-12 17:33:46 +01:00
// Otherwise risky, throwing fails the test
$this -> assertTrue ( true );
2016-06-03 17:01:21 +02:00
}
2017-03-14 17:50:24 +01:00
/** @expectedException \Amp\Loop\InvalidWatcherError */
2017-03-10 21:31:57 +01:00
function testExceptionOnReferenceNonexistentWatcher () {
2016-12-23 01:43:09 +01:00
try {
$this -> loop -> reference ( " nonexistentWatcher " );
2017-03-14 17:50:24 +01:00
} catch ( InvalidWatcherError $e ) {
2016-12-23 01:43:09 +01:00
$this -> assertEquals ( " nonexistentWatcher " , $e -> getWatcherId ());
throw $e ;
}
2016-06-07 01:53:28 +02:00
}
2017-03-14 17:50:24 +01:00
/** @expectedException \Amp\Loop\InvalidWatcherError */
2017-03-10 21:31:57 +01:00
function testExceptionOnUnreferenceNonexistentWatcher () {
2016-12-23 01:43:09 +01:00
try {
$this -> loop -> unreference ( " nonexistentWatcher " );
2017-03-14 17:50:24 +01:00
} catch ( InvalidWatcherError $e ) {
2016-12-23 01:43:09 +01:00
$this -> assertEquals ( " nonexistentWatcher " , $e -> getWatcherId ());
throw $e ;
}
2016-06-07 01:53:28 +02:00
}
2017-03-14 17:50:24 +01:00
/** @expectedException \Amp\Loop\InvalidWatcherError */
2016-06-07 01:53:28 +02:00
function testWatcherInvalidityOnDefer () {
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) {
$loop -> defer ( function ( $watcher ) use ( $loop ) {
2016-08-30 22:39:47 +02:00
$loop -> enable ( $watcher );
2016-06-07 01:53:28 +02:00
});
});
}
2017-03-14 17:50:24 +01:00
/** @expectedException \Amp\Loop\InvalidWatcherError */
2016-06-07 01:53:28 +02:00
function testWatcherInvalidityOnDelay () {
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) {
$loop -> delay ( $msDelay = 0 , function ( $watcher ) use ( $loop ) {
2016-08-30 22:39:47 +02:00
$loop -> enable ( $watcher );
2016-06-07 01:53:28 +02:00
});
});
}
2017-03-10 21:31:57 +01:00
function testEventsNotExecutedInSameTickAsEnabled () {
2016-06-07 01:53:28 +02:00
$this -> start ( function ( Driver $loop ) {
2017-03-10 21:31:57 +01:00
$loop -> defer ( function () use ( $loop ) {
$loop -> defer ( function () use ( $loop , & $diswatchers , & $watchers ) {
$loop -> defer ( function () use ( $loop , $diswatchers ) {
2016-06-07 01:53:28 +02:00
foreach ( $diswatchers as $watcher ) {
$loop -> disable ( $watcher );
}
2017-03-10 21:31:57 +01:00
$loop -> defer ( function () use ( $loop , $diswatchers ) {
$loop -> defer ( function () use ( $loop , $diswatchers ) {
2016-06-07 01:53:28 +02:00
foreach ( $diswatchers as $watcher ) {
$loop -> cancel ( $watcher );
}
});
2016-06-07 19:25:59 +02:00
foreach ( $diswatchers as $watcher ) {
$loop -> enable ( $watcher );
}
2016-06-07 01:53:28 +02:00
});
});
2016-06-07 19:25:59 +02:00
foreach ( $watchers as $watcher ) {
$loop -> cancel ( $watcher );
}
foreach ( $diswatchers as $watcher ) {
$loop -> disable ( $watcher );
$loop -> enable ( $watcher );
}
2016-06-07 01:53:28 +02:00
});
2017-03-10 21:31:57 +01:00
$f = function () use ( $loop ) {
2016-06-07 01:53:28 +02:00
$watchers [] = $loop -> defer ([ $this , " fail " ]);
$watchers [] = $loop -> delay ( $msDelay = 0 , [ $this , " fail " ]);
$watchers [] = $loop -> repeat ( $msDelay = 0 , [ $this , " fail " ]);
$watchers [] = $loop -> onWritable ( STDIN , [ $this , " fail " ]);
return $watchers ;
};
$watchers = $f ();
$diswatchers = $f ();
});
});
2017-03-12 17:33:46 +01:00
// Otherwise risky, as we only rely on $this->fail()
$this -> assertTrue ( true );
2016-06-07 01:53:28 +02:00
}
2017-03-10 21:31:57 +01:00
function testEnablingWatcherAllowsSubsequentInvocation () {
2016-06-03 17:01:21 +02:00
$loop = $this -> loop ;
$increment = 0 ;
2017-03-10 21:31:57 +01:00
$watcherId = $loop -> defer ( function () use ( & $increment ) {
2016-06-03 17:01:21 +02:00
$increment ++ ;
});
$loop -> disable ( $watcherId );
$loop -> delay ( $msDelay = 5 , [ $loop , " stop " ]);
$loop -> run ();
$this -> assertEquals ( 0 , $increment );
$loop -> enable ( $watcherId );
$loop -> delay ( $msDelay = 5 , [ $loop , " stop " ]);
$loop -> run ();
$this -> assertEquals ( 1 , $increment );
}
2017-03-10 21:31:57 +01:00
function testUnresolvedEventsAreReenabledOnRunFollowingPreviousStop () {
2016-06-03 17:01:21 +02:00
$increment = 0 ;
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) use ( & $increment ) {
2016-06-03 17:01:21 +02:00
$loop -> defer ([ $loop , " stop " ]);
$loop -> run ();
$loop -> defer ( function () use ( & $increment , $loop ) {
$loop -> delay ( $msDelay = 100 , function () use ( $loop , & $increment ) {
$increment ++ ;
$loop -> stop ();
});
});
$this -> assertEquals ( 0 , $increment );
\usleep ( 5000 );
});
$this -> assertEquals ( 1 , $increment );
}
2017-03-10 21:31:57 +01:00
function testTimerWatcherParameterOrder () {
$this -> start ( function ( Driver $loop ) {
2016-06-03 17:01:21 +02:00
$counter = 0 ;
$loop -> defer ( function ( $watcherId ) use ( $loop , & $counter ) {
$this -> assertInternalType ( " string " , $watcherId );
if ( ++ $counter === 3 ) {
$loop -> stop ();
}
});
$loop -> delay ( $msDelay = 5 , function ( $watcherId ) use ( $loop , & $counter ) {
$this -> assertInternalType ( " string " , $watcherId );
if ( ++ $counter === 3 ) {
$loop -> stop ();
}
});
$loop -> repeat ( $msDelay = 5 , function ( $watcherId ) use ( $loop , & $counter ) {
$this -> assertInternalType ( " string " , $watcherId );
$loop -> cancel ( $watcherId );
if ( ++ $counter === 3 ) {
$loop -> stop ();
}
});
});
}
2017-03-10 21:31:57 +01:00
function testStreamWatcherParameterOrder () {
$this -> start ( function ( Driver $loop ) use ( & $invoked ) {
2016-06-03 17:01:21 +02:00
$invoked = 0 ;
$loop -> onWritable ( STDOUT , function ( $watcherId , $stream ) use ( $loop , & $invoked ) {
$this -> assertInternalType ( " string " , $watcherId );
$this -> assertSame ( STDOUT , $stream );
$invoked ++ ;
$loop -> cancel ( $watcherId );
});
});
$this -> assertSame ( 1 , $invoked );
}
2017-03-10 21:31:57 +01:00
function testDisablingWatcherPreventsSubsequentInvocation () {
$this -> start ( function ( Driver $loop ) {
2016-06-03 17:01:21 +02:00
$increment = 0 ;
$watcherId = $loop -> defer ( function () use ( & $increment ) {
$increment ++ ;
});
$loop -> disable ( $watcherId );
$loop -> delay ( $msDelay = 5 , [ $loop , " stop " ]);
$this -> assertEquals ( 0 , $increment );
});
}
2017-03-10 21:31:57 +01:00
function testImmediateExecution () {
2016-06-03 17:01:21 +02:00
$loop = $this -> loop ;
$increment = 0 ;
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) use ( & $increment ) {
2016-06-03 17:01:21 +02:00
$loop -> defer ( function () use ( & $increment ) {
$increment ++ ;
});
$loop -> defer ([ $loop , " stop " ]);
});
$this -> assertEquals ( 1 , $increment );
}
2017-03-10 21:31:57 +01:00
function testImmediatelyCallbacksDoNotRecurseInSameTick () {
2016-06-03 17:01:21 +02:00
$increment = 0 ;
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) use ( & $increment ) {
2016-06-03 17:01:21 +02:00
$loop -> defer ( function () use ( $loop , & $increment ) {
$increment ++ ;
$loop -> defer ( function () use ( & $increment ) {
$increment ++ ;
});
});
$loop -> defer ([ $loop , " stop " ]);
});
$this -> assertEquals ( 1 , $increment );
}
2017-03-10 21:31:57 +01:00
function testRunExecutesEventsUntilExplicitlyStopped () {
2016-06-03 17:01:21 +02:00
$increment = 0 ;
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) use ( & $increment ) {
2016-06-03 17:01:21 +02:00
$loop -> repeat ( $msInterval = 5 , function ( $watcherId ) use ( $loop , & $increment ) {
$increment ++ ;
if ( $increment === 10 ) {
$loop -> cancel ( $watcherId );
}
});
});
$this -> assertEquals ( 10 , $increment );
}
/**
* @ expectedException \Exception
* @ expectedExceptionMessage loop error
*/
2017-03-10 21:31:57 +01:00
function testLoopAllowsExceptionToBubbleUpDuringStart () {
$this -> start ( function ( Driver $loop ) {
$loop -> defer ( function () {
2016-06-03 17:01:21 +02:00
throw new \Exception ( " loop error " );
});
});
2016-05-24 10:37:25 +02:00
}
/**
* @ expectedException \RuntimeException
2016-06-03 17:01:21 +02:00
* @ expectedExceptionMessage test
2016-05-24 10:37:25 +02:00
*/
2017-03-10 21:31:57 +01:00
function testLoopAllowsExceptionToBubbleUpFromRepeatingAlarmDuringStart () {
$this -> start ( function ( Driver $loop ) {
2016-06-03 17:01:21 +02:00
$loop -> repeat ( $msInterval = 1 , function () {
throw new \RuntimeException ( " test " );
});
});
}
2016-05-24 10:37:25 +02:00
2017-03-10 21:31:57 +01:00
function testErrorHandlerCapturesUncaughtException () {
2016-06-03 17:01:21 +02:00
$msg = " " ;
2017-03-10 21:31:57 +01:00
$this -> loop -> setErrorHandler ( $f = function () {
});
$oldErrorHandler = $this -> loop -> setErrorHandler ( function ( \Exception $error ) use ( & $msg ) {
2016-06-03 17:01:21 +02:00
$msg = $error -> getMessage ();
});
2016-12-28 19:56:19 +01:00
$this -> assertEquals ( $f , $oldErrorHandler );
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) {
$loop -> defer ( function () {
2016-06-03 17:01:21 +02:00
throw new \Exception ( " loop error " );
});
});
$this -> assertSame ( " loop error " , $msg );
}
2016-05-24 10:37:25 +02:00
2016-06-03 17:01:21 +02:00
/**
* @ expectedException \Exception
* @ expectedExceptionMessage errorception
*/
2017-03-10 21:31:57 +01:00
function testOnErrorFailure () {
$this -> loop -> setErrorHandler ( function () {
2016-06-03 17:01:21 +02:00
throw new \Exception ( " errorception " );
});
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) {
$loop -> delay ( $msDelay = 5 , function () {
2016-06-03 17:01:21 +02:00
throw new \Exception ( " error " );
});
});
}
2016-05-24 10:37:25 +02:00
2016-06-03 17:01:21 +02:00
/**
* @ expectedException \RuntimeException
* @ expectedExceptionMessage test
*/
2017-03-10 21:31:57 +01:00
function testLoopException () {
$this -> start ( function ( Driver $loop ) {
$loop -> defer ( function () use ( $loop ) {
2016-06-03 17:01:21 +02:00
// force next tick, outside of primary startup tick
2017-03-10 21:31:57 +01:00
$loop -> defer ( function () {
2016-06-03 17:01:21 +02:00
throw new \RuntimeException ( " test " );
});
});
});
}
2017-03-10 21:31:57 +01:00
function testOnSignalWatcher () {
2017-03-14 22:47:54 +01:00
$this -> checkForSignalCapability ();
2016-06-03 17:01:21 +02:00
$this -> expectOutputString ( " caught SIGUSR1 " );
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) {
$loop -> delay ( $msDelay = 1 , function () use ( $loop ) {
2016-06-03 17:01:21 +02:00
\posix_kill ( \getmypid (), \SIGUSR1 );
2017-03-10 21:31:57 +01:00
$loop -> delay ( $msDelay = 10 , function () use ( $loop ) {
2016-06-03 17:01:21 +02:00
$loop -> stop ();
});
});
2017-03-10 21:31:57 +01:00
$loop -> onSignal ( SIGUSR1 , function ( $watcherId ) use ( $loop ) {
2016-06-03 17:01:21 +02:00
$loop -> cancel ( $watcherId );
echo " caught SIGUSR1 " ;
});
});
}
2017-03-10 21:31:57 +01:00
function testInitiallyDisabledOnSignalWatcher () {
2017-03-14 22:47:54 +01:00
$this -> checkForSignalCapability ();
2016-06-03 17:01:21 +02:00
$this -> expectOutputString ( " caught SIGUSR1 " );
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) {
2017-03-25 21:47:30 +01:00
$stop = $loop -> delay ( $msDelay = 100 , function () use ( $loop ) {
2016-06-26 17:12:33 +02:00
echo " ERROR: manual stop " ;
$loop -> stop ();
});
2017-03-10 21:31:57 +01:00
$watcherId = $loop -> onSignal ( SIGUSR1 , function ( $watcherId ) use ( $loop , $stop ) {
2016-06-03 17:01:21 +02:00
echo " caught SIGUSR1 " ;
2016-06-26 17:12:33 +02:00
$loop -> disable ( $stop );
$loop -> disable ( $watcherId );
});
$loop -> disable ( $watcherId );
2017-03-10 21:31:57 +01:00
$loop -> delay ( $msDelay = 1 , function () use ( $loop , $watcherId ) {
2016-06-26 17:12:33 +02:00
$loop -> enable ( $watcherId );
2017-03-10 21:31:57 +01:00
$loop -> delay ( $msDelay = 1 , function () {
2016-08-14 21:14:43 +02:00
\posix_kill ( \getmypid (), SIGUSR1 );
2016-06-26 17:12:33 +02:00
});
});
});
}
2017-03-10 21:31:57 +01:00
function testNestedLoopSignalDispatch () {
2017-03-14 22:47:54 +01:00
$this -> checkForSignalCapability ();
2016-06-26 17:12:33 +02:00
2016-08-14 21:14:43 +02:00
$this -> expectOutputString ( " inner SIGUSR2 \n outer SIGUSR1 \n " );
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) {
$loop -> delay ( $msDelay = 30 , function () use ( $loop ) {
2016-06-26 17:12:33 +02:00
$loop -> stop ();
});
$loop -> onSignal ( SIGUSR1 , function () use ( $loop ) {
echo " outer SIGUSR1 \n " ;
2016-06-03 17:01:21 +02:00
$loop -> stop ();
2016-06-26 17:12:33 +02:00
});
2016-06-03 17:01:21 +02:00
2016-06-26 17:12:33 +02:00
$loop -> delay ( $msDelay = 1 , function () {
2017-03-10 22:20:28 +01:00
/** @var Driver $loop */
$loop = ( $this -> getFactory ())();
2017-03-10 21:31:57 +01:00
$stop = $loop -> delay ( $msDelay = 10 , function () use ( $loop ) {
2016-06-26 17:12:33 +02:00
echo " ERROR: manual stop " ;
$loop -> stop ();
});
2016-08-14 21:14:43 +02:00
$loop -> onSignal ( SIGUSR2 , function ( $watcherId ) use ( $loop , $stop ) {
echo " inner SIGUSR2 \n " ;
2016-06-26 17:12:33 +02:00
$loop -> cancel ( $stop );
$loop -> cancel ( $watcherId );
});
$loop -> delay ( $msDelay = 1 , function () {
2016-08-14 21:14:43 +02:00
\posix_kill ( \getmypid (), SIGUSR2 );
2016-06-03 17:01:21 +02:00
});
2016-06-26 17:12:33 +02:00
$loop -> run ();
});
$loop -> delay ( $msDelay = 20 , function () {
\posix_kill ( \getmypid (), \SIGUSR1 );
2016-06-03 17:01:21 +02:00
});
2016-05-24 10:37:25 +02:00
});
}
2017-03-10 21:31:57 +01:00
function testCancelRemovesWatcher () {
2017-03-12 17:33:46 +01:00
$invoked = false ;
$this -> start ( function ( Driver $loop ) use ( & $invoked ) {
2016-06-03 17:01:21 +02:00
$watcherId = $loop -> delay ( $msDelay = 10 , function () {
$this -> fail ( 'Watcher was not cancelled as expected' );
});
2017-03-12 17:33:46 +01:00
$loop -> defer ( function () use ( $loop , $watcherId , & $invoked ) {
2016-06-03 17:01:21 +02:00
$loop -> cancel ( $watcherId );
2017-03-12 17:33:46 +01:00
$invoked = true ;
2016-06-03 17:01:21 +02:00
});
2017-03-12 17:33:46 +01:00
2016-06-03 17:01:21 +02:00
$loop -> delay ( $msDelay = 5 , [ $loop , " stop " ]);
});
2017-03-12 17:33:46 +01:00
$this -> assertTrue ( $invoked );
2016-06-03 17:01:21 +02:00
}
2016-05-24 10:37:25 +02:00
2017-03-10 21:31:57 +01:00
function testOnWritableWatcher () {
2016-06-03 17:01:21 +02:00
$flag = false ;
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) use ( & $flag ) {
2016-06-03 17:01:21 +02:00
$loop -> onWritable ( STDOUT , function () use ( $loop , & $flag ) {
$flag = true ;
$loop -> stop ();
});
$loop -> delay ( $msDelay = 5 , [ $loop , " stop " ]);
});
$this -> assertTrue ( $flag );
}
2016-05-24 10:37:25 +02:00
2017-03-10 21:31:57 +01:00
function testInitiallyDisabledWriteWatcher () {
2016-06-03 17:01:21 +02:00
$increment = 0 ;
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) {
2016-06-03 17:01:21 +02:00
$watcherId = $loop -> onWritable ( STDOUT , function () use ( & $increment ) {
$increment ++ ;
});
$loop -> disable ( $watcherId );
$loop -> delay ( $msDelay = 5 , [ $loop , " stop " ]);
});
$this -> assertSame ( 0 , $increment );
}
2017-03-10 21:31:57 +01:00
function testInitiallyDisabledWriteWatcherIsTriggeredOnceEnabled () {
2016-06-03 17:01:21 +02:00
$this -> expectOutputString ( " 12 " );
$this -> start ( function ( Driver $loop ) {
$watcherId = $loop -> onWritable ( STDOUT , function () use ( $loop ) {
echo 2 ;
$loop -> stop ();
});
$loop -> disable ( $watcherId );
$loop -> defer ( function () use ( $loop , $watcherId ) {
$loop -> enable ( $watcherId );
echo 1 ;
});
});
}
2016-06-07 01:53:28 +02:00
/** @expectedException \RuntimeException */
2017-03-10 21:31:57 +01:00
function testStreamWatcherDoesntSwallowExceptions () {
$this -> start ( function ( Driver $loop ) {
2016-06-03 17:01:21 +02:00
$loop -> onWritable ( STDOUT , function () {
throw new \RuntimeException ;
});
$loop -> delay ( $msDelay = 5 , [ $loop , " stop " ]);
});
}
2017-03-10 21:31:57 +01:00
function testReactorRunsUntilNoWatchersRemain () {
2016-06-03 17:01:21 +02:00
$var1 = $var2 = 0 ;
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) use ( & $var1 , & $var2 ) {
2016-06-03 17:01:21 +02:00
$loop -> repeat ( $msDelay = 1 , function ( $watcherId ) use ( $loop , & $var1 ) {
if ( ++ $var1 === 3 ) {
$loop -> cancel ( $watcherId );
}
});
$loop -> onWritable ( STDOUT , function ( $watcherId ) use ( $loop , & $var2 ) {
if ( ++ $var2 === 4 ) {
$loop -> cancel ( $watcherId );
}
});
});
$this -> assertSame ( 3 , $var1 );
$this -> assertSame ( 4 , $var2 );
}
2017-03-10 21:31:57 +01:00
function testReactorRunsUntilNoWatchersRemainWhenStartedDeferred () {
2016-06-03 17:01:21 +02:00
$var1 = $var2 = 0 ;
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) use ( & $var1 , & $var2 ) {
$loop -> defer ( function () use ( $loop , & $var1 , & $var2 ) {
2016-06-03 17:01:21 +02:00
$loop -> repeat ( $msDelay = 1 , function ( $watcherId ) use ( $loop , & $var1 ) {
if ( ++ $var1 === 3 ) {
$loop -> cancel ( $watcherId );
}
});
$loop -> onWritable ( STDOUT , function ( $watcherId ) use ( $loop , & $var2 ) {
if ( ++ $var2 === 4 ) {
$loop -> cancel ( $watcherId );
}
});
});
});
$this -> assertSame ( 3 , $var1 );
$this -> assertSame ( 4 , $var2 );
}
2017-03-10 21:31:57 +01:00
function testOptionalCallbackDataPassedOnInvocation () {
2016-06-03 17:01:21 +02:00
$callbackData = new \StdClass ;
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) use ( $callbackData ) {
2016-06-03 17:01:21 +02:00
$loop -> defer ( function ( $watcherId , $callbackData ) use ( $loop ) {
$callbackData -> defer = true ;
}, $callbackData );
$loop -> delay ( $msDelay = 1 , function ( $watcherId , $callbackData ) use ( $loop ) {
$callbackData -> delay = true ;
}, $callbackData );
$loop -> repeat ( $msDelay = 1 , function ( $watcherId , $callbackData ) use ( $loop ) {
$callbackData -> repeat = true ;
$loop -> cancel ( $watcherId );
}, $callbackData );
$loop -> onWritable ( STDERR , function ( $watcherId , $stream , $callbackData ) use ( $loop ) {
$callbackData -> onWritable = true ;
$loop -> cancel ( $watcherId );
}, $callbackData );
});
$this -> assertTrue ( $callbackData -> defer );
$this -> assertTrue ( $callbackData -> delay );
$this -> assertTrue ( $callbackData -> repeat );
$this -> assertTrue ( $callbackData -> onWritable );
}
2016-06-07 01:53:28 +02:00
2017-03-10 21:31:57 +01:00
function testLoopStopPreventsTimerExecution () {
2016-12-23 01:43:09 +01:00
$t = microtime ( 1 );
2017-03-10 21:31:57 +01:00
$this -> start ( function ( Driver $loop ) {
2016-12-23 01:43:09 +01:00
$loop -> delay ( $msDelay = 10000 , function () {
$this -> fail ( " Timer was executed despite stopped loop " );
});
$loop -> defer ( function () use ( $loop ) {
$loop -> stop ();
});
});
$this -> assertTrue ( $t + 0.1 > microtime ( 1 ));
}
2017-01-06 17:21:37 +01:00
function testDeferEnabledInNextTick () {
2017-03-10 21:31:57 +01:00
$tick = function () {
$this -> loop -> defer ( function () {
2017-01-06 17:21:37 +01:00
$this -> loop -> stop ();
});
$this -> loop -> run ();
};
$invoked = 0 ;
2017-01-06 17:51:50 +01:00
$watcher = $this -> loop -> onWritable ( STDOUT , function () use ( & $invoked ) {
2017-01-06 17:21:37 +01:00
$invoked ++ ;
});
$tick ();
$tick ();
$tick ();
2017-01-06 17:51:50 +01:00
$this -> loop -> disable ( $watcher );
$this -> loop -> enable ( $watcher );
2017-01-06 17:21:37 +01:00
$tick (); // disable + immediate enable after a tick should have no effect either
$this -> assertEquals ( 4 , $invoked );
}
2016-08-14 21:14:43 +02:00
// getState and setState are final, but test it here again to be sure
2016-06-07 01:53:28 +02:00
function testRegistry () {
2016-08-14 21:14:43 +02:00
$this -> assertNull ( $this -> loop -> getState ( " foo " ));
$this -> loop -> setState ( " foo " , NAN );
$this -> assertTrue ( is_nan ( $this -> loop -> getState ( " foo " )));
$this -> loop -> setState ( " foo " , " 1 " );
$this -> assertNull ( $this -> loop -> getState ( " bar " ));
$this -> loop -> setState ( " baz " , - INF );
2016-06-07 01:53:28 +02:00
// running must not affect state
$this -> loop -> defer ([ $this -> loop , " stop " ]);
$this -> loop -> run ();
2016-08-14 21:14:43 +02:00
$this -> assertSame ( - INF , $this -> loop -> getState ( " baz " ));
$this -> assertSame ( " 1 " , $this -> loop -> getState ( " foo " ));
2016-06-07 01:53:28 +02:00
}
/** @dataProvider provideRegistryValues */
function testRegistryValues ( $val ) {
2016-08-14 21:14:43 +02:00
$this -> loop -> setState ( " foo " , $val );
$this -> assertSame ( $val , $this -> loop -> getState ( " foo " ));
2016-06-07 01:53:28 +02:00
}
2016-05-24 10:37:25 +02:00
2016-06-07 01:53:28 +02:00
function provideRegistryValues () {
return [
[ " string " ],
[ 0 ],
[ PHP_INT_MIN ],
[ - 1.0 ],
[ true ],
[ false ],
[[]],
[ null ],
[ new \StdClass ],
];
2016-05-24 10:37:25 +02:00
}
2017-03-12 21:02:26 +01:00
2017-03-15 00:34:37 +01:00
function testRethrowsFromCallbacks () {
2017-03-15 07:32:43 +01:00
foreach ([ " onReadable " , " onWritable " , " defer " , " delay " , " repeat " , " onSignal " ] as $watcher ) {
$promises = [
new Failure ( new \Exception ( " rethrow test " )),
new RejectedReactPromise ( new \Exception ( " rethrow test " )),
new Coroutine (( function () {
if ( false ) {
yield ;
}
2017-03-12 21:02:26 +01:00
2017-03-15 07:32:43 +01:00
throw new \Exception ( " rethrow test " );
})()),
( function () {
if ( false ) {
yield ;
}
2017-03-12 21:02:26 +01:00
2017-03-15 07:32:43 +01:00
throw new \Exception ( " rethrow test " );
})(),
null ,
];
2017-03-15 00:34:37 +01:00
2017-03-15 07:32:43 +01:00
foreach ( $promises as $promise ) {
2017-03-15 00:34:37 +01:00
if ( $watcher === " onSignal " ) {
$this -> checkForSignalCapability ();
}
try {
$args = [];
switch ( $watcher ) {
case " onSignal " :
$args [] = SIGUSR1 ;
break ;
case " onWritable " :
$args [] = STDOUT ;
break ;
case " onReadable " :
$ends = stream_socket_pair ( \stripos ( PHP_OS , " win " ) === 0 ? STREAM_PF_INET : STREAM_PF_UNIX , STREAM_SOCK_STREAM , STREAM_IPPROTO_IP );
fwrite ( $ends [ 0 ], " trigger readability watcher " );
$args [] = $ends [ 1 ];
break ;
case " delay " :
case " repeat " :
$args [] = 5 ;
break ;
}
if ( $promise === null ) {
$args [] = function ( $watcherId ) {
$this -> loop -> cancel ( $watcherId );
throw new \Exception ( " rethrow test " );
};
} else {
$args [] = function ( $watcherId ) use ( $promise ) {
$this -> loop -> cancel ( $watcherId );
return $promise ;
};
}
call_user_func_array ([ $this -> loop , $watcher ], $args );
if ( $watcher == " onSignal " ) {
$this -> loop -> delay ( 100 , function () {
\posix_kill ( \getmypid (), \SIGUSR1 );
});
}
$this -> loop -> run ();
$this -> fail ( " Didn't throw expected exception. " );
} catch ( \Exception $e ) {
$this -> assertSame ( " rethrow test " , $e -> getMessage ());
}
}
}
2017-03-12 21:02:26 +01:00
}
2017-04-19 18:16:37 +02:00
public function testTimerIntervalCountedWhenNotRunning () {
2017-04-20 16:59:46 +02:00
\usleep ( 501000 ); // 501ms instead of 500ms to allow for variations in timing.
2017-04-19 18:16:37 +02:00
$start = \microtime ( true );
$this -> loop -> delay ( 1000 , function () use ( $start ) {
2017-04-20 16:59:46 +02:00
$this -> assertLessThan ( 0.5 , \microtime ( true ) - $start );
2017-04-19 18:16:37 +02:00
});
$this -> loop -> run ();
}
2016-05-24 10:37:25 +02:00
}