| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- <?php
- namespace React\Tests\Dns\Query;
- use React\Tests\Dns\TestCase;
- use React\Dns\Query\RetryExecutor;
- use React\Dns\Query\Query;
- use React\Dns\Model\Message;
- use React\Dns\Query\TimeoutException;
- use React\Dns\Model\Record;
- use React\Promise;
- use React\Promise\Deferred;
- use React\Dns\Query\CancellationException;
- class RetryExecutorTest extends TestCase
- {
- /**
- * @covers React\Dns\Query\RetryExecutor
- * @test
- */
- public function queryShouldDelegateToDecoratedExecutor()
- {
- $executor = $this->createExecutorMock();
- $executor
- ->expects($this->once())
- ->method('query')
- ->with($this->isInstanceOf('React\Dns\Query\Query'))
- ->will($this->returnValue($this->expectPromiseOnce()));
- $retryExecutor = new RetryExecutor($executor, 2);
- $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN);
- $retryExecutor->query($query);
- }
- /**
- * @covers React\Dns\Query\RetryExecutor
- * @test
- */
- public function queryShouldRetryQueryOnTimeout()
- {
- $response = $this->createStandardResponse();
- $executor = $this->createExecutorMock();
- $executor
- ->expects($this->exactly(2))
- ->method('query')
- ->with($this->isInstanceOf('React\Dns\Query\Query'))
- ->will($this->onConsecutiveCalls(
- $this->returnCallback(function ($query) {
- return Promise\reject(new TimeoutException("timeout"));
- }),
- $this->returnCallback(function ($query) use ($response) {
- return Promise\resolve($response);
- })
- ));
- $callback = $this->createCallableMock();
- $callback
- ->expects($this->once())
- ->method('__invoke')
- ->with($this->isInstanceOf('React\Dns\Model\Message'));
- $errorback = $this->expectCallableNever();
- $retryExecutor = new RetryExecutor($executor, 2);
- $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN);
- $retryExecutor->query($query)->then($callback, $errorback);
- }
- /**
- * @covers React\Dns\Query\RetryExecutor
- * @test
- */
- public function queryShouldStopRetryingAfterSomeAttempts()
- {
- $executor = $this->createExecutorMock();
- $executor
- ->expects($this->exactly(3))
- ->method('query')
- ->with($this->isInstanceOf('React\Dns\Query\Query'))
- ->will($this->returnCallback(function ($query) {
- return Promise\reject(new TimeoutException("timeout"));
- }));
- $retryExecutor = new RetryExecutor($executor, 2);
- $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN);
- $promise = $retryExecutor->query($query);
- $exception = null;
- $promise->then(null, function ($reason) use (&$exception) {
- $exception = $reason;
- });
- assert($exception instanceof \RuntimeException);
- $this->assertInstanceOf('RuntimeException', $exception);
- $this->assertEquals('DNS query for igor.io (A) failed: too many retries', $exception->getMessage());
- $this->assertEquals(0, $exception->getCode());
- $this->assertInstanceOf('React\Dns\Query\TimeoutException', $exception->getPrevious());
- $this->assertNotEquals('', $exception->getTraceAsString());
- }
- /**
- * @covers React\Dns\Query\RetryExecutor
- * @test
- */
- public function queryShouldForwardNonTimeoutErrors()
- {
- $executor = $this->createExecutorMock();
- $executor
- ->expects($this->once())
- ->method('query')
- ->with($this->isInstanceOf('React\Dns\Query\Query'))
- ->will($this->returnCallback(function ($query) {
- return Promise\reject(new \Exception);
- }));
- $callback = $this->expectCallableNever();
- $errorback = $this->createCallableMock();
- $errorback
- ->expects($this->once())
- ->method('__invoke')
- ->with($this->isInstanceOf('Exception'));
- $retryExecutor = new RetryExecutor($executor, 2);
- $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN);
- $retryExecutor->query($query)->then($callback, $errorback);
- }
- /**
- * @covers React\Dns\Query\RetryExecutor
- * @test
- */
- public function queryShouldCancelQueryOnCancel()
- {
- $cancelled = 0;
- $executor = $this->createExecutorMock();
- $executor
- ->expects($this->once())
- ->method('query')
- ->with($this->isInstanceOf('React\Dns\Query\Query'))
- ->will($this->returnCallback(function ($query) use (&$cancelled) {
- $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
- ++$cancelled;
- $reject(new CancellationException('Cancelled'));
- });
- return $deferred->promise();
- })
- );
- $retryExecutor = new RetryExecutor($executor, 2);
- $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN);
- $promise = $retryExecutor->query($query);
- $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
- $this->assertEquals(0, $cancelled);
- $promise->cancel();
- $this->assertEquals(1, $cancelled);
- }
- /**
- * @covers React\Dns\Query\RetryExecutor
- * @test
- */
- public function queryShouldCancelSecondQueryOnCancel()
- {
- $deferred = new Deferred();
- $cancelled = 0;
- $executor = $this->createExecutorMock();
- $executor
- ->expects($this->exactly(2))
- ->method('query')
- ->with($this->isInstanceOf('React\Dns\Query\Query'))
- ->will($this->onConsecutiveCalls(
- $this->returnValue($deferred->promise()),
- $this->returnCallback(function ($query) use (&$cancelled) {
- $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
- ++$cancelled;
- $reject(new CancellationException('Cancelled'));
- });
- return $deferred->promise();
- })
- ));
- $retryExecutor = new RetryExecutor($executor, 2);
- $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN);
- $promise = $retryExecutor->query($query);
- $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
- // first query will time out after a while and this sends the next query
- $deferred->reject(new TimeoutException());
- $this->assertEquals(0, $cancelled);
- $promise->cancel();
- $this->assertEquals(1, $cancelled);
- }
- /**
- * @covers React\Dns\Query\RetryExecutor
- * @test
- */
- public function queryShouldNotCauseGarbageReferencesOnSuccess()
- {
- if (class_exists('React\Promise\When')) {
- $this->markTestSkipped('Not supported on legacy Promise v1 API');
- }
- $executor = $this->createExecutorMock();
- $executor
- ->expects($this->once())
- ->method('query')
- ->with($this->isInstanceOf('React\Dns\Query\Query'))
- ->willReturn(Promise\resolve($this->createStandardResponse()));
- $retryExecutor = new RetryExecutor($executor, 0);
- while (gc_collect_cycles()) {
- // collect all garbage cycles
- }
- $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN);
- $retryExecutor->query($query);
- $this->assertEquals(0, gc_collect_cycles());
- }
- /**
- * @covers React\Dns\Query\RetryExecutor
- * @test
- */
- public function queryShouldNotCauseGarbageReferencesOnTimeoutErrors()
- {
- if (class_exists('React\Promise\When')) {
- $this->markTestSkipped('Not supported on legacy Promise v1 API');
- }
- $executor = $this->createExecutorMock();
- $executor
- ->expects($this->any())
- ->method('query')
- ->with($this->isInstanceOf('React\Dns\Query\Query'))
- ->willReturn(Promise\reject(new TimeoutException("timeout")));
- $retryExecutor = new RetryExecutor($executor, 0);
- while (gc_collect_cycles()) {
- // collect all garbage cycles
- }
- $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN);
- $promise = $retryExecutor->query($query);
- $promise->then(null, $this->expectCallableOnce()); // avoid reporting unhandled rejection
- $this->assertEquals(0, gc_collect_cycles());
- }
- /**
- * @covers React\Dns\Query\RetryExecutor
- * @test
- */
- public function queryShouldNotCauseGarbageReferencesOnCancellation()
- {
- if (class_exists('React\Promise\When')) {
- $this->markTestSkipped('Not supported on legacy Promise v1 API');
- }
- $deferred = new Deferred(function () {
- throw new \RuntimeException();
- });
- $executor = $this->createExecutorMock();
- $executor
- ->expects($this->once())
- ->method('query')
- ->with($this->isInstanceOf('React\Dns\Query\Query'))
- ->willReturn($deferred->promise());
- $retryExecutor = new RetryExecutor($executor, 0);
- while (gc_collect_cycles()) {
- // collect all garbage cycles
- }
- $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN);
- $promise = $retryExecutor->query($query);
- $promise->cancel();
- $promise = null;
- $this->assertEquals(0, gc_collect_cycles());
- }
- /**
- * @covers React\Dns\Query\RetryExecutor
- * @test
- */
- public function queryShouldNotCauseGarbageReferencesOnNonTimeoutErrors()
- {
- if (class_exists('React\Promise\When')) {
- $this->markTestSkipped('Not supported on legacy Promise v1 API');
- }
- $executor = $this->createExecutorMock();
- $executor
- ->expects($this->once())
- ->method('query')
- ->with($this->isInstanceOf('React\Dns\Query\Query'))
- ->will($this->returnCallback(function ($query) {
- return Promise\reject(new \Exception);
- }));
- $retryExecutor = new RetryExecutor($executor, 2);
- while (gc_collect_cycles()) {
- // collect all garbage cycles
- }
- $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN);
- $promise = $retryExecutor->query($query);
- $promise->then(null, $this->expectCallableOnce()); // avoid reporting unhandled rejection
- $this->assertEquals(0, gc_collect_cycles());
- }
- protected function expectPromiseOnce($return = null)
- {
- $mock = $this->createPromiseMock();
- $mock
- ->expects($this->once())
- ->method('then')
- ->will($this->returnValue(Promise\resolve($return)));
- return $mock;
- }
- protected function createExecutorMock()
- {
- return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
- }
- protected function createPromiseMock()
- {
- return $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
- }
- protected function createStandardResponse()
- {
- $response = new Message();
- $response->qr = true;
- $response->questions[] = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN);
- $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131');
- return $response;
- }
- }
|