diff --git a/composer.json b/composer.json index 75be8677..54d2550a 100644 --- a/composer.json +++ b/composer.json @@ -27,17 +27,14 @@ "psr/event-dispatcher": "^1.0", "psr/log": "^1.1", "yiisoft/friendly-exception": "^1.0", + "yiisoft/injector": "^1.0", "yiisoft/serializer": "^3.0@dev" }, "require-dev": { - "infection/infection": "^0.16.0", - "pda/pheanstalk": "*", + "infection/infection": "^0.17.0", + "phan/phan": "^3.0", "phpunit/phpunit": "^9.3", - "yiisoft/composer-config-plugin": "^1.0@dev", - "yiisoft/di": "^3.0@dev", - "yiisoft/log": "^3.0@dev", - "yiisoft/yii-console": "^3.0@dev", - "phan/phan": "^3.0" + "yiisoft/test-support": "3.0.x-dev" }, "suggest": { "ext-pcntl": "Need for process signals" diff --git a/config/common.php b/config/common.php index 9b61febf..a5056139 100644 --- a/config/common.php +++ b/config/common.php @@ -3,9 +3,10 @@ use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\EventDispatcher\ListenerProviderInterface; -use Yiisoft\Di\Container; use Yiisoft\EventDispatcher\Dispatcher\Dispatcher; use Yiisoft\EventDispatcher\Provider\Provider; +use Yiisoft\Serializer\JsonSerializer; +use Yiisoft\Serializer\SerializerInterface; use Yiisoft\Factory\Definitions\Reference; use Yiisoft\Yii\Queue\Cli\LoopInterface; use Yiisoft\Yii\Queue\Cli\SignalLoop; @@ -21,5 +22,6 @@ WorkerInterface::class => Reference::to(QueueWorker::class), ListenerProviderInterface::class => Provider::class, ContainerInterface::class => fn (ContainerInterface $container) => $container, - LoopInterface::class => SignalLoop::class + LoopInterface::class => SignalLoop::class, + SerializerInterface::class => JsonSerializer::class, ]; diff --git a/infection.json.dist b/infection.json.dist index d319ab15..74847b5c 100644 --- a/infection.json.dist +++ b/infection.json.dist @@ -5,12 +5,13 @@ ] }, "logs": { - "text": "php:\/\/stderr", + "text": "infection.log", "badge": { "branch": "master" } }, "mutators": { - "@default": true + "@default": true, + "ProtectedVisibility": false } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 337e4813..acb1319b 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,19 +1,22 @@ - + + + + ./src + + - ./tests/unit + ./tests/Unit - - - - ./src - - diff --git a/src/Driver/DriverInterface.php b/src/Driver/DriverInterface.php index 21cb9114..e8ccb114 100644 --- a/src/Driver/DriverInterface.php +++ b/src/Driver/DriverInterface.php @@ -6,10 +6,10 @@ use InvalidArgumentException; use Yiisoft\Yii\Queue\Enum\JobStatus; +use Yiisoft\Yii\Queue\Message\MessageInterface; +use Yiisoft\Yii\Queue\Payload\AttemptsRestrictedPayloadInterface; use Yiisoft\Yii\Queue\Payload\DelayablePayloadInterface; use Yiisoft\Yii\Queue\Payload\PrioritisedPayloadInterface; -use Yiisoft\Yii\Queue\Payload\AttemptsRestrictedPayloadInterface; -use Yiisoft\Yii\Queue\MessageInterface; interface DriverInterface { diff --git a/src/Driver/SynchronousDriver.php b/src/Driver/SynchronousDriver.php index efa2c5e0..954060fd 100644 --- a/src/Driver/SynchronousDriver.php +++ b/src/Driver/SynchronousDriver.php @@ -7,8 +7,8 @@ use InvalidArgumentException; use Yiisoft\Yii\Queue\Cli\LoopInterface; use Yiisoft\Yii\Queue\Enum\JobStatus; +use Yiisoft\Yii\Queue\Message\MessageInterface; use Yiisoft\Yii\Queue\Payload\PayloadInterface; -use Yiisoft\Yii\Queue\MessageInterface; use Yiisoft\Yii\Queue\Queue; use Yiisoft\Yii\Queue\QueueDependentInterface; use Yiisoft\Yii\Queue\Worker\WorkerInterface; diff --git a/src/Event/AfterExecution.php b/src/Event/AfterExecution.php index af844432..b9972b62 100644 --- a/src/Event/AfterExecution.php +++ b/src/Event/AfterExecution.php @@ -4,7 +4,7 @@ namespace Yiisoft\Yii\Queue\Event; -use Yiisoft\Yii\Queue\MessageInterface; +use Yiisoft\Yii\Queue\Message\MessageInterface; use Yiisoft\Yii\Queue\Queue; final class AfterExecution diff --git a/src/Event/AfterPush.php b/src/Event/AfterPush.php index f5f8dce8..6ff16443 100644 --- a/src/Event/AfterPush.php +++ b/src/Event/AfterPush.php @@ -4,7 +4,7 @@ namespace Yiisoft\Yii\Queue\Event; -use Yiisoft\Yii\Queue\MessageInterface; +use Yiisoft\Yii\Queue\Message\MessageInterface; use Yiisoft\Yii\Queue\Queue; final class AfterPush diff --git a/src/Event/BeforeExecution.php b/src/Event/BeforeExecution.php index 5effa25b..7faaa63c 100644 --- a/src/Event/BeforeExecution.php +++ b/src/Event/BeforeExecution.php @@ -4,7 +4,7 @@ namespace Yiisoft\Yii\Queue\Event; -use Yiisoft\Yii\Queue\MessageInterface; +use Yiisoft\Yii\Queue\Message\MessageInterface; use Yiisoft\Yii\Queue\Queue; final class BeforeExecution diff --git a/src/Event/BeforePush.php b/src/Event/BeforePush.php index 2d07b033..8d812653 100644 --- a/src/Event/BeforePush.php +++ b/src/Event/BeforePush.php @@ -4,7 +4,7 @@ namespace Yiisoft\Yii\Queue\Event; -use Yiisoft\Yii\Queue\MessageInterface; +use Yiisoft\Yii\Queue\Message\MessageInterface; use Yiisoft\Yii\Queue\Queue; final class BeforePush diff --git a/src/Event/JobFailure.php b/src/Event/JobFailure.php index 19b773f6..e565390e 100644 --- a/src/Event/JobFailure.php +++ b/src/Event/JobFailure.php @@ -6,7 +6,7 @@ use Psr\EventDispatcher\StoppableEventInterface; use Throwable; -use Yiisoft\Yii\Queue\MessageInterface; +use Yiisoft\Yii\Queue\Message\MessageInterface; use Yiisoft\Yii\Queue\Queue; final class JobFailure implements StoppableEventInterface diff --git a/src/Exception/JobFailureException.php b/src/Exception/JobFailureException.php index 70a9b6bd..ff114646 100644 --- a/src/Exception/JobFailureException.php +++ b/src/Exception/JobFailureException.php @@ -6,7 +6,7 @@ use RuntimeException; use Throwable; -use Yiisoft\Yii\Queue\MessageInterface; +use Yiisoft\Yii\Queue\Message\MessageInterface; class JobFailureException extends RuntimeException { diff --git a/src/Exception/PayloadNotSupportedException.php b/src/Exception/PayloadNotSupportedException.php index e8dbf0b2..4c08bab9 100644 --- a/src/Exception/PayloadNotSupportedException.php +++ b/src/Exception/PayloadNotSupportedException.php @@ -68,7 +68,7 @@ public function getSolution(): ?string The solution is in one of these: - Check which interfaces does $driverClass support and remove not supported interfaces from $payloadName. - Use another driver which supports all interfaces you need. Officially supported drivers are: - - None yet :) Work is in progress. + - yiisoft/yii-queue-amqp SOLUTION; } } diff --git a/src/Message.php b/src/Message/Message.php similarity index 96% rename from src/Message.php rename to src/Message/Message.php index 3fcdae7d..2fddff8a 100644 --- a/src/Message.php +++ b/src/Message/Message.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\Yii\Queue; +namespace Yiisoft\Yii\Queue\Message; class Message implements MessageInterface { diff --git a/src/MessageInterface.php b/src/Message/MessageInterface.php similarity index 94% rename from src/MessageInterface.php rename to src/Message/MessageInterface.php index ad8b37ff..45a7fab4 100644 --- a/src/MessageInterface.php +++ b/src/Message/MessageInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\Yii\Queue; +namespace Yiisoft\Yii\Queue\Message; interface MessageInterface { diff --git a/src/Payload/BasicPayload.php b/src/Payload/BasicPayload.php index c69e71e7..d3224231 100644 --- a/src/Payload/BasicPayload.php +++ b/src/Payload/BasicPayload.php @@ -7,6 +7,9 @@ class BasicPayload implements PayloadInterface { protected string $name; + /** + * @var mixed $data It can be anything what can be serialized with the queue driver serializer + */ protected $data; protected array $meta; diff --git a/src/Payload/PayloadDefinition.php b/src/Payload/PayloadDefinition.php deleted file mode 100644 index 99af551c..00000000 --- a/src/Payload/PayloadDefinition.php +++ /dev/null @@ -1,41 +0,0 @@ -name = $name; - $this->data = $data; - $this->properties = $properties; - $this->class = $class; - } - - public function getClass(): ?string - { - return $this->class; - } - - public function getData() - { - return $this->data; - } - - public function getName(): string - { - return $this->name; - } - - public function getProperties(): ?array - { - return $this->properties; - } -} diff --git a/src/Queue.php b/src/Queue.php index 5d7191ac..2029a3d7 100644 --- a/src/Queue.php +++ b/src/Queue.php @@ -12,11 +12,13 @@ use Yiisoft\Yii\Queue\Event\BeforePush; use Yiisoft\Yii\Queue\Event\JobFailure; use Yiisoft\Yii\Queue\Exception\PayloadNotSupportedException; +use Yiisoft\Yii\Queue\Message\Message; +use Yiisoft\Yii\Queue\Message\MessageInterface; +use Yiisoft\Yii\Queue\Payload\AttemptsRestrictedPayloadInterface; use Yiisoft\Yii\Queue\Payload\BasicPayload; use Yiisoft\Yii\Queue\Payload\DelayablePayloadInterface; use Yiisoft\Yii\Queue\Payload\PayloadInterface; use Yiisoft\Yii\Queue\Payload\PrioritisedPayloadInterface; -use Yiisoft\Yii\Queue\Payload\AttemptsRestrictedPayloadInterface; use Yiisoft\Yii\Queue\Worker\WorkerInterface; /** @@ -107,13 +109,19 @@ public function push(PayloadInterface $payload): ?string /** * Execute all existing jobs and exit + * + * @param int $max */ - public function run(): void + public function run(int $max = 0): void { $this->logger->debug('Start processing queue messages.'); $count = 0; - while ($this->loop->canContinue() && $message = $this->driver->nextMessage()) { + while ( + ($max <= 0 || $max > $count) + && $this->loop->canContinue() + && $message = $this->driver->nextMessage() + ) { $this->handle($message); $count++; } diff --git a/src/Worker/Worker.php b/src/Worker/Worker.php index 821d9914..c11658ee 100644 --- a/src/Worker/Worker.php +++ b/src/Worker/Worker.php @@ -14,7 +14,7 @@ use Yiisoft\Yii\Queue\Event\BeforeExecution; use Yiisoft\Yii\Queue\Event\JobFailure; use Yiisoft\Yii\Queue\Exception\JobFailureException; -use Yiisoft\Yii\Queue\MessageInterface; +use Yiisoft\Yii\Queue\Message\MessageInterface; use Yiisoft\Yii\Queue\Queue; final class Worker implements WorkerInterface diff --git a/src/Worker/WorkerInterface.php b/src/Worker/WorkerInterface.php index 905848de..5678c3eb 100644 --- a/src/Worker/WorkerInterface.php +++ b/src/Worker/WorkerInterface.php @@ -4,7 +4,7 @@ namespace Yiisoft\Yii\Queue\Worker; -use Yiisoft\Yii\Queue\MessageInterface; +use Yiisoft\Yii\Queue\Message\MessageInterface; use Yiisoft\Yii\Queue\Queue; interface WorkerInterface diff --git a/tests/App/DelayablePayload.php b/tests/App/DelayablePayload.php index 2c265935..027ef1e0 100644 --- a/tests/App/DelayablePayload.php +++ b/tests/App/DelayablePayload.php @@ -8,6 +8,8 @@ class DelayablePayload extends SimplePayload implements DelayablePayloadInterface { + protected string $name = 'delayable'; + public function getDelay(): int { return 1; diff --git a/tests/App/DummyInterface.php b/tests/App/DummyInterface.php new file mode 100644 index 00000000..77745734 --- /dev/null +++ b/tests/App/DummyInterface.php @@ -0,0 +1,7 @@ +jobExecutionTimes; - } - - public function simple(MessageInterface $message): void - { - $this->jobExecutionTimes++; - } - - public function exceptional(MessageInterface $message): void - { - $this->jobExecutionTimes++; - throw new RuntimeException('Test exception'); - } - - public function retryable(MessageInterface $message): void - { - if ($message->getPayloadMeta()[PayloadInterface::META_KEY_ATTEMPTS] > 1) { - throw new RuntimeException('Test exception'); - } - - $this->jobExecutionTimes++; - } -} diff --git a/tests/App/RetryablePayload.php b/tests/App/RetryablePayload.php index 7e37903a..40d37e27 100644 --- a/tests/App/RetryablePayload.php +++ b/tests/App/RetryablePayload.php @@ -4,18 +4,14 @@ namespace Yiisoft\Yii\Queue\Tests\App; -use RuntimeException; use Yiisoft\Yii\Queue\Payload\AttemptsRestrictedPayloadInterface; class RetryablePayload extends SimplePayload implements AttemptsRestrictedPayloadInterface { - public function getAttempts(): int - { - return 2; - } + protected string $name = 'retryable'; - public function getName(): string + public function getAttempts(): int { - return 'retryable'; + return 1; } } diff --git a/tests/App/SimplePayload.php b/tests/App/SimplePayload.php index a6822ad5..65c1921f 100644 --- a/tests/App/SimplePayload.php +++ b/tests/App/SimplePayload.php @@ -13,11 +13,11 @@ */ class SimplePayload implements PayloadInterface { - public bool $executed = false; + protected string $name = 'simple'; public function getName(): string { - return 'simple'; + return $this->name; } public function getData(): string @@ -29,4 +29,9 @@ public function getMeta(): array { return []; } + + public function setName(string $name): void + { + $this->name = $name; + } } diff --git a/tests/App/config/console.php b/tests/App/config/console.php deleted file mode 100644 index a53c8ba9..00000000 --- a/tests/App/config/console.php +++ /dev/null @@ -1,26 +0,0 @@ - NullLogger::class, - DriverInterface::class => SynchronousDriver::class, - Worker::class => new ReplaceValue( - [ - '__class' => Worker::class, - '__construct()' => [ - [ - 'simple' => [QueueHandler::class, 'simple'], - 'exceptional' => [QueueHandler::class, 'exceptional'], - 'retryable' => [QueueHandler::class, 'retryable'], - ], - ], - ] - ), -]; diff --git a/tests/TestCase.php b/tests/TestCase.php index 027ca92a..887fbb16 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -8,10 +8,29 @@ namespace Yiisoft\Yii\Queue\Tests; -use Yiisoft\Composer\Config\Builder; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Container\ContainerInterface; +use Psr\EventDispatcher\EventDispatcherInterface; use PHPUnit\Framework\TestCase as BaseTestCase; -use Yiisoft\Di\Container; -use Yiisoft\Yii\Event\EventConfigurator; +use Psr\Log\NullLogger; +use RuntimeException; +use Yiisoft\Injector\Injector; +use Yiisoft\Test\Support\Container\SimpleContainer; +use Yiisoft\Test\Support\EventDispatcher\SimpleEventDispatcher; +use Yiisoft\Yii\Queue\Cli\LoopInterface; +use Yiisoft\Yii\Queue\Cli\SignalLoop; +use Yiisoft\Yii\Queue\Driver\DriverInterface; +use Yiisoft\Yii\Queue\Driver\SynchronousDriver; +use Yiisoft\Yii\Queue\Event\AfterExecution; +use Yiisoft\Yii\Queue\Event\AfterPush; +use Yiisoft\Yii\Queue\Event\BeforeExecution; +use Yiisoft\Yii\Queue\Event\BeforePush; +use Yiisoft\Yii\Queue\Event\JobFailure; +use Yiisoft\Yii\Queue\Exception\PayloadNotSupportedException; +use Yiisoft\Yii\Queue\Queue; +use Yiisoft\Yii\Queue\Tests\App\RetryablePayload; +use Yiisoft\Yii\Queue\Worker\Worker; +use Yiisoft\Yii\Queue\Worker\WorkerInterface; /** * Base Test Case. @@ -20,12 +39,195 @@ */ abstract class TestCase extends BaseTestCase { - public Container $container; + protected ?ContainerInterface $container = null; + protected ?Queue $queue = null; + protected ?DriverInterface $driver = null; + protected ?LoopInterface $loop = null; + protected ?WorkerInterface $worker = null; + protected ?EventDispatcherInterface $dispatcher = null; + protected array $eventHandlers = []; + protected int $executionTimes; protected function setUp(): void { - $this->container = new Container(require Builder::path('tests-app')); - $eventConfigurator = $this->container->get(EventConfigurator::class); - $eventConfigurator->registerListeners(require Builder::path('events-console')); + parent::setUp(); + + $this->container = null; + $this->queue = null; + $this->driver = null; + $this->loop = null; + $this->worker = null; + $this->dispatcher = null; + $this->eventHandlers = []; + $this->executionTimes = 0; + } + + protected function getQueue(): Queue + { + if ($this->queue === null) { + $this->queue = $this->createQueue(); + } + + return $this->queue; + } + + /** + * @return DriverInterface|MockObject + */ + protected function getDriver(): DriverInterface + { + if ($this->driver === null) { + $this->driver = $this->createDriver($this->needsRealDriver()); + } + + return $this->driver; + } + + protected function getLoop(): LoopInterface + { + if ($this->loop === null) { + $this->loop = $this->createLoop(); + } + + return $this->loop; + } + + protected function getWorker(): WorkerInterface + { + if ($this->worker === null) { + $this->worker = $this->createWorker(); + } + + return $this->worker; + } + + protected function getEventDispatcher(): SimpleEventDispatcher + { + if ($this->dispatcher === null) { + $this->dispatcher = $this->createEventDispatcher(); + } + + return $this->dispatcher; + } + + protected function getContainer(): ContainerInterface + { + if ($this->container === null) { + $this->container = $this->createContainer(); + } + + return $this->container; + } + + protected function createQueue(): Queue + { + return new Queue( + $this->getDriver(), + $this->getEventDispatcher(), + $this->getWorker(), + $this->getLoop(), + new NullLogger() + ); + } + + protected function createDriver(bool $realDriver = false): DriverInterface + { + if ($realDriver) { + return new SynchronousDriver($this->getLoop(), $this->getWorker()); + } + + return $this->createMock(DriverInterface::class); + } + + protected function createLoop(): LoopInterface + { + return new SignalLoop(); + } + + protected function createWorker(): WorkerInterface + { + return new Worker( + $this->getMessageHandlers(), + $this->getEventDispatcher(), + new NullLogger(), + new Injector($this->getContainer()), + $this->getContainer() + ); + } + + protected function createEventDispatcher(): SimpleEventDispatcher + { + return new SimpleEventDispatcher(...$this->getEventHandlers()); + } + + protected function createContainer(): ContainerInterface + { + return new SimpleContainer($this->getContainerDefinitions()); + } + + protected function getContainerDefinitions(): array + { + return []; + } + + protected function setEventHandlers(callable ...$handlers): void + { + $this->eventHandlers = $handlers; + } + + protected function getEventHandlers(): array + { + return $this->eventHandlers; + } + + protected function getMessageHandlers(): array + { + return [ + 'simple' => fn () => $this->executionTimes++, + 'exceptional' => function () { + $this->executionTimes++; + + throw new RuntimeException('test'); + }, + 'retryable' => function () { + $this->executionTimes++; + + throw new RuntimeException('test'); + }, + 'not-supported' => function () { + throw new PayloadNotSupportedException($this->driver, new RetryablePayload()); + }, + ]; + } + + protected function needsRealDriver(): bool + { + return false; + } + + protected function assertEvents(array $events = []): void + { + $default = [ + BeforePush::class => 0, + AfterPush::class => 0, + BeforeExecution::class => 0, + AfterExecution::class => 0, + JobFailure::class => 0, + ]; + foreach (array_merge($default, $events) as $event => $timesExecuted) { + self::assertEquals($timesExecuted, $this->getEventsCount($event)); + } + } + + protected function getEventsCount(string $className): int + { + $result = 0; + foreach ($this->getEventDispatcher()->getEvents() as $event) { + if ($event instanceof $className) { + $result++; + } + } + + return $result; } } diff --git a/tests/Unit/ExceptionTest.php b/tests/Unit/ExceptionTest.php new file mode 100644 index 00000000..96a3b2c6 --- /dev/null +++ b/tests/Unit/ExceptionTest.php @@ -0,0 +1,57 @@ +getPayload(); + $driver = $this->getDriver(); + $driverClass = get_class($driver); + + $exception = new PayloadNotSupportedException($driver, $payload); + self::assertStringContainsString( + $payload->getName(), + $exception->getMessage(), + 'Payload name must be included' + ); + self::assertStringContainsString( + $driverClass, + $exception->getMessage(), + 'Driver class must be included' + ); + self::assertStringContainsString( + DelayablePayloadInterface::class, + $exception->getSolution(), + 'DelayablePayloadInterface must be included to the exception message as it is a default interface and the payload implements it' + ); + self::assertStringNotContainsString( + PrioritisedPayloadInterface::class, + $exception->getSolution(), + 'PrioritisedPayloadInterface must not be included as it is not implemented in the payload' + ); + self::assertStringNotContainsString( + DummyInterface::class, + $exception->getMessage(), + 'DummyInterface must not be included as it is not a part of yii-queue package yet it is implemented in the payload' + ); + self::assertEquals("Payload is not supported by current queue driver", $exception->getName()); + } + + private function getPayload(): PayloadInterface + { + return new class() extends DelayablePayload implements DummyInterface { + }; + } +} diff --git a/tests/unit/JobStatusTest.php b/tests/Unit/JobStatusTest.php similarity index 72% rename from tests/unit/JobStatusTest.php rename to tests/Unit/JobStatusTest.php index b1c623d4..65fce8b6 100644 --- a/tests/unit/JobStatusTest.php +++ b/tests/Unit/JobStatusTest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Yiisoft\Yii\Queue\Tests\unit; +namespace Yiisoft\Yii\Queue\Tests\Unit; use Yiisoft\Yii\Queue\Enum\JobStatus; use Yiisoft\Yii\Queue\Tests\TestCase; -class JobStatusTest extends TestCase +final class JobStatusTest extends TestCase { public function getStatusPairs(): array { @@ -41,14 +41,18 @@ public function getStatusPairs(): array /** * @dataProvider getStatusPairs + * + * @param string $statusName + * @param string $positiveMethod + * @param array $negatives */ public function testInstanceValue(string $statusName, string $positiveMethod, array $negatives): void { $status = JobStatus::$statusName(); - $this->assertTrue($status->$positiveMethod(), "$positiveMethod must be true for status $statusName"); + self::assertTrue($status->$positiveMethod(), "$positiveMethod must be true for status $statusName"); foreach ($negatives as $negative) { - $this->assertFalse($status->$negative(), "$negative must be false for status $statusName"); + self::assertFalse($status->$negative(), "$negative must be false for status $statusName"); } } } diff --git a/tests/Unit/QueueDependentInterfaceTest.php b/tests/Unit/QueueDependentInterfaceTest.php new file mode 100644 index 00000000..dab2eee0 --- /dev/null +++ b/tests/Unit/QueueDependentInterfaceTest.php @@ -0,0 +1,99 @@ +queue = $queue; + } + + public function nextMessage(): ?MessageInterface + { + } + + public function status(string $id): JobStatus + { + } + + public function push(MessageInterface $message): ?string + { + } + + public function subscribe(callable $handler): void + { + } + + public function canPush(MessageInterface $message): bool + { + } + }; + $independent = new class() implements DriverInterface { + public ?Queue $queue = null; + + public function setQueue(Queue $queue): void + { + $this->queue = $queue; + } + + public function nextMessage(): ?MessageInterface + { + } + + public function status(string $id): JobStatus + { + } + + public function push(MessageInterface $message): ?string + { + } + + public function subscribe(callable $handler): void + { + } + + public function canPush(MessageInterface $message): bool + { + } + }; + + return [ + [$dependent], + [$independent], + ]; + } + + /** + * @dataProvider driverProvider + * + * @param DriverInterface $driver + */ + public function testDependencyResolved(DriverInterface $driver): void + { + new Queue( + $driver, + $this->getEventDispatcher(), + $this->getWorker(), + $this->getLoop(), + new NullLogger() + ); + + self::assertEquals($driver instanceof QueueDependentInterface, $driver->queue instanceof Queue); + } +} diff --git a/tests/Unit/QueueTest.php b/tests/Unit/QueueTest.php new file mode 100644 index 00000000..055d7797 --- /dev/null +++ b/tests/Unit/QueueTest.php @@ -0,0 +1,198 @@ +needsRealDriver = true; + } + + protected function needsRealDriver(): bool + { + return $this->needsRealDriver; + } + + public function testPushSuccessful(): void + { + $this->needsRealDriver = false; + $this->getDriver()->method('canPush')->willReturn(true); + + $queue = $this->getQueue(); + $job = new SimplePayload(); + $queue->push($job); + + $this->assertEvents([BeforePush::class => 1, AfterPush::class => 1]); + } + + public function testPushNotSuccessful(): void + { + $this->needsRealDriver = false; + $this->getDriver()->method('canPush')->willReturn(false); + $exception = null; + + $queue = $this->getQueue(); + $job = new DelayablePayload(); + try { + $queue->push($job); + } catch (PayloadNotSupportedException $exception) { + } finally { + self::assertInstanceOf(PayloadNotSupportedException::class, $exception); + $this->assertEvents([BeforePush::class => 1]); + } + } + + public function testRun(): void + { + $queue = $this->getQueue(); + $job = new SimplePayload(); + $job2 = clone $job; + $queue->push($job); + $queue->push($job2); + $queue->run(); + + self::assertEquals(2, $this->executionTimes); + + $events = [ + BeforePush::class => 2, + AfterPush::class => 2, + BeforeExecution::class => 2, + AfterExecution::class => 2, + ]; + $this->assertEvents($events); + } + + public function testRunPartly(): void + { + $queue = $this->getQueue(); + $job = new SimplePayload(); + $job2 = clone $job; + $queue->push($job); + $queue->push($job2); + $queue->run(1); + + self::assertEquals(1, $this->executionTimes); + + $events = [ + BeforePush::class => 2, + AfterPush::class => 2, + BeforeExecution::class => 1, + AfterExecution::class => 1, + ]; + $this->assertEvents($events); + } + + public function testListen(): void + { + $queue = $this->getQueue(); + $job = new SimplePayload(); + $job2 = clone $job; + $queue->push($job); + $queue->push($job2); + $queue->listen(); + + self::assertEquals(2, $this->executionTimes); + + $events = [ + BeforePush::class => 2, + AfterPush::class => 2, + BeforeExecution::class => 2, + AfterExecution::class => 2, + ]; + $this->assertEvents($events); + } + + public function testJobRetry(): void + { + self::markTestSkipped('The logic will be refactored in https://github.com/yiisoft/yii-queue/issues/59'); + + $exception = null; + + $queue = $this->getQueue(); + $payload = new RetryablePayload(); + $queue->push($payload); + + try { + $queue->run(); + } catch (RuntimeException $exception) { + } finally { + self::assertInstanceOf(RuntimeException::class, $exception); + self::assertEquals( + "Processing of message #0 is stopped because of an exception:\ntest.", + $exception->getMessage() + ); + self::assertEquals(2, $this->executionTimes); + + $events = [ + BeforePush::class => 2, + AfterPush::class => 2, + BeforeExecution::class => 2, + JobFailure::class => 2, + ]; + $this->assertEvents($events); + } + } + + public function testJobRetryFail(): void + { + self::markTestSkipped('The logic will be refactored in https://github.com/yiisoft/yii-queue/issues/59'); + + $queue = $this->getQueue(); + $payload = new RetryablePayload(); + $payload->setName('not-supported'); + $queue->push($payload); + $exception = null; + + try { + $queue->run(); + } catch (PayloadNotSupportedException $exception) { + } finally { + $message = SynchronousDriver::class . ' does not support payload "retryable".'; + self::assertInstanceOf(PayloadNotSupportedException::class, $exception); + self::assertEquals($message, $exception->getMessage()); + self::assertEquals(0, $this->executionTimes); + + $events = [ + BeforePush::class => 1, + AfterPush::class => 1, + BeforeExecution::class => 1, + JobFailure::class => 1, + ]; + $this->assertEvents($events); + } + } + + public function testStatus(): void + { + $queue = $this->getQueue(); + $job = new SimplePayload(); + $id = $queue->push($job); + + $status = $queue->status($id); + self::assertTrue($status->isWaiting()); + + $queue->run(); + $status = $queue->status($id); + self::assertTrue($status->isDone()); + } +} diff --git a/tests/Unit/SynchronousDriverTest.php b/tests/Unit/SynchronousDriverTest.php new file mode 100644 index 00000000..3ec90f85 --- /dev/null +++ b/tests/Unit/SynchronousDriverTest.php @@ -0,0 +1,42 @@ +getQueue(); + $job = new SimplePayload(); + $id = $queue->push($job); + $wrongId = "$id "; + self::assertEquals(JobStatus::waiting(), $queue->status($wrongId)); + } + + public function testIdSetting(): void + { + $message = new Message('simple', [], []); + $driver = $this->getDriver(); + $driver->setQueue($this->createMock(Queue::class)); + + $ids = []; + $ids[] = $driver->push($message); + $ids[] = $driver->push($message); + $ids[] = $driver->push($message); + + self::assertCount(3, array_unique($ids)); + } +} diff --git a/tests/Unit/WorkerTest.php b/tests/Unit/WorkerTest.php new file mode 100644 index 00000000..9a545de9 --- /dev/null +++ b/tests/Unit/WorkerTest.php @@ -0,0 +1,78 @@ +executionTimes = 0; + $message = new Message('simple', '', []); + $queue = $this->createMock(Queue::class); + $this->getWorker()->process($message, $queue); + + self::assertEquals(1, $this->executionTimes); + } + + /** + * Check job execution is prevented + */ + public function testJobNotExecuted(): void + { + $handler = static function ($event) { + if ($event instanceof BeforeExecution) { + $event->stopExecution(); + } + }; + $this->setEventHandlers($handler); + + $message = new Message('simple', '', []); + $queue = $this->createMock(Queue::class); + $this->getWorker()->process($message, $queue); + + self::assertEquals(0, $this->executionTimes); + } + + /** + * Check job throws exception + */ + public function testThrowException(): void + { + $this->expectException(RuntimeException::class); + + $message = new Message('exceptional', '', []); + $queue = $this->createMock(Queue::class); + $this->getWorker()->process($message, $queue); + } + + /** + * Check exception throwing is prevented + */ + public function testThrowExceptionPrevented(): void + { + $handler = static function ($event) { + if ($event instanceof JobFailure) { + $event->preventThrowing(); + } + }; + $this->setEventHandlers($handler); + + $message = new Message('exceptional', '', []); + $queue = $this->createMock(Queue::class); + $this->getWorker()->process($message, $queue); + + self::assertEquals(1, $this->executionTimes); + } +} diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml deleted file mode 100644 index 689d58e4..00000000 --- a/tests/docker-compose.yml +++ /dev/null @@ -1,79 +0,0 @@ -version: "3.5" -services: - - # https://docs.docker.com/samples/library/php/ - php74: - build: - context: .. - dockerfile: tests/docker/php/7.4/Dockerfile - volumes: - - ./runtime/.composer74:/root/.composer - - ..:/code - dns: &php_dns - - 8.8.8.8 - - 4.4.4.4 - environment: &php_environment - RABBITMQ_HOST: rabbitmq - RABBITMQ_USER: guest - RABBITMQ_PASSWORD: guest - BEANSTALK_HOST: beanstalk - GEARMAN_HOST: gearmand - COMPOSER_ALLOW_SUPERUSER: 1 - depends_on: &php_depends_on - - mysql - - postgres - - redis - - rabbitmq - - beanstalk - - gearmand - networks: - net: {} - - # https://docs.docker.com/samples/library/mysql/ - mysql: - image: mysql:5.7 - environment: - MYSQL_ALLOW_EMPTY_PASSWORD: 1 - MYSQL_USER: yii2_queue_test - MYSQL_PASSWORD: yii2_queue_test - MYSQL_DATABASE: yii2_queue_test - networks: - net: {} - - # https://docs.docker.com/samples/library/postgres/ - postgres: - image: postgres:10.4 - environment: - POSTGRES_USER: yii2_queue_test - POSTGRES_PASSWORD: yii2_queue_test - POSTGRES_DB: yii2_queue_test - networks: - net: {} - - # https://docs.docker.com/samples/library/redis/ - redis: - image: redis:4.0 - networks: - net: {} - - # https://docs.docker.com/samples/library/rabbitmq/ - rabbitmq: - image: rabbitmq:3.7 - networks: - net: {} - - # https://hub.docker.com/r/schickling/beanstalkd/ - beanstalk: - image: schickling/beanstalkd - networks: - net: {} - - # https://hub.docker.com/r/artefactual/gearmand/ - gearmand: - image: artefactual/gearmand - networks: - net: {} - -networks: - net: - name: yii2_queue_net diff --git a/tests/unit/QueueTest.php b/tests/unit/QueueTest.php deleted file mode 100644 index 5cb97e9b..00000000 --- a/tests/unit/QueueTest.php +++ /dev/null @@ -1,101 +0,0 @@ -eventManager = $this->createMock(EventManager::class); - - $configurator = $this->container->get(EventConfigurator::class); - $configurator->registerListeners([BeforePush::class => [[$this->eventManager, 'beforePushHandler']]]); - $configurator->registerListeners([AfterPush::class => [[$this->eventManager, 'afterPushHandler']]]); - $configurator->registerListeners([BeforeExecution::class => [[$this->eventManager, 'beforeExecutionHandler']]]); - $configurator->registerListeners([AfterExecution::class => [[$this->eventManager, 'afterExecutionHandler']]]); - $configurator->registerListeners([JobFailure::class => [[$this->eventManager, 'jobFailureHandler']]]); - } - - public function testPushSuccessful(): void - { - $this->eventManager->expects(self::once())->method('beforePushHandler'); - $this->eventManager->expects(self::once())->method('afterPushHandler'); - $this->eventManager->expects(self::never())->method('beforeExecutionHandler'); - $this->eventManager->expects(self::never())->method('afterExecutionHandler'); - $this->eventManager->expects(self::never())->method('jobFailureHandler'); - - $queue = $this->container->get(Queue::class); - $job = $this->container->get(SimplePayload::class); - $id = $queue->push($job); - - $this->assertNotEquals('', $id, 'Pushed message should has an id'); - } - - public function testPushNotSuccessful(): void - { - $this->expectException(PayloadNotSupportedException::class); - $this->eventManager->expects(self::once())->method('beforePushHandler'); - $this->eventManager->expects(self::never())->method('afterPushHandler'); - $this->eventManager->expects(self::never())->method('beforeExecutionHandler'); - $this->eventManager->expects(self::never())->method('afterExecutionHandler'); - $this->eventManager->expects(self::never())->method('jobFailureHandler'); - - $queue = $this->container->get(Queue::class); - $job = $this->container->get(DelayablePayload::class); - $queue->push($job); - } - - public function testJobRetry(): void - { - $this->eventManager->expects(self::exactly(2))->method('beforePushHandler'); - $this->eventManager->expects(self::exactly(2))->method('afterPushHandler'); - $this->eventManager->expects(self::exactly(2))->method('beforeExecutionHandler'); - $this->eventManager->expects(self::once())->method('afterExecutionHandler'); - $this->eventManager->expects(self::once())->method('jobFailureHandler'); - - $queue = $this->container->get(Queue::class); - $payload = $this->container->get(RetryablePayload::class); - $queue->push($payload); - $queue->run(); - - $this->assertEquals(1, $this->container->get(QueueHandler::class)->getJobExecutionTimes()); - } - - public function testStatus(): void - { - $queue = $this->container->get(Queue::class); - $job = $this->container->get(SimplePayload::class); - $id = $queue->push($job); - - $status = $queue->status($id); - $this->assertEquals(true, $status->isWaiting()); - - $queue->run(); - $status = $queue->status($id); - $this->assertEquals(true, $status->isDone()); - } -} diff --git a/tests/unit/SynchronousDriverTest.php b/tests/unit/SynchronousDriverTest.php deleted file mode 100644 index 2c253c48..00000000 --- a/tests/unit/SynchronousDriverTest.php +++ /dev/null @@ -1,63 +0,0 @@ -container->get(Queue::class); - $job = $this->container->get($class); - - if (!$available) { - $this->expectException(PayloadNotSupportedException::class); - } - - $id = $queue->push($job); - - if ($available) { - $this->assertTrue($id >= 0); - } - } - - public static function getJobTypes(): array - { - return [ - 'Simple job' => [ - SimplePayload::class, - true, - ], - DelayablePayloadInterface::class => [ - DelayablePayload::class, - false, - ], - PrioritisedPayloadInterface::class => [ - PrioritizedPayload::class, - false, - ], - AttemptsRestrictedPayloadInterface::class => [ - RetryablePayload::class, - true, - ], - ]; - } -} diff --git a/tests/unit/WorkerTest.php b/tests/unit/WorkerTest.php deleted file mode 100644 index 8a469b3a..00000000 --- a/tests/unit/WorkerTest.php +++ /dev/null @@ -1,83 +0,0 @@ -worker = $this->container->get(Worker::class); - } - - /** - * Check normal job execution - */ - public function testJobExecuted(): void - { - $message = new Message('simple', '', []); - $queue = $this->createMock(Queue::class); - - $this->worker->process($message, $queue); - $this->assertEquals(1, $this->container->get(QueueHandler::class)->getJobExecutionTimes()); - } - - /** - * Check job execution is prevented - */ - public function testJobNotExecuted(): void - { - $handler = fn (BeforeExecution $event) => $event->stopExecution(); - $this->container->get(EventConfigurator::class)->registerListeners([BeforeExecution::class => [$handler]]); - - $message = new Message('simple', '', []); - $queue = $this->createMock(Queue::class); - $this->worker->process($message, $queue); - - $this->assertEquals(0, $this->container->get(QueueHandler::class)->getJobExecutionTimes()); - } - - /** - * Check job throws exception - */ - public function testThrowException(): void - { - $this->expectException(RuntimeException::class); - - $message = new Message('exceptional', '', []); - $queue = $this->createMock(Queue::class); - $this->worker->process($message, $queue); - } - - /** - * Check exception throwing is prevented - */ - public function testThrowExceptionPrevented(): void - { - $handler = fn (JobFailure $event) => $event->preventThrowing(); - $this->container->get(EventConfigurator::class)->registerListeners([JobFailure::class => [$handler]]); - - $message = new Message('exceptional', '', []); - $queue = $this->createMock(Queue::class); - $this->worker->process($message, $queue); - - $this->assertEquals(1, $this->container->get(QueueHandler::class)->getJobExecutionTimes()); - } -} diff --git a/tests/yii b/tests/yii deleted file mode 100755 index 3f7c91d3..00000000 --- a/tests/yii +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env php -get(Application::class)->run(); -})();