getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $executor = new UdpTransportExecutor($input, $loop); $ref = new \ReflectionProperty($executor, 'nameserver'); $ref->setAccessible(true); $value = $ref->getValue($executor); $this->assertEquals($expected, $value); } public static function provideDefaultPortProvider() { return array( array( '8.8.8.8', 'udp://8.8.8.8:53' ), array( '1.2.3.4:5', 'udp://1.2.3.4:5' ), array( 'udp://1.2.3.4', 'udp://1.2.3.4:53' ), array( 'udp://1.2.3.4:53', 'udp://1.2.3.4:53' ), array( '::1', 'udp://[::1]:53' ), array( '[::1]:53', 'udp://[::1]:53' ) ); } public function testCtorWithoutLoopShouldAssignDefaultLoop() { $executor = new UdpTransportExecutor('127.0.0.1'); $ref = new \ReflectionProperty($executor, 'loop'); $ref->setAccessible(true); $loop = $ref->getValue($executor); $this->assertInstanceOf('React\EventLoop\LoopInterface', $loop); } public function testCtorShouldThrowWhenNameserverAddressIsInvalid() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $this->setExpectedException('InvalidArgumentException'); new UdpTransportExecutor('///', $loop); } public function testCtorShouldThrowWhenNameserverAddressContainsHostname() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $this->setExpectedException('InvalidArgumentException'); new UdpTransportExecutor('localhost', $loop); } public function testCtorShouldThrowWhenNameserverSchemeIsInvalid() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $this->setExpectedException('InvalidArgumentException'); new UdpTransportExecutor('tcp://1.2.3.4', $loop); } public function testQueryRejectsIfMessageExceedsUdpSize() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $loop->expects($this->never())->method('addReadStream'); $executor = new UdpTransportExecutor('8.8.8.8:53', $loop); $query = new Query('google.' . str_repeat('.com', 200), Message::TYPE_A, Message::CLASS_IN); $promise = $executor->query($query); $this->assertInstanceOf('React\Promise\PromiseInterface', $promise); $exception = null; $promise->then(null, function ($reason) use (&$exception) { $exception = $reason; }); $this->setExpectedException( 'RuntimeException', 'DNS query for ' . $query->name . ' (A) failed: Query too large for UDP transport', defined('SOCKET_EMSGSIZE') ? SOCKET_EMSGSIZE : 90 ); throw $exception; } public function testQueryRejectsIfServerConnectionFails() { if (defined('HHVM_VERSION')) { $this->markTestSkipped('HHVM reports different error message for invalid addresses'); } $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $loop->expects($this->never())->method('addReadStream'); $executor = new UdpTransportExecutor('::1', $loop); $ref = new \ReflectionProperty($executor, 'nameserver'); $ref->setAccessible(true); $ref->setValue($executor, '///'); $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN); $promise = $executor->query($query); $this->assertInstanceOf('React\Promise\PromiseInterface', $promise); $exception = null; $promise->then(null, function ($reason) use (&$exception) { $exception = $reason; }); $this->setExpectedException( 'RuntimeException', 'DNS query for google.com (A) failed: Unable to connect to DNS server /// (Failed to parse address "///")' ); throw $exception; } public function testQueryRejectsIfSendToServerFailsAfterConnectionWithoutCallingCustomErrorHandler() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $loop->expects($this->never())->method('addReadStream'); $executor = new UdpTransportExecutor('0.0.0.0', $loop); // increase hard-coded maximum packet size to allow sending excessive data $ref = new \ReflectionProperty($executor, 'maxPacketSize'); $ref->setAccessible(true); $ref->setValue($executor, PHP_INT_MAX); $error = null; set_error_handler(function ($_, $errstr) use (&$error) { $error = $errstr; }); $query = new Query(str_repeat('a.', 100000) . '.example', Message::TYPE_A, Message::CLASS_IN); $promise = $executor->query($query); restore_error_handler(); $this->assertNull($error); $this->assertInstanceOf('React\Promise\PromiseInterface', $promise); $exception = null; $promise->then(null, function ($reason) use (&$exception) { $exception = $reason; }); // ECONNREFUSED (Connection refused) on Linux, EMSGSIZE (Message too long) on macOS $this->setExpectedException( 'RuntimeException', 'DNS query for ' . $query->name . ' (A) failed: Unable to send query to DNS server udp://0.0.0.0:53 (' ); throw $exception; } public function testQueryKeepsPendingIfReadFailsBecauseServerRefusesConnection() { $socket = null; $callback = null; $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $loop->expects($this->once())->method('addReadStream')->with($this->callback(function ($ref) use (&$socket) { $socket = $ref; return true; }), $this->callback(function ($ref) use (&$callback) { $callback = $ref; return true; })); $executor = new UdpTransportExecutor('0.0.0.0', $loop); $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN); $promise = $executor->query($query); $this->assertNotNull($socket); $callback($socket); $this->assertInstanceOf('React\Promise\PromiseInterface', $promise); $pending = true; $promise->then(function () use (&$pending) { $pending = false; }, function () use (&$pending) { $pending = false; }); $this->assertTrue($pending); } /** * @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('8.8.8.8:53', $loop); $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN); $promise = $executor->query($query); $promise->cancel(); $exception = null; $promise->then(null, function ($reason) use (&$exception) { $exception = $reason; }); /** @var \React\Dns\Query\CancellationException $exception */ $this->assertInstanceOf('React\Dns\Query\CancellationException', $exception); $this->assertEquals('DNS query for google.com (A) has been cancelled', $exception->getMessage()); } public function testQueryKeepsPendingIfServerSendsInvalidMessage() { $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); Loop::removeReadStream($server); fclose($server); }); $address = stream_socket_get_name($server, false); $executor = new UdpTransportExecutor($address); $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN); $wait = true; $promise = $executor->query($query)->then( null, function ($e) use (&$wait) { $wait = false; } ); \React\Async\await(\React\Promise\Timer\sleep(0.2)); $this->assertTrue($wait); $promise->cancel(); } public function testQueryKeepsPendingIfServerSendsInvalidId() { $parser = new Parser(); $dumper = new BinaryDumper(); $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->id = 0; stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer); Loop::removeReadStream($server); fclose($server); }); $address = stream_socket_get_name($server, false); $executor = new UdpTransportExecutor($address); $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN); $wait = true; $promise = $executor->query($query)->then( null, function ($e) use (&$wait) { $wait = false; } ); \React\Async\await(\React\Promise\Timer\sleep(0.2)); $this->assertTrue($wait); $promise->cancel(); } public function testQueryRejectsIfServerSendsTruncatedResponse() { $parser = new Parser(); $dumper = new BinaryDumper(); $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->tc = true; stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer); Loop::removeReadStream($server); fclose($server); }); $address = stream_socket_get_name($server, false); $executor = new UdpTransportExecutor($address); $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN); $promise = $executor->query($query); $this->setExpectedException( 'RuntimeException', 'DNS query for google.com (A) failed: The DNS server udp://' . $address . ' returned a truncated result for a UDP query', defined('SOCKET_EMSGSIZE') ? SOCKET_EMSGSIZE : 90 ); \React\Async\await(\React\Promise\Timer\timeout($promise, 0.1)); } public function testQueryResolvesIfServerSendsValidResponse() { $parser = new Parser(); $dumper = new BinaryDumper(); $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); Loop::removeReadStream($server); fclose($server); }); $address = stream_socket_get_name($server, false); $executor = new UdpTransportExecutor($address); $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN); $promise = $executor->query($query); $response = \React\Async\await(\React\Promise\Timer\timeout($promise, 0.2)); $this->assertInstanceOf('React\Dns\Model\Message', $response); } }