351 lines
11 KiB
PHP
Executable File
351 lines
11 KiB
PHP
Executable File
<?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('8.8.8.8', $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, 1345656451);
|
|
$retryExecutor->query('8.8.8.8', $query);
|
|
}
|
|
|
|
/**
|
|
* @covers React\Dns\Query\RetryExecutor
|
|
* @test
|
|
*/
|
|
public function queryShouldRetryQueryOnTimeout()
|
|
{
|
|
$response = $this->createStandardResponse();
|
|
|
|
$executor = $this->createExecutorMock();
|
|
$executor
|
|
->expects($this->exactly(2))
|
|
->method('query')
|
|
->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
|
->will($this->onConsecutiveCalls(
|
|
$this->returnCallback(function ($domain, $query) {
|
|
return Promise\reject(new TimeoutException("timeout"));
|
|
}),
|
|
$this->returnCallback(function ($domain, $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, 1345656451);
|
|
$retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback);
|
|
}
|
|
|
|
/**
|
|
* @covers React\Dns\Query\RetryExecutor
|
|
* @test
|
|
*/
|
|
public function queryShouldStopRetryingAfterSomeAttempts()
|
|
{
|
|
$executor = $this->createExecutorMock();
|
|
$executor
|
|
->expects($this->exactly(3))
|
|
->method('query')
|
|
->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
|
->will($this->returnCallback(function ($domain, $query) {
|
|
return Promise\reject(new TimeoutException("timeout"));
|
|
}));
|
|
|
|
$callback = $this->expectCallableNever();
|
|
|
|
$errorback = $this->createCallableMock();
|
|
$errorback
|
|
->expects($this->once())
|
|
->method('__invoke')
|
|
->with($this->isInstanceOf('RuntimeException'));
|
|
|
|
$retryExecutor = new RetryExecutor($executor, 2);
|
|
|
|
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
|
$retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback);
|
|
}
|
|
|
|
/**
|
|
* @covers React\Dns\Query\RetryExecutor
|
|
* @test
|
|
*/
|
|
public function queryShouldForwardNonTimeoutErrors()
|
|
{
|
|
$executor = $this->createExecutorMock();
|
|
$executor
|
|
->expects($this->once())
|
|
->method('query')
|
|
->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
|
->will($this->returnCallback(function ($domain, $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, 1345656451);
|
|
$retryExecutor->query('8.8.8.8', $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('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
|
->will($this->returnCallback(function ($domain, $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, 1345656451);
|
|
$promise = $retryExecutor->query('8.8.8.8', $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('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
|
->will($this->onConsecutiveCalls(
|
|
$this->returnValue($deferred->promise()),
|
|
$this->returnCallback(function ($domain, $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, 1345656451);
|
|
$promise = $retryExecutor->query('8.8.8.8', $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('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
|
->willReturn(Promise\resolve($this->createStandardResponse()));
|
|
|
|
$retryExecutor = new RetryExecutor($executor, 0);
|
|
|
|
gc_collect_cycles();
|
|
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
|
$retryExecutor->query('8.8.8.8', $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('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
|
->willReturn(Promise\reject(new TimeoutException("timeout")));
|
|
|
|
$retryExecutor = new RetryExecutor($executor, 0);
|
|
|
|
gc_collect_cycles();
|
|
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
|
$retryExecutor->query('8.8.8.8', $query);
|
|
|
|
$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('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
|
->willReturn($deferred->promise());
|
|
|
|
$retryExecutor = new RetryExecutor($executor, 0);
|
|
|
|
gc_collect_cycles();
|
|
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
|
$promise = $retryExecutor->query('8.8.8.8', $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('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
|
->will($this->returnCallback(function ($domain, $query) {
|
|
return Promise\reject(new \Exception);
|
|
}));
|
|
|
|
$retryExecutor = new RetryExecutor($executor, 2);
|
|
|
|
gc_collect_cycles();
|
|
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
|
$retryExecutor->query('8.8.8.8', $query);
|
|
|
|
$this->assertEquals(0, gc_collect_cycles());
|
|
}
|
|
|
|
protected function expectPromiseOnce($return = null)
|
|
{
|
|
$mock = $this->createPromiseMock();
|
|
$mock
|
|
->expects($this->once())
|
|
->method('then')
|
|
->will($this->returnValue($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->header->set('qr', 1);
|
|
$response->questions[] = new Record('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');
|
|
$response->prepare();
|
|
|
|
return $response;
|
|
}
|
|
}
|
|
|