2016-12-30 02:16:04 +01:00
< ? php
2015-08-29 03:55:30 +02:00
2016-08-23 23:47:40 +02:00
namespace Amp\Parallel\Test\Worker ;
2016-08-18 18:04:48 +02:00
2019-08-27 19:17:41 +02:00
use Amp\Parallel\Context\StatusError ;
2020-02-11 18:06:30 +01:00
use Amp\Parallel\Sync\ContextPanicError ;
2017-12-13 23:29:44 +01:00
use Amp\Parallel\Sync\SerializationException ;
2019-02-21 00:39:39 +01:00
use Amp\Parallel\Worker\BasicEnvironment ;
2017-11-23 04:38:11 +01:00
use Amp\Parallel\Worker\Environment ;
use Amp\Parallel\Worker\Task ;
2020-02-11 18:06:30 +01:00
use Amp\Parallel\Worker\TaskFailureError ;
use Amp\Parallel\Worker\TaskFailureException ;
2018-06-07 21:11:45 +02:00
use Amp\Parallel\Worker\WorkerException ;
2019-08-27 19:17:41 +02:00
use Amp\PHPUnit\AsyncTestCase ;
2015-08-29 03:55:30 +02:00
2018-10-07 16:50:45 +02:00
class NonAutoloadableTask implements Task
{
public function run ( Environment $environment )
{
2017-11-23 04:38:11 +01:00
return 1 ;
}
}
2019-08-27 19:17:41 +02:00
abstract class AbstractWorkerTest extends AsyncTestCase
2018-10-07 16:50:45 +02:00
{
2015-08-29 03:55:30 +02:00
/**
2019-02-21 00:39:39 +01:00
* @ param string $envClassName
* @ param string | null $autoloadPath
*
2016-08-23 23:47:40 +02:00
* @ return \Amp\Parallel\Worker\Worker
2015-08-29 03:55:30 +02:00
*/
2019-02-21 00:39:39 +01:00
abstract protected function createWorker ( string $envClassName = BasicEnvironment :: class , string $autoloadPath = null );
2015-08-29 03:55:30 +02:00
2018-10-07 16:50:45 +02:00
public function testWorkerConstantDefined ()
{
2019-08-27 19:17:41 +02:00
$worker = $this -> createWorker ();
$this -> assertTrue ( yield $worker -> enqueue ( new Fixtures\ConstantTask ));
yield $worker -> shutdown ();
2017-07-28 06:49:43 +02:00
}
2018-10-07 16:50:45 +02:00
public function testIsRunning ()
{
2019-08-27 19:17:41 +02:00
$worker = $this -> createWorker ();
$this -> assertTrue ( $worker -> isRunning ());
2015-08-29 03:55:30 +02:00
2019-08-27 19:17:41 +02:00
$worker -> enqueue ( new Fixtures\TestTask ( 42 )); // Enqueue a task to start the worker.
2017-12-13 21:14:31 +01:00
2019-08-27 19:17:41 +02:00
$this -> assertTrue ( $worker -> isRunning ());
2015-08-29 03:55:30 +02:00
2019-08-27 19:17:41 +02:00
yield $worker -> shutdown ();
$this -> assertFalse ( $worker -> isRunning ());
2015-08-29 03:55:30 +02:00
}
2018-10-07 16:50:45 +02:00
public function testIsIdleOnStart ()
{
2019-08-27 19:17:41 +02:00
$worker = $this -> createWorker ();
2015-08-29 03:55:30 +02:00
2019-08-27 19:17:41 +02:00
$this -> assertTrue ( $worker -> isIdle ());
2015-08-29 03:55:30 +02:00
2019-08-27 19:17:41 +02:00
yield $worker -> shutdown ();
2015-08-29 03:55:30 +02:00
}
2018-10-07 17:14:27 +02:00
public function testEnqueueShouldThrowStatusError ()
{
2019-08-27 19:17:41 +02:00
$this -> expectException ( StatusError :: class );
$this -> expectExceptionMessage ( 'The worker has been shut down' );
2018-10-07 17:14:27 +02:00
2019-08-27 19:17:41 +02:00
$worker = $this -> createWorker ();
2018-10-07 17:14:27 +02:00
2019-08-27 19:17:41 +02:00
$this -> assertTrue ( $worker -> isIdle ());
yield $worker -> shutdown ();
yield $worker -> enqueue ( new Fixtures\TestTask ( 42 ));
2018-10-07 17:14:27 +02:00
}
2018-10-07 16:50:45 +02:00
public function testEnqueue ()
{
2019-08-27 19:17:41 +02:00
$worker = $this -> createWorker ();
2015-08-29 03:55:30 +02:00
2019-08-27 19:17:41 +02:00
$returnValue = yield $worker -> enqueue ( new Fixtures\TestTask ( 42 ));
$this -> assertEquals ( 42 , $returnValue );
2015-08-29 03:55:30 +02:00
2019-08-27 19:17:41 +02:00
yield $worker -> shutdown ();
2015-08-29 03:55:30 +02:00
}
2018-10-07 16:50:45 +02:00
public function testEnqueueMultipleSynchronous ()
{
2019-08-27 19:17:41 +02:00
$worker = $this -> createWorker ();
2017-05-18 09:51:31 +02:00
2019-08-27 19:17:41 +02:00
$values = yield \Amp\Promise\all ([
2019-01-25 21:29:32 +01:00
$worker -> enqueue ( new Fixtures\TestTask ( 42 )),
$worker -> enqueue ( new Fixtures\TestTask ( 56 )),
$worker -> enqueue ( new Fixtures\TestTask ( 72 ))
2016-01-23 07:00:56 +01:00
]);
2015-12-12 01:15:15 +01:00
2019-08-27 19:17:41 +02:00
$this -> assertEquals ([ 42 , 56 , 72 ], $values );
2015-12-12 01:15:15 +01:00
2019-08-27 19:17:41 +02:00
yield $worker -> shutdown ();
2015-12-12 01:15:15 +01:00
}
2018-10-07 16:50:45 +02:00
public function testEnqueueMultipleAsynchronous ()
{
2019-08-27 19:17:41 +02:00
$worker = $this -> createWorker ();
2017-12-06 01:37:33 +01:00
2019-08-27 19:17:41 +02:00
$promises = [
2019-01-25 21:29:32 +01:00
$worker -> enqueue ( new Fixtures\TestTask ( 42 , 200 )),
$worker -> enqueue ( new Fixtures\TestTask ( 56 , 300 )),
$worker -> enqueue ( new Fixtures\TestTask ( 72 , 100 ))
2017-12-06 01:37:33 +01:00
];
2019-08-27 19:17:41 +02:00
$expected = [ 42 , 56 , 72 ];
foreach ( $promises as $promise ) {
$promise -> onResolve ( function ( $e , $v ) use ( & $expected ) {
$this -> assertSame ( \array_shift ( $expected ), $v );
});
}
2017-12-06 01:37:33 +01:00
2019-08-27 19:17:41 +02:00
yield $promises ; // Wait until all tasks have finished before invoking $worker->shutdown().
2018-06-07 21:11:45 +02:00
2019-08-27 19:17:41 +02:00
yield $worker -> shutdown ();
2017-12-06 01:37:33 +01:00
}
2018-10-07 16:50:45 +02:00
public function testEnqueueMultipleThenShutdown ()
{
2019-08-27 19:17:41 +02:00
$worker = $this -> createWorker ();
2018-06-07 21:11:45 +02:00
2019-08-27 19:17:41 +02:00
$promises = [
2019-01-25 21:29:32 +01:00
$worker -> enqueue ( new Fixtures\TestTask ( 42 , 200 )),
$worker -> enqueue ( new Fixtures\TestTask ( 56 , 300 )),
$worker -> enqueue ( new Fixtures\TestTask ( 72 , 100 ))
2018-06-07 21:11:45 +02:00
];
2019-08-27 19:17:41 +02:00
yield $worker -> shutdown ();
2018-06-07 21:11:45 +02:00
2019-08-27 19:17:41 +02:00
\array_shift ( $promises ); // First task will succeed.
2018-06-07 21:11:45 +02:00
2019-08-27 19:17:41 +02:00
foreach ( $promises as $promise ) {
$promise -> onResolve ( function ( $e , $v ) {
$this -> assertInstanceOf ( WorkerException :: class , $e );
});
}
2018-06-07 21:11:45 +02:00
}
2018-10-07 16:50:45 +02:00
public function testNotIdleOnEnqueue ()
{
2019-08-27 19:17:41 +02:00
$worker = $this -> createWorker ();
2015-08-29 03:55:30 +02:00
2019-08-27 19:17:41 +02:00
$coroutine = $worker -> enqueue ( new Fixtures\TestTask ( 42 ));
$this -> assertFalse ( $worker -> isIdle ());
yield $coroutine ;
2015-08-29 03:55:30 +02:00
2019-08-27 19:17:41 +02:00
yield $worker -> shutdown ();
2015-08-29 03:55:30 +02:00
}
2015-11-11 09:59:22 +01:00
2018-10-07 16:50:45 +02:00
public function testKill ()
{
2020-02-11 00:40:52 +01:00
$this -> setTimeout ( 500 );
2019-08-27 19:17:41 +02:00
2015-12-12 06:31:50 +01:00
$worker = $this -> createWorker ();
2017-12-13 21:14:31 +01:00
2019-01-25 21:29:32 +01:00
$worker -> enqueue ( new Fixtures\TestTask ( 42 ));
2015-11-11 09:59:22 +01:00
2019-08-27 19:17:41 +02:00
$worker -> kill ();
2015-11-11 09:59:22 +01:00
$this -> assertFalse ( $worker -> isRunning ());
}
2017-11-23 04:38:11 +01:00
2019-01-25 21:29:32 +01:00
public function testFailingTaskWithException ()
{
2019-08-27 19:17:41 +02:00
$worker = $this -> createWorker ();
2019-01-25 21:29:32 +01:00
2019-08-27 19:17:41 +02:00
try {
yield $worker -> enqueue ( new Fixtures\FailingTask ( \Exception :: class ));
2020-02-11 18:06:30 +01:00
} catch ( TaskFailureException $exception ) {
$this -> assertSame ( \Exception :: class , $exception -> getOriginalClassName ());
2019-08-27 19:17:41 +02:00
}
2019-01-25 21:29:32 +01:00
2019-08-27 19:17:41 +02:00
yield $worker -> shutdown ();
2019-01-25 21:29:32 +01:00
}
public function testFailingTaskWithError ()
{
2019-08-27 19:17:41 +02:00
$worker = $this -> createWorker ();
2019-01-25 21:29:32 +01:00
2019-08-27 19:17:41 +02:00
try {
yield $worker -> enqueue ( new Fixtures\FailingTask ( \Error :: class ));
2020-02-11 18:06:30 +01:00
} catch ( TaskFailureError $exception ) {
$this -> assertSame ( \Error :: class , $exception -> getOriginalClassName ());
2019-08-27 19:17:41 +02:00
}
2019-01-25 21:29:32 +01:00
2019-08-27 19:17:41 +02:00
yield $worker -> shutdown ();
2019-01-25 21:29:32 +01:00
}
public function testFailingTaskWithPreviousException ()
{
2019-08-27 19:17:41 +02:00
$worker = $this -> createWorker ();
2019-01-25 21:29:32 +01:00
2019-08-27 19:17:41 +02:00
try {
yield $worker -> enqueue ( new Fixtures\FailingTask ( \Error :: class , \Exception :: class ));
2020-02-11 18:06:30 +01:00
} catch ( TaskFailureError $exception ) {
$this -> assertSame ( \Error :: class , $exception -> getOriginalClassName ());
2019-08-27 19:17:41 +02:00
$previous = $exception -> getPrevious ();
2020-02-11 18:06:30 +01:00
$this -> assertInstanceOf ( TaskFailureException :: class , $previous );
$this -> assertSame ( \Exception :: class , $previous -> getOriginalClassName ());
2019-08-27 19:17:41 +02:00
}
yield $worker -> shutdown ();
2019-01-25 21:29:32 +01:00
}
2018-10-07 16:50:45 +02:00
public function testNonAutoloadableTask ()
{
2019-08-27 19:17:41 +02:00
$worker = $this -> createWorker ();
2017-11-23 04:38:11 +01:00
2019-08-27 19:17:41 +02:00
try {
yield $worker -> enqueue ( new NonAutoloadableTask );
$this -> fail ( " Tasks that cannot be autoloaded should throw an exception " );
2020-02-11 18:06:30 +01:00
} catch ( TaskFailureError $exception ) {
$this -> assertSame ( " Error " , $exception -> getOriginalClassName ());
2019-08-27 19:17:41 +02:00
$this -> assertGreaterThan ( 0 , \strpos ( $exception -> getMessage (), \sprintf ( " Classes implementing %s " , Task :: class )));
}
yield $worker -> shutdown ();
2017-11-23 04:38:11 +01:00
}
2017-12-13 23:29:44 +01:00
2018-10-07 16:50:45 +02:00
public function testUnserializableTask ()
{
2019-08-27 19:17:41 +02:00
$worker = $this -> createWorker ();
2017-12-13 23:29:44 +01:00
2019-08-27 19:17:41 +02:00
try {
yield $worker -> enqueue ( new class implements Task { // Anonymous classes are not serializable.
public function run ( Environment $environment )
{
}
});
$this -> fail ( " Tasks that cannot be serialized should throw an exception " );
} catch ( SerializationException $exception ) {
$this -> assertSame ( 0 , \strpos ( $exception -> getMessage (), " The given data cannot be sent because it is not serializable " ));
}
yield $worker -> shutdown ();
2017-12-13 23:29:44 +01:00
}
2017-12-14 03:56:28 +01:00
2018-10-24 05:10:12 +02:00
public function testUnserializableResult ()
{
2019-08-27 19:17:41 +02:00
$worker = $this -> createWorker ();
2018-10-24 05:10:12 +02:00
2019-08-27 19:17:41 +02:00
try {
yield $worker -> enqueue ( new Fixtures\UnserializableResultTask );
$this -> fail ( " Tasks results that cannot be serialized should throw an exception " );
2020-02-11 18:06:30 +01:00
} catch ( TaskFailureException $exception ) {
2019-08-27 19:17:41 +02:00
$this -> assertSame ( 0 , \strpos ( $exception -> getMessage (), " Uncaught Amp \ Parallel \ Sync \ SerializationException in worker " ));
}
yield $worker -> shutdown ();
2018-10-24 05:10:12 +02:00
}
public function testNonAutoloadableResult ()
{
2019-08-27 19:17:41 +02:00
$worker = $this -> createWorker ();
2018-10-24 05:10:12 +02:00
2019-08-27 19:17:41 +02:00
try {
yield $worker -> enqueue ( new Fixtures\NonAutoloadableResultTask );
$this -> fail ( " Tasks results that cannot be autoloaded should throw an exception " );
} catch ( \Error $exception ) {
$this -> assertSame ( 0 , \strpos ( $exception -> getMessage (), " Class instances returned from Amp \ Parallel \ Worker \T ask::run() must be autoloadable by the Composer autoloader " ));
}
yield $worker -> shutdown ();
2018-10-24 05:10:12 +02:00
}
2018-10-07 16:50:45 +02:00
public function testUnserializableTaskFollowedByValidTask ()
{
2019-08-27 19:17:41 +02:00
$worker = $this -> createWorker ();
2017-12-14 03:56:28 +01:00
2019-08-27 19:17:41 +02:00
$promise1 = $worker -> enqueue ( new class implements Task { // Anonymous classes are not serializable.
public function run ( Environment $environment )
{
}
});
$promise2 = $worker -> enqueue ( new Fixtures\TestTask ( 42 ));
2017-12-14 03:56:28 +01:00
2019-08-27 19:17:41 +02:00
$this -> assertSame ( 42 , yield $promise2 );
2017-12-14 03:56:28 +01:00
2019-08-27 19:17:41 +02:00
yield $worker -> shutdown ();
2017-12-14 03:56:28 +01:00
}
2019-02-21 00:39:39 +01:00
public function testCustomAutoloader ()
{
2019-08-27 19:17:41 +02:00
$worker = $this -> createWorker ( BasicEnvironment :: class , __DIR__ . '/Fixtures/custom-bootstrap.php' );
2019-02-21 00:39:39 +01:00
2019-08-27 19:17:41 +02:00
$this -> assertTrue ( yield $worker -> enqueue ( new Fixtures\AutoloadTestTask ));
2019-02-21 00:39:39 +01:00
2019-08-27 19:17:41 +02:00
yield $worker -> shutdown ();
2019-02-21 00:39:39 +01:00
}
public function testInvalidCustomAutoloader ()
{
2020-02-11 18:06:30 +01:00
$this -> expectException ( ContextPanicError :: class );
2019-02-22 22:43:42 +01:00
$this -> expectExceptionMessage ( 'No file found at bootstrap file path given' );
2019-02-21 00:39:39 +01:00
2019-08-27 19:17:41 +02:00
$worker = $this -> createWorker ( BasicEnvironment :: class , __DIR__ . '/Fixtures/not-found.php' );
2019-02-21 00:39:39 +01:00
2019-08-27 19:17:41 +02:00
$this -> assertTrue ( yield $worker -> enqueue ( new Fixtures\AutoloadTestTask ));
2019-02-21 00:39:39 +01:00
2019-08-27 19:17:41 +02:00
yield $worker -> shutdown ();
2019-02-21 00:39:39 +01:00
}
2015-08-29 03:55:30 +02:00
}