This commit is contained in:
2022-10-23 01:39:27 +02:00
parent 8c17aab483
commit 1929b84685
4130 changed files with 479334 additions and 0 deletions

10
vendor/react/dns/tests/CallableStub.php vendored Executable file
View File

@@ -0,0 +1,10 @@
<?php
namespace React\Tests\Dns;
class CallableStub
{
public function __invoke()
{
}
}

189
vendor/react/dns/tests/Config/ConfigTest.php vendored Executable file
View File

@@ -0,0 +1,189 @@
<?php
namespace React\Tests\Dns\Config;
use React\Tests\Dns\TestCase;
use React\Dns\Config\Config;
class ConfigTest extends TestCase
{
public function testLoadsSystemDefault()
{
$config = Config::loadSystemConfigBlocking();
$this->assertInstanceOf('React\Dns\Config\Config', $config);
}
public function testLoadsDefaultPath()
{
if (DIRECTORY_SEPARATOR === '\\') {
$this->markTestSkipped('Not supported on Windows');
}
$config = Config::loadResolvConfBlocking();
$this->assertInstanceOf('React\Dns\Config\Config', $config);
}
public function testLoadsFromExplicitPath()
{
$config = Config::loadResolvConfBlocking(__DIR__ . '/../Fixtures/etc/resolv.conf');
$this->assertEquals(array('8.8.8.8'), $config->nameservers);
}
/**
* @expectedException RuntimeException
*/
public function testLoadThrowsWhenPathIsInvalid()
{
Config::loadResolvConfBlocking(__DIR__ . '/invalid.conf');
}
public function testParsesSingleEntryFile()
{
$contents = 'nameserver 8.8.8.8';
$expected = array('8.8.8.8');
$config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents));
$this->assertEquals($expected, $config->nameservers);
}
public function testParsesNameserverEntriesFromAverageFileCorrectly()
{
$contents = '#
# Mac OS X Notice
#
# This file is not used by the host name and address resolution
# or the DNS query routing mechanisms used by most processes on
# this Mac OS X system.
#
# This file is automatically generated.
#
domain v.cablecom.net
nameserver 127.0.0.1
nameserver ::1
';
$expected = array('127.0.0.1', '::1');
$config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents));
$this->assertEquals($expected, $config->nameservers);
}
public function testParsesEmptyFileWithoutNameserverEntries()
{
$contents = '';
$expected = array();
$config = Config::loadResolvConfBlocking('data://text/plain;base64,');
$this->assertEquals($expected, $config->nameservers);
}
public function testParsesFileAndIgnoresCommentsAndInvalidNameserverEntries()
{
$contents = '
# nameserver 1.2.3.4
; nameserver 2.3.4.5
nameserver 3.4.5.6 # nope
nameserver 4.5.6.7 5.6.7.8
nameserver 6.7.8.9
NameServer 7.8.9.10
';
$expected = array();
$config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents));
$this->assertEquals($expected, $config->nameservers);
}
public function testLoadsFromWmicOnWindows()
{
if (DIRECTORY_SEPARATOR !== '\\') {
$this->markTestSkipped('Only on Windows');
}
$config = Config::loadWmicBlocking();
$this->assertInstanceOf('React\Dns\Config\Config', $config);
}
public function testLoadsSingleEntryFromWmicOutput()
{
$contents = '
Node,DNSServerSearchOrder
ACE,
ACE,{192.168.2.1}
ACE,
';
$expected = array('192.168.2.1');
$config = Config::loadWmicBlocking($this->echoCommand($contents));
$this->assertEquals($expected, $config->nameservers);
}
public function testLoadsEmptyListFromWmicOutput()
{
$contents = '
Node,DNSServerSearchOrder
ACE,
';
$expected = array();
$config = Config::loadWmicBlocking($this->echoCommand($contents));
$this->assertEquals($expected, $config->nameservers);
}
public function testLoadsSingleEntryForMultipleNicsFromWmicOutput()
{
$contents = '
Node,DNSServerSearchOrder
ACE,
ACE,{192.168.2.1}
ACE,
ACE,{192.168.2.2}
ACE,
';
$expected = array('192.168.2.1', '192.168.2.2');
$config = Config::loadWmicBlocking($this->echoCommand($contents));
$this->assertEquals($expected, $config->nameservers);
}
public function testLoadsMultipleEntriesForSingleNicWithSemicolonFromWmicOutput()
{
$contents = '
Node,DNSServerSearchOrder
ACE,
ACE,{192.168.2.1;192.168.2.2}
ACE,
';
$expected = array('192.168.2.1', '192.168.2.2');
$config = Config::loadWmicBlocking($this->echoCommand($contents));
$this->assertEquals($expected, $config->nameservers);
}
public function testLoadsMultipleEntriesForSingleNicWithQuotesFromWmicOutput()
{
$contents = '
Node,DNSServerSearchOrder
ACE,
ACE,{"192.168.2.1","192.168.2.2"}
ACE,
';
$expected = array('192.168.2.1', '192.168.2.2');
$config = Config::loadWmicBlocking($this->echoCommand($contents));
$this->assertEquals($expected, $config->nameservers);
}
private function echoCommand($output)
{
return 'echo ' . escapeshellarg($output);
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace React\Test\Dns\Config;
use PHPUnit\Framework\TestCase;
use React\Dns\Config\FilesystemFactory;
class FilesystemFactoryTest extends TestCase
{
/** @test */
public function parseEtcResolvConfShouldParseCorrectly()
{
$contents = '#
# Mac OS X Notice
#
# This file is not used by the host name and address resolution
# or the DNS query routing mechanisms used by most processes on
# this Mac OS X system.
#
# This file is automatically generated.
#
domain v.cablecom.net
nameserver 127.0.0.1
nameserver 8.8.8.8
';
$expected = array('127.0.0.1', '8.8.8.8');
$capturedConfig = null;
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$factory = new FilesystemFactory($loop);
$factory->parseEtcResolvConf($contents)->then(function ($config) use (&$capturedConfig) {
$capturedConfig = $config;
});
$this->assertNotNull($capturedConfig);
$this->assertSame($expected, $capturedConfig->nameservers);
}
/** @test */
public function createShouldLoadStuffFromFilesystem()
{
$this->markTestIncomplete('Filesystem API is incomplete');
$expected = array('8.8.8.8');
$triggerListener = null;
$capturedConfig = null;
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop
->expects($this->once())
->method('addReadStream')
->will($this->returnCallback(function ($stream, $listener) use (&$triggerListener) {
$triggerListener = function () use ($stream, $listener) {
call_user_func($listener, $stream);
};
}));
$factory = new FilesystemFactory($loop);
$factory->create(__DIR__.'/../Fixtures/etc/resolv.conf')->then(function ($config) use (&$capturedConfig) {
$capturedConfig = $config;
});
$triggerListener();
$this->assertNotNull($capturedConfig);
$this->assertSame($expected, $capturedConfig->nameservers);
}
}

View File

@@ -0,0 +1,170 @@
<?php
namespace React\Tests\Dns\Config;
use React\Tests\Dns\TestCase;
use React\Dns\Config\HostsFile;
class HostsFileTest extends TestCase
{
public function testLoadsFromDefaultPath()
{
$hosts = HostsFile::loadFromPathBlocking();
$this->assertInstanceOf('React\Dns\Config\HostsFile', $hosts);
}
public function testDefaultShouldHaveLocalhostMapped()
{
if (DIRECTORY_SEPARATOR === '\\') {
$this->markTestSkipped('Not supported on Windows');
}
$hosts = HostsFile::loadFromPathBlocking();
$this->assertContains('127.0.0.1', $hosts->getIpsForHost('localhost'));
}
/**
* @expectedException RuntimeException
*/
public function testLoadThrowsForInvalidPath()
{
HostsFile::loadFromPathBlocking('does/not/exist');
}
public function testContainsSingleLocalhostEntry()
{
$hosts = new HostsFile('127.0.0.1 localhost');
$this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('localhost'));
$this->assertEquals(array(), $hosts->getIpsForHost('example.com'));
}
public function testNonIpReturnsNothingForInvalidHosts()
{
$hosts = new HostsFile('a b');
$this->assertEquals(array(), $hosts->getIpsForHost('a'));
$this->assertEquals(array(), $hosts->getIpsForHost('b'));
}
public function testIgnoresIpv6ZoneId()
{
$hosts = new HostsFile('fe80::1%lo0 localhost');
$this->assertEquals(array('fe80::1'), $hosts->getIpsForHost('localhost'));
}
public function testSkipsComments()
{
$hosts = new HostsFile('# start' . PHP_EOL .'#127.0.0.1 localhost' . PHP_EOL . '127.0.0.2 localhost # example.com');
$this->assertEquals(array('127.0.0.2'), $hosts->getIpsForHost('localhost'));
$this->assertEquals(array(), $hosts->getIpsForHost('example.com'));
}
public function testContainsSingleLocalhostEntryWithCaseIgnored()
{
$hosts = new HostsFile('127.0.0.1 LocalHost');
$this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('LOCALHOST'));
}
public function testEmptyFileContainsNothing()
{
$hosts = new HostsFile('');
$this->assertEquals(array(), $hosts->getIpsForHost('example.com'));
}
public function testSingleEntryWithMultipleNames()
{
$hosts = new HostsFile('127.0.0.1 localhost example.com');
$this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('example.com'));
$this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('localhost'));
}
public function testMergesEntriesOverMultipleLines()
{
$hosts = new HostsFile("127.0.0.1 localhost\n127.0.0.2 localhost\n127.0.0.3 a localhost b\n127.0.0.4 a localhost");
$this->assertEquals(array('127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'), $hosts->getIpsForHost('localhost'));
}
public function testMergesIpv4AndIpv6EntriesOverMultipleLines()
{
$hosts = new HostsFile("127.0.0.1 localhost\n::1 localhost");
$this->assertEquals(array('127.0.0.1', '::1'), $hosts->getIpsForHost('localhost'));
}
public function testReverseLookup()
{
$hosts = new HostsFile('127.0.0.1 localhost');
$this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.1'));
$this->assertEquals(array(), $hosts->getHostsForIp('192.168.1.1'));
}
public function testReverseSkipsComments()
{
$hosts = new HostsFile("# start\n#127.0.0.1 localhosted\n127.0.0.2\tlocalhost\t# example.com\n\t127.0.0.3\t\texample.org\t\t");
$this->assertEquals(array(), $hosts->getHostsForIp('127.0.0.1'));
$this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.2'));
$this->assertEquals(array('example.org'), $hosts->getHostsForIp('127.0.0.3'));
}
public function testReverseNonIpReturnsNothing()
{
$hosts = new HostsFile('127.0.0.1 localhost');
$this->assertEquals(array(), $hosts->getHostsForIp('localhost'));
$this->assertEquals(array(), $hosts->getHostsForIp('127.0.0.1.1'));
}
public function testReverseNonIpReturnsNothingForInvalidHosts()
{
$hosts = new HostsFile('a b');
$this->assertEquals(array(), $hosts->getHostsForIp('a'));
$this->assertEquals(array(), $hosts->getHostsForIp('b'));
}
public function testReverseLookupReturnsLowerCaseHost()
{
$hosts = new HostsFile('127.0.0.1 LocalHost');
$this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.1'));
}
public function testReverseLookupChecksNormalizedIpv6()
{
$hosts = new HostsFile('FE80::00a1 localhost');
$this->assertEquals(array('localhost'), $hosts->getHostsForIp('fe80::A1'));
}
public function testReverseLookupIgnoresIpv6ZoneId()
{
$hosts = new HostsFile('fe80::1%lo0 localhost');
$this->assertEquals(array('localhost'), $hosts->getHostsForIp('fe80::1'));
}
public function testReverseLookupReturnsMultipleHostsOverSingleLine()
{
$hosts = new HostsFile("::1 ip6-localhost ip6-loopback");
$this->assertEquals(array('ip6-localhost', 'ip6-loopback'), $hosts->getHostsForIp('::1'));
}
public function testReverseLookupReturnsMultipleHostsOverMultipleLines()
{
$hosts = new HostsFile("::1 ip6-localhost\n::1 ip6-loopback");
$this->assertEquals(array('ip6-localhost', 'ip6-loopback'), $hosts->getHostsForIp('::1'));
}
}

View File

@@ -0,0 +1 @@
nameserver 8.8.8.8

View File

@@ -0,0 +1,171 @@
<?php
namespace React\Tests\Dns;
use React\EventLoop\Factory as LoopFactory;
use React\Dns\Resolver\Factory;
use React\Dns\RecordNotFoundException;
use React\Dns\Model\Message;
class FunctionalTest extends TestCase
{
public function setUp()
{
$this->loop = LoopFactory::create();
$factory = new Factory();
$this->resolver = $factory->create('8.8.8.8', $this->loop);
}
public function testResolveLocalhostResolves()
{
$promise = $this->resolver->resolve('localhost');
$promise->then($this->expectCallableOnce(), $this->expectCallableNever());
$this->loop->run();
}
public function testResolveAllLocalhostResolvesWithArray()
{
$promise = $this->resolver->resolveAll('localhost', Message::TYPE_A);
$promise->then($this->expectCallableOnceWith($this->isType('array')), $this->expectCallableNever());
$this->loop->run();
}
/**
* @group internet
*/
public function testResolveGoogleResolves()
{
$promise = $this->resolver->resolve('google.com');
$promise->then($this->expectCallableOnce(), $this->expectCallableNever());
$this->loop->run();
}
/**
* @group internet
*/
public function testResolveAllGoogleMxResolvesWithCache()
{
$factory = new Factory();
$this->resolver = $factory->createCached('8.8.8.8', $this->loop);
$promise = $this->resolver->resolveAll('google.com', Message::TYPE_MX);
$promise->then($this->expectCallableOnceWith($this->isType('array')), $this->expectCallableNever());
$this->loop->run();
}
/**
* @group internet
*/
public function testResolveInvalidRejects()
{
$ex = $this->callback(function ($param) {
return ($param instanceof RecordNotFoundException && $param->getCode() === Message::RCODE_NAME_ERROR);
});
$promise = $this->resolver->resolve('example.invalid');
$promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($ex));
$this->loop->run();
}
public function testResolveCancelledRejectsImmediately()
{
$ex = $this->callback(function ($param) {
return ($param instanceof \RuntimeException && $param->getMessage() === 'DNS query for google.com has been cancelled');
});
$promise = $this->resolver->resolve('google.com');
$promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($ex));
$promise->cancel();
$time = microtime(true);
$this->loop->run();
$time = microtime(true) - $time;
$this->assertLessThan(0.1, $time);
}
public function testInvalidResolverDoesNotResolveGoogle()
{
$factory = new Factory();
$this->resolver = $factory->create('255.255.255.255', $this->loop);
$promise = $this->resolver->resolve('google.com');
$promise->then($this->expectCallableNever(), $this->expectCallableOnce());
}
public function testResolveShouldNotCauseGarbageReferencesWhenUsingInvalidNameserver()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$factory = new Factory();
$this->resolver = $factory->create('255.255.255.255', $this->loop);
gc_collect_cycles();
$promise = $this->resolver->resolve('google.com');
unset($promise);
$this->assertEquals(0, gc_collect_cycles());
}
public function testResolveCachedShouldNotCauseGarbageReferencesWhenUsingInvalidNameserver()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$factory = new Factory();
$this->resolver = $factory->createCached('255.255.255.255', $this->loop);
gc_collect_cycles();
$promise = $this->resolver->resolve('google.com');
unset($promise);
$this->assertEquals(0, gc_collect_cycles());
}
public function testCancelResolveShouldNotCauseGarbageReferences()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$factory = new Factory();
$this->resolver = $factory->create('127.0.0.1', $this->loop);
gc_collect_cycles();
$promise = $this->resolver->resolve('google.com');
$promise->cancel();
$promise = null;
$this->assertEquals(0, gc_collect_cycles());
}
public function testCancelResolveCachedShouldNotCauseGarbageReferences()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$factory = new Factory();
$this->resolver = $factory->createCached('127.0.0.1', $this->loop);
gc_collect_cycles();
$promise = $this->resolver->resolve('google.com');
$promise->cancel();
$promise = null;
$this->assertEquals(0, gc_collect_cycles());
}
}

31
vendor/react/dns/tests/Model/MessageTest.php vendored Executable file
View File

@@ -0,0 +1,31 @@
<?php
namespace React\Tests\Dns\Model;
use PHPUnit\Framework\TestCase;
use React\Dns\Query\Query;
use React\Dns\Model\Message;
class MessageTest extends TestCase
{
public function testCreateRequestDesiresRecusion()
{
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$request = Message::createRequestForQuery($query);
$this->assertTrue($request->header->isQuery());
$this->assertSame(1, $request->header->get('rd'));
}
public function testCreateResponseWithNoAnswers()
{
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$answers = array();
$request = Message::createResponseWithAnswersForQuery($query, $answers);
$this->assertFalse($request->header->isQuery());
$this->assertTrue($request->header->isResponse());
$this->assertEquals(0, $request->header->get('anCount'));
$this->assertEquals(Message::RCODE_OK, $request->getResponseCode());
}
}

View File

@@ -0,0 +1,278 @@
<?php
namespace React\Tests\Dns\Protocol;
use PHPUnit\Framework\TestCase;
use React\Dns\Protocol\BinaryDumper;
use React\Dns\Model\Message;
use React\Dns\Model\Record;
class BinaryDumperTest extends TestCase
{
public function testToBinaryRequestMessage()
{
$data = "";
$data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
$data .= "00 01 00 01"; // question: type A, class IN
$expected = $this->formatHexDump($data);
$request = new Message();
$request->header->set('id', 0x7262);
$request->header->set('rd', 1);
$request->questions[] = array(
'name' => 'igor.io',
'type' => Message::TYPE_A,
'class' => Message::CLASS_IN,
);
$request->prepare();
$dumper = new BinaryDumper();
$data = $dumper->toBinary($request);
$data = $this->convertBinaryToHexDump($data);
$this->assertSame($expected, $data);
}
public function testToBinaryRequestMessageWithCustomOptForEdns0()
{
$data = "";
$data .= "72 62 01 00 00 01 00 00 00 00 00 01"; // header
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
$data .= "00 01 00 01"; // question: type A, class IN
$data .= "00"; // additional: (empty hostname)
$data .= "00 29 03 e8 00 00 00 00 00 00 "; // additional: type OPT, class UDP size, TTL 0, no RDATA
$expected = $this->formatHexDump($data);
$request = new Message();
$request->header->set('id', 0x7262);
$request->header->set('rd', 1);
$request->questions[] = array(
'name' => 'igor.io',
'type' => Message::TYPE_A,
'class' => Message::CLASS_IN,
);
$request->additional[] = new Record('', 41, 1000, 0, '');
$request->prepare();
$dumper = new BinaryDumper();
$data = $dumper->toBinary($request);
$data = $this->convertBinaryToHexDump($data);
$this->assertSame($expected, $data);
}
public function testToBinaryResponseMessageWithoutRecords()
{
$data = "";
$data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
$data .= "00 01 00 01"; // question: type A, class IN
$expected = $this->formatHexDump($data);
$response = new Message();
$response->header->set('id', 0x7262);
$response->header->set('rd', 1);
$response->header->set('rcode', Message::RCODE_OK);
$response->questions[] = array(
'name' => 'igor.io',
'type' => Message::TYPE_A,
'class' => Message::CLASS_IN
);
$response->prepare();
$dumper = new BinaryDumper();
$data = $dumper->toBinary($response);
$data = $this->convertBinaryToHexDump($data);
$this->assertSame($expected, $data);
}
public function testToBinaryForResponseWithSRVRecord()
{
$data = "";
$data .= "72 62 01 00 00 01 00 01 00 00 00 00"; // header
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
$data .= "00 21 00 01"; // question: type SRV, class IN
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
$data .= "00 21 00 01"; // answer: type SRV, class IN
$data .= "00 01 51 80"; // answer: ttl 86400
$data .= "00 0c"; // answer: rdlength 12
$data .= "00 0a 00 14 1f 90 04 74 65 73 74 00"; // answer: rdata priority 10, weight 20, port 8080 test
$expected = $this->formatHexDump($data);
$response = new Message();
$response->header->set('id', 0x7262);
$response->header->set('rd', 1);
$response->header->set('rcode', Message::RCODE_OK);
$response->questions[] = array(
'name' => 'igor.io',
'type' => Message::TYPE_SRV,
'class' => Message::CLASS_IN
);
$response->answers[] = new Record('igor.io', Message::TYPE_SRV, Message::CLASS_IN, 86400, array(
'priority' => 10,
'weight' => 20,
'port' => 8080,
'target' => 'test'
));
$response->prepare();
$dumper = new BinaryDumper();
$data = $dumper->toBinary($response);
$data = $this->convertBinaryToHexDump($data);
$this->assertSame($expected, $data);
}
public function testToBinaryForResponseWithSOARecord()
{
$data = "";
$data .= "72 62 01 00 00 01 00 01 00 00 00 00"; // header
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
$data .= "00 06 00 01"; // question: type SOA, class IN
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
$data .= "00 06 00 01"; // answer: type SOA, class IN
$data .= "00 01 51 80"; // answer: ttl 86400
$data .= "00 27"; // answer: rdlength 39
$data .= "02 6e 73 05 68 65 6c 6c 6f 00"; // answer: rdata ns.hello (mname)
$data .= "01 65 05 68 65 6c 6c 6f 00"; // answer: rdata e.hello (rname)
$data .= "78 49 28 d5 00 00 2a 30 00 00 0e 10"; // answer: rdata 2018060501, 10800, 3600
$data .= "00 09 3e 68 00 00 0e 10"; // answer: 605800, 3600
$expected = $this->formatHexDump($data);
$response = new Message();
$response->header->set('id', 0x7262);
$response->header->set('rd', 1);
$response->header->set('rcode', Message::RCODE_OK);
$response->questions[] = array(
'name' => 'igor.io',
'type' => Message::TYPE_SOA,
'class' => Message::CLASS_IN
);
$response->answers[] = new Record('igor.io', Message::TYPE_SOA, Message::CLASS_IN, 86400, array(
'mname' => 'ns.hello',
'rname' => 'e.hello',
'serial' => 2018060501,
'refresh' => 10800,
'retry' => 3600,
'expire' => 605800,
'minimum' => 3600
));
$response->prepare();
$dumper = new BinaryDumper();
$data = $dumper->toBinary($response);
$data = $this->convertBinaryToHexDump($data);
$this->assertSame($expected, $data);
}
public function testToBinaryForResponseWithMultipleAnswerRecords()
{
$data = "";
$data .= "72 62 01 00 00 01 00 04 00 00 00 00"; // header
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
$data .= "00 ff 00 01"; // question: type ANY, class IN
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
$data .= "00 01 00 01 00 00 00 00 00 04"; // answer: type A, class IN, TTL 0, 4 bytes
$data .= "7f 00 00 01"; // answer: 127.0.0.1
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
$data .= "00 1c 00 01 00 00 00 00 00 10"; // question: type AAAA, class IN, TTL 0, 16 bytes
$data .= "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01"; // answer: ::1
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
$data .= "00 10 00 01 00 00 00 00 00 0c"; // answer: type TXT, class IN, TTL 0, 12 bytes
$data .= "05 68 65 6c 6c 6f 05 77 6f 72 6c 64"; // answer: hello, world
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
$data .= "00 0f 00 01 00 00 00 00 00 03"; // anwser: type MX, class IN, TTL 0, 3 bytes
$data .= "00 00 00"; // priority 0, no target
$expected = $this->formatHexDump($data);
$response = new Message();
$response->header->set('id', 0x7262);
$response->header->set('rd', 1);
$response->header->set('rcode', Message::RCODE_OK);
$response->questions[] = array(
'name' => 'igor.io',
'type' => Message::TYPE_ANY,
'class' => Message::CLASS_IN
);
$response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 0, '127.0.0.1');
$response->answers[] = new Record('igor.io', Message::TYPE_AAAA, Message::CLASS_IN, 0, '::1');
$response->answers[] = new Record('igor.io', Message::TYPE_TXT, Message::CLASS_IN, 0, array('hello', 'world'));
$response->answers[] = new Record('igor.io', Message::TYPE_MX, Message::CLASS_IN, 0, array('priority' => 0, 'target' => ''));
$response->prepare();
$dumper = new BinaryDumper();
$data = $dumper->toBinary($response);
$data = $this->convertBinaryToHexDump($data);
$this->assertSame($expected, $data);
}
public function testToBinaryForResponseWithAnswerAndAdditionalRecord()
{
$data = "";
$data .= "72 62 01 00 00 01 00 01 00 00 00 01"; // header
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
$data .= "00 02 00 01"; // question: type NS, class IN
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
$data .= "00 02 00 01 00 00 00 00 00 0d"; // answer: type NS, class IN, TTL 0, 10 bytes
$data .= "07 65 78 61 6d 70 6c 65 03 63 6f 6d 00"; // answer: example.com
$data .= "07 65 78 61 6d 70 6c 65 03 63 6f 6d 00"; // additional: example.com
$data .= "00 01 00 01 00 00 00 00 00 04"; // additional: type A, class IN, TTL 0, 4 bytes
$data .= "7f 00 00 01"; // additional: 127.0.0.1
$expected = $this->formatHexDump($data);
$response = new Message();
$response->header->set('id', 0x7262);
$response->header->set('rd', 1);
$response->header->set('rcode', Message::RCODE_OK);
$response->questions[] = array(
'name' => 'igor.io',
'type' => Message::TYPE_NS,
'class' => Message::CLASS_IN
);
$response->answers[] = new Record('igor.io', Message::TYPE_NS, Message::CLASS_IN, 0, 'example.com');
$response->additional[] = new Record('example.com', Message::TYPE_A, Message::CLASS_IN, 0, '127.0.0.1');
$response->prepare();
$dumper = new BinaryDumper();
$data = $dumper->toBinary($response);
$data = $this->convertBinaryToHexDump($data);
$this->assertSame($expected, $data);
}
private function convertBinaryToHexDump($input)
{
return $this->formatHexDump(implode('', unpack('H*', $input)));
}
private function formatHexDump($input)
{
return implode(' ', str_split(str_replace(' ', '', $input), 2));
}
}

1033
vendor/react/dns/tests/Protocol/ParserTest.php vendored Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,100 @@
<?php
namespace React\Tests\Dns\Query;
use React\Tests\Dns\TestCase;
use React\Dns\Query\CachedExecutor;
use React\Dns\Query\Query;
use React\Dns\Model\Message;
use React\Dns\Model\Record;
use React\Promise;
class CachedExecutorTest extends TestCase
{
/**
* @covers React\Dns\Query\CachedExecutor
* @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->createPromiseMock()));
$cache = $this->getMockBuilder('React\Dns\Query\RecordCache')
->disableOriginalConstructor()
->getMock();
$cache
->expects($this->once())
->method('lookup')
->will($this->returnValue(Promise\reject()));
$cachedExecutor = new CachedExecutor($executor, $cache);
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$cachedExecutor->query('8.8.8.8', $query);
}
/**
* @covers React\Dns\Query\CachedExecutor
* @test
*/
public function callingQueryTwiceShouldUseCachedResult()
{
$cachedRecords = array(new Record('igor.io', Message::TYPE_A, Message::CLASS_IN));
$executor = $this->createExecutorMock();
$executor
->expects($this->once())
->method('query')
->will($this->callQueryCallbackWithAddress('178.79.169.131'));
$cache = $this->getMockBuilder('React\Dns\Query\RecordCache')
->disableOriginalConstructor()
->getMock();
$cache
->expects($this->at(0))
->method('lookup')
->with($this->isInstanceOf('React\Dns\Query\Query'))
->will($this->returnValue(Promise\reject()));
$cache
->expects($this->at(1))
->method('storeResponseMessage')
->with($this->isType('integer'), $this->isInstanceOf('React\Dns\Model\Message'));
$cache
->expects($this->at(2))
->method('lookup')
->with($this->isInstanceOf('React\Dns\Query\Query'))
->will($this->returnValue(Promise\resolve($cachedRecords)));
$cachedExecutor = new CachedExecutor($executor, $cache);
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$cachedExecutor->query('8.8.8.8', $query, function () {}, function () {});
$cachedExecutor->query('8.8.8.8', $query, function () {}, function () {});
}
private function callQueryCallbackWithAddress($address)
{
return $this->returnCallback(function ($nameserver, $query) use ($address) {
$response = new Message();
$response->header->set('qr', 1);
$response->questions[] = new Record($query->name, $query->type, $query->class);
$response->answers[] = new Record($query->name, $query->type, $query->class, 3600, $address);
return Promise\resolve($response);
});
}
private function createExecutorMock()
{
return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
}
private function createPromiseMock()
{
return $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
}
}

View File

@@ -0,0 +1,183 @@
<?php
namespace React\Tests\Dns\Query;
use React\Dns\Model\Message;
use React\Dns\Query\CachingExecutor;
use React\Dns\Query\Query;
use React\Promise\Promise;
use React\Tests\Dns\TestCase;
use React\Promise\Deferred;
use React\Dns\Model\Record;
class CachingExecutorTest extends TestCase
{
public function testQueryWillReturnPendingPromiseWhenCacheIsPendingWithoutSendingQueryToFallbackExecutor()
{
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$fallback->expects($this->never())->method('query');
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(new Promise(function () { }));
$executor = new CachingExecutor($fallback, $cache);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('8.8.8.8', $query);
$promise->then($this->expectCallableNever(), $this->expectCallableNever());
}
public function testQueryWillReturnPendingPromiseWhenCacheReturnsMissAndWillSendSameQueryToFallbackExecutor()
{
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$fallback->expects($this->once())->method('query')->with('8.8.8.8', $query)->willReturn(new Promise(function () { }));
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$cache->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
$executor = new CachingExecutor($fallback, $cache);
$promise = $executor->query('8.8.8.8', $query);
$promise->then($this->expectCallableNever(), $this->expectCallableNever());
}
public function testQueryWillReturnResolvedPromiseWhenCacheReturnsHitWithoutSendingQueryToFallbackExecutor()
{
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$fallback->expects($this->never())->method('query');
$message = new Message();
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve($message));
$executor = new CachingExecutor($fallback, $cache);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('8.8.8.8', $query);
$promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
}
public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesAndSaveMessageToCacheWithMinimumTtlFromRecord()
{
$message = new Message();
$message->answers[] = new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3700, '127.0.0.1');
$message->answers[] = new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3600, '127.0.0.1');
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$fallback->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve(null));
$cache->expects($this->once())->method('set')->with('reactphp.org:1:1', $message, 3600);
$executor = new CachingExecutor($fallback, $cache);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('8.8.8.8', $query);
$promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
}
public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesAndSaveMessageToCacheWithDefaultTtl()
{
$message = new Message();
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$fallback->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve(null));
$cache->expects($this->once())->method('set')->with('reactphp.org:1:1', $message, 60);
$executor = new CachingExecutor($fallback, $cache);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('8.8.8.8', $query);
$promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
}
public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesWithTruncatedResponseButShouldNotSaveTruncatedMessageToCache()
{
$message = new Message();
$message->header->set('tc', 1);
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$fallback->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve(null));
$cache->expects($this->never())->method('set');
$executor = new CachingExecutor($fallback, $cache);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('8.8.8.8', $query);
$promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
}
public function testQueryWillReturnRejectedPromiseWhenCacheReturnsMissAndFallbackExecutorRejects()
{
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$fallback->expects($this->once())->method('query')->willReturn(\React\Promise\reject(new \RuntimeException()));
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$cache->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
$executor = new CachingExecutor($fallback, $cache);
$promise = $executor->query('8.8.8.8', $query);
$promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
}
public function testCancelQueryWillReturnRejectedPromiseAndCancelPendingPromiseFromCache()
{
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$fallback->expects($this->never())->method('query');
$pending = new Promise(function () { }, $this->expectCallableOnce());
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn($pending);
$executor = new CachingExecutor($fallback, $cache);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('8.8.8.8', $query);
$promise->cancel();
$promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
}
public function testCancelQueryWillReturnRejectedPromiseAndCancelPendingPromiseFromFallbackExecutorWhenCacheReturnsMiss()
{
$pending = new Promise(function () { }, $this->expectCallableOnce());
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$fallback->expects($this->once())->method('query')->willReturn($pending);
$deferred = new Deferred();
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn($deferred->promise());
$executor = new CachingExecutor($fallback, $cache);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('8.8.8.8', $query);
$deferred->resolve(null);
$promise->cancel();
$promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
}
}

View File

@@ -0,0 +1,233 @@
<?php
use React\Dns\Query\CoopExecutor;
use React\Dns\Model\Message;
use React\Dns\Query\Query;
use React\Promise\Promise;
use React\Tests\Dns\TestCase;
use React\Promise\Deferred;
class CoopExecutorTest extends TestCase
{
public function testQueryOnceWillPassExactQueryToBaseExecutor()
{
$pending = new Promise(function () { });
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->once())->method('query')->with('8.8.8.8', $query)->willReturn($pending);
$connector = new CoopExecutor($base);
$connector->query('8.8.8.8', $query);
}
public function testQueryOnceWillResolveWhenBaseExecutorResolves()
{
$message = new Message();
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
$connector = new CoopExecutor($base);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $connector->query('8.8.8.8', $query);
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
$promise->then($this->expectCallableOnceWith($message));
}
public function testQueryOnceWillRejectWhenBaseExecutorRejects()
{
$exception = new RuntimeException();
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->once())->method('query')->willReturn(\React\Promise\reject($exception));
$connector = new CoopExecutor($base);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $connector->query('8.8.8.8', $query);
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
$promise->then(null, $this->expectCallableOnceWith($exception));
}
public function testQueryTwoDifferentQueriesWillPassExactQueryToBaseExecutorTwice()
{
$pending = new Promise(function () { });
$query1 = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$query2 = new Query('reactphp.org', Message::TYPE_AAAA, Message::CLASS_IN);
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->exactly(2))->method('query')->withConsecutive(
array('8.8.8.8', $query1),
array('8.8.8.8', $query2)
)->willReturn($pending);
$connector = new CoopExecutor($base);
$connector->query('8.8.8.8', $query1);
$connector->query('8.8.8.8', $query2);
}
public function testQueryTwiceWillPassExactQueryToBaseExecutorOnceWhenQueryIsStillPending()
{
$pending = new Promise(function () { });
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->once())->method('query')->with('8.8.8.8', $query)->willReturn($pending);
$connector = new CoopExecutor($base);
$connector->query('8.8.8.8', $query);
$connector->query('8.8.8.8', $query);
}
public function testQueryTwiceWillPassExactQueryToBaseExecutorTwiceWhenFirstQueryIsAlreadyResolved()
{
$deferred = new Deferred();
$pending = new Promise(function () { });
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->exactly(2))->method('query')->with('8.8.8.8', $query)->willReturnOnConsecutiveCalls($deferred->promise(), $pending);
$connector = new CoopExecutor($base);
$connector->query('8.8.8.8', $query);
$deferred->resolve(new Message());
$connector->query('8.8.8.8', $query);
}
public function testQueryTwiceWillPassExactQueryToBaseExecutorTwiceWhenFirstQueryIsAlreadyRejected()
{
$deferred = new Deferred();
$pending = new Promise(function () { });
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->exactly(2))->method('query')->with('8.8.8.8', $query)->willReturnOnConsecutiveCalls($deferred->promise(), $pending);
$connector = new CoopExecutor($base);
$connector->query('8.8.8.8', $query);
$deferred->reject(new RuntimeException());
$connector->query('8.8.8.8', $query);
}
public function testCancelQueryWillCancelPromiseFromBaseExecutorAndReject()
{
$promise = new Promise(function () { }, $this->expectCallableOnce());
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->once())->method('query')->willReturn($promise);
$connector = new CoopExecutor($base);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $connector->query('8.8.8.8', $query);
$promise->cancel();
$promise->then(null, $this->expectCallableOnce());
}
public function testCancelOneQueryWhenOtherQueryIsStillPendingWillNotCancelPromiseFromBaseExecutorAndRejectCancelled()
{
$promise = new Promise(function () { }, $this->expectCallableNever());
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->once())->method('query')->willReturn($promise);
$connector = new CoopExecutor($base);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise1 = $connector->query('8.8.8.8', $query);
$promise2 = $connector->query('8.8.8.8', $query);
$promise1->cancel();
$promise1->then(null, $this->expectCallableOnce());
$promise2->then(null, $this->expectCallableNever());
}
public function testCancelSecondQueryWhenFirstQueryIsStillPendingWillNotCancelPromiseFromBaseExecutorAndRejectCancelled()
{
$promise = new Promise(function () { }, $this->expectCallableNever());
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->once())->method('query')->willReturn($promise);
$connector = new CoopExecutor($base);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise1 = $connector->query('8.8.8.8', $query);
$promise2 = $connector->query('8.8.8.8', $query);
$promise2->cancel();
$promise2->then(null, $this->expectCallableOnce());
$promise1->then(null, $this->expectCallableNever());
}
public function testCancelAllPendingQueriesWillCancelPromiseFromBaseExecutorAndRejectCancelled()
{
$promise = new Promise(function () { }, $this->expectCallableOnce());
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->once())->method('query')->willReturn($promise);
$connector = new CoopExecutor($base);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise1 = $connector->query('8.8.8.8', $query);
$promise2 = $connector->query('8.8.8.8', $query);
$promise1->cancel();
$promise2->cancel();
$promise1->then(null, $this->expectCallableOnce());
$promise2->then(null, $this->expectCallableOnce());
}
public function testQueryTwiceWillQueryBaseExecutorTwiceIfFirstQueryHasAlreadyBeenCancelledWhenSecondIsStarted()
{
$promise = new Promise(function () { }, $this->expectCallableOnce());
$pending = new Promise(function () { });
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->exactly(2))->method('query')->willReturnOnConsecutiveCalls($promise, $pending);
$connector = new CoopExecutor($base);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise1 = $connector->query('8.8.8.8', $query);
$promise1->cancel();
$promise2 = $connector->query('8.8.8.8', $query);
$promise1->then(null, $this->expectCallableOnce());
$promise2->then(null, $this->expectCallableNever());
}
public function testCancelQueryShouldNotCauseGarbageReferences()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$deferred = new Deferred(function () {
throw new \RuntimeException();
});
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$base->expects($this->once())->method('query')->willReturn($deferred->promise());
$connector = new CoopExecutor($base);
gc_collect_cycles();
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$promise = $connector->query('8.8.8.8', $query);
$promise->cancel();
$promise = null;
$this->assertEquals(0, gc_collect_cycles());
}
}

308
vendor/react/dns/tests/Query/ExecutorTest.php vendored Executable file
View File

@@ -0,0 +1,308 @@
<?php
namespace React\Tests\Dns\Query;
use Clue\React\Block;
use React\Dns\Query\Executor;
use React\Dns\Query\Query;
use React\Dns\Model\Message;
use React\Dns\Model\Record;
use React\Dns\Protocol\BinaryDumper;
use React\Tests\Dns\TestCase;
class ExecutorTest extends TestCase
{
private $loop;
private $parser;
private $dumper;
private $executor;
public function setUp()
{
$this->loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$this->parser = $this->getMockBuilder('React\Dns\Protocol\Parser')->getMock();
$this->dumper = new BinaryDumper();
$this->executor = new Executor($this->loop, $this->parser, $this->dumper);
}
/** @test */
public function queryShouldCreateUdpRequest()
{
$timer = $this->createTimerMock();
$this->loop
->expects($this->any())
->method('addTimer')
->will($this->returnValue($timer));
$this->executor = $this->createExecutorMock();
$this->executor
->expects($this->once())
->method('createConnection')
->with('8.8.8.8:53', 'udp')
->will($this->returnNewConnectionMock(false));
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$this->executor->query('8.8.8.8:53', $query);
}
/** @test */
public function resolveShouldRejectIfRequestIsLargerThan512Bytes()
{
$query = new Query(str_repeat('a', 512).'.igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$promise = $this->executor->query('8.8.8.8:53', $query);
$this->setExpectedException('RuntimeException', 'DNS query for ' . $query->name . ' failed: Requested transport "tcp" not available, only UDP is supported in this version');
Block\await($promise, $this->loop);
}
/** @test */
public function resolveShouldCloseConnectionWhenCancelled()
{
$conn = $this->createConnectionMock(false);
$conn->expects($this->once())->method('close');
$timer = $this->createTimerMock();
$this->loop
->expects($this->any())
->method('addTimer')
->will($this->returnValue($timer));
$this->executor = $this->createExecutorMock();
$this->executor
->expects($this->once())
->method('createConnection')
->with('8.8.8.8:53', 'udp')
->will($this->returnValue($conn));
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$promise = $this->executor->query('8.8.8.8:53', $query);
$promise->cancel();
$this->setExpectedException('React\Dns\Query\CancellationException', 'DNS query for igor.io has been cancelled');
Block\await($promise, $this->loop);
}
/** @test */
public function resolveShouldNotStartOrCancelTimerWhenCancelledWithTimeoutIsNull()
{
$this->loop
->expects($this->never())
->method('addTimer');
$this->executor = new Executor($this->loop, $this->parser, $this->dumper, null);
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$promise = $this->executor->query('127.0.0.1:53', $query);
$promise->cancel();
$this->setExpectedException('React\Dns\Query\CancellationException', 'DNS query for igor.io has been cancelled');
Block\await($promise, $this->loop);
}
/** @test */
public function resolveShouldRejectIfResponseIsTruncated()
{
$timer = $this->createTimerMock();
$this->loop
->expects($this->any())
->method('addTimer')
->will($this->returnValue($timer));
$this->parser
->expects($this->once())
->method('parseMessage')
->will($this->returnTruncatedResponse());
$this->executor = $this->createExecutorMock();
$this->executor
->expects($this->once())
->method('createConnection')
->with('8.8.8.8:53', 'udp')
->will($this->returnNewConnectionMock());
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$this->executor->query('8.8.8.8:53', $query);
}
/** @test */
public function resolveShouldFailIfUdpThrow()
{
$this->loop
->expects($this->never())
->method('addTimer');
$this->parser
->expects($this->never())
->method('parseMessage');
$this->executor = $this->createExecutorMock();
$this->executor
->expects($this->once())
->method('createConnection')
->with('8.8.8.8:53', 'udp')
->will($this->throwException(new \Exception('Nope')));
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$promise = $this->executor->query('8.8.8.8:53', $query);
$this->setExpectedException('RuntimeException', 'DNS query for igor.io failed: Nope');
Block\await($promise, $this->loop);
}
/** @test */
public function resolveShouldCancelTimerWhenFullResponseIsReceived()
{
$conn = $this->createConnectionMock();
$this->parser
->expects($this->once())
->method('parseMessage')
->will($this->returnStandardResponse());
$this->executor = $this->createExecutorMock();
$this->executor
->expects($this->at(0))
->method('createConnection')
->with('8.8.8.8:53', 'udp')
->will($this->returnNewConnectionMock());
$timer = $this->createTimerMock();
$this->loop
->expects($this->once())
->method('addTimer')
->with(5, $this->isInstanceOf('Closure'))
->will($this->returnValue($timer));
$this->loop
->expects($this->once())
->method('cancelTimer')
->with($timer);
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$this->executor->query('8.8.8.8:53', $query);
}
/** @test */
public function resolveShouldCloseConnectionOnTimeout()
{
$this->executor = $this->createExecutorMock();
$this->executor
->expects($this->at(0))
->method('createConnection')
->with('8.8.8.8:53', 'udp')
->will($this->returnNewConnectionMock(false));
$timer = $this->createTimerMock();
$this->loop
->expects($this->never())
->method('cancelTimer');
$this->loop
->expects($this->once())
->method('addTimer')
->with(5, $this->isInstanceOf('Closure'))
->will($this->returnCallback(function ($time, $callback) use (&$timerCallback, $timer) {
$timerCallback = $callback;
return $timer;
}));
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$promise = $this->executor->query('8.8.8.8:53', $query);
$this->assertNotNull($timerCallback);
$timerCallback();
$this->setExpectedException('React\Dns\Query\TimeoutException', 'DNS query for igor.io timed out');
Block\await($promise, $this->loop);
}
private function returnStandardResponse()
{
$that = $this;
$callback = function ($data) use ($that) {
$response = new Message();
$that->convertMessageToStandardResponse($response);
return $response;
};
return $this->returnCallback($callback);
}
private function returnTruncatedResponse()
{
$that = $this;
$callback = function ($data) use ($that) {
$response = new Message();
$that->convertMessageToTruncatedResponse($response);
return $response;
};
return $this->returnCallback($callback);
}
public function convertMessageToStandardResponse(Message $response)
{
$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;
}
public function convertMessageToTruncatedResponse(Message $response)
{
$this->convertMessageToStandardResponse($response);
$response->header->set('tc', 1);
$response->prepare();
return $response;
}
private function returnNewConnectionMock($emitData = true)
{
$conn = $this->createConnectionMock($emitData);
$callback = function () use ($conn) {
return $conn;
};
return $this->returnCallback($callback);
}
private function createConnectionMock($emitData = true)
{
$conn = $this->getMockBuilder('React\Stream\DuplexStreamInterface')->getMock();
$conn
->expects($this->any())
->method('on')
->with('data', $this->isInstanceOf('Closure'))
->will($this->returnCallback(function ($name, $callback) use ($emitData) {
$emitData && $callback(null);
}));
return $conn;
}
private function createTimerMock()
{
return $this->getMockBuilder(
interface_exists('React\EventLoop\TimerInterface') ? 'React\EventLoop\TimerInterface' : 'React\EventLoop\Timer\TimerInterface'
)->getMock();
}
private function createExecutorMock()
{
return $this->getMockBuilder('React\Dns\Query\Executor')
->setConstructorArgs(array($this->loop, $this->parser, $this->dumper))
->setMethods(array('createConnection'))
->getMock();
}
}

View File

@@ -0,0 +1,126 @@
<?php
namespace React\Tests\Dns\Query;
use React\Tests\Dns\TestCase;
use React\Dns\Query\HostsFileExecutor;
use React\Dns\Query\Query;
use React\Dns\Model\Message;
class HostsFileExecutorTest extends TestCase
{
private $hosts;
private $fallback;
private $executor;
public function setUp()
{
$this->hosts = $this->getMockBuilder('React\Dns\Config\HostsFile')->disableOriginalConstructor()->getMock();
$this->fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$this->executor = new HostsFileExecutor($this->hosts, $this->fallback);
}
public function testDoesNotTryToGetIpsForMxQuery()
{
$this->hosts->expects($this->never())->method('getIpsForHost');
$this->fallback->expects($this->once())->method('query');
$this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_MX, Message::CLASS_IN, 0));
}
public function testFallsBackIfNoIpsWereFound()
{
$this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array());
$this->fallback->expects($this->once())->method('query');
$this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0));
}
public function testReturnsResponseMessageIfIpsWereFound()
{
$this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('127.0.0.1'));
$this->fallback->expects($this->never())->method('query');
$ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0));
}
public function testFallsBackIfNoIpv4Matches()
{
$this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('::1'));
$this->fallback->expects($this->once())->method('query');
$ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0));
}
public function testReturnsResponseMessageIfIpv6AddressesWereFound()
{
$this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('::1'));
$this->fallback->expects($this->never())->method('query');
$ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_AAAA, Message::CLASS_IN, 0));
}
public function testFallsBackIfNoIpv6Matches()
{
$this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('127.0.0.1'));
$this->fallback->expects($this->once())->method('query');
$ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_AAAA, Message::CLASS_IN, 0));
}
public function testDoesReturnReverseIpv4Lookup()
{
$this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn(array('localhost'));
$this->fallback->expects($this->never())->method('query');
$this->executor->query('8.8.8.8', new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
}
public function testFallsBackIfNoReverseIpv4Matches()
{
$this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn(array());
$this->fallback->expects($this->once())->method('query');
$this->executor->query('8.8.8.8', new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
}
public function testDoesReturnReverseIpv6Lookup()
{
$this->hosts->expects($this->once())->method('getHostsForIp')->with('2a02:2e0:3fe:100::6')->willReturn(array('ip6-localhost'));
$this->fallback->expects($this->never())->method('query');
$this->executor->query('8.8.8.8', new Query('6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.e.f.3.0.0.e.2.0.2.0.a.2.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
}
public function testFallsBackForInvalidAddress()
{
$this->hosts->expects($this->never())->method('getHostsForIp');
$this->fallback->expects($this->once())->method('query');
$this->executor->query('8.8.8.8', new Query('example.com', Message::TYPE_PTR, Message::CLASS_IN, 0));
}
public function testReverseFallsBackForInvalidIpv4Address()
{
$this->hosts->expects($this->never())->method('getHostsForIp');
$this->fallback->expects($this->once())->method('query');
$this->executor->query('8.8.8.8', new Query('::1.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
}
public function testReverseFallsBackForInvalidLengthIpv6Address()
{
$this->hosts->expects($this->never())->method('getHostsForIp');
$this->fallback->expects($this->once())->method('query');
$this->executor->query('8.8.8.8', new Query('abcd.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
}
public function testReverseFallsBackForInvalidHexIpv6Address()
{
$this->hosts->expects($this->never())->method('getHostsForIp');
$this->fallback->expects($this->once())->method('query');
$this->executor->query('8.8.8.8', new Query('zZz.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace React\Tests\Dns\Query;
use PHPUnit\Framework\TestCase;
use React\Dns\Query\RecordBag;
use React\Dns\Model\Message;
use React\Dns\Model\Record;
class RecordBagTest extends TestCase
{
/**
* @covers React\Dns\Query\RecordBag
* @test
*/
public function emptyBagShouldBeEmpty()
{
$recordBag = new RecordBag();
$this->assertSame(array(), $recordBag->all());
}
/**
* @covers React\Dns\Query\RecordBag
* @test
*/
public function setShouldSetTheValue()
{
$currentTime = 1345656451;
$recordBag = new RecordBag();
$recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600));
$records = $recordBag->all();
$this->assertCount(1, $records);
$this->assertSame('igor.io', $records[0]->name);
$this->assertSame(Message::TYPE_A, $records[0]->type);
$this->assertSame(Message::CLASS_IN, $records[0]->class);
}
/**
* @covers React\Dns\Query\RecordBag
* @test
*/
public function setShouldAcceptMxRecord()
{
$currentTime = 1345656451;
$recordBag = new RecordBag();
$recordBag->set($currentTime, new Record('igor.io', Message::TYPE_MX, Message::CLASS_IN, 3600, array('priority' => 10, 'target' => 'igor.io')));
$records = $recordBag->all();
$this->assertCount(1, $records);
$this->assertSame('igor.io', $records[0]->name);
$this->assertSame(Message::TYPE_MX, $records[0]->type);
$this->assertSame(Message::CLASS_IN, $records[0]->class);
$this->assertSame(array('priority' => 10, 'target' => 'igor.io'), $records[0]->data);
}
/**
* @covers React\Dns\Query\RecordBag
* @test
*/
public function setShouldSetManyValues()
{
$currentTime = 1345656451;
$recordBag = new RecordBag();
$recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
$recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'));
$records = $recordBag->all();
$this->assertCount(2, $records);
$this->assertSame('igor.io', $records[0]->name);
$this->assertSame(Message::TYPE_A, $records[0]->type);
$this->assertSame(Message::CLASS_IN, $records[0]->class);
$this->assertSame('178.79.169.131', $records[0]->data);
$this->assertSame('igor.io', $records[1]->name);
$this->assertSame(Message::TYPE_A, $records[1]->type);
$this->assertSame(Message::CLASS_IN, $records[1]->class);
$this->assertSame('178.79.169.132', $records[1]->data);
}
}

View File

@@ -0,0 +1,160 @@
<?php
namespace React\Tests\Dns\Query;
use PHPUnit\Framework\TestCase;
use React\Cache\ArrayCache;
use React\Dns\Model\Message;
use React\Dns\Model\Record;
use React\Dns\Query\RecordCache;
use React\Dns\Query\Query;
use React\Promise\PromiseInterface;
use React\Promise\Promise;
class RecordCacheTest extends TestCase
{
/**
* @covers React\Dns\Query\RecordCache
* @test
*/
public function lookupOnCacheMissShouldReturnNull()
{
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$base = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$base->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
$cache = new RecordCache($base);
$promise = $cache->lookup($query);
$this->assertInstanceOf('React\Promise\RejectedPromise', $promise);
}
/**
* @covers React\Dns\Query\RecordCache
* @test
*/
public function storeRecordPendingCacheDoesNotSetCache()
{
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$pending = new Promise(function () { });
$base = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$base->expects($this->once())->method('get')->willReturn($pending);
$base->expects($this->never())->method('set');
$cache = new RecordCache($base);
$cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
}
/**
* @covers React\Dns\Query\RecordCache
* @test
*/
public function storeRecordOnCacheMissSetsCache()
{
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$base = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$base->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
$base->expects($this->once())->method('set')->with($this->isType('string'), $this->isType('string'));
$cache = new RecordCache($base);
$cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
}
/**
* @covers React\Dns\Query\RecordCache
* @test
*/
public function storeRecordShouldMakeLookupSucceed()
{
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$cache = new RecordCache(new ArrayCache());
$cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
$promise = $cache->lookup($query);
$this->assertInstanceOf('React\Promise\FulfilledPromise', $promise);
$cachedRecords = $this->getPromiseValue($promise);
$this->assertCount(1, $cachedRecords);
$this->assertSame('178.79.169.131', $cachedRecords[0]->data);
}
/**
* @covers React\Dns\Query\RecordCache
* @test
*/
public function storeTwoRecordsShouldReturnBoth()
{
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$cache = new RecordCache(new ArrayCache());
$cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
$cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'));
$promise = $cache->lookup($query);
$this->assertInstanceOf('React\Promise\FulfilledPromise', $promise);
$cachedRecords = $this->getPromiseValue($promise);
$this->assertCount(2, $cachedRecords);
$this->assertSame('178.79.169.131', $cachedRecords[0]->data);
$this->assertSame('178.79.169.132', $cachedRecords[1]->data);
}
/**
* @covers React\Dns\Query\RecordCache
* @test
*/
public function storeResponseMessageShouldStoreAllAnswerValues()
{
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$response = new Message();
$response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131');
$response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132');
$response->prepare();
$cache = new RecordCache(new ArrayCache());
$cache->storeResponseMessage($query->currentTime, $response);
$promise = $cache->lookup($query);
$this->assertInstanceOf('React\Promise\FulfilledPromise', $promise);
$cachedRecords = $this->getPromiseValue($promise);
$this->assertCount(2, $cachedRecords);
$this->assertSame('178.79.169.131', $cachedRecords[0]->data);
$this->assertSame('178.79.169.132', $cachedRecords[1]->data);
}
/**
* @covers React\Dns\Query\RecordCache
* @test
*/
public function expireShouldExpireDeadRecords()
{
$cachedTime = 1345656451;
$currentTime = $cachedTime + 3605;
$cache = new RecordCache(new ArrayCache());
$cache->storeRecord($cachedTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
$cache->expire($currentTime);
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, $currentTime);
$promise = $cache->lookup($query);
$this->assertInstanceOf('React\Promise\RejectedPromise', $promise);
}
private function getPromiseValue(PromiseInterface $promise)
{
$capturedValue = null;
$promise->then(function ($value) use (&$capturedValue) {
$capturedValue = $value;
});
return $capturedValue;
}
}

View File

@@ -0,0 +1,350 @@
<?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;
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace React\Tests\Dns\Query;
use React\Dns\Query\TimeoutExecutor;
use React\Dns\Query\Query;
use React\Dns\Model\Message;
use React\Promise\Deferred;
use React\Dns\Query\CancellationException;
use React\Tests\Dns\TestCase;
use React\EventLoop\Factory;
use React\Promise;
class TimeoutExecutorTest extends TestCase
{
public function setUp()
{
$this->loop = Factory::create();
$this->wrapped = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
$this->executor = new TimeoutExecutor($this->wrapped, 5.0, $this->loop);
}
public function testCancellingPromiseWillCancelWrapped()
{
$cancelled = 0;
$this->wrapped
->expects($this->once())
->method('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();
}));
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$promise = $this->executor->query('8.8.8.8:53', $query);
$this->assertEquals(0, $cancelled);
$promise->cancel();
$this->assertEquals(1, $cancelled);
$promise->then($this->expectCallableNever(), $this->expectCallableOnce());
}
public function testResolvesPromiseWhenWrappedResolves()
{
$this->wrapped
->expects($this->once())
->method('query')
->willReturn(Promise\resolve('0.0.0.0'));
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$promise = $this->executor->query('8.8.8.8:53', $query);
$promise->then($this->expectCallableOnce(), $this->expectCallableNever());
}
public function testRejectsPromiseWhenWrappedRejects()
{
$this->wrapped
->expects($this->once())
->method('query')
->willReturn(Promise\reject(new \RuntimeException()));
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$promise = $this->executor->query('8.8.8.8:53', $query);
$promise->then($this->expectCallableNever(), $this->expectCallableOnceWith(new \RuntimeException()));
}
public function testWrappedWillBeCancelledOnTimeout()
{
$this->executor = new TimeoutExecutor($this->wrapped, 0, $this->loop);
$cancelled = 0;
$this->wrapped
->expects($this->once())
->method('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();
}));
$callback = $this->expectCallableNever();
$errorback = $this->createCallableMock();
$errorback
->expects($this->once())
->method('__invoke')
->with($this->logicalAnd(
$this->isInstanceOf('React\Dns\Query\TimeoutException'),
$this->attribute($this->equalTo('DNS query for igor.io timed out'), 'message')
));
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
$this->executor->query('8.8.8.8:53', $query)->then($callback, $errorback);
$this->assertEquals(0, $cancelled);
$this->loop->run();
$this->assertEquals(1, $cancelled);
}
}

View File

@@ -0,0 +1,215 @@
<?php
namespace React\Tests\Dns\Query;
use React\Dns\Model\Message;
use React\Dns\Protocol\BinaryDumper;
use React\Dns\Protocol\Parser;
use React\Dns\Query\Query;
use React\Dns\Query\UdpTransportExecutor;
use React\EventLoop\Factory;
use React\Tests\Dns\TestCase;
class UdpTransportExecutorTest extends TestCase
{
public function testQueryRejectsIfMessageExceedsUdpSize()
{
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->never())->method('addReadStream');
$dumper = $this->getMockBuilder('React\Dns\Protocol\BinaryDumper')->getMock();
$dumper->expects($this->once())->method('toBinary')->willReturn(str_repeat('.', 513));
$executor = new UdpTransportExecutor($loop, null, $dumper);
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('8.8.8.8:53', $query);
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
$promise->then(null, $this->expectCallableOnce());
}
public function testQueryRejectsIfServerConnectionFails()
{
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->never())->method('addReadStream');
$executor = new UdpTransportExecutor($loop);
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('///', $query);
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
$promise->then(null, $this->expectCallableOnce());
}
/**
* @group internet
*/
public function testQueryRejectsOnCancellation()
{
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->once())->method('addReadStream');
$loop->expects($this->once())->method('removeReadStream');
$executor = new UdpTransportExecutor($loop);
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query('8.8.8.8:53', $query);
$promise->cancel();
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
$promise->then(null, $this->expectCallableOnce());
}
public function testQueryKeepsPendingIfServerRejectsNetworkPacket()
{
$loop = Factory::create();
$executor = new UdpTransportExecutor($loop);
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
$wait = true;
$promise = $executor->query('127.0.0.1:1', $query)->then(
null,
function ($e) use (&$wait) {
$wait = false;
throw $e;
}
);
\Clue\React\Block\sleep(0.2, $loop);
$this->assertTrue($wait);
}
public function testQueryKeepsPendingIfServerSendInvalidMessage()
{
$loop = Factory::create();
$server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
$loop->addReadStream($server, function ($server) {
$data = stream_socket_recvfrom($server, 512, 0, $peer);
stream_socket_sendto($server, 'invalid', 0, $peer);
});
$address = stream_socket_get_name($server, false);
$executor = new UdpTransportExecutor($loop);
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
$wait = true;
$promise = $executor->query($address, $query)->then(
null,
function ($e) use (&$wait) {
$wait = false;
throw $e;
}
);
\Clue\React\Block\sleep(0.2, $loop);
$this->assertTrue($wait);
}
public function testQueryKeepsPendingIfServerSendInvalidId()
{
$parser = new Parser();
$dumper = new BinaryDumper();
$loop = Factory::create();
$server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
$loop->addReadStream($server, function ($server) use ($parser, $dumper) {
$data = stream_socket_recvfrom($server, 512, 0, $peer);
$message = $parser->parseMessage($data);
$message->header->set('id', 0);
stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer);
});
$address = stream_socket_get_name($server, false);
$executor = new UdpTransportExecutor($loop, $parser, $dumper);
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
$wait = true;
$promise = $executor->query($address, $query)->then(
null,
function ($e) use (&$wait) {
$wait = false;
throw $e;
}
);
\Clue\React\Block\sleep(0.2, $loop);
$this->assertTrue($wait);
}
public function testQueryRejectsIfServerSendsTruncatedResponse()
{
$parser = new Parser();
$dumper = new BinaryDumper();
$loop = Factory::create();
$server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
$loop->addReadStream($server, function ($server) use ($parser, $dumper) {
$data = stream_socket_recvfrom($server, 512, 0, $peer);
$message = $parser->parseMessage($data);
$message->header->set('tc', 1);
stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer);
});
$address = stream_socket_get_name($server, false);
$executor = new UdpTransportExecutor($loop, $parser, $dumper);
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
$wait = true;
$promise = $executor->query($address, $query)->then(
null,
function ($e) use (&$wait) {
$wait = false;
throw $e;
}
);
// run loop for short period to ensure we detect connection ICMP rejection error
\Clue\React\Block\sleep(0.01, $loop);
if ($wait) {
\Clue\React\Block\sleep(0.2, $loop);
}
$this->assertFalse($wait);
}
public function testQueryResolvesIfServerSendsValidResponse()
{
$parser = new Parser();
$dumper = new BinaryDumper();
$loop = Factory::create();
$server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
$loop->addReadStream($server, function ($server) use ($parser, $dumper) {
$data = stream_socket_recvfrom($server, 512, 0, $peer);
$message = $parser->parseMessage($data);
stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer);
});
$address = stream_socket_get_name($server, false);
$executor = new UdpTransportExecutor($loop, $parser, $dumper);
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query($address, $query);
$response = \Clue\React\Block\await($promise, $loop, 0.2);
$this->assertInstanceOf('React\Dns\Model\Message', $response);
}
}

View File

@@ -0,0 +1,120 @@
<?php
namespace React\Tests\Dns\Resolver;
use React\Dns\Resolver\Factory;
use React\Tests\Dns\TestCase;
use React\Dns\Query\HostsFileExecutor;
class FactoryTest extends TestCase
{
/** @test */
public function createShouldCreateResolver()
{
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$factory = new Factory();
$resolver = $factory->create('8.8.8.8:53', $loop);
$this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
}
/** @test */
public function createWithoutPortShouldCreateResolverWithDefaultPort()
{
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$factory = new Factory();
$resolver = $factory->create('8.8.8.8', $loop);
$this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
$this->assertSame('8.8.8.8:53', $this->getResolverPrivateMemberValue($resolver, 'nameserver'));
}
/** @test */
public function createCachedShouldCreateResolverWithCachingExecutor()
{
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$factory = new Factory();
$resolver = $factory->createCached('8.8.8.8:53', $loop);
$this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
$executor = $this->getResolverPrivateExecutor($resolver);
$this->assertInstanceOf('React\Dns\Query\CachingExecutor', $executor);
$cache = $this->getCachingExecutorPrivateMemberValue($executor, 'cache');
$this->assertInstanceOf('React\Cache\ArrayCache', $cache);
}
/** @test */
public function createCachedShouldCreateResolverWithCachingExecutorWithCustomCache()
{
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$factory = new Factory();
$resolver = $factory->createCached('8.8.8.8:53', $loop, $cache);
$this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
$executor = $this->getResolverPrivateExecutor($resolver);
$this->assertInstanceOf('React\Dns\Query\CachingExecutor', $executor);
$cacheProperty = $this->getCachingExecutorPrivateMemberValue($executor, 'cache');
$this->assertSame($cache, $cacheProperty);
}
/**
* @test
* @dataProvider factoryShouldAddDefaultPortProvider
*/
public function factoryShouldAddDefaultPort($input, $expected)
{
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$factory = new Factory();
$resolver = $factory->create($input, $loop);
$this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
$this->assertSame($expected, $this->getResolverPrivateMemberValue($resolver, 'nameserver'));
}
public static function factoryShouldAddDefaultPortProvider()
{
return array(
array('8.8.8.8', '8.8.8.8:53'),
array('1.2.3.4:5', '1.2.3.4:5'),
array('localhost', 'localhost:53'),
array('localhost:1234', 'localhost:1234'),
array('::1', '[::1]:53'),
array('[::1]:53', '[::1]:53')
);
}
private function getResolverPrivateExecutor($resolver)
{
$executor = $this->getResolverPrivateMemberValue($resolver, 'executor');
// extract underlying executor that may be wrapped in multiple layers of hosts file executors
while ($executor instanceof HostsFileExecutor) {
$reflector = new \ReflectionProperty('React\Dns\Query\HostsFileExecutor', 'fallback');
$reflector->setAccessible(true);
$executor = $reflector->getValue($executor);
}
return $executor;
}
private function getResolverPrivateMemberValue($resolver, $field)
{
$reflector = new \ReflectionProperty('React\Dns\Resolver\Resolver', $field);
$reflector->setAccessible(true);
return $reflector->getValue($resolver);
}
private function getCachingExecutorPrivateMemberValue($resolver, $field)
{
$reflector = new \ReflectionProperty('React\Dns\Query\CachingExecutor', $field);
$reflector->setAccessible(true);
return $reflector->getValue($resolver);
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace React\Tests\Dns\Resolver;
use PHPUnit\Framework\TestCase;
use React\Dns\Resolver\Resolver;
use React\Dns\Query\Query;
use React\Dns\Model\Message;
use React\Dns\Model\Record;
class ResolveAliasesTest extends TestCase
{
/**
* @covers React\Dns\Resolver\Resolver::resolveAliases
* @covers React\Dns\Resolver\Resolver::valuesByNameAndType
* @dataProvider provideAliasedAnswers
*/
public function testResolveAliases(array $expectedAnswers, array $answers, $name)
{
$executor = $this->createExecutorMock();
$resolver = new Resolver('8.8.8.8:53', $executor);
$answers = $resolver->resolveAliases($answers, $name);
$this->assertEquals($expectedAnswers, $answers);
}
public function provideAliasedAnswers()
{
return array(
array(
array('178.79.169.131'),
array(
new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
),
'igor.io',
),
array(
array('178.79.169.131', '178.79.169.132', '178.79.169.133'),
array(
new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'),
new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.133'),
),
'igor.io',
),
array(
array('178.79.169.131'),
array(
new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
),
'igor.io',
),
array(
array(),
array(
new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN),
new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN),
),
'igor.io',
),
array(
array('178.79.169.131'),
array(
new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'),
new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
),
'igor.io',
),
array(
array('178.79.169.131'),
array(
new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'),
new Record('foo.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'bar.igor.io'),
new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
),
'igor.io',
),
array(
array('178.79.169.131', '178.79.169.132', '178.79.169.133'),
array(
new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'),
new Record('foo.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'bar.igor.io'),
new Record('bar.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'baz.igor.io'),
new Record('bar.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'qux.igor.io'),
new Record('baz.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
new Record('baz.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'),
new Record('qux.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.133'),
),
'igor.io',
),
);
}
private function createExecutorMock()
{
return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
}
}

View File

@@ -0,0 +1,251 @@
<?php
namespace React\Tests\Dns\Resolver;
use React\Dns\Resolver\Resolver;
use React\Dns\Query\Query;
use React\Dns\Model\Message;
use React\Dns\Model\Record;
use React\Promise;
use React\Tests\Dns\TestCase;
use React\Dns\RecordNotFoundException;
class ResolverTest extends TestCase
{
/** @test */
public function resolveShouldQueryARecords()
{
$executor = $this->createExecutorMock();
$executor
->expects($this->once())
->method('query')
->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
->will($this->returnCallback(function ($nameserver, $query) {
$response = new Message();
$response->header->set('qr', 1);
$response->questions[] = new Record($query->name, $query->type, $query->class);
$response->answers[] = new Record($query->name, $query->type, $query->class, 3600, '178.79.169.131');
return Promise\resolve($response);
}));
$resolver = new Resolver('8.8.8.8:53', $executor);
$resolver->resolve('igor.io')->then($this->expectCallableOnceWith('178.79.169.131'));
}
/** @test */
public function resolveAllShouldQueryGivenRecords()
{
$executor = $this->createExecutorMock();
$executor
->expects($this->once())
->method('query')
->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
->will($this->returnCallback(function ($nameserver, $query) {
$response = new Message();
$response->header->set('qr', 1);
$response->questions[] = new Record($query->name, $query->type, $query->class);
$response->answers[] = new Record($query->name, $query->type, $query->class, 3600, '::1');
return Promise\resolve($response);
}));
$resolver = new Resolver('8.8.8.8:53', $executor);
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then($this->expectCallableOnceWith(array('::1')));
}
/** @test */
public function resolveAllShouldIgnoreRecordsWithOtherTypes()
{
$executor = $this->createExecutorMock();
$executor
->expects($this->once())
->method('query')
->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
->will($this->returnCallback(function ($nameserver, $query) {
$response = new Message();
$response->header->set('qr', 1);
$response->questions[] = new Record($query->name, $query->type, $query->class);
$response->answers[] = new Record($query->name, Message::TYPE_TXT, $query->class, 3600, array('ignored'));
$response->answers[] = new Record($query->name, $query->type, $query->class, 3600, '::1');
return Promise\resolve($response);
}));
$resolver = new Resolver('8.8.8.8:53', $executor);
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then($this->expectCallableOnceWith(array('::1')));
}
/** @test */
public function resolveAllShouldReturnMultipleValuesForAlias()
{
$executor = $this->createExecutorMock();
$executor
->expects($this->once())
->method('query')
->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
->will($this->returnCallback(function ($nameserver, $query) {
$response = new Message();
$response->header->set('qr', 1);
$response->questions[] = new Record($query->name, $query->type, $query->class);
$response->answers[] = new Record($query->name, Message::TYPE_CNAME, $query->class, 3600, 'example.com');
$response->answers[] = new Record('example.com', $query->type, $query->class, 3600, '::1');
$response->answers[] = new Record('example.com', $query->type, $query->class, 3600, '::2');
$response->prepare();
return Promise\resolve($response);
}));
$resolver = new Resolver('8.8.8.8:53', $executor);
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(
$this->expectCallableOnceWith($this->equalTo(array('::1', '::2')))
);
}
/** @test */
public function resolveShouldQueryARecordsAndIgnoreCase()
{
$executor = $this->createExecutorMock();
$executor
->expects($this->once())
->method('query')
->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
->will($this->returnCallback(function ($nameserver, $query) {
$response = new Message();
$response->header->set('qr', 1);
$response->questions[] = new Record('Blog.wyrihaximus.net', $query->type, $query->class);
$response->answers[] = new Record('Blog.wyrihaximus.net', $query->type, $query->class, 3600, '178.79.169.131');
return Promise\resolve($response);
}));
$resolver = new Resolver('8.8.8.8:53', $executor);
$resolver->resolve('blog.wyrihaximus.net')->then($this->expectCallableOnceWith('178.79.169.131'));
}
/** @test */
public function resolveShouldFilterByName()
{
$executor = $this->createExecutorMock();
$executor
->expects($this->once())
->method('query')
->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
->will($this->returnCallback(function ($nameserver, $query) {
$response = new Message();
$response->header->set('qr', 1);
$response->questions[] = new Record($query->name, $query->type, $query->class);
$response->answers[] = new Record('foo.bar', $query->type, $query->class, 3600, '178.79.169.131');
return Promise\resolve($response);
}));
$errback = $this->expectCallableOnceWith($this->isInstanceOf('React\Dns\RecordNotFoundException'));
$resolver = new Resolver('8.8.8.8:53', $executor);
$resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback);
}
/**
* @test
*/
public function resolveWithNoAnswersShouldCallErrbackIfGiven()
{
$executor = $this->createExecutorMock();
$executor
->expects($this->once())
->method('query')
->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
->will($this->returnCallback(function ($nameserver, $query) {
$response = new Message();
$response->header->set('qr', 1);
$response->questions[] = new Record($query->name, $query->type, $query->class);
return Promise\resolve($response);
}));
$errback = $this->expectCallableOnceWith($this->callback(function ($param) {
return ($param instanceof RecordNotFoundException && $param->getCode() === 0 && $param->getMessage() === 'DNS query for igor.io did not return a valid answer (NOERROR / NODATA)');
}));
$resolver = new Resolver('8.8.8.8:53', $executor);
$resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback);
}
public function provideRcodeErrors()
{
return array(
array(
Message::RCODE_FORMAT_ERROR,
'DNS query for example.com returned an error response (Format Error)',
),
array(
Message::RCODE_SERVER_FAILURE,
'DNS query for example.com returned an error response (Server Failure)',
),
array(
Message::RCODE_NAME_ERROR,
'DNS query for example.com returned an error response (Non-Existent Domain / NXDOMAIN)'
),
array(
Message::RCODE_NOT_IMPLEMENTED,
'DNS query for example.com returned an error response (Not Implemented)'
),
array(
Message::RCODE_REFUSED,
'DNS query for example.com returned an error response (Refused)'
),
array(
99,
'DNS query for example.com returned an error response (Unknown error response code 99)'
)
);
}
/**
* @test
* @dataProvider provideRcodeErrors
*/
public function resolveWithRcodeErrorShouldCallErrbackIfGiven($code, $expectedMessage)
{
$executor = $this->createExecutorMock();
$executor
->expects($this->once())
->method('query')
->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
->will($this->returnCallback(function ($nameserver, $query) use ($code) {
$response = new Message();
$response->header->set('qr', 1);
$response->header->set('rcode', $code);
$response->questions[] = new Record($query->name, $query->type, $query->class);
return Promise\resolve($response);
}));
$errback = $this->expectCallableOnceWith($this->callback(function ($param) use ($code, $expectedMessage) {
return ($param instanceof RecordNotFoundException && $param->getCode() === $code && $param->getMessage() === $expectedMessage);
}));
$resolver = new Resolver('8.8.8.8:53', $executor);
$resolver->resolve('example.com')->then($this->expectCallableNever(), $errback);
}
public function testLegacyExtractAddress()
{
$executor = $this->createExecutorMock();
$resolver = new Resolver('8.8.8.8:53', $executor);
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
$response = Message::createResponseWithAnswersForQuery($query, array(
new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3600, '1.2.3.4')
));
$ret = $resolver->extractAddress($query, $response);
$this->assertEquals('1.2.3.4', $ret);
}
private function createExecutorMock()
{
return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
}
}

61
vendor/react/dns/tests/TestCase.php vendored Executable file
View File

@@ -0,0 +1,61 @@
<?php
namespace React\Tests\Dns;
use PHPUnit\Framework\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
protected function expectCallableOnce()
{
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke');
return $mock;
}
protected function expectCallableOnceWith($value)
{
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($value);
return $mock;
}
protected function expectCallableNever()
{
$mock = $this->createCallableMock();
$mock
->expects($this->never())
->method('__invoke');
return $mock;
}
protected function createCallableMock()
{
return $this->getMockBuilder('React\Tests\Dns\CallableStub')->getMock();
}
public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null)
{
if (method_exists($this, 'expectException')) {
// PHPUnit 5
$this->expectException($exception);
if ($exceptionMessage !== '') {
$this->expectExceptionMessage($exceptionMessage);
}
if ($exceptionCode !== null) {
$this->expectExceptionCode($exceptionCode);
}
} else {
// legacy PHPUnit 4
parent::setExpectedException($exception, $exceptionMessage, $exceptionCode);
}
}
}