diff --git a/.gitattributes b/.gitattributes index f658344c..bd4d5b13 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,6 +2,7 @@ /.github/ export-ignore /.gitignore export-ignore /examples export-ignore +/phpstan.neon.dist export-ignore /phpunit.xml.dist export-ignore /phpunit.xml.legacy export-ignore /tests export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f3a7a1f..9b02fdb2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,3 +45,26 @@ jobs: ini-file: development - run: composer install - run: vendor/bin/phpunit --coverage-text + + PHPStan: + name: PHPStan (PHP ${{ matrix.php }}) + runs-on: ubuntu-22.04 + strategy: + matrix: + php: + - 8.3 + - 8.2 + - 8.1 + - 8.0 + - 7.4 + - 7.3 + - 7.2 + - 7.1 + steps: + - uses: actions/checkout@v4 + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + - run: composer install + - run: vendor/bin/phpstan diff --git a/composer.json b/composer.json index f2723317..59598905 100644 --- a/composer.json +++ b/composer.json @@ -32,6 +32,7 @@ "react/promise": "^3.2 || ^2.7 || ^1.2.1" }, "require-dev": { + "phpstan/phpstan": "1.11.1 || 1.4.10", "phpunit/phpunit": "^9.6 || ^7.5", "react/async": "^4.3 || ^3 || ^2", "react/promise-timer": "^1.11" diff --git a/examples/01-one.php b/examples/01-one.php index 3396087c..92730cad 100644 --- a/examples/01-one.php +++ b/examples/01-one.php @@ -17,4 +17,6 @@ $resolver->resolve($name)->then(function ($ip) use ($name) { echo 'IP for ' . $name . ': ' . $ip . PHP_EOL; -}, 'printf'); +}, static function (Throwable $error) { + echo $error; +}); diff --git a/examples/02-concurrent.php b/examples/02-concurrent.php index 780b52ab..207c6598 100644 --- a/examples/02-concurrent.php +++ b/examples/02-concurrent.php @@ -21,5 +21,7 @@ foreach ($names as $name) { $resolver->resolve($name)->then(function ($ip) use ($name) { echo 'IP for ' . $name . ': ' . $ip . PHP_EOL; - }, 'printf'); + }, static function (Throwable $error) { + echo $error; +}); } diff --git a/examples/03-cached.php b/examples/03-cached.php index e90eee27..6f3ef19a 100644 --- a/examples/03-cached.php +++ b/examples/03-cached.php @@ -18,22 +18,30 @@ $resolver->resolve($name)->then(function ($ip) use ($name) { echo 'IP for ' . $name . ': ' . $ip . PHP_EOL; -}, 'printf'); +}, static function (Throwable $error) { + echo $error; +}); Loop::addTimer(1.0, function() use ($name, $resolver) { $resolver->resolve($name)->then(function ($ip) use ($name) { echo 'IP for ' . $name . ': ' . $ip . PHP_EOL; - }, 'printf'); + }, static function (Throwable $error) { + echo $error; +}); }); Loop::addTimer(2.0, function() use ($name, $resolver) { $resolver->resolve($name)->then(function ($ip) use ($name) { echo 'IP for ' . $name . ': ' . $ip . PHP_EOL; - }, 'printf'); + }, static function (Throwable $error) { + echo $error; +}); }); Loop::addTimer(3.0, function() use ($name, $resolver) { $resolver->resolve($name)->then(function ($ip) use ($name) { echo 'IP for ' . $name . ': ' . $ip . PHP_EOL; - }, 'printf'); + }, static function (Throwable $error) { + echo $error; +}); }); diff --git a/examples/11-all-ips.php b/examples/11-all-ips.php index cf8019a6..03d5fa70 100644 --- a/examples/11-all-ips.php +++ b/examples/11-all-ips.php @@ -15,15 +15,16 @@ $resolver = $factory->create($config); $name = $argv[1] ?? 'www.google.com'; +assert(is_string($name)); -$resolver->resolveAll($name, Message::TYPE_A)->then(function (array $ips) use ($name) { +$resolver->resolveAll($name, Message::TYPE_A)->then(static function (array $ips) use ($name): void { echo 'IPv4 addresses for ' . $name . ': ' . implode(', ', $ips) . PHP_EOL; -}, function (Exception $e) use ($name) { +}, function (Throwable $e) use ($name) { echo 'No IPv4 addresses for ' . $name . ': ' . $e->getMessage() . PHP_EOL; }); -$resolver->resolveAll($name, Message::TYPE_AAAA)->then(function (array $ips) use ($name) { +$resolver->resolveAll($name, Message::TYPE_AAAA)->then(static function (array $ips) use ($name): void { echo 'IPv6 addresses for ' . $name . ': ' . implode(', ', $ips) . PHP_EOL; -}, function (Exception $e) use ($name) { +}, function (Throwable $e) use ($name) { echo 'No IPv6 addresses for ' . $name . ': ' . $e->getMessage() . PHP_EOL; }); diff --git a/examples/12-all-types.php b/examples/12-all-types.php index acccbce9..650fe443 100644 --- a/examples/12-all-types.php +++ b/examples/12-all-types.php @@ -17,10 +17,13 @@ $resolver = $factory->create($config); $name = $argv[1] ?? 'google.com'; -$type = constant('React\Dns\Model\Message::TYPE_' . ($argv[2] ?? 'TXT')); +assert(is_string($name)); -$resolver->resolveAll($name, $type)->then(function (array $values) { +$type = constant('React\Dns\Model\Message::TYPE_' . ($type ?? 'TXT')); +assert(is_int($type)); + +$resolver->resolveAll($name, $type)->then(static function (array $values): void { var_dump($values); -}, function (Exception $e) { +}, function (Throwable $e) { echo $e->getMessage() . PHP_EOL; }); diff --git a/examples/13-reverse-dns.php b/examples/13-reverse-dns.php index dfd89337..bdd75726 100644 --- a/examples/13-reverse-dns.php +++ b/examples/13-reverse-dns.php @@ -15,19 +15,22 @@ $resolver = $factory->create($config); $ip = $argv[1] ?? '8.8.8.8'; +assert(is_string($ip)); +$ip = @inet_pton($ip); -if (@inet_pton($ip) === false) { +if ($ip === false) { exit('Error: Given argument is not a valid IP' . PHP_EOL); } if (strpos($ip, ':') === false) { - $name = inet_ntop(strrev(inet_pton($ip))) . '.in-addr.arpa'; + $name = inet_ntop(strrev($ip)) . '.in-addr.arpa'; } else { - $name = wordwrap(strrev(bin2hex(inet_pton($ip))), 1, '.', true) . '.ip6.arpa'; + $name = wordwrap(strrev(bin2hex($ip)), 1, '.', true) . '.ip6.arpa'; } +assert(is_string($name)); $resolver->resolveAll($name, Message::TYPE_PTR)->then(function (array $names) { var_dump($names); -}, function (Exception $e) { - echo $e->getMessage() . PHP_EOL; +}, static function (Throwable $error) { + echo $error; }); diff --git a/examples/91-query-a-and-aaaa.php b/examples/91-query-a-and-aaaa.php index 44fdee8e..8d2643b5 100644 --- a/examples/91-query-a-and-aaaa.php +++ b/examples/91-query-a-and-aaaa.php @@ -10,17 +10,24 @@ $executor = new UdpTransportExecutor('8.8.8.8:53'); $name = $argv[1] ?? 'www.google.com'; +assert(is_string($name)); $ipv4Query = new Query($name, Message::TYPE_A, Message::CLASS_IN); $ipv6Query = new Query($name, Message::TYPE_AAAA, Message::CLASS_IN); -$executor->query($ipv4Query)->then(function (Message $message) { +$executor->query($ipv4Query)->then(static function (Message $message): void { foreach ($message->answers as $answer) { + assert(\is_string($answer->data)); echo 'IPv4: ' . $answer->data . PHP_EOL; } -}, 'printf'); -$executor->query($ipv6Query)->then(function (Message $message) { +}, static function (Throwable $error) { + echo $error; +}); +$executor->query($ipv6Query)->then(static function (Message $message): void { foreach ($message->answers as $answer) { + assert(\is_string($answer->data)); echo 'IPv6: ' . $answer->data . PHP_EOL; } -}, 'printf'); +}, static function (Throwable $error) { + echo $error; +}); diff --git a/examples/92-query-any.php b/examples/92-query-any.php index 036a7ded..6bb00a67 100644 --- a/examples/92-query-any.php +++ b/examples/92-query-any.php @@ -14,63 +14,74 @@ $executor = new TcpTransportExecutor('8.8.8.8:53'); $name = $argv[1] ?? 'google.com'; +assert(is_string($name)); $any = new Query($name, Message::TYPE_ANY, Message::CLASS_IN); -$executor->query($any)->then(function (Message $message) { +$executor->query($any)->then(function (Message $message): void { foreach ($message->answers as $answer) { - /* @var $answer Record */ - $data = $answer->data; switch ($answer->type) { case Message::TYPE_A: + assert(\is_string($data)); $type = 'A'; break; case Message::TYPE_AAAA: + assert(\is_string($data)); $type = 'AAAA'; break; case Message::TYPE_NS: + assert(\is_string($data)); $type = 'NS'; break; case Message::TYPE_PTR: + assert(\is_string($data)); $type = 'PTR'; break; case Message::TYPE_CNAME: + assert(\is_string($data)); $type = 'CNAME'; break; case Message::TYPE_TXT: + assert(\is_array($data)); // TXT records can contain a list of (binary) strings for each record. // here, we assume this is printable ASCII and simply concatenate output $type = 'TXT'; $data = implode('', $data); break; case Message::TYPE_MX: + assert(\is_array($data)); // MX records contain "priority" and "target", only dump its values here $type = 'MX'; $data = implode(' ', $data); break; case Message::TYPE_SRV: + assert(\is_array($data)); // SRV records contain priority, weight, port and target, dump structure here $type = 'SRV'; $data = json_encode($data); break; case Message::TYPE_SSHFP: + assert(\is_array($data)); // SSHFP records contain algorithm, fingerprint type and hex fingerprint value $type = 'SSHFP'; $data = implode(' ', $data); break; case Message::TYPE_SOA: + assert(\is_array($data)); // SOA records contain structured data, dump structure here $type = 'SOA'; $data = json_encode($data); break; case Message::TYPE_CAA: + assert(\is_array($data)); // CAA records contains flag, tag and value $type = 'CAA'; $data = $data['flag'] . ' ' . $data['tag'] . ' "' . $data['value'] . '"'; break; default: + assert(\is_string($data)); // unknown type uses HEX format $type = 'TYPE' . $answer->type; $data = wordwrap(strtoupper(bin2hex($data)), 2, ' ', true); @@ -78,4 +89,6 @@ echo $type . ': ' . $data . PHP_EOL; } -}, 'printf'); +}, static function (Throwable $error) { + echo $error; +}); diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 00000000..0fe275fd --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,7 @@ +parameters: + level: max + + paths: + - examples/ + - src/ + - tests/ diff --git a/src/Config/Config.php b/src/Config/Config.php index 0b19e9af..52b1cd3b 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -26,7 +26,7 @@ final class Config * @return self * @codeCoverageIgnore */ - public static function loadSystemConfigBlocking() + public static function loadSystemConfigBlocking(): self { // Use WMIC output on Windows if (DIRECTORY_SEPARATOR === '\\') { @@ -71,7 +71,7 @@ public static function loadSystemConfigBlocking() * @return self * @throws RuntimeException if the path can not be loaded (does not exist) */ - public static function loadResolvConfBlocking($path = null) + public static function loadResolvConfBlocking(?string $path = null): self { if ($path === null) { $path = '/etc/resolv.conf'; @@ -122,10 +122,10 @@ public static function loadResolvConfBlocking($path = null) * @return self * @link https://ss64.com/nt/wmic.html */ - public static function loadWmicBlocking($command = null) + public static function loadWmicBlocking(?string $command = null): self { $contents = shell_exec($command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV' : $command); - preg_match_all('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i', $contents, $matches); + preg_match_all('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i', $contents, $matches); /** @phpstan-ignore-line */ $config = new self(); $config->nameservers = $matches[1]; @@ -133,5 +133,8 @@ public static function loadWmicBlocking($command = null) return $config; } + /** + * @var array + */ public $nameservers = []; } diff --git a/src/Config/HostsFile.php b/src/Config/HostsFile.php index 1333102d..d1d16bcd 100644 --- a/src/Config/HostsFile.php +++ b/src/Config/HostsFile.php @@ -24,10 +24,9 @@ class HostsFile /** * Returns the default path for the hosts file on this system * - * @return string * @codeCoverageIgnore */ - public static function getDefaultPath() + public static function getDefaultPath(): string { // use static path for all Unix-based systems if (DIRECTORY_SEPARATOR !== '\\') { @@ -59,7 +58,7 @@ public static function getDefaultPath() * @return self * @throws RuntimeException if the path can not be loaded (does not exist) */ - public static function loadFromPathBlocking($path = null) + public static function loadFromPathBlocking(?string $path = null): self { if ($path === null) { $path = self::getDefaultPath(); @@ -73,6 +72,9 @@ public static function loadFromPathBlocking($path = null) return new self($contents); } + /** + * @var string + */ private $contents; /** @@ -80,9 +82,13 @@ public static function loadFromPathBlocking($path = null) * * @param string $contents */ - public function __construct($contents) + public function __construct(string $contents) { - // remove all comments from the contents + /** + * remove all comments from the contents + * + * @var string $contents + */ $contents = preg_replace('/[ \t]*#.*/', '', strtolower($contents)); $this->contents = $contents; @@ -92,17 +98,27 @@ public function __construct($contents) * Returns all IPs for the given hostname * * @param string $name - * @return string[] + * @return iterable */ - public function getIpsForHost($name) + public function getIpsForHost(string $name): iterable { $name = strtolower($name); + $lines = preg_split('/\r?\n/', $this->contents); + if (!is_array($lines)) { + return []; + } + $ips = []; - foreach (preg_split('/\r?\n/', $this->contents) as $line) { + foreach ($lines as $line) { $parts = preg_split('/\s+/', $line); + + if (!is_array($parts)) { + continue; + } + $ip = array_shift($parts); - if ($parts && array_search($name, $parts) !== false) { + if ($ip !== null && $parts && array_search($name, $parts) !== false) { // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`) if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) { $ip = substr($ip, 0, $pos); @@ -121,9 +137,9 @@ public function getIpsForHost($name) * Returns all hostnames for the given IPv4 or IPv6 address * * @param string $ip - * @return string[] + * @return iterable */ - public function getHostsForIp($ip) + public function getHostsForIp(string $ip): iterable { // check binary representation of IP to avoid string case and short notation $ip = @inet_pton($ip); @@ -131,9 +147,17 @@ public function getHostsForIp($ip) return []; } - $names = []; - foreach (preg_split('/\r?\n/', $this->contents) as $line) { + $lines = preg_split('/\r?\n/', $this->contents); + if (!is_array($lines)) { + return []; + } + + $ips = []; + foreach ($lines as $line) { $parts = preg_split('/\s+/', $line, -1, PREG_SPLIT_NO_EMPTY); + if (!is_array($parts)) { + continue; + } $addr = (string) array_shift($parts); // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`) @@ -143,11 +167,11 @@ public function getHostsForIp($ip) if (@inet_pton($addr) === $ip) { foreach ($parts as $part) { - $names[] = $part; + $ips[] = $part; } } } - return $names; + return $ips; } } diff --git a/src/Model/Record.php b/src/Model/Record.php index c20403f5..05f5f3b1 100644 --- a/src/Model/Record.php +++ b/src/Model/Record.php @@ -131,18 +131,18 @@ final class Record * considered a BC break. See the format definition of known types above * for more details. * - * @var string|string[]|array + * @var string|array|array */ public $data; /** - * @param string $name - * @param int $type - * @param int $class - * @param int $ttl - * @param string|string[]|array $data + * @param string $name + * @param int $type + * @param int $class + * @param int $ttl + * @param string|array|array $data */ - public function __construct($name, $type, $class, $ttl, $data) + public function __construct(string $name, int $type, int $class, int $ttl, $data) { $this->name = $name; $this->type = $type; diff --git a/src/Protocol/BinaryDumper.php b/src/Protocol/BinaryDumper.php index 6f4030f6..323a67b2 100644 --- a/src/Protocol/BinaryDumper.php +++ b/src/Protocol/BinaryDumper.php @@ -8,11 +8,7 @@ final class BinaryDumper { - /** - * @param Message $message - * @return string - */ - public function toBinary(Message $message) + public function toBinary(Message $message): string { $data = ''; @@ -25,11 +21,7 @@ public function toBinary(Message $message) return $data; } - /** - * @param Message $message - * @return string - */ - private function headerToBinary(Message $message) + private function headerToBinary(Message $message): string { $data = ''; @@ -56,10 +48,9 @@ private function headerToBinary(Message $message) } /** - * @param Query[] $questions - * @return string + * @param array $questions */ - private function questionToBinary(array $questions) + private function questionToBinary(array $questions): string { $data = ''; @@ -72,30 +63,32 @@ private function questionToBinary(array $questions) } /** - * @param Record[] $records - * @return string + * @param array $records */ - private function recordsToBinary(array $records) + private function recordsToBinary(array $records): string { $data = ''; foreach ($records as $record) { - /* @var $record Record */ switch ($record->type) { case Message::TYPE_A: case Message::TYPE_AAAA: + assert(\is_string($record->data)); $binary = \inet_pton($record->data); break; case Message::TYPE_CNAME: case Message::TYPE_NS: case Message::TYPE_PTR: + assert(\is_string($record->data)); $binary = $this->domainNameToBinary($record->data); break; case Message::TYPE_TXT: case Message::TYPE_SPF: + assert(\is_array($record->data)); $binary = $this->textsToBinary($record->data); break; case Message::TYPE_MX: + assert(\is_array($record->data)); $binary = \pack( 'n', $record->data['priority'] @@ -103,6 +96,7 @@ private function recordsToBinary(array $records) $binary .= $this->domainNameToBinary($record->data['target']); break; case Message::TYPE_SRV: + assert(\is_array($record->data)); $binary = \pack( 'n*', $record->data['priority'], @@ -112,6 +106,7 @@ private function recordsToBinary(array $records) $binary .= $this->domainNameToBinary($record->data['target']); break; case Message::TYPE_SOA: + assert(\is_array($record->data)); $binary = $this->domainNameToBinary($record->data['mname']); $binary .= $this->domainNameToBinary($record->data['rname']); $binary .= \pack( @@ -124,6 +119,7 @@ private function recordsToBinary(array $records) ); break; case Message::TYPE_CAA: + assert(\is_array($record->data)); $binary = \pack( 'C*', $record->data['flag'], @@ -133,6 +129,7 @@ private function recordsToBinary(array $records) $binary .= $record->data['value']; break; case Message::TYPE_SSHFP: + assert(\is_array($record->data)); $binary = \pack( 'CCH*', $record->data['algorithm'], @@ -141,10 +138,11 @@ private function recordsToBinary(array $records) ); break; case Message::TYPE_OPT: + assert(\is_array($record->data)); $binary = ''; foreach ($record->data as $opt => $value) { if ($opt === Message::OPT_TCP_KEEPALIVE && $value !== null) { - $value = \pack('n', round($value * 10)); + $value = \pack('n', round((float) $value * 10)); } $binary .= \pack('n*', $opt, \strlen((string) $value)) . $value; } @@ -155,18 +153,17 @@ private function recordsToBinary(array $records) } $data .= $this->domainNameToBinary($record->name); - $data .= \pack('nnNn', $record->type, $record->class, $record->ttl, \strlen($binary)); - $data .= $binary; + $data .= \pack('nnNn', $record->type, $record->class, $record->ttl, \strlen($binary)); /** @phpstan-ignore-line */ + $data .= $binary; /** @phpstan-ignore-line */ } return $data; } /** - * @param string[] $texts - * @return string + * @param array $texts */ - private function textsToBinary(array $texts) + private function textsToBinary(array $texts): string { $data = ''; foreach ($texts as $text) { @@ -175,11 +172,7 @@ private function textsToBinary(array $texts) return $data; } - /** - * @param string $host - * @return string - */ - private function domainNameToBinary($host) + private function domainNameToBinary(string $host): string { if ($host === '') { return "\0"; @@ -189,7 +182,7 @@ private function domainNameToBinary($host) return $this->textsToBinary( \array_map( 'stripcslashes', - \preg_split( + \preg_split( /** @phpstan-ignore-line */ '/(?parse($data, 0); if ($message === null) { @@ -31,18 +29,13 @@ public function parseMessage($data) return $message; } - /** - * @param string $data - * @param int $consumed - * @return ?Message - */ - private function parse($data, $consumed) + private function parse(string $data, int $consumed): ?Message { if (!isset($data[12 - 1])) { return null; } - list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', substr($data, 0, 12))); + list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', substr($data, 0, 12))); /** @phpstan-ignore-line */ $message = new Message(); $message->id = $id; @@ -58,7 +51,7 @@ private function parse($data, $consumed) // parse all questions for ($i = $qdCount; $i > 0; --$i) { list($question, $consumed) = $this->parseQuestion($data, $consumed); - if ($question === null) { + if ($question === null || $consumed === null) { return null; } else { $message->questions[] = $question; @@ -68,7 +61,7 @@ private function parse($data, $consumed) // parse all answer records for ($i = $anCount; $i > 0; --$i) { list($record, $consumed) = $this->parseRecord($data, $consumed); - if ($record === null) { + if ($record === null || $consumed === null) { return null; } else { $message->answers[] = $record; @@ -78,7 +71,7 @@ private function parse($data, $consumed) // parse all authority records for ($i = $nsCount; $i > 0; --$i) { list($record, $consumed) = $this->parseRecord($data, $consumed); - if ($record === null) { + if ($record === null || $consumed === null) { return null; } else { $message->authority[] = $record; @@ -88,7 +81,7 @@ private function parse($data, $consumed) // parse all additional records for ($i = $arCount; $i > 0; --$i) { list($record, $consumed) = $this->parseRecord($data, $consumed); - if ($record === null) { + if ($record === null || $consumed === null) { return null; } else { $message->additional[] = $record; @@ -99,18 +92,17 @@ private function parse($data, $consumed) } /** - * @param string $data - * @param int $consumed - * @return array + * @return array{Query, int}|array{null, null} */ - private function parseQuestion($data, $consumed) + private function parseQuestion(string $data, int $consumed): array { list($labels, $consumed) = $this->readLabels($data, $consumed); - if ($labels === null || !isset($data[$consumed + 4 - 1])) { + if ($labels === null || $consumed === null || !isset($data[$consumed + 4 - 1])) { return [null, null]; } + /** @phpstan-ignore-next-line Since the format string is static the function will never return false. */ list($type, $class) = array_values(unpack('n*', substr($data, $consumed, 4))); $consumed += 4; @@ -125,21 +117,21 @@ private function parseQuestion($data, $consumed) } /** - * @param string $data - * @param int $consumed - * @return array An array with a parsed Record on success or array with null if data is invalid/incomplete + * @return array{Record, int}|array{null, null} An array with a parsed Record on success or array with null if data is invalid/incomplete */ - private function parseRecord($data, $consumed) + private function parseRecord(string $data, int $consumed): array { list($name, $consumed) = $this->readDomain($data, $consumed); - if ($name === null || !isset($data[$consumed + 10 - 1])) { + if ($name === null || $consumed === null || !isset($data[$consumed + 10 - 1])) { return [null, null]; } + /** @phpstan-ignore-next-line Since the format string is static the function will never return false. */ list($type, $class) = array_values(unpack('n*', substr($data, $consumed, 4))); $consumed += 4; + /** @phpstan-ignore-next-line Since the format string is static the function will never return false. */ list($ttl) = array_values(unpack('N', substr($data, $consumed, 4))); $consumed += 4; @@ -148,6 +140,7 @@ private function parseRecord($data, $consumed) $ttl = 0; } + /** @phpstan-ignore-next-line Since the format string is static the function will never return false. */ list($rdLength) = array_values(unpack('n', substr($data, $consumed, 2))); $consumed += 2; @@ -160,12 +153,12 @@ private function parseRecord($data, $consumed) if (Message::TYPE_A === $type) { if ($rdLength === 4) { - $rdata = inet_ntop(substr($data, $consumed, $rdLength)); + $rdata = (string)inet_ntop(substr($data, $consumed, $rdLength)); $consumed += $rdLength; } } elseif (Message::TYPE_AAAA === $type) { if ($rdLength === 16) { - $rdata = inet_ntop(substr($data, $consumed, $rdLength)); + $rdata = (string)inet_ntop(substr($data, $consumed, $rdLength)); $consumed += $rdLength; } } elseif (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type || Message::TYPE_NS === $type) { @@ -179,6 +172,7 @@ private function parseRecord($data, $consumed) } } elseif (Message::TYPE_MX === $type) { if ($rdLength > 2) { + /** @phpstan-ignore-next-line Since the format string is static the function will never return false. */ list($priority) = array_values(unpack('n', substr($data, $consumed, 2))); list($target, $consumed) = $this->readDomain($data, $consumed + 2); @@ -189,6 +183,7 @@ private function parseRecord($data, $consumed) } } elseif (Message::TYPE_SRV === $type) { if ($rdLength > 6) { + /** @phpstan-ignore-next-line Since the format string is static the function will never return false. */ list($priority, $weight, $port) = array_values(unpack('n*', substr($data, $consumed, 6))); list($target, $consumed) = $this->readDomain($data, $consumed + 6); @@ -201,6 +196,7 @@ private function parseRecord($data, $consumed) } } elseif (Message::TYPE_SSHFP === $type) { if ($rdLength > 2) { + /** @phpstan-ignore-next-line Since the format string is static the function will never return false. */ list($algorithm, $hash) = \array_values(\unpack('C*', \substr($data, $consumed, 2))); $fingerprint = \bin2hex(\substr($data, $consumed + 2, $rdLength - 2)); $consumed += $rdLength; @@ -213,9 +209,16 @@ private function parseRecord($data, $consumed) } } elseif (Message::TYPE_SOA === $type) { list($mname, $consumed) = $this->readDomain($data, $consumed); + if ($consumed === null) { + return [null, null]; + } list($rname, $consumed) = $this->readDomain($data, $consumed); + if ($consumed === null) { + return [null, null]; + } - if ($mname !== null && $rname !== null && isset($data[$consumed + 20 - 1])) { + if (isset($data[$consumed + 20 - 1])) { + /** @phpstan-ignore-next-line Since the format string is static the function will never return false. */ list($serial, $refresh, $retry, $expire, $minimum) = array_values(unpack('N*', substr($data, $consumed, 20))); $consumed += 20; @@ -232,11 +235,13 @@ private function parseRecord($data, $consumed) } elseif (Message::TYPE_OPT === $type) { $rdata = []; while (isset($data[$consumed + 4 - 1])) { + /** @phpstan-ignore-next-line Since the format string is static the function will never return false. */ list($code, $length) = array_values(unpack('n*', substr($data, $consumed, 4))); $value = (string) substr($data, $consumed + 4, $length); if ($code === Message::OPT_TCP_KEEPALIVE && $value === '') { $value = null; } elseif ($code === Message::OPT_TCP_KEEPALIVE && $length === 2) { + /** @phpstan-ignore-next-line Since the format string is static the function will never return false. */ list($value) = array_values(unpack('n', $value)); $value = round($value * 0.1, 1); } elseif ($code === Message::OPT_TCP_KEEPALIVE) { @@ -247,6 +252,7 @@ private function parseRecord($data, $consumed) } } elseif (Message::TYPE_CAA === $type) { if ($rdLength > 3) { + /** @phpstan-ignore-next-line Since the format string is static the function will never return false. */ list($flag, $tagLength) = array_values(unpack('C*', substr($data, $consumed, 2))); if ($tagLength > 0 && $rdLength - 2 - $tagLength > 0) { @@ -278,11 +284,14 @@ private function parseRecord($data, $consumed) ]; } - private function readDomain($data, $consumed) + /** + * @return array{string, int}|array{null, null} + */ + private function readDomain(string $data, int $consumed): array { list ($labels, $consumed) = $this->readLabels($data, $consumed); - if ($labels === null) { + if ($labels === null || $consumed === null) { return [null, null]; } @@ -302,13 +311,12 @@ function ($label) { } /** - * @param string $data - * @param int $consumed - * @param int $compressionDepth maximum depth for compressed labels to avoid unreasonable recursion - * @return array + * @param int $compressionDepth maximum depth for compressed labels to avoid unreasonable recursion + * @return array{array, int}|array{null, null} */ - private function readLabels($data, $consumed, $compressionDepth = 127) + private function readLabels(string $data, int $consumed, int $compressionDepth = 127) { + /** @var array $labels */ $labels = []; while (true) { diff --git a/src/Query/CachingExecutor.php b/src/Query/CachingExecutor.php index 03d56c95..39f2d47d 100644 --- a/src/Query/CachingExecutor.php +++ b/src/Query/CachingExecutor.php @@ -5,6 +5,7 @@ use React\Cache\CacheInterface; use React\Dns\Model\Message; use React\Promise\Promise; +use React\Promise\PromiseInterface; final class CachingExecutor implements ExecutorInterface { @@ -15,7 +16,14 @@ final class CachingExecutor implements ExecutorInterface */ const TTL = 60; + /** + * @var ExecutorInterface + */ private $executor; + + /** + * @var CacheInterface + */ private $cache; public function __construct(ExecutorInterface $executor, CacheInterface $cache) @@ -24,14 +32,14 @@ public function __construct(ExecutorInterface $executor, CacheInterface $cache) $this->cache = $cache; } - public function query(Query $query) + public function query(Query $query): PromiseInterface { $id = $query->name . ':' . $query->type . ':' . $query->class; $pending = $this->cache->get($id); return new Promise(function ($resolve, $reject) use ($query, $id, &$pending) { - $pending->then( - function ($message) use ($query, $id, &$pending) { + $pending->then( /** @phpstan-ignore-line $pending will never be null when we reach this */ + function (?Message $message) use ($query, $id, &$pending) { // return cached response message on cache hit if ($message !== null) { return $message; @@ -55,7 +63,7 @@ function (Message $message) use ($id) { }); }, function ($_, $reject) use (&$pending, $query) { $reject(new \RuntimeException('DNS query for ' . $query->describe() . ' has been cancelled')); - $pending->cancel(); + $pending->cancel(); /** @phpstan-ignore-line $pending will never be null when we reach this */ $pending = null; }); } diff --git a/src/Query/CoopExecutor.php b/src/Query/CoopExecutor.php index 3ce41691..4313f156 100644 --- a/src/Query/CoopExecutor.php +++ b/src/Query/CoopExecutor.php @@ -2,7 +2,9 @@ namespace React\Dns\Query; +use React\Dns\Model\Message; use React\Promise\Promise; +use React\Promise\PromiseInterface; /** * Cooperatively resolves hosts via the given base executor to ensure same query is not run concurrently @@ -36,8 +38,19 @@ */ final class CoopExecutor implements ExecutorInterface { + /** + * @var ExecutorInterface + */ private $executor; + + /** + * @var array> + */ private $pending = []; + + /** + * @var array + */ private $counts = []; public function __construct(ExecutorInterface $base) @@ -45,7 +58,7 @@ public function __construct(ExecutorInterface $base) $this->executor = $base; } - public function query(Query $query) + public function query(Query $query): PromiseInterface { $key = $this->serializeQueryToIdentity($query); if (isset($this->pending[$key])) { @@ -68,19 +81,20 @@ public function query(Query $query) // Return a child promise awaiting the pending query. // Cancelling this child promise should only cancel the pending query // when no other child promise is awaiting the same query. + /** @var Promise */ return new Promise(function ($resolve, $reject) use ($promise) { $promise->then($resolve, $reject); }, function () use (&$promise, $key, $query) { if (--$this->counts[$key] < 1) { unset($this->pending[$key], $this->counts[$key]); - $promise->cancel(); + $promise->cancel(); /** @phpstan-ignore-line $promise will never be null when we reach this */ $promise = null; } throw new \RuntimeException('DNS query for ' . $query->describe() . ' has been cancelled'); }); } - private function serializeQueryToIdentity(Query $query) + private function serializeQueryToIdentity(Query $query): string { return sprintf('%s:%s:%s', $query->name, $query->type, $query->class); } diff --git a/src/Query/ExecutorInterface.php b/src/Query/ExecutorInterface.php index 0bc3945f..3e99fb72 100644 --- a/src/Query/ExecutorInterface.php +++ b/src/Query/ExecutorInterface.php @@ -2,6 +2,9 @@ namespace React\Dns\Query; +use React\Dns\Model\Message; +use React\Promise\PromiseInterface; + interface ExecutorInterface { /** @@ -36,8 +39,8 @@ interface ExecutorInterface * ``` * * @param Query $query - * @return \React\Promise\PromiseInterface<\React\Dns\Model\Message> + * @return PromiseInterface * resolves with response message on success or rejects with an Exception on error */ - public function query(Query $query); + public function query(Query $query): PromiseInterface; } diff --git a/src/Query/FallbackExecutor.php b/src/Query/FallbackExecutor.php index 6999e1b8..bc26edaf 100644 --- a/src/Query/FallbackExecutor.php +++ b/src/Query/FallbackExecutor.php @@ -2,11 +2,20 @@ namespace React\Dns\Query; +use React\Dns\Model\Message; use React\Promise\Promise; +use React\Promise\PromiseInterface; final class FallbackExecutor implements ExecutorInterface { + /** + * @var ExecutorInterface + */ private $executor; + + /** + * @var ExecutorInterface + */ private $fallback; public function __construct(ExecutorInterface $executor, ExecutorInterface $fallback) @@ -15,13 +24,15 @@ public function __construct(ExecutorInterface $executor, ExecutorInterface $fall $this->fallback = $fallback; } - public function query(Query $query) + public function query(Query $query): PromiseInterface { + /** @var bool $cancelled */ $cancelled = false; $promise = $this->executor->query($query); + /** @var Promise */ return new Promise(function ($resolve, $reject) use (&$promise, $query, &$cancelled) { - $promise->then($resolve, function (\Exception $e1) use ($query, $resolve, $reject, &$cancelled, &$promise) { + $promise->then($resolve, function (\Throwable $e1) use ($query, $resolve, $reject, &$cancelled, &$promise) { // reject if primary resolution rejected due to cancellation if ($cancelled) { $reject($e1); @@ -29,7 +40,7 @@ public function query(Query $query) } // start fallback query if primary query rejected - $promise = $this->fallback->query($query)->then($resolve, function (\Exception $e2) use ($e1, $reject) { + $promise = $this->fallback->query($query)->then($resolve, function (\Throwable $e2) use ($e1, $reject) { $append = $e2->getMessage(); if (($pos = strpos($append, ':')) !== false) { $append = substr($append, $pos + 2); diff --git a/src/Query/HostsFileExecutor.php b/src/Query/HostsFileExecutor.php index 590f8abe..1bff35d2 100644 --- a/src/Query/HostsFileExecutor.php +++ b/src/Query/HostsFileExecutor.php @@ -5,6 +5,7 @@ use React\Dns\Config\HostsFile; use React\Dns\Model\Message; use React\Dns\Model\Record; +use React\Promise\PromiseInterface; use function React\Promise\resolve; /** @@ -16,7 +17,14 @@ */ final class HostsFileExecutor implements ExecutorInterface { + /** + * @var HostsFile + */ private $hosts; + + /** + * @var ExecutorInterface + */ private $fallback; public function __construct(HostsFile $hosts, ExecutorInterface $fallback) @@ -25,7 +33,7 @@ public function __construct(HostsFile $hosts, ExecutorInterface $fallback) $this->fallback = $fallback; } - public function query(Query $query) + public function query(Query $query): PromiseInterface { if ($query->class === Message::CLASS_IN && ($query->type === Message::TYPE_A || $query->type === Message::TYPE_AAAA)) { // forward lookup for type A or AAAA @@ -64,7 +72,7 @@ public function query(Query $query) return $this->fallback->query($query); } - private function getIpFromHost($host) + private function getIpFromHost(string $host): ?string { if (substr($host, -13) === '.in-addr.arpa') { // IPv4: read as IP and reverse bytes @@ -73,7 +81,12 @@ private function getIpFromHost($host) return null; } - return inet_ntop(strrev($ip)); + $inetIp = inet_ntop(strrev($ip)); + if ($inetIp !== false) { + return $inetIp; + } + + return null; } elseif (substr($host, -9) === '.ip6.arpa') { // IPv6: replace dots, reverse nibbles and interpret as hexadecimal string $ip = @inet_ntop(pack('H*', strrev(str_replace('.', '', substr($host, 0, -9))))); diff --git a/src/Query/Query.php b/src/Query/Query.php index 4cdfe63f..6685a484 100644 --- a/src/Query/Query.php +++ b/src/Query/Query.php @@ -11,7 +11,7 @@ * contain fields for resulting TTL and resulting record data (IPs etc.). * * @link https://tools.ietf.org/html/rfc1035#section-4.1.2 - * @see \React\Dns\Message\Record + * @see \React\Dns\Model\Record */ final class Query { @@ -35,7 +35,7 @@ final class Query * @param int $type query type, see Message::TYPE_* constants * @param int $class query class, see Message::CLASS_IN constant */ - public function __construct($name, $type, $class) + public function __construct(string $name, int $type, int $class) { $this->name = $name; $this->type = $type; @@ -51,7 +51,7 @@ public function __construct($name, $type, $class) * @return string "example.com (A)" or "example.com (CLASS0 TYPE1234)" * @link https://tools.ietf.org/html/rfc3597 */ - public function describe() + public function describe(): string { $class = $this->class !== Message::CLASS_IN ? 'CLASS' . $this->class . ' ' : ''; diff --git a/src/Query/RetryExecutor.php b/src/Query/RetryExecutor.php index e68dcc61..f8f89bf7 100644 --- a/src/Query/RetryExecutor.php +++ b/src/Query/RetryExecutor.php @@ -2,39 +2,55 @@ namespace React\Dns\Query; +use React\Dns\Model\Message; use React\Promise\Deferred; use React\Promise\PromiseInterface; final class RetryExecutor implements ExecutorInterface { + /** + * @var ExecutorInterface + */ private $executor; + + /** + * @var int + */ private $retries; - public function __construct(ExecutorInterface $executor, $retries = 2) + public function __construct(ExecutorInterface $executor, int $retries = 2) { $this->executor = $executor; $this->retries = $retries; } - public function query(Query $query) + public function query(Query $query): PromiseInterface { return $this->tryQuery($query, $this->retries); } - public function tryQuery(Query $query, $retries) + /** + * @param Query $query + * @param int $retries + * @return PromiseInterface + */ + public function tryQuery(Query $query, int $retries): PromiseInterface { + /** @var ?PromiseInterface $promise */ + $promise = null; + /** @var Deferred $deferred */ $deferred = new Deferred(function () use (&$promise) { - if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) { + if ($promise !== null && \method_exists($promise, 'cancel')) { $promise->cancel(); } }); - $success = function ($value) use ($deferred, &$errorback) { + $success = static function (Message $value) use ($deferred, &$errorback) { $errorback = null; $deferred->resolve($value); }; - $errorback = function ($e) use ($deferred, &$promise, $query, $success, &$errorback, &$retries) { + $errorback = function (\Throwable $e) use ($deferred, &$promise, $query, $success, &$errorback, &$retries) { if (!$e instanceof TimeoutException) { $errorback = null; $deferred->reject($e); @@ -50,6 +66,7 @@ public function tryQuery(Query $query, $retries) // what a lovely piece of code! $r = new \ReflectionProperty(\Exception::class, 'trace'); $r->setAccessible(true); + /** @var array, file: string, line: int}|array{file: string, line: int}> $trace */ $trace = $r->getValue($e); // Exception trace arguments are not available on some PHP 7.4 installs diff --git a/src/Query/SelectiveTransportExecutor.php b/src/Query/SelectiveTransportExecutor.php index b76632fd..35ecf575 100644 --- a/src/Query/SelectiveTransportExecutor.php +++ b/src/Query/SelectiveTransportExecutor.php @@ -2,7 +2,9 @@ namespace React\Dns\Query; +use React\Dns\Model\Message; use React\Promise\Promise; +use React\Promise\PromiseInterface; /** * Send DNS queries over a UDP or TCP/IP stream transport. @@ -52,7 +54,14 @@ */ class SelectiveTransportExecutor implements ExecutorInterface { + /** + * @var ExecutorInterface + */ private $datagramExecutor; + + /** + * @var ExecutorInterface + */ private $streamExecutor; public function __construct(ExecutorInterface $datagramExecutor, ExecutorInterface $streamExecutor) @@ -61,14 +70,15 @@ public function __construct(ExecutorInterface $datagramExecutor, ExecutorInterfa $this->streamExecutor = $streamExecutor; } - public function query(Query $query) + public function query(Query $query): PromiseInterface { $pending = $this->datagramExecutor->query($query); - return new Promise(function ($resolve, $reject) use (&$pending, $query) { + /** @var Promise */ + return new Promise(function ($resolve, $reject) use (&$pending, $query): void { $pending->then( $resolve, - function ($e) use (&$pending, $query, $resolve, $reject) { + function (\Throwable $e) use (&$pending, $query, $resolve, $reject) { if ($e->getCode() === (\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90)) { $pending = $this->streamExecutor->query($query)->then($resolve, $reject); } else { @@ -77,7 +87,7 @@ function ($e) use (&$pending, $query, $resolve, $reject) { } ); }, function () use (&$pending) { - $pending->cancel(); + $pending->cancel(); /** @phpstan-ignore-line $pending will never be null when we reach this */ $pending = null; }); } diff --git a/src/Query/TcpTransportExecutor.php b/src/Query/TcpTransportExecutor.php index c3900e4f..937319ed 100644 --- a/src/Query/TcpTransportExecutor.php +++ b/src/Query/TcpTransportExecutor.php @@ -7,7 +7,9 @@ use React\Dns\Protocol\Parser; use React\EventLoop\Loop; use React\EventLoop\LoopInterface; +use React\EventLoop\TimerInterface; use React\Promise\Deferred; +use React\Promise\PromiseInterface; use function React\Promise\reject; /** @@ -78,24 +80,25 @@ */ class TcpTransportExecutor implements ExecutorInterface { + /** @var string */ private $nameserver; + + /** @var LoopInterface */ private $loop; + + /** @var Parser */ private $parser; + + /** @var BinaryDumper */ private $dumper; - /** - * @var ?resource - */ + /** @var ?resource */ private $socket; - /** - * @var Deferred[] - */ + /** @var array> */ private $pending = []; - /** - * @var string[] - */ + /** @var array */ private $names = []; /** @@ -117,18 +120,22 @@ class TcpTransportExecutor implements ExecutorInterface */ private $idlePeriod = 0.001; - /** - * @var ?\React\EventLoop\TimerInterface - */ + /** @var ?TimerInterface */ private $idleTimer; + /** @var string */ private $writeBuffer = ''; + + /** @var bool */ private $writePending = false; + /** @var string */ private $readBuffer = ''; + + /** @var bool */ private $readPending = false; - /** @var string */ + /** @var int<0, max> */ private $readChunk = 0xffff; /** @@ -153,7 +160,7 @@ public function __construct($nameserver, ?LoopInterface $loop = null) $this->dumper = new BinaryDumper(); } - public function query(Query $query) + public function query(Query $query): PromiseInterface { $request = Message::createRequestForQuery($query); @@ -174,6 +181,7 @@ public function query(Query $query) if ($this->socket === null) { // create async TCP/IP connection (may take a while) + /** @var resource|false $socket */ $socket = @\stream_socket_client($this->nameserver, $errno, $errstr, 0, \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT); if ($socket === false) { return reject(new \RuntimeException( @@ -202,6 +210,9 @@ public function query(Query $query) $this->loop->addWriteStream($this->socket, [$this, 'handleWritable']); } + /** + * @var Deferred $deferred + */ $deferred = new Deferred(function () use ($request) { // remove from list of pending names, but remember pending query $name = $this->names[$request->id]; @@ -220,16 +231,20 @@ public function query(Query $query) /** * @internal */ - public function handleWritable() + public function handleWritable(): void { if ($this->readPending === false) { + /** @phpstan-ignore-next-line $this->socket will never be null when we reach this */ $name = @\stream_socket_get_name($this->socket, true); if ($name === false) { // Connection failed? Check socket error if available for underlying errno/errstr. // @codeCoverageIgnoreStart if (\function_exists('socket_import_stream')) { + /** @phpstan-ignore-next-line $this->socket will never be null when we reach this */ $socket = \socket_import_stream($this->socket); + /** @phpstan-ignore-next-line $this->socket will never be null when we reach this */ $errno = \socket_get_option($socket, \SOL_SOCKET, \SO_ERROR); + /** @phpstan-ignore-next-line $errno will always be an int due to the option we pass to \socket_get_option */ $errstr = \socket_strerror($errno); } else { $errno = \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNREFUSED : 111; @@ -237,24 +252,29 @@ public function handleWritable() } // @codeCoverageIgnoreEnd + /** @phpstan-ignore-next-line $errno will always be an int due to the option we pass to \socket_get_option */ $this->closeError('Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')', $errno); return; } $this->readPending = true; + /** @phpstan-ignore-next-line $this->socket will never be null when we reach this */ $this->loop->addReadStream($this->socket, [$this, 'handleRead']); } $errno = 0; $errstr = ''; - \set_error_handler(function ($_, $error) use (&$errno, &$errstr) { + \set_error_handler(function (int $_, string $error) use (&$errno, &$errstr): bool { // Match errstr from PHP's warning message. // fwrite(): Send of 327712 bytes failed with errno=32 Broken pipe \preg_match('/errno=(\d+) (.+)/', $error, $m); $errno = (int) ($m[1] ?? 0); $errstr = $m[2] ?? $error; + + return true; }); + /** @phpstan-ignore-next-line $this->socket will never be null when we reach this */ $written = \fwrite($this->socket, $this->writeBuffer); \restore_error_handler(); @@ -270,6 +290,7 @@ public function handleWritable() if (isset($this->writeBuffer[$written])) { $this->writeBuffer = \substr($this->writeBuffer, $written); } else { + /** @phpstan-ignore-next-line $this->socket will never be null when we reach this */ $this->loop->removeWriteStream($this->socket); $this->writePending = false; $this->writeBuffer = ''; @@ -279,10 +300,11 @@ public function handleWritable() /** * @internal */ - public function handleRead() + public function handleRead(): void { // read one chunk of data from the DNS server // any error is fatal, this is a stream of TCP/IP data + /** @phpstan-ignore-next-line $this->socket will never be null when we reach this */ $chunk = @\fread($this->socket, $this->readChunk); if ($chunk === false || $chunk === '') { $this->closeError('Connection to DNS server ' . $this->nameserver . ' lost'); @@ -295,7 +317,11 @@ public function handleRead() // response message header contains at least 12 bytes while (isset($this->readBuffer[11])) { // read response message length from first 2 bytes and ensure we have length + data in buffer - list(, $length) = \unpack('n', $this->readBuffer); + $list = \unpack('n', $this->readBuffer); + if (!is_array($list)) { + return; + } + list(, $length) = $list; if (!isset($this->readBuffer[$length + 1])) { return; } @@ -328,19 +354,19 @@ public function handleRead() /** * @internal - * @param string $reason - * @param int $code */ - public function closeError($reason, $code = 0) + public function closeError(string $reason, int $code = 0): void { $this->readBuffer = ''; if ($this->readPending) { + /** @phpstan-ignore-next-line $this->socket will never be null when we reach this */ $this->loop->removeReadStream($this->socket); $this->readPending = false; } $this->writeBuffer = ''; if ($this->writePending) { + /** @phpstan-ignore-next-line $this->socket will never be null when we reach this */ $this->loop->removeWriteStream($this->socket); $this->writePending = false; } @@ -350,7 +376,7 @@ public function closeError($reason, $code = 0) $this->idleTimer = null; } - @\fclose($this->socket); + @\fclose($this->socket); /** @phpstan-ignore-line */ $this->socket = null; foreach ($this->names as $id => $name) { @@ -365,7 +391,7 @@ public function closeError($reason, $code = 0) /** * @internal */ - public function checkIdle() + public function checkIdle(): void { if ($this->idleTimer === null && !$this->names) { $this->idleTimer = $this->loop->addTimer($this->idlePeriod, function () { diff --git a/src/Query/TimeoutExecutor.php b/src/Query/TimeoutExecutor.php index 4901db6d..127a1d46 100644 --- a/src/Query/TimeoutExecutor.php +++ b/src/Query/TimeoutExecutor.php @@ -2,36 +2,57 @@ namespace React\Dns\Query; +use React\Dns\Model\Message; +use React\Dns\Model\Record; use React\EventLoop\Loop; use React\EventLoop\LoopInterface; +use React\EventLoop\TimerInterface; use React\Promise\Promise; +use React\Promise\PromiseInterface; final class TimeoutExecutor implements ExecutorInterface { + /** + * @var ExecutorInterface + */ private $executor; + + /** + * @var LoopInterface + */ private $loop; + + /** + * @var float|int + */ private $timeout; - public function __construct(ExecutorInterface $executor, $timeout, ?LoopInterface $loop = null) + public function __construct(ExecutorInterface $executor, float $timeout, ?LoopInterface $loop = null) { $this->executor = $executor; $this->loop = $loop ?: Loop::get(); $this->timeout = $timeout; } - public function query(Query $query) + public function query(Query $query): PromiseInterface { $promise = $this->executor->query($query); - return new Promise(function ($resolve, $reject) use ($promise, $query) { + /** + * @var Promise + */ + return new Promise(function ($resolve, $reject) use ($promise, $query): void { + /** + * @var null|false|TimerInterface $timer + */ $timer = null; - $promise = $promise->then(function ($v) use (&$timer, $resolve) { + $promise = $promise->then(function (Message $v) use (&$timer, $resolve): void { if ($timer) { $this->loop->cancelTimer($timer); } $timer = false; $resolve($v); - }, function ($v) use (&$timer, $reject) { + }, function (\Throwable $v) use (&$timer, $reject): void { if ($timer) { $this->loop->cancelTimer($timer); } @@ -45,21 +66,21 @@ public function query(Query $query) } // start timeout timer which will cancel the pending promise - $timer = $this->loop->addTimer($this->timeout, function () use (&$promise, $reject, $query) { + $timer = $this->loop->addTimer($this->timeout, static function () use (&$promise, $reject, $query): void { $reject(new TimeoutException( 'DNS query for ' . $query->describe() . ' timed out' )); // Cancel pending query to clean up any underlying resources and references. // Avoid garbage references in call stack by passing pending promise by reference. - assert(\method_exists($promise, 'cancel')); + assert(\method_exists($promise, 'cancel')); /** @phpstan-ignore-line $pending will never be null when we reach this */ $promise->cancel(); $promise = null; }); - }, function () use (&$promise) { + }, static function () use (&$promise): void { // Cancelling this promise will cancel the pending query, thus triggering the rejection logic above. // Avoid garbage references in call stack by passing pending promise by reference. - assert(\method_exists($promise, 'cancel')); + assert(\method_exists($promise, 'cancel')); /** @phpstan-ignore-line $pending will never be null when we reach this */ $promise->cancel(); $promise = null; }); diff --git a/src/Query/UdpTransportExecutor.php b/src/Query/UdpTransportExecutor.php index d1cb86e2..78d6007c 100644 --- a/src/Query/UdpTransportExecutor.php +++ b/src/Query/UdpTransportExecutor.php @@ -8,6 +8,7 @@ use React\EventLoop\Loop; use React\EventLoop\LoopInterface; use React\Promise\Deferred; +use React\Promise\PromiseInterface; use function React\Promise\reject; /** @@ -83,15 +84,27 @@ */ final class UdpTransportExecutor implements ExecutorInterface { + /** + * @var string + */ private $nameserver; + /** + * @var LoopInterface + */ private $loop; + /** + * @var Parser + */ private $parser; + /** + * @var BinaryDumper + */ private $dumper; /** * maximum UDP packet size to send and receive * - * @var int + * @var int<0, max> */ private $maxPacketSize = 512; @@ -99,7 +112,7 @@ final class UdpTransportExecutor implements ExecutorInterface * @param string $nameserver * @param ?LoopInterface $loop */ - public function __construct($nameserver, ?LoopInterface $loop = null) + public function __construct(string $nameserver, ?LoopInterface $loop = null) { if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2 && \strpos($nameserver, '://') === false) { // several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets @@ -117,7 +130,7 @@ public function __construct($nameserver, ?LoopInterface $loop = null) $this->dumper = new BinaryDumper(); } - public function query(Query $query) + public function query(Query $query): PromiseInterface { $request = Message::createRequestForQuery($query); @@ -143,7 +156,7 @@ public function query(Query $query) // set socket to non-blocking and immediately try to send (fill write buffer) \stream_set_blocking($socket, false); - \set_error_handler(function ($_, $error) use (&$errno, &$errstr) { + \set_error_handler(function (int $_, string $error) use (&$errno, &$errstr): bool { // Write may potentially fail, but most common errors are already caught by connection check above. // Among others, macOS is known to report here when trying to send to broadcast address. // This can also be reproduced by writing data exceeding `stream_set_chunk_size()` to a server refusing UDP data. @@ -151,6 +164,8 @@ public function query(Query $query) \preg_match('/errno=(\d+) (.+)/', $error, $m); $errno = (int) ($m[1] ?? 0); $errstr = $m[2] ?? $error; + + return true; }); $written = \fwrite($socket, $queryData); @@ -164,6 +179,9 @@ public function query(Query $query) )); } + /** + * @var Deferred $deferred + */ $deferred = new Deferred(function () use ($socket, $query) { // cancellation should remove socket from loop and close socket $this->loop->removeReadStream($socket); diff --git a/src/Resolver/Factory.php b/src/Resolver/Factory.php index eedebe1a..14ebf0d7 100644 --- a/src/Resolver/Factory.php +++ b/src/Resolver/Factory.php @@ -32,11 +32,11 @@ final class Factory * * @param Config|string $config DNS Config object (recommended) or single nameserver address * @param ?LoopInterface $loop - * @return \React\Dns\Resolver\ResolverInterface + * @return ResolverInterface * @throws \InvalidArgumentException for invalid DNS server address * @throws \UnderflowException when given DNS Config object has an empty list of nameservers */ - public function create($config, ?LoopInterface $loop = null) + public function create($config, ?LoopInterface $loop = null): ResolverInterface { $executor = $this->decorateHostsFileExecutor($this->createExecutor($config, $loop ?: Loop::get())); @@ -55,11 +55,11 @@ public function create($config, ?LoopInterface $loop = null) * @param Config|string $config DNS Config object (recommended) or single nameserver address * @param ?LoopInterface $loop * @param ?CacheInterface $cache - * @return \React\Dns\Resolver\ResolverInterface + * @return ResolverInterface * @throws \InvalidArgumentException for invalid DNS server address * @throws \UnderflowException when given DNS Config object has an empty list of nameservers */ - public function createCached($config, ?LoopInterface $loop = null, ?CacheInterface $cache = null) + public function createCached($config, ?LoopInterface $loop = null, ?CacheInterface $cache = null): ResolverInterface { // default to keeping maximum of 256 responses in cache unless explicitly given if (!($cache instanceof CacheInterface)) { @@ -80,7 +80,7 @@ public function createCached($config, ?LoopInterface $loop = null, ?CacheInterfa * @return ExecutorInterface * @codeCoverageIgnore */ - private function decorateHostsFileExecutor(ExecutorInterface $executor) + private function decorateHostsFileExecutor(ExecutorInterface $executor): ExecutorInterface { try { $executor = new HostsFileExecutor( @@ -110,7 +110,7 @@ private function decorateHostsFileExecutor(ExecutorInterface $executor) * @throws \InvalidArgumentException for invalid DNS server address * @throws \UnderflowException when given DNS Config object has an empty list of nameservers */ - private function createExecutor($nameserver, LoopInterface $loop) + private function createExecutor($nameserver, LoopInterface $loop): ExecutorInterface { if ($nameserver instanceof Config) { if (!$nameserver->nameservers) { @@ -123,7 +123,7 @@ private function createExecutor($nameserver, LoopInterface $loop) $secondary = next($nameserver->nameservers); $tertiary = next($nameserver->nameservers); - if ($tertiary !== false) { + if ($tertiary !== false && $secondary !== false) { // 3 DNS servers given => nest first with fallback for second and third return new CoopExecutor( new RetryExecutor( @@ -161,7 +161,7 @@ private function createExecutor($nameserver, LoopInterface $loop) * @return ExecutorInterface * @throws \InvalidArgumentException for invalid DNS server address */ - private function createSingleExecutor($nameserver, LoopInterface $loop) + private function createSingleExecutor(string $nameserver, LoopInterface $loop): ExecutorInterface { $parts = \parse_url($nameserver); @@ -185,7 +185,7 @@ private function createSingleExecutor($nameserver, LoopInterface $loop) * @return TimeoutExecutor * @throws \InvalidArgumentException for invalid DNS server address */ - private function createTcpExecutor($nameserver, LoopInterface $loop) + private function createTcpExecutor(string $nameserver, LoopInterface $loop): ExecutorInterface { return new TimeoutExecutor( new TcpTransportExecutor($nameserver, $loop), @@ -200,7 +200,7 @@ private function createTcpExecutor($nameserver, LoopInterface $loop) * @return TimeoutExecutor * @throws \InvalidArgumentException for invalid DNS server address */ - private function createUdpExecutor($nameserver, LoopInterface $loop) + private function createUdpExecutor(string $nameserver, LoopInterface $loop): ExecutorInterface { return new TimeoutExecutor( new UdpTransportExecutor( diff --git a/src/Resolver/Resolver.php b/src/Resolver/Resolver.php index 6108b3da..9b3abe8b 100644 --- a/src/Resolver/Resolver.php +++ b/src/Resolver/Resolver.php @@ -3,15 +3,20 @@ namespace React\Dns\Resolver; use React\Dns\Model\Message; +use React\Dns\Model\Record; use React\Dns\Query\ExecutorInterface; use React\Dns\Query\Query; use React\Dns\RecordNotFoundException; +use React\Promise\PromiseInterface; /** * @see ResolverInterface for the base interface */ final class Resolver implements ResolverInterface { + /** + * @var ExecutorInterface + */ private $executor; public function __construct(ExecutorInterface $executor) @@ -19,14 +24,14 @@ public function __construct(ExecutorInterface $executor) $this->executor = $executor; } - public function resolve($domain) + public function resolve(string $domain): PromiseInterface { return $this->resolveAll($domain, Message::TYPE_A)->then(function (array $ips) { return $ips[array_rand($ips)]; }); } - public function resolveAll($domain, $type) + public function resolveAll(string $domain, int $type): PromiseInterface { $query = new Query($domain, $type, Message::CLASS_IN); @@ -38,15 +43,14 @@ public function resolveAll($domain, $type) } /** - * [Internal] extract all resource record values from response for this query + * Extract all resource record values from response for this query * * @param Query $query * @param Message $response - * @return array - * @throws RecordNotFoundException when response indicates an error or contains no data - * @internal + * @return array + */ - public function extractValues(Query $query, Message $response) + private function extractValues(Query $query, Message $response): array { // reject if response code indicates this is an error response message $code = $response->rcode; @@ -90,12 +94,12 @@ public function extractValues(Query $query, Message $response) } /** - * @param \React\Dns\Model\Record[] $answers - * @param string $name - * @param int $type - * @return array + * @param array $answers + * @param string $name + * @param int $type + * @return array */ - private function valuesByNameAndType(array $answers, $name, $type) + private function valuesByNameAndType(array $answers, string $name, int $type): array { // return all record values for this name and type (if any) $named = $this->filterByName($answers, $name); @@ -119,28 +123,59 @@ private function valuesByNameAndType(array $answers, $name, $type) return $records; } - private function filterByName(array $answers, $name) + /** + * @param array $answers + * @return array + */ + private function filterByName(array $answers, string $name): array { return $this->filterByField($answers, 'name', $name); } - private function filterByType(array $answers, $type) + /** + * @param array $answers + * @return array + */ + private function filterByType(array $answers, int $type): array { return $this->filterByField($answers, 'type', $type); } - private function filterByField(array $answers, $field, $value) + /** + * @param array $answers + * @param string|int $value + * @return array + */ + private function filterByField(array $answers, string $field, $value): array { - $value = strtolower($value); - return array_filter($answers, function ($answer) use ($field, $value) { - return $value === strtolower($answer->$field); + if (is_string($value)) { + $value = strtolower($value); + } + return array_filter($answers, static function (Record $answer) use ($field, $value) { + return $value === (is_string($value) ? strtolower($answer->$field) : $answer->$field); }); } - private function mapRecordData(array $records) + /** + * @param array $records + * @return array + */ + private function mapRecordData(array $records): array { - return array_map(function ($record) { - return $record->data; - }, $records); + $recordData = []; + + foreach ($records as $record) { + if (is_array($record->data)) { + foreach ($record->data as $recordDataItem) { + $recordData[] = $recordDataItem; + } + + continue; + } + + $recordData[] = $record->data; + } + + return $recordData; } } diff --git a/src/Resolver/ResolverInterface.php b/src/Resolver/ResolverInterface.php index 555a1cb1..3698c38a 100644 --- a/src/Resolver/ResolverInterface.php +++ b/src/Resolver/ResolverInterface.php @@ -2,6 +2,8 @@ namespace React\Dns\Resolver; +use React\Promise\PromiseInterface; + interface ResolverInterface { /** @@ -39,10 +41,10 @@ interface ResolverInterface * ``` * * @param string $domain - * @return \React\Promise\PromiseInterface + * @return PromiseInterface * resolves with a single IP address on success or rejects with an Exception on error. */ - public function resolve($domain); + public function resolve(string $domain): PromiseInterface; /** * Resolves all record values for the given $domain name and query $type. @@ -86,9 +88,10 @@ public function resolve($domain); * $promise->cancel(); * ``` * - * @param string $domain - * @return \React\Promise\PromiseInterface + * @param string $domain domain to resolve + * @param int $type query type, see Message::TYPE_* constants + * @return PromiseInterface> * Resolves with all record values on success or rejects with an Exception on error. */ - public function resolveAll($domain, $type); + public function resolveAll(string $domain, int $type): PromiseInterface; } diff --git a/tests/Config/ConfigTest.php b/tests/Config/ConfigTest.php index 46d2f53d..313ed675 100644 --- a/tests/Config/ConfigTest.php +++ b/tests/Config/ConfigTest.php @@ -7,14 +7,14 @@ class ConfigTest extends TestCase { - public function testLoadsSystemDefault() + public function testLoadsSystemDefault(): void { $config = Config::loadSystemConfigBlocking(); $this->assertInstanceOf(Config::class, $config); } - public function testLoadsDefaultPath() + public function testLoadsDefaultPath(): void { if (DIRECTORY_SEPARATOR === '\\') { $this->markTestSkipped('Not supported on Windows'); @@ -25,20 +25,20 @@ public function testLoadsDefaultPath() $this->assertInstanceOf(Config::class, $config); } - public function testLoadsFromExplicitPath() + public function testLoadsFromExplicitPath(): void { $config = Config::loadResolvConfBlocking(__DIR__ . '/../Fixtures/etc/resolv.conf'); $this->assertEquals(['8.8.8.8'], $config->nameservers); } - public function testLoadThrowsWhenPathIsInvalid() + public function testLoadThrowsWhenPathIsInvalid(): void { $this->expectException(\RuntimeException::class); Config::loadResolvConfBlocking(__DIR__ . '/invalid.conf'); } - public function testParsesSingleEntryFile() + public function testParsesSingleEntryFile(): void { $contents = 'nameserver 8.8.8.8'; $expected = ['8.8.8.8']; @@ -47,7 +47,7 @@ public function testParsesSingleEntryFile() $this->assertEquals($expected, $config->nameservers); } - public function testParsesNameserverWithoutIpv6ScopeId() + public function testParsesNameserverWithoutIpv6ScopeId(): void { $contents = 'nameserver ::1%lo'; $expected = ['::1']; @@ -56,7 +56,7 @@ public function testParsesNameserverWithoutIpv6ScopeId() $this->assertEquals($expected, $config->nameservers); } - public function testParsesNameserverEntriesFromAverageFileCorrectly() + public function testParsesNameserverEntriesFromAverageFileCorrectly(): void { $contents = '# # Mac OS X Notice @@ -77,7 +77,7 @@ public function testParsesNameserverEntriesFromAverageFileCorrectly() $this->assertEquals($expected, $config->nameservers); } - public function testParsesEmptyFileWithoutNameserverEntries() + public function testParsesEmptyFileWithoutNameserverEntries(): void { $expected = []; @@ -85,7 +85,7 @@ public function testParsesEmptyFileWithoutNameserverEntries() $this->assertEquals($expected, $config->nameservers); } - public function testParsesFileAndIgnoresCommentsAndInvalidNameserverEntries() + public function testParsesFileAndIgnoresCommentsAndInvalidNameserverEntries(): void { $contents = ' # nameserver 1.2.3.4 @@ -103,7 +103,7 @@ public function testParsesFileAndIgnoresCommentsAndInvalidNameserverEntries() $this->assertEquals($expected, $config->nameservers); } - public function testLoadsFromWmicOnWindows() + public function testLoadsFromWmicOnWindows(): void { if (DIRECTORY_SEPARATOR !== '\\') { // WMIC is Windows-only tool and not supported on other platforms @@ -118,7 +118,7 @@ public function testLoadsFromWmicOnWindows() $this->assertInstanceOf(Config::class, $config); } - public function testLoadsSingleEntryFromWmicOutput() + public function testLoadsSingleEntryFromWmicOutput(): void { $contents = ' Node,DNSServerSearchOrder @@ -133,7 +133,7 @@ public function testLoadsSingleEntryFromWmicOutput() $this->assertEquals($expected, $config->nameservers); } - public function testLoadsEmptyListFromWmicOutput() + public function testLoadsEmptyListFromWmicOutput(): void { $contents = ' Node,DNSServerSearchOrder @@ -146,7 +146,7 @@ public function testLoadsEmptyListFromWmicOutput() $this->assertEquals($expected, $config->nameservers); } - public function testLoadsSingleEntryForMultipleNicsFromWmicOutput() + public function testLoadsSingleEntryForMultipleNicsFromWmicOutput(): void { $contents = ' Node,DNSServerSearchOrder @@ -163,7 +163,7 @@ public function testLoadsSingleEntryForMultipleNicsFromWmicOutput() $this->assertEquals($expected, $config->nameservers); } - public function testLoadsMultipleEntriesForSingleNicWithSemicolonFromWmicOutput() + public function testLoadsMultipleEntriesForSingleNicWithSemicolonFromWmicOutput(): void { $contents = ' Node,DNSServerSearchOrder @@ -178,7 +178,7 @@ public function testLoadsMultipleEntriesForSingleNicWithSemicolonFromWmicOutput( $this->assertEquals($expected, $config->nameservers); } - public function testLoadsMultipleEntriesForSingleNicWithQuotesFromWmicOutput() + public function testLoadsMultipleEntriesForSingleNicWithQuotesFromWmicOutput(): void { $contents = ' Node,DNSServerSearchOrder @@ -193,7 +193,7 @@ public function testLoadsMultipleEntriesForSingleNicWithQuotesFromWmicOutput() $this->assertEquals($expected, $config->nameservers); } - private function echoCommand($output) + private function echoCommand(string $output): string { return 'echo ' . escapeshellarg($output); } diff --git a/tests/Config/HostsFileTest.php b/tests/Config/HostsFileTest.php index a8440896..c8d14be6 100644 --- a/tests/Config/HostsFileTest.php +++ b/tests/Config/HostsFileTest.php @@ -7,14 +7,14 @@ class HostsFileTest extends TestCase { - public function testLoadsFromDefaultPath() + public function testLoadsFromDefaultPath(): void { $hosts = HostsFile::loadFromPathBlocking(); $this->assertInstanceOf(HostsFile::class, $hosts); } - public function testDefaultShouldHaveLocalhostMapped() + public function testDefaultShouldHaveLocalhostMapped(): void { if (DIRECTORY_SEPARATOR === '\\') { $this->markTestSkipped('Not supported on Windows'); @@ -25,13 +25,13 @@ public function testDefaultShouldHaveLocalhostMapped() $this->assertContains('127.0.0.1', $hosts->getIpsForHost('localhost')); } - public function testLoadThrowsForInvalidPath() + public function testLoadThrowsForInvalidPath(): void { $this->expectException(\RuntimeException::class); HostsFile::loadFromPathBlocking('does/not/exist'); } - public function testContainsSingleLocalhostEntry() + public function testContainsSingleLocalhostEntry(): void { $hosts = new HostsFile('127.0.0.1 localhost'); @@ -39,7 +39,7 @@ public function testContainsSingleLocalhostEntry() $this->assertEquals([], $hosts->getIpsForHost('example.com')); } - public function testNonIpReturnsNothingForInvalidHosts() + public function testNonIpReturnsNothingForInvalidHosts(): void { $hosts = new HostsFile('a b'); @@ -47,14 +47,14 @@ public function testNonIpReturnsNothingForInvalidHosts() $this->assertEquals([], $hosts->getIpsForHost('b')); } - public function testIgnoresIpv6ZoneId() + public function testIgnoresIpv6ZoneId(): void { $hosts = new HostsFile('fe80::1%lo0 localhost'); $this->assertEquals(['fe80::1'], $hosts->getIpsForHost('localhost')); } - public function testSkipsComments() + public function testSkipsComments(): void { $hosts = new HostsFile('# start' . PHP_EOL .'#127.0.0.1 localhost' . PHP_EOL . '127.0.0.2 localhost # example.com'); @@ -62,21 +62,21 @@ public function testSkipsComments() $this->assertEquals([], $hosts->getIpsForHost('example.com')); } - public function testContainsSingleLocalhostEntryWithCaseIgnored() + public function testContainsSingleLocalhostEntryWithCaseIgnored(): void { $hosts = new HostsFile('127.0.0.1 LocalHost'); $this->assertEquals(['127.0.0.1'], $hosts->getIpsForHost('LOCALHOST')); } - public function testEmptyFileContainsNothing() + public function testEmptyFileContainsNothing(): void { $hosts = new HostsFile(''); $this->assertEquals([], $hosts->getIpsForHost('example.com')); } - public function testSingleEntryWithMultipleNames() + public function testSingleEntryWithMultipleNames(): void { $hosts = new HostsFile('127.0.0.1 localhost example.com'); @@ -84,21 +84,21 @@ public function testSingleEntryWithMultipleNames() $this->assertEquals(['127.0.0.1'], $hosts->getIpsForHost('localhost')); } - public function testMergesEntriesOverMultipleLines() + public function testMergesEntriesOverMultipleLines(): void { $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(['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], $hosts->getIpsForHost('localhost')); } - public function testMergesIpv4AndIpv6EntriesOverMultipleLines() + public function testMergesIpv4AndIpv6EntriesOverMultipleLines(): void { $hosts = new HostsFile("127.0.0.1 localhost\n::1 localhost"); $this->assertEquals(['127.0.0.1', '::1'], $hosts->getIpsForHost('localhost')); } - public function testReverseLookup() + public function testReverseLookup(): void { $hosts = new HostsFile('127.0.0.1 localhost'); @@ -106,7 +106,7 @@ public function testReverseLookup() $this->assertEquals([], $hosts->getHostsForIp('192.168.1.1')); } - public function testReverseSkipsComments() + public function testReverseSkipsComments(): void { $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"); @@ -115,7 +115,7 @@ public function testReverseSkipsComments() $this->assertEquals(['example.org'], $hosts->getHostsForIp('127.0.0.3')); } - public function testReverseNonIpReturnsNothing() + public function testReverseNonIpReturnsNothing(): void { $hosts = new HostsFile('127.0.0.1 localhost'); @@ -123,7 +123,7 @@ public function testReverseNonIpReturnsNothing() $this->assertEquals([], $hosts->getHostsForIp('127.0.0.1.1')); } - public function testReverseNonIpReturnsNothingForInvalidHosts() + public function testReverseNonIpReturnsNothingForInvalidHosts(): void { $hosts = new HostsFile('a b'); @@ -131,35 +131,35 @@ public function testReverseNonIpReturnsNothingForInvalidHosts() $this->assertEquals([], $hosts->getHostsForIp('b')); } - public function testReverseLookupReturnsLowerCaseHost() + public function testReverseLookupReturnsLowerCaseHost(): void { $hosts = new HostsFile('127.0.0.1 LocalHost'); $this->assertEquals(['localhost'], $hosts->getHostsForIp('127.0.0.1')); } - public function testReverseLookupChecksNormalizedIpv6() + public function testReverseLookupChecksNormalizedIpv6(): void { $hosts = new HostsFile('FE80::00a1 localhost'); $this->assertEquals(['localhost'], $hosts->getHostsForIp('fe80::A1')); } - public function testReverseLookupIgnoresIpv6ZoneId() + public function testReverseLookupIgnoresIpv6ZoneId(): void { $hosts = new HostsFile('fe80::1%lo0 localhost'); $this->assertEquals(['localhost'], $hosts->getHostsForIp('fe80::1')); } - public function testReverseLookupReturnsMultipleHostsOverSingleLine() + public function testReverseLookupReturnsMultipleHostsOverSingleLine(): void { $hosts = new HostsFile("::1 ip6-localhost ip6-loopback"); $this->assertEquals(['ip6-localhost', 'ip6-loopback'], $hosts->getHostsForIp('::1')); } - public function testReverseLookupReturnsMultipleHostsOverMultipleLines() + public function testReverseLookupReturnsMultipleHostsOverMultipleLines(): void { $hosts = new HostsFile("::1 ip6-localhost\n::1 ip6-loopback"); diff --git a/tests/FunctionalResolverTest.php b/tests/FunctionalResolverTest.php index 25900cc0..95e0fc1a 100644 --- a/tests/FunctionalResolverTest.php +++ b/tests/FunctionalResolverTest.php @@ -6,22 +6,26 @@ use React\Dns\Query\CancellationException; use React\Dns\RecordNotFoundException; use React\Dns\Resolver\Factory; +use React\Dns\Resolver\ResolverInterface; use React\EventLoop\Loop; class FunctionalResolverTest extends TestCase { + /** + * @var ResolverInterface + */ private $resolver; /** * @before */ - public function setUpResolver() + public function setUpResolver(): void { $factory = new Factory(); $this->resolver = $factory->create('8.8.8.8'); } - public function testResolveLocalhostResolves() + public function testResolveLocalhostResolves(): void { $promise = $this->resolver->resolve('localhost'); $promise->then($this->expectCallableOnce(), $this->expectCallableNever()); @@ -29,7 +33,7 @@ public function testResolveLocalhostResolves() Loop::run(); } - public function testResolveAllLocalhostResolvesWithArray() + public function testResolveAllLocalhostResolvesWithArray(): void { $promise = $this->resolver->resolveAll('localhost', Message::TYPE_A); $promise->then($this->expectCallableOnceWith($this->isType('array')), $this->expectCallableNever()); @@ -40,7 +44,7 @@ public function testResolveAllLocalhostResolvesWithArray() /** * @group internet */ - public function testResolveGoogleResolves() + public function testResolveGoogleResolves(): void { $promise = $this->resolver->resolve('google.com'); $promise->then($this->expectCallableOnce(), $this->expectCallableNever()); @@ -51,7 +55,7 @@ public function testResolveGoogleResolves() /** * @group internet */ - public function testResolveGoogleOverUdpResolves() + public function testResolveGoogleOverUdpResolves(): void { $factory = new Factory(); $this->resolver = $factory->create('udp://8.8.8.8'); @@ -65,7 +69,7 @@ public function testResolveGoogleOverUdpResolves() /** * @group internet */ - public function testResolveGoogleOverTcpResolves() + public function testResolveGoogleOverTcpResolves(): void { $factory = new Factory(); $this->resolver = $factory->create('tcp://8.8.8.8'); @@ -79,7 +83,7 @@ public function testResolveGoogleOverTcpResolves() /** * @group internet */ - public function testResolveAllGoogleMxResolvesWithCache() + public function testResolveAllGoogleMxResolvesWithCache(): void { $factory = new Factory(); $this->resolver = $factory->createCached('8.8.8.8'); @@ -92,7 +96,7 @@ public function testResolveAllGoogleMxResolvesWithCache() /** * @group internet */ - public function testResolveAllGoogleCaaResolvesWithCache() + public function testResolveAllGoogleCaaResolvesWithCache(): void { $factory = new Factory(); $this->resolver = $factory->createCached('8.8.8.8'); @@ -106,7 +110,7 @@ public function testResolveAllGoogleCaaResolvesWithCache() /** * @group internet */ - public function testResolveInvalidRejects() + public function testResolveInvalidRejects(): void { $promise = $this->resolver->resolve('example.invalid'); @@ -123,7 +127,7 @@ public function testResolveInvalidRejects() $this->assertEquals(Message::RCODE_NAME_ERROR, $exception->getCode()); } - public function testResolveCancelledRejectsImmediately() + public function testResolveCancelledRejectsImmediately(): void { $promise = $this->resolver->resolve('google.com'); $promise->cancel(); @@ -147,7 +151,7 @@ public function testResolveCancelledRejectsImmediately() /** * @group internet */ - public function testResolveAllInvalidTypeRejects() + public function testResolveAllInvalidTypeRejects(): void { $promise = $this->resolver->resolveAll('google.com', Message::TYPE_PTR); @@ -164,7 +168,7 @@ public function testResolveAllInvalidTypeRejects() $this->assertEquals(0, $exception->getCode()); } - public function testInvalidResolverDoesNotResolveGoogle() + public function testInvalidResolverDoesNotResolveGoogle(): void { $factory = new Factory(); $this->resolver = $factory->create('255.255.255.255'); @@ -173,7 +177,7 @@ public function testInvalidResolverDoesNotResolveGoogle() $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); } - public function testResolveShouldNotCauseGarbageReferencesWhenUsingInvalidNameserver() + public function testResolveShouldNotCauseGarbageReferencesWhenUsingInvalidNameserver(): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); @@ -195,7 +199,7 @@ public function testResolveShouldNotCauseGarbageReferencesWhenUsingInvalidNamese $this->assertEquals(0, gc_collect_cycles()); } - public function testResolveCachedShouldNotCauseGarbageReferencesWhenUsingInvalidNameserver() + public function testResolveCachedShouldNotCauseGarbageReferencesWhenUsingInvalidNameserver(): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); @@ -217,7 +221,7 @@ public function testResolveCachedShouldNotCauseGarbageReferencesWhenUsingInvalid $this->assertEquals(0, gc_collect_cycles()); } - public function testCancelResolveShouldNotCauseGarbageReferences() + public function testCancelResolveShouldNotCauseGarbageReferences(): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); @@ -237,7 +241,7 @@ public function testCancelResolveShouldNotCauseGarbageReferences() $this->assertEquals(0, gc_collect_cycles()); } - public function testCancelResolveCachedShouldNotCauseGarbageReferences() + public function testCancelResolveCachedShouldNotCauseGarbageReferences(): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); diff --git a/tests/Model/MessageTest.php b/tests/Model/MessageTest.php index ba0abcc9..aa44cf9e 100644 --- a/tests/Model/MessageTest.php +++ b/tests/Model/MessageTest.php @@ -8,7 +8,7 @@ class MessageTest extends TestCase { - public function testCreateRequestDesiresRecusion() + public function testCreateRequestDesiresRecusion(): void { $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN); $request = Message::createRequestForQuery($query); @@ -17,7 +17,7 @@ public function testCreateRequestDesiresRecusion() $this->assertTrue($request->rd); } - public function testCreateResponseWithNoAnswers() + public function testCreateResponseWithNoAnswers(): void { $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN); $answers = []; diff --git a/tests/Protocol/BinaryDumperTest.php b/tests/Protocol/BinaryDumperTest.php index 64d6b981..92588580 100644 --- a/tests/Protocol/BinaryDumperTest.php +++ b/tests/Protocol/BinaryDumperTest.php @@ -10,7 +10,7 @@ class BinaryDumperTest extends TestCase { - public function testToBinaryRequestMessage() + public function testToBinaryRequestMessage(): void { $data = ""; $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header @@ -36,7 +36,7 @@ public function testToBinaryRequestMessage() $this->assertSame($expected, $data); } - public function testToBinaryRequestMessageWithUnknownAuthorityTypeEncodesValueAsBinary() + public function testToBinaryRequestMessageWithUnknownAuthorityTypeEncodesValueAsBinary(): void { $data = ""; $data .= "72 62 01 00 00 01 00 00 00 01 00 00"; // header @@ -66,7 +66,7 @@ public function testToBinaryRequestMessageWithUnknownAuthorityTypeEncodesValueAs $this->assertSame($expected, $data); } - public function testToBinaryRequestMessageWithAdditionalOptForEdns0() + public function testToBinaryRequestMessageWithAdditionalOptForEdns0(): void { $data = ""; $data .= "72 62 01 00 00 01 00 00 00 00 00 01"; // header @@ -96,7 +96,7 @@ public function testToBinaryRequestMessageWithAdditionalOptForEdns0() $this->assertSame($expected, $data); } - public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithOptTcpKeepAliveDesired() + public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithOptTcpKeepAliveDesired(): void { $data = ""; $data .= "72 62 01 00 00 01 00 00 00 00 00 01"; // header @@ -129,7 +129,7 @@ public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithOptTcpKee $this->assertSame($expected, $data); } - public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithOptTcpKeepAliveGiven() + public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithOptTcpKeepAliveGiven(): void { $data = ""; $data .= "72 62 01 00 00 01 00 00 00 00 00 01"; // header @@ -162,7 +162,7 @@ public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithOptTcpKee $this->assertSame($expected, $data); } - public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithOptPadding() + public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithOptPadding(): void { $data = ""; $data .= "72 62 01 00 00 01 00 00 00 00 00 01"; // header @@ -195,7 +195,7 @@ public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithOptPaddin $this->assertSame($expected, $data); } - public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithCustomOptCodes() + public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithCustomOptCodes(): void { $data = ""; $data .= "72 62 01 00 00 01 00 00 00 00 00 01"; // header @@ -230,7 +230,7 @@ public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithCustomOpt $this->assertSame($expected, $data); } - public function testToBinaryResponseMessageWithoutRecords() + public function testToBinaryResponseMessageWithoutRecords(): void { $data = ""; $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header @@ -257,7 +257,7 @@ public function testToBinaryResponseMessageWithoutRecords() $this->assertSame($expected, $data); } - public function testToBinaryForResponseWithSRVRecord() + public function testToBinaryForResponseWithSRVRecord(): void { $data = ""; $data .= "72 62 01 00 00 01 00 01 00 00 00 00"; // header @@ -296,7 +296,7 @@ public function testToBinaryForResponseWithSRVRecord() $this->assertSame($expected, $data); } - public function testToBinaryForResponseWithSOARecord() + public function testToBinaryForResponseWithSOARecord(): void { $data = ""; $data .= "72 62 01 00 00 01 00 01 00 00 00 00"; // header @@ -341,7 +341,7 @@ public function testToBinaryForResponseWithSOARecord() $this->assertSame($expected, $data); } - public function testToBinaryForResponseWithPTRRecordWithSpecialCharactersEscaped() + public function testToBinaryForResponseWithPTRRecordWithSpecialCharactersEscaped(): void { $data = ""; $data .= "72 62 01 00 00 01 00 01 00 00 00 00"; // header @@ -382,7 +382,7 @@ public function testToBinaryForResponseWithPTRRecordWithSpecialCharactersEscaped $this->assertSame($expected, $data); } - public function testToBinaryForResponseWithMultipleAnswerRecords() + public function testToBinaryForResponseWithMultipleAnswerRecords(): void { $data = ""; $data .= "72 62 01 00 00 01 00 07 00 00 00 00"; // header @@ -446,7 +446,7 @@ public function testToBinaryForResponseWithMultipleAnswerRecords() $this->assertSame($expected, $data); } - public function testToBinaryForResponseWithAnswerAndAdditionalRecord() + public function testToBinaryForResponseWithAnswerAndAdditionalRecord(): void { $data = ""; $data .= "72 62 01 00 00 01 00 01 00 00 00 01"; // header @@ -482,12 +482,13 @@ public function testToBinaryForResponseWithAnswerAndAdditionalRecord() $this->assertSame($expected, $data); } - private function convertBinaryToHexDump($input) + private function convertBinaryToHexDump(string $input): string { + /** @phpstan-ignore-next-line unpack won't error on this line as our format is correct */ return $this->formatHexDump(implode('', unpack('H*', $input))); } - private function formatHexDump($input) + private function formatHexDump(string $input): string { return implode(' ', str_split(str_replace(' ', '', $input), 2)); } diff --git a/tests/Protocol/ParserTest.php b/tests/Protocol/ParserTest.php index dfd9e1c7..7ea65eac 100644 --- a/tests/Protocol/ParserTest.php +++ b/tests/Protocol/ParserTest.php @@ -8,12 +8,15 @@ class ParserTest extends TestCase { + /** + * @var Parser + */ private $parser; /** * @before */ - public function setUpParser() + public function setUpParser(): void { $this->parser = new Parser(); } @@ -21,12 +24,15 @@ public function setUpParser() /** * @dataProvider provideConvertTcpDumpToBinary */ - public function testConvertTcpDumpToBinary($expected, $data) + public function testConvertTcpDumpToBinary(string $expected, string $data): void { $this->assertSame($expected, $this->convertTcpDumpToBinary($data)); } - public function provideConvertTcpDumpToBinary() + /** + * @return iterable + */ + public function provideConvertTcpDumpToBinary(): iterable { yield [chr(0x72).chr(0x62), "72 62"]; yield [chr(0x72).chr(0x62).chr(0x01).chr(0x00), "72 62 01 00"]; @@ -34,7 +40,7 @@ public function provideConvertTcpDumpToBinary() yield [chr(0x01).chr(0x00).chr(0x01), "01 00 01"]; } - public function testParseRequest() + public function testParseRequest(): void { $data = ""; $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header @@ -63,7 +69,7 @@ public function testParseRequest() $this->assertSame(Message::CLASS_IN, $request->questions[0]->class); } - public function testParseResponse() + public function testParseResponse(): void { $data = ""; $data .= "72 62 81 80 00 01 00 01 00 00 00 00"; // header @@ -101,7 +107,7 @@ public function testParseResponse() $this->assertSame('178.79.169.131', $response->answers[0]->data); } - public function testParseRequestWithTwoQuestions() + public function testParseRequestWithTwoQuestions(): void { $data = ""; $data .= "72 62 01 00 00 02 00 00 00 00 00 00"; // header @@ -123,7 +129,7 @@ public function testParseRequestWithTwoQuestions() $this->assertSame(Message::CLASS_IN, $request->questions[1]->class); } - public function testParseAnswerWithInlineData() + public function testParseAnswerWithInlineData(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -142,7 +148,7 @@ public function testParseAnswerWithInlineData() $this->assertSame('178.79.169.131', $response->answers[0]->data); } - public function testParseAnswerWithExcessiveTtlReturnsZeroTtl() + public function testParseAnswerWithExcessiveTtlReturnsZeroTtl(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -161,7 +167,7 @@ public function testParseAnswerWithExcessiveTtlReturnsZeroTtl() $this->assertSame('178.79.169.131', $response->answers[0]->data); } - public function testParseAnswerWithTtlExactlyBoundaryReturnsZeroTtl() + public function testParseAnswerWithTtlExactlyBoundaryReturnsZeroTtl(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -180,7 +186,7 @@ public function testParseAnswerWithTtlExactlyBoundaryReturnsZeroTtl() $this->assertSame('178.79.169.131', $response->answers[0]->data); } - public function testParseAnswerWithMaximumTtlReturnsExactTtl() + public function testParseAnswerWithMaximumTtlReturnsExactTtl(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -199,7 +205,7 @@ public function testParseAnswerWithMaximumTtlReturnsExactTtl() $this->assertSame('178.79.169.131', $response->answers[0]->data); } - public function testParseAnswerWithUnknownType() + public function testParseAnswerWithUnknownType(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -218,7 +224,7 @@ public function testParseAnswerWithUnknownType() $this->assertSame('hello', $response->answers[0]->data); } - public function testParseResponseWithCnameAndOffsetPointers() + public function testParseResponseWithCnameAndOffsetPointers(): void { $data = ""; $data .= "9e 8d 81 80 00 01 00 01 00 00 00 00"; // header @@ -248,7 +254,7 @@ public function testParseResponseWithCnameAndOffsetPointers() $this->assertSame('googlemail.l.google.com', $response->answers[0]->data); } - public function testParseAAAAResponse() + public function testParseAAAAResponse(): void { $data = ""; $data .= "cd 72 81 80 00 01 00 01 00 00 00 00 06"; // header @@ -286,7 +292,7 @@ public function testParseAAAAResponse() $this->assertSame('2a00:1450:4009:809::200e', $response->answers[0]->data); } - public function testParseTXTResponse() + public function testParseTXTResponse(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -305,7 +311,7 @@ public function testParseTXTResponse() $this->assertSame(['hello'], $response->answers[0]->data); } - public function testParseSPFResponse() + public function testParseSPFResponse(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -324,7 +330,7 @@ public function testParseSPFResponse() $this->assertSame(['hello'], $response->answers[0]->data); } - public function testParseTXTResponseMultiple() + public function testParseTXTResponseMultiple(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -343,7 +349,7 @@ public function testParseTXTResponseMultiple() $this->assertSame(['hello', 'world'], $response->answers[0]->data); } - public function testParseMXResponse() + public function testParseMXResponse(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -362,7 +368,7 @@ public function testParseMXResponse() $this->assertSame(['priority' => 10, 'target' => 'hello'], $response->answers[0]->data); } - public function testParseSRVResponse() + public function testParseSRVResponse(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -389,7 +395,7 @@ public function testParseSRVResponse() ); } - public function testParseMessageResponseWithTwoAnswers() + public function testParseMessageResponseWithTwoAnswers(): void { $data = ""; $data .= "bc 73 81 80 00 01 00 02 00 00 00 00"; // header @@ -431,7 +437,7 @@ public function testParseMessageResponseWithTwoAnswers() $this->assertSame('193.223.78.152', $response->answers[1]->data); } - public function testParseMessageResponseWithTwoAuthorityRecords() + public function testParseMessageResponseWithTwoAuthorityRecords(): void { $data = ""; $data .= "bc 73 81 80 00 01 00 00 00 02 00 00"; // header @@ -475,7 +481,7 @@ public function testParseMessageResponseWithTwoAuthorityRecords() $this->assertSame('193.223.78.152', $response->authority[1]->data); } - public function testParseMessageResponseWithAnswerAndAdditionalRecord() + public function testParseMessageResponseWithAnswerAndAdditionalRecord(): void { $data = ""; $data .= "bc 73 81 80 00 01 00 01 00 00 00 01"; // header @@ -520,7 +526,7 @@ public function testParseMessageResponseWithAnswerAndAdditionalRecord() $this->assertSame('193.223.78.152', $response->additional[0]->data); } - public function testParseNSResponse() + public function testParseNSResponse(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -539,7 +545,7 @@ public function testParseNSResponse() $this->assertSame('hello', $response->answers[0]->data); } - public function testParseSSHFPResponse() + public function testParseSSHFPResponse(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -558,7 +564,7 @@ public function testParseSSHFPResponse() $this->assertSame(['algorithm' => 1, 'type' => 1, 'fingerprint' => '69ac090c'], $response->answers[0]->data); } - public function testParseOptResponseWithoutOptions() + public function testParseOptResponseWithoutOptions(): void { $data = ""; $data .= "00"; // answer: empty domain @@ -576,7 +582,7 @@ public function testParseOptResponseWithoutOptions() $this->assertSame([], $response->answers[0]->data); } - public function testParseOptResponseWithOptTcpKeepaliveDesired() + public function testParseOptResponseWithOptTcpKeepaliveDesired(): void { $data = ""; $data .= "00"; // answer: empty domain @@ -595,7 +601,7 @@ public function testParseOptResponseWithOptTcpKeepaliveDesired() $this->assertSame([Message::OPT_TCP_KEEPALIVE => null], $response->answers[0]->data); } - public function testParseOptResponseWithOptTcpKeepaliveGiven() + public function testParseOptResponseWithOptTcpKeepaliveGiven(): void { $data = ""; $data .= "00"; // answer: empty domain @@ -614,7 +620,7 @@ public function testParseOptResponseWithOptTcpKeepaliveGiven() $this->assertSame([Message::OPT_TCP_KEEPALIVE => 1.2], $response->answers[0]->data); } - public function testParseOptResponseWithCustomOptions() + public function testParseOptResponseWithCustomOptions(): void { $data = ""; $data .= "00"; // answer: empty domain @@ -634,7 +640,7 @@ public function testParseOptResponseWithCustomOptions() $this->assertSame([0xa0 => 'foo', 0x01 => ''], $response->answers[0]->data); } - public function testParseSOAResponse() + public function testParseSOAResponse(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -667,7 +673,7 @@ public function testParseSOAResponse() ); } - public function testParseCAAResponse() + public function testParseCAAResponse(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -687,7 +693,7 @@ public function testParseCAAResponse() $this->assertSame(['flag' => 0, 'tag' => 'issue', 'value' => 'letsencrypt.org'], $response->answers[0]->data); } - public function testParsePTRResponse() + public function testParsePTRResponse(): void { $data = ""; $data .= "5d d8 81 80 00 01 00 01 00 00 00 00"; // header @@ -727,7 +733,7 @@ public function testParsePTRResponse() $this->assertSame('google-public-dns-b.google.com', $response->answers[0]->data); } - public function testParsePTRResponseWithSpecialCharactersEscaped() + public function testParsePTRResponseWithSpecialCharactersEscaped(): void { $data = ""; $data .= "5d d8 81 80 00 01 00 01 00 00 00 00"; // header @@ -757,7 +763,7 @@ public function testParsePTRResponseWithSpecialCharactersEscaped() $this->assertSame('3rd\.\ Floor\ Copy\ Room._printer._tcp.dns-sd.org', $response->answers[0]->data); } - public function testParseIncompleteQuestionThrows() + public function testParseIncompleteQuestionThrows(): void { $data = ""; $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header @@ -770,7 +776,7 @@ public function testParseIncompleteQuestionThrows() $this->parser->parseMessage($data); } - public function testParseIncompleteQuestionLabelThrows() + public function testParseIncompleteQuestionLabelThrows(): void { $data = ""; $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header @@ -782,7 +788,7 @@ public function testParseIncompleteQuestionLabelThrows() $this->parser->parseMessage($data); } - public function testParseIncompleteQuestionNameThrows() + public function testParseIncompleteQuestionNameThrows(): void { $data = ""; $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header @@ -794,7 +800,7 @@ public function testParseIncompleteQuestionNameThrows() $this->parser->parseMessage($data); } - public function testParseIncompleteOffsetPointerInQuestionNameThrows() + public function testParseIncompleteOffsetPointerInQuestionNameThrows(): void { $data = ""; $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header @@ -806,7 +812,7 @@ public function testParseIncompleteOffsetPointerInQuestionNameThrows() $this->parser->parseMessage($data); } - public function testParseInvalidOffsetPointerInQuestionNameThrows() + public function testParseInvalidOffsetPointerInQuestionNameThrows(): void { $data = ""; $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header @@ -818,7 +824,7 @@ public function testParseInvalidOffsetPointerInQuestionNameThrows() $this->parser->parseMessage($data); } - public function testParseInvalidOffsetPointerToSameLabelInQuestionNameThrows() + public function testParseInvalidOffsetPointerToSameLabelInQuestionNameThrows(): void { $data = ""; $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header @@ -830,7 +836,7 @@ public function testParseInvalidOffsetPointerToSameLabelInQuestionNameThrows() $this->parser->parseMessage($data); } - public function testParseInvalidOffsetPointerToPreviousLabelInQuestionNameThrows() + public function testParseInvalidOffsetPointerToPreviousLabelInQuestionNameThrows(): void { $data = ""; $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header @@ -842,7 +848,7 @@ public function testParseInvalidOffsetPointerToPreviousLabelInQuestionNameThrows $this->parser->parseMessage($data); } - public function testParseInvalidOffsetPointerToStartOfMessageInQuestionNameThrows() + public function testParseInvalidOffsetPointerToStartOfMessageInQuestionNameThrows(): void { $data = ""; $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header @@ -854,7 +860,7 @@ public function testParseInvalidOffsetPointerToStartOfMessageInQuestionNameThrow $this->parser->parseMessage($data); } - public function testParseIncompleteAnswerFieldsThrows() + public function testParseIncompleteAnswerFieldsThrows(): void { $data = ""; $data .= "72 62 81 80 00 01 00 01 00 00 00 00"; // header @@ -868,7 +874,7 @@ public function testParseIncompleteAnswerFieldsThrows() $this->parser->parseMessage($data); } - public function testParseMessageResponseWithIncompleteAuthorityRecordThrows() + public function testParseMessageResponseWithIncompleteAuthorityRecordThrows(): void { $data = ""; $data .= "72 62 81 80 00 01 00 00 00 01 00 00"; // header @@ -882,7 +888,7 @@ public function testParseMessageResponseWithIncompleteAuthorityRecordThrows() $this->parser->parseMessage($data); } - public function testParseMessageResponseWithIncompleteAdditionalRecordThrows() + public function testParseMessageResponseWithIncompleteAdditionalRecordThrows(): void { $data = ""; $data .= "72 62 81 80 00 01 00 00 00 00 00 01"; // header @@ -896,7 +902,7 @@ public function testParseMessageResponseWithIncompleteAdditionalRecordThrows() $this->parser->parseMessage($data); } - public function testParseIncompleteAnswerRecordDataThrows() + public function testParseIncompleteAnswerRecordDataThrows(): void { $data = ""; $data .= "72 62 81 80 00 01 00 01 00 00 00 00"; // header @@ -913,7 +919,7 @@ public function testParseIncompleteAnswerRecordDataThrows() $this->parser->parseMessage($data); } - public function testParseInvalidNSResponseWhereDomainNameIsMissing() + public function testParseInvalidNSResponseWhereDomainNameIsMissing(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -925,7 +931,7 @@ public function testParseInvalidNSResponseWhereDomainNameIsMissing() $this->parseAnswer($data); } - public function testParseInvalidAResponseWhereIPIsMissing() + public function testParseInvalidAResponseWhereIPIsMissing(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -937,7 +943,7 @@ public function testParseInvalidAResponseWhereIPIsMissing() $this->parseAnswer($data); } - public function testParseInvalidAAAAResponseWhereIPIsMissing() + public function testParseInvalidAAAAResponseWhereIPIsMissing(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -949,7 +955,7 @@ public function testParseInvalidAAAAResponseWhereIPIsMissing() $this->parseAnswer($data); } - public function testParseInvalidTXTResponseWhereTxtChunkExceedsLimit() + public function testParseInvalidTXTResponseWhereTxtChunkExceedsLimit(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -962,7 +968,7 @@ public function testParseInvalidTXTResponseWhereTxtChunkExceedsLimit() $this->parseAnswer($data); } - public function testParseInvalidMXResponseWhereDomainNameIsIncomplete() + public function testParseInvalidMXResponseWhereDomainNameIsIncomplete(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -975,7 +981,7 @@ public function testParseInvalidMXResponseWhereDomainNameIsIncomplete() $this->parseAnswer($data); } - public function testParseInvalidMXResponseWhereDomainNameIsMissing() + public function testParseInvalidMXResponseWhereDomainNameIsMissing(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -988,7 +994,7 @@ public function testParseInvalidMXResponseWhereDomainNameIsMissing() $this->parseAnswer($data); } - public function testParseInvalidSRVResponseWhereDomainNameIsIncomplete() + public function testParseInvalidSRVResponseWhereDomainNameIsIncomplete(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -1001,7 +1007,7 @@ public function testParseInvalidSRVResponseWhereDomainNameIsIncomplete() $this->parseAnswer($data); } - public function testParseInvalidSRVResponseWhereDomainNameIsMissing() + public function testParseInvalidSRVResponseWhereDomainNameIsMissing(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -1014,7 +1020,7 @@ public function testParseInvalidSRVResponseWhereDomainNameIsMissing() $this->parseAnswer($data); } - public function testParseInvalidSSHFPResponseWhereRecordIsTooSmall() + public function testParseInvalidSSHFPResponseWhereRecordIsTooSmall(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -1027,7 +1033,7 @@ public function testParseInvalidSSHFPResponseWhereRecordIsTooSmall() $this->parseAnswer($data); } - public function testParseInvalidOPTResponseWhereRecordIsTooSmall() + public function testParseInvalidOPTResponseWhereRecordIsTooSmall(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -1040,7 +1046,7 @@ public function testParseInvalidOPTResponseWhereRecordIsTooSmall() $this->parseAnswer($data); } - public function testParseInvalidOPTResponseWhereRecordLengthDoesNotMatchOptType() + public function testParseInvalidOPTResponseWhereRecordLengthDoesNotMatchOptType(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -1053,7 +1059,7 @@ public function testParseInvalidOPTResponseWhereRecordLengthDoesNotMatchOptType( $this->parseAnswer($data); } - public function testParseInvalidSOAResponseWhereFlagsAreMissing() + public function testParseInvalidSOAResponseWhereFlagsAreMissing(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -1067,7 +1073,7 @@ public function testParseInvalidSOAResponseWhereFlagsAreMissing() $this->parseAnswer($data); } - public function testParseInvalidCAAResponseEmtpyData() + public function testParseInvalidCAAResponseEmtpyData(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -1079,7 +1085,7 @@ public function testParseInvalidCAAResponseEmtpyData() $this->parseAnswer($data); } - public function testParseInvalidCAAResponseMissingValue() + public function testParseInvalidCAAResponseMissingValue(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -1092,7 +1098,7 @@ public function testParseInvalidCAAResponseMissingValue() $this->parseAnswer($data); } - public function testParseInvalidCAAResponseIncompleteTag() + public function testParseInvalidCAAResponseIncompleteTag(): void { $data = ""; $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io @@ -1106,14 +1112,14 @@ public function testParseInvalidCAAResponseIncompleteTag() $this->parseAnswer($data); } - private function convertTcpDumpToBinary($input) + private function convertTcpDumpToBinary(string $input): string { // sudo ngrep -d en1 -x port 53 return pack('H*', str_replace(' ', '', $input)); } - private function parseAnswer($answerData) + private function parseAnswer(string $answerData): Message { $data = "72 62 81 80 00 00 00 01 00 00 00 00"; // header with one answer only $data .= $answerData; diff --git a/tests/Query/CachingExecutorTest.php b/tests/Query/CachingExecutorTest.php index f51299f3..8de6eb18 100644 --- a/tests/Query/CachingExecutorTest.php +++ b/tests/Query/CachingExecutorTest.php @@ -16,7 +16,7 @@ class CachingExecutorTest extends TestCase { - public function testQueryWillReturnPendingPromiseWhenCacheIsPendingWithoutSendingQueryToFallbackExecutor() + public function testQueryWillReturnPendingPromiseWhenCacheIsPendingWithoutSendingQueryToFallbackExecutor(): void { $fallback = $this->createMock(ExecutorInterface::class); $fallback->expects($this->never())->method('query'); @@ -33,7 +33,7 @@ public function testQueryWillReturnPendingPromiseWhenCacheIsPendingWithoutSendin $promise->then($this->expectCallableNever(), $this->expectCallableNever()); } - public function testQueryWillReturnPendingPromiseWhenCacheReturnsMissAndWillSendSameQueryToFallbackExecutor() + public function testQueryWillReturnPendingPromiseWhenCacheReturnsMissAndWillSendSameQueryToFallbackExecutor(): void { $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN); @@ -50,7 +50,7 @@ public function testQueryWillReturnPendingPromiseWhenCacheReturnsMissAndWillSend $promise->then($this->expectCallableNever(), $this->expectCallableNever()); } - public function testQueryWillReturnResolvedPromiseWhenCacheReturnsHitWithoutSendingQueryToFallbackExecutor() + public function testQueryWillReturnResolvedPromiseWhenCacheReturnsHitWithoutSendingQueryToFallbackExecutor(): void { $fallback = $this->createMock(ExecutorInterface::class); $fallback->expects($this->never())->method('query'); @@ -68,7 +68,7 @@ public function testQueryWillReturnResolvedPromiseWhenCacheReturnsHitWithoutSend $promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever()); } - public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesAndSaveMessageToCacheWithMinimumTtlFromRecord() + public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesAndSaveMessageToCacheWithMinimumTtlFromRecord(): void { $message = new Message(); $message->answers[] = new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3700, '127.0.0.1'); @@ -89,7 +89,7 @@ public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbac $promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever()); } - public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesAndSaveMessageToCacheWithDefaultTtl() + public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesAndSaveMessageToCacheWithDefaultTtl(): void { $message = new Message(); $fallback = $this->createMock(ExecutorInterface::class); @@ -108,7 +108,7 @@ public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbac $promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever()); } - public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesWithTruncatedResponseButShouldNotSaveTruncatedMessageToCache() + public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesWithTruncatedResponseButShouldNotSaveTruncatedMessageToCache(): void { $message = new Message(); $message->tc = true; @@ -128,7 +128,7 @@ public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbac $promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever()); } - public function testQueryWillReturnRejectedPromiseWhenCacheReturnsMissAndFallbackExecutorRejects() + public function testQueryWillReturnRejectedPromiseWhenCacheReturnsMissAndFallbackExecutorRejects(): void { $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN); @@ -145,7 +145,7 @@ public function testQueryWillReturnRejectedPromiseWhenCacheReturnsMissAndFallbac $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($exception)); } - public function testCancelQueryWillReturnRejectedPromiseAndCancelPendingPromiseFromCache() + public function testCancelQueryWillReturnRejectedPromiseAndCancelPendingPromiseFromCache(): void { $fallback = $this->createMock(ExecutorInterface::class); $fallback->expects($this->never())->method('query'); @@ -171,7 +171,7 @@ public function testCancelQueryWillReturnRejectedPromiseAndCancelPendingPromiseF $this->assertEquals('DNS query for reactphp.org (A) has been cancelled', $exception->getMessage()); } - public function testCancelQueryWillReturnRejectedPromiseAndCancelPendingPromiseFromFallbackExecutorWhenCacheReturnsMiss() + public function testCancelQueryWillReturnRejectedPromiseAndCancelPendingPromiseFromFallbackExecutorWhenCacheReturnsMiss(): void { $pending = new Promise(function () { }, $this->expectCallableOnce()); $fallback = $this->createMock(ExecutorInterface::class); diff --git a/tests/Query/CoopExecutorTest.php b/tests/Query/CoopExecutorTest.php index 52ab4104..2bd4737c 100644 --- a/tests/Query/CoopExecutorTest.php +++ b/tests/Query/CoopExecutorTest.php @@ -13,7 +13,7 @@ class CoopExecutorTest extends TestCase { - public function testQueryOnceWillPassExactQueryToBaseExecutor() + public function testQueryOnceWillPassExactQueryToBaseExecutor(): void { $pending = new Promise(function () { }); $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN); @@ -24,7 +24,7 @@ public function testQueryOnceWillPassExactQueryToBaseExecutor() $connector->query($query); } - public function testQueryOnceWillResolveWhenBaseExecutorResolves() + public function testQueryOnceWillResolveWhenBaseExecutorResolves(): void { $message = new Message(); @@ -40,7 +40,7 @@ public function testQueryOnceWillResolveWhenBaseExecutorResolves() $promise->then($this->expectCallableOnceWith($message)); } - public function testQueryOnceWillRejectWhenBaseExecutorRejects() + public function testQueryOnceWillRejectWhenBaseExecutorRejects(): void { $exception = new RuntimeException(); @@ -56,7 +56,7 @@ public function testQueryOnceWillRejectWhenBaseExecutorRejects() $promise->then(null, $this->expectCallableOnceWith($exception)); } - public function testQueryTwoDifferentQueriesWillPassExactQueryToBaseExecutorTwice() + public function testQueryTwoDifferentQueriesWillPassExactQueryToBaseExecutorTwice(): void { $pending = new Promise(function () { }); $query1 = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN); @@ -72,7 +72,7 @@ public function testQueryTwoDifferentQueriesWillPassExactQueryToBaseExecutorTwic $connector->query($query2); } - public function testQueryTwiceWillPassExactQueryToBaseExecutorOnceWhenQueryIsStillPending() + public function testQueryTwiceWillPassExactQueryToBaseExecutorOnceWhenQueryIsStillPending(): void { $pending = new Promise(function () { }); $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN); @@ -84,7 +84,7 @@ public function testQueryTwiceWillPassExactQueryToBaseExecutorOnceWhenQueryIsSti $connector->query($query); } - public function testQueryTwiceWillPassExactQueryToBaseExecutorTwiceWhenFirstQueryIsAlreadyResolved() + public function testQueryTwiceWillPassExactQueryToBaseExecutorTwiceWhenFirstQueryIsAlreadyResolved(): void { $deferred = new Deferred(); $pending = new Promise(function () { }); @@ -101,7 +101,7 @@ public function testQueryTwiceWillPassExactQueryToBaseExecutorTwiceWhenFirstQuer $connector->query($query); } - public function testQueryTwiceWillPassExactQueryToBaseExecutorTwiceWhenFirstQueryIsAlreadyRejected() + public function testQueryTwiceWillPassExactQueryToBaseExecutorTwiceWhenFirstQueryIsAlreadyRejected(): void { $deferred = new Deferred(); $pending = new Promise(function () { }); @@ -120,7 +120,7 @@ public function testQueryTwiceWillPassExactQueryToBaseExecutorTwiceWhenFirstQuer $connector->query($query); } - public function testCancelQueryWillCancelPromiseFromBaseExecutorAndReject() + public function testCancelQueryWillCancelPromiseFromBaseExecutorAndReject(): void { $promise = new Promise(function () { }, $this->expectCallableOnce()); @@ -143,7 +143,7 @@ public function testCancelQueryWillCancelPromiseFromBaseExecutorAndReject() $this->assertEquals('DNS query for reactphp.org (A) has been cancelled', $exception->getMessage()); } - public function testCancelOneQueryWhenOtherQueryIsStillPendingWillNotCancelPromiseFromBaseExecutorAndRejectCancelled() + public function testCancelOneQueryWhenOtherQueryIsStillPendingWillNotCancelPromiseFromBaseExecutorAndRejectCancelled(): void { $promise = new Promise(function () { }, $this->expectCallableNever()); @@ -161,7 +161,7 @@ public function testCancelOneQueryWhenOtherQueryIsStillPendingWillNotCancelPromi $promise2->then(null, $this->expectCallableNever()); } - public function testCancelSecondQueryWhenFirstQueryIsStillPendingWillNotCancelPromiseFromBaseExecutorAndRejectCancelled() + public function testCancelSecondQueryWhenFirstQueryIsStillPendingWillNotCancelPromiseFromBaseExecutorAndRejectCancelled(): void { $promise = new Promise(function () { }, $this->expectCallableNever()); @@ -179,7 +179,7 @@ public function testCancelSecondQueryWhenFirstQueryIsStillPendingWillNotCancelPr $promise1->then(null, $this->expectCallableNever()); } - public function testCancelAllPendingQueriesWillCancelPromiseFromBaseExecutorAndRejectCancelled() + public function testCancelAllPendingQueriesWillCancelPromiseFromBaseExecutorAndRejectCancelled(): void { $promise = new Promise(function () { }, $this->expectCallableOnce()); @@ -198,7 +198,7 @@ public function testCancelAllPendingQueriesWillCancelPromiseFromBaseExecutorAndR $promise2->then(null, $this->expectCallableOnce()); } - public function testQueryTwiceWillQueryBaseExecutorTwiceIfFirstQueryHasAlreadyBeenCancelledWhenSecondIsStarted() + public function testQueryTwiceWillQueryBaseExecutorTwiceIfFirstQueryHasAlreadyBeenCancelledWhenSecondIsStarted(): void { $promise = new Promise(function () { }, $this->expectCallableOnce()); $pending = new Promise(function () { }); @@ -219,7 +219,7 @@ public function testQueryTwiceWillQueryBaseExecutorTwiceIfFirstQueryHasAlreadyBe $promise2->then(null, $this->expectCallableNever()); } - public function testCancelQueryShouldNotCauseGarbageReferences() + public function testCancelQueryShouldNotCauseGarbageReferences(): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); diff --git a/tests/Query/FallbackExecutorTest.php b/tests/Query/FallbackExecutorTest.php index 434aa71e..54194051 100644 --- a/tests/Query/FallbackExecutorTest.php +++ b/tests/Query/FallbackExecutorTest.php @@ -14,7 +14,7 @@ class FallbackExecutorTest extends TestCase { - public function testQueryWillReturnPendingPromiseWhenPrimaryExecutorIsStillPending() + public function testQueryWillReturnPendingPromiseWhenPrimaryExecutorIsStillPending(): void { $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN); @@ -31,7 +31,7 @@ public function testQueryWillReturnPendingPromiseWhenPrimaryExecutorIsStillPendi $promise->then($this->expectCallableNever(), $this->expectCallableNever()); } - public function testQueryWillResolveWithMessageWhenPrimaryExecutorResolvesWithMessage() + public function testQueryWillResolveWithMessageWhenPrimaryExecutorResolvesWithMessage(): void { $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN); @@ -48,7 +48,7 @@ public function testQueryWillResolveWithMessageWhenPrimaryExecutorResolvesWithMe $promise->then($this->expectCallableOnceWith($this->isInstanceOf(Message::class)), $this->expectCallableNever()); } - public function testQueryWillReturnPendingPromiseWhenPrimaryExecutorRejectsPromiseAndSecondaryExecutorIsStillPending() + public function testQueryWillReturnPendingPromiseWhenPrimaryExecutorRejectsPromiseAndSecondaryExecutorIsStillPending(): void { $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN); @@ -66,7 +66,7 @@ public function testQueryWillReturnPendingPromiseWhenPrimaryExecutorRejectsPromi $promise->then($this->expectCallableNever(), $this->expectCallableNever()); } - public function testQueryWillResolveWithMessageWhenPrimaryExecutorRejectsPromiseAndSecondaryExecutorResolvesWithMessage() + public function testQueryWillResolveWithMessageWhenPrimaryExecutorRejectsPromiseAndSecondaryExecutorResolvesWithMessage(): void { $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN); @@ -84,7 +84,7 @@ public function testQueryWillResolveWithMessageWhenPrimaryExecutorRejectsPromise $promise->then($this->expectCallableOnceWith($this->isInstanceOf(Message::class)), $this->expectCallableNever()); } - public function testQueryWillRejectWithExceptionMessagesConcatenatedAfterColonWhenPrimaryExecutorRejectsPromiseAndSecondaryExecutorRejectsPromiseWithMessageWithColon() + public function testQueryWillRejectWithExceptionMessagesConcatenatedAfterColonWhenPrimaryExecutorRejectsPromiseAndSecondaryExecutorRejectsPromiseWithMessageWithColon(): void { $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN); @@ -99,7 +99,7 @@ public function testQueryWillRejectWithExceptionMessagesConcatenatedAfterColonWh $promise = $executor->query($query); $this->assertInstanceOf(PromiseInterface::class, $promise); - $promise->then($this->expectCallableNever(), $this->expectCallableOnce($this->isInstanceOf(\Exception::class))); + $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); $exception = null; $promise->then(null, function ($reason) use (&$exception) { @@ -110,7 +110,7 @@ public function testQueryWillRejectWithExceptionMessagesConcatenatedAfterColonWh $this->assertEquals('DNS query for reactphp.org (A) failed: Unable to connect to DNS server A. Unable to connect to DNS server B', $exception->getMessage()); } - public function testQueryWillRejectWithExceptionMessagesConcatenatedInFullWhenPrimaryExecutorRejectsPromiseAndSecondaryExecutorRejectsPromiseWithMessageWithNoColon() + public function testQueryWillRejectWithExceptionMessagesConcatenatedInFullWhenPrimaryExecutorRejectsPromiseAndSecondaryExecutorRejectsPromiseWithMessageWithNoColon(): void { $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN); @@ -125,7 +125,7 @@ public function testQueryWillRejectWithExceptionMessagesConcatenatedInFullWhenPr $promise = $executor->query($query); $this->assertInstanceOf(PromiseInterface::class, $promise); - $promise->then($this->expectCallableNever(), $this->expectCallableOnce($this->isInstanceOf(\Exception::class))); + $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); $exception = null; $promise->then(null, function ($reason) use (&$exception) { @@ -136,7 +136,7 @@ public function testQueryWillRejectWithExceptionMessagesConcatenatedInFullWhenPr $this->assertEquals('Reason A. Reason B', $exception->getMessage()); } - public function testCancelQueryWillReturnRejectedPromiseWithoutCallingSecondaryExecutorWhenPrimaryExecutorIsStillPending() + public function testCancelQueryWillReturnRejectedPromiseWithoutCallingSecondaryExecutorWhenPrimaryExecutorIsStillPending(): void { $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN); @@ -155,7 +155,7 @@ public function testCancelQueryWillReturnRejectedPromiseWithoutCallingSecondaryE $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); } - public function testCancelQueryWillReturnRejectedPromiseWhenPrimaryExecutorRejectsAndSecondaryExecutorIsStillPending() + public function testCancelQueryWillReturnRejectedPromiseWhenPrimaryExecutorRejectsAndSecondaryExecutorIsStillPending(): void { $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN); @@ -174,7 +174,7 @@ public function testCancelQueryWillReturnRejectedPromiseWhenPrimaryExecutorRejec $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); } - public function testCancelQueryShouldNotCauseGarbageReferencesWhenCancellingPrimaryExecutor() + public function testCancelQueryShouldNotCauseGarbageReferencesWhenCancellingPrimaryExecutor(): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); @@ -201,7 +201,7 @@ public function testCancelQueryShouldNotCauseGarbageReferencesWhenCancellingPrim $this->assertEquals(0, gc_collect_cycles()); } - public function testCancelQueryShouldNotCauseGarbageReferencesWhenCancellingSecondaryExecutor() + public function testCancelQueryShouldNotCauseGarbageReferencesWhenCancellingSecondaryExecutor(): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); diff --git a/tests/Query/HostsFileExecutorTest.php b/tests/Query/HostsFileExecutorTest.php index 5bf8f0f5..820c420f 100644 --- a/tests/Query/HostsFileExecutorTest.php +++ b/tests/Query/HostsFileExecutorTest.php @@ -2,6 +2,7 @@ namespace React\Tests\Dns\Query; +use PHPUnit\Framework\MockObject\MockObject; use React\Dns\Config\HostsFile; use React\Dns\Model\Message; use React\Dns\Query\ExecutorInterface; @@ -11,21 +12,32 @@ class HostsFileExecutorTest extends TestCase { + /** + * @var HostsFile&MockObject + */ private $hosts; + + /** + * @var ExecutorInterface&MockObject + */ private $fallback; + + /** + * @var ExecutorInterface + */ private $executor; /** * @before */ - public function setUpMocks() + public function setUpMocks(): void { $this->hosts = $this->createMock(HostsFile::class); $this->fallback = $this->createMock(ExecutorInterface::class); $this->executor = new HostsFileExecutor($this->hosts, $this->fallback); } - public function testDoesNotTryToGetIpsForMxQuery() + public function testDoesNotTryToGetIpsForMxQuery(): void { $this->hosts->expects($this->never())->method('getIpsForHost'); $this->fallback->expects($this->once())->method('query'); @@ -33,7 +45,7 @@ public function testDoesNotTryToGetIpsForMxQuery() $this->executor->query(new Query('google.com', Message::TYPE_MX, Message::CLASS_IN)); } - public function testFallsBackIfNoIpsWereFound() + public function testFallsBackIfNoIpsWereFound(): void { $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn([]); $this->fallback->expects($this->once())->method('query'); @@ -41,7 +53,7 @@ public function testFallsBackIfNoIpsWereFound() $this->executor->query(new Query('google.com', Message::TYPE_A, Message::CLASS_IN)); } - public function testReturnsResponseMessageIfIpsWereFound() + public function testReturnsResponseMessageIfIpsWereFound(): void { $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(['127.0.0.1']); $this->fallback->expects($this->never())->method('query'); @@ -49,7 +61,7 @@ public function testReturnsResponseMessageIfIpsWereFound() $ret = $this->executor->query(new Query('google.com', Message::TYPE_A, Message::CLASS_IN)); } - public function testFallsBackIfNoIpv4Matches() + public function testFallsBackIfNoIpv4Matches(): void { $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(['::1']); $this->fallback->expects($this->once())->method('query'); @@ -57,7 +69,7 @@ public function testFallsBackIfNoIpv4Matches() $ret = $this->executor->query(new Query('google.com', Message::TYPE_A, Message::CLASS_IN)); } - public function testReturnsResponseMessageIfIpv6AddressesWereFound() + public function testReturnsResponseMessageIfIpv6AddressesWereFound(): void { $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(['::1']); $this->fallback->expects($this->never())->method('query'); @@ -65,7 +77,7 @@ public function testReturnsResponseMessageIfIpv6AddressesWereFound() $ret = $this->executor->query(new Query('google.com', Message::TYPE_AAAA, Message::CLASS_IN)); } - public function testFallsBackIfNoIpv6Matches() + public function testFallsBackIfNoIpv6Matches(): void { $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(['127.0.0.1']); $this->fallback->expects($this->once())->method('query'); @@ -73,7 +85,7 @@ public function testFallsBackIfNoIpv6Matches() $ret = $this->executor->query(new Query('google.com', Message::TYPE_AAAA, Message::CLASS_IN)); } - public function testDoesReturnReverseIpv4Lookup() + public function testDoesReturnReverseIpv4Lookup(): void { $this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn(['localhost']); $this->fallback->expects($this->never())->method('query'); @@ -81,7 +93,7 @@ public function testDoesReturnReverseIpv4Lookup() $this->executor->query(new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN)); } - public function testFallsBackIfNoReverseIpv4Matches() + public function testFallsBackIfNoReverseIpv4Matches(): void { $this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn([]); $this->fallback->expects($this->once())->method('query'); @@ -89,7 +101,7 @@ public function testFallsBackIfNoReverseIpv4Matches() $this->executor->query(new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN)); } - public function testDoesReturnReverseIpv6Lookup() + public function testDoesReturnReverseIpv6Lookup(): void { $this->hosts->expects($this->once())->method('getHostsForIp')->with('2a02:2e0:3fe:100::6')->willReturn(['ip6-localhost']); $this->fallback->expects($this->never())->method('query'); @@ -97,7 +109,7 @@ public function testDoesReturnReverseIpv6Lookup() $this->executor->query(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)); } - public function testFallsBackForInvalidAddress() + public function testFallsBackForInvalidAddress(): void { $this->hosts->expects($this->never())->method('getHostsForIp'); $this->fallback->expects($this->once())->method('query'); @@ -105,7 +117,7 @@ public function testFallsBackForInvalidAddress() $this->executor->query(new Query('example.com', Message::TYPE_PTR, Message::CLASS_IN)); } - public function testReverseFallsBackForInvalidIpv4Address() + public function testReverseFallsBackForInvalidIpv4Address(): void { $this->hosts->expects($this->never())->method('getHostsForIp'); $this->fallback->expects($this->once())->method('query'); @@ -113,7 +125,7 @@ public function testReverseFallsBackForInvalidIpv4Address() $this->executor->query(new Query('::1.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN)); } - public function testReverseFallsBackForInvalidLengthIpv6Address() + public function testReverseFallsBackForInvalidLengthIpv6Address(): void { $this->hosts->expects($this->never())->method('getHostsForIp'); $this->fallback->expects($this->once())->method('query'); @@ -121,7 +133,7 @@ public function testReverseFallsBackForInvalidLengthIpv6Address() $this->executor->query(new Query('abcd.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN)); } - public function testReverseFallsBackForInvalidHexIpv6Address() + public function testReverseFallsBackForInvalidHexIpv6Address(): void { $this->hosts->expects($this->never())->method('getHostsForIp'); $this->fallback->expects($this->once())->method('query'); diff --git a/tests/Query/QueryTest.php b/tests/Query/QueryTest.php index 79935ba2..b4d2ce8a 100644 --- a/tests/Query/QueryTest.php +++ b/tests/Query/QueryTest.php @@ -8,14 +8,14 @@ class QueryTest extends TestCase { - public function testDescribeSimpleAQuery() + public function testDescribeSimpleAQuery(): void { $query = new Query('example.com', Message::TYPE_A, Message::CLASS_IN); $this->assertEquals('example.com (A)', $query->describe()); } - public function testDescribeUnknownType() + public function testDescribeUnknownType(): void { $query = new Query('example.com', 0, 0); diff --git a/tests/Query/RetryExecutorTest.php b/tests/Query/RetryExecutorTest.php index c7695c76..44eea5ef 100644 --- a/tests/Query/RetryExecutorTest.php +++ b/tests/Query/RetryExecutorTest.php @@ -2,6 +2,7 @@ namespace React\Tests\Dns\Query; +use PHPUnit\Framework\MockObject\MockObject; use React\Dns\Model\Message; use React\Dns\Model\Record; use React\Dns\Query\CancellationException; @@ -18,10 +19,10 @@ class RetryExecutorTest extends TestCase { /** - * @covers React\Dns\Query\RetryExecutor + * @covers \React\Dns\Query\RetryExecutor * @test */ - public function queryShouldDelegateToDecoratedExecutor() + public function queryShouldDelegateToDecoratedExecutor(): void { $executor = $this->createExecutorMock(); $executor @@ -37,10 +38,10 @@ public function queryShouldDelegateToDecoratedExecutor() } /** - * @covers React\Dns\Query\RetryExecutor + * @covers \React\Dns\Query\RetryExecutor * @test */ - public function queryShouldRetryQueryOnTimeout() + public function queryShouldRetryQueryOnTimeout(): void { $response = $this->createStandardResponse(); @@ -58,6 +59,7 @@ public function queryShouldRetryQueryOnTimeout() }) )); + /** @var (callable(Message): void)&MockObject $callback */ $callback = $this->createCallableMock(); $callback ->expects($this->once()) @@ -73,10 +75,10 @@ public function queryShouldRetryQueryOnTimeout() } /** - * @covers React\Dns\Query\RetryExecutor + * @covers \React\Dns\Query\RetryExecutor * @test */ - public function queryShouldStopRetryingAfterSomeAttempts() + public function queryShouldStopRetryingAfterSomeAttempts(): void { $executor = $this->createExecutorMock(); $executor @@ -106,10 +108,10 @@ public function queryShouldStopRetryingAfterSomeAttempts() } /** - * @covers React\Dns\Query\RetryExecutor + * @covers \React\Dns\Query\RetryExecutor * @test */ - public function queryShouldForwardNonTimeoutErrors() + public function queryShouldForwardNonTimeoutErrors(): void { $executor = $this->createExecutorMock(); $executor @@ -122,6 +124,7 @@ public function queryShouldForwardNonTimeoutErrors() $callback = $this->expectCallableNever(); + /** @var (callable(\Throwable): void)&MockObject $errorback */ $errorback = $this->createCallableMock(); $errorback ->expects($this->once()) @@ -135,10 +138,10 @@ public function queryShouldForwardNonTimeoutErrors() } /** - * @covers React\Dns\Query\RetryExecutor + * @covers \React\Dns\Query\RetryExecutor * @test */ - public function queryShouldCancelQueryOnCancel() + public function queryShouldCancelQueryOnCancel(): void { $cancelled = 0; @@ -170,10 +173,10 @@ public function queryShouldCancelQueryOnCancel() } /** - * @covers React\Dns\Query\RetryExecutor + * @covers \React\Dns\Query\RetryExecutor * @test */ - public function queryShouldCancelSecondQueryOnCancel() + public function queryShouldCancelSecondQueryOnCancel(): void { $deferred = new Deferred(); $cancelled = 0; @@ -211,10 +214,10 @@ public function queryShouldCancelSecondQueryOnCancel() } /** - * @covers React\Dns\Query\RetryExecutor + * @covers \React\Dns\Query\RetryExecutor * @test */ - public function queryShouldNotCauseGarbageReferencesOnSuccess() + public function queryShouldNotCauseGarbageReferencesOnSuccess(): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); @@ -240,10 +243,10 @@ public function queryShouldNotCauseGarbageReferencesOnSuccess() } /** - * @covers React\Dns\Query\RetryExecutor + * @covers \React\Dns\Query\RetryExecutor * @test */ - public function queryShouldNotCauseGarbageReferencesOnTimeoutErrors() + public function queryShouldNotCauseGarbageReferencesOnTimeoutErrors(): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); @@ -271,10 +274,10 @@ public function queryShouldNotCauseGarbageReferencesOnTimeoutErrors() } /** - * @covers React\Dns\Query\RetryExecutor + * @covers \React\Dns\Query\RetryExecutor * @test */ - public function queryShouldNotCauseGarbageReferencesOnCancellation() + public function queryShouldNotCauseGarbageReferencesOnCancellation(): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); @@ -306,10 +309,10 @@ public function queryShouldNotCauseGarbageReferencesOnCancellation() } /** - * @covers React\Dns\Query\RetryExecutor + * @covers \React\Dns\Query\RetryExecutor * @test */ - public function queryShouldNotCauseGarbageReferencesOnNonTimeoutErrors() + public function queryShouldNotCauseGarbageReferencesOnNonTimeoutErrors(): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); @@ -338,7 +341,11 @@ public function queryShouldNotCauseGarbageReferencesOnNonTimeoutErrors() $this->assertEquals(0, gc_collect_cycles()); } - protected function expectPromiseOnce($return = null) + /** + * @param ?mixed $return + * @return PromiseInterface + */ + protected function expectPromiseOnce($return = null): PromiseInterface { $mock = $this->createPromiseMock(); $mock @@ -349,17 +356,23 @@ protected function expectPromiseOnce($return = null) return $mock; } + /** + * @return ExecutorInterface&MockObject + */ protected function createExecutorMock() { return $this->createMock(ExecutorInterface::class); } + /** + * @return PromiseInterface&MockObject + */ protected function createPromiseMock() { return $this->createMock(PromiseInterface::class); } - protected function createStandardResponse() + protected function createStandardResponse(): Message { $response = new Message(); $response->qr = true; diff --git a/tests/Query/SelectiveTransportExecutorTest.php b/tests/Query/SelectiveTransportExecutorTest.php index 5f439f3b..854cbd0b 100644 --- a/tests/Query/SelectiveTransportExecutorTest.php +++ b/tests/Query/SelectiveTransportExecutorTest.php @@ -2,6 +2,7 @@ namespace React\Tests\Dns\Query; +use PHPUnit\Framework\MockObject\MockObject; use React\Dns\Model\Message; use React\Dns\Query\ExecutorInterface; use React\Dns\Query\Query; @@ -14,14 +15,25 @@ class SelectiveTransportExecutorTest extends TestCase { + /** + * @var ExecutorInterface&MockObject + */ private $datagram; + + /** + * @var ExecutorInterface&MockObject + */ private $stream; + + /** + * @var ExecutorInterface + */ private $executor; /** * @before */ - public function setUpMocks() + public function setUpMocks(): void { $this->datagram = $this->createMock(ExecutorInterface::class); $this->stream = $this->createMock(ExecutorInterface::class); @@ -29,7 +41,7 @@ public function setUpMocks() $this->executor = new SelectiveTransportExecutor($this->datagram, $this->stream); } - public function testQueryResolvesWhenDatagramTransportResolvesWithoutUsingStreamTransport() + public function testQueryResolvesWhenDatagramTransportResolvesWithoutUsingStreamTransport(): void { $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN); @@ -50,7 +62,7 @@ public function testQueryResolvesWhenDatagramTransportResolvesWithoutUsingStream $promise->then($this->expectCallableOnceWith($response)); } - public function testQueryResolvesWhenStreamTransportResolvesAfterDatagramTransportRejectsWithSizeError() + public function testQueryResolvesWhenStreamTransportResolvesAfterDatagramTransportRejectsWithSizeError(): void { $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN); @@ -73,7 +85,7 @@ public function testQueryResolvesWhenStreamTransportResolvesAfterDatagramTranspo $promise->then($this->expectCallableOnceWith($response)); } - public function testQueryRejectsWhenDatagramTransportRejectsWithRuntimeExceptionWithoutUsingStreamTransport() + public function testQueryRejectsWhenDatagramTransportRejectsWithRuntimeExceptionWithoutUsingStreamTransport(): void { $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN); @@ -92,7 +104,7 @@ public function testQueryRejectsWhenDatagramTransportRejectsWithRuntimeException $promise->then(null, $this->expectCallableOnce()); } - public function testQueryRejectsWhenStreamTransportRejectsAfterDatagramTransportRejectsWithSizeError() + public function testQueryRejectsWhenStreamTransportRejectsAfterDatagramTransportRejectsWithSizeError(): void { $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN); @@ -113,7 +125,7 @@ public function testQueryRejectsWhenStreamTransportRejectsAfterDatagramTransport $promise->then(null, $this->expectCallableOnce()); } - public function testCancelPromiseWillCancelPromiseFromDatagramExecutor() + public function testCancelPromiseWillCancelPromiseFromDatagramExecutor(): void { $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN); @@ -127,7 +139,7 @@ public function testCancelPromiseWillCancelPromiseFromDatagramExecutor() $promise->cancel(); } - public function testCancelPromiseWillCancelPromiseFromStreamExecutorWhenDatagramExecutorRejectedWithTruncatedResponse() + public function testCancelPromiseWillCancelPromiseFromStreamExecutorWhenDatagramExecutorRejectedWithTruncatedResponse(): void { $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN); @@ -149,7 +161,7 @@ public function testCancelPromiseWillCancelPromiseFromStreamExecutorWhenDatagram $promise->cancel(); } - public function testCancelPromiseShouldNotCreateAnyGarbageReferences() + public function testCancelPromiseShouldNotCreateAnyGarbageReferences(): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); @@ -176,7 +188,7 @@ public function testCancelPromiseShouldNotCreateAnyGarbageReferences() $this->assertEquals(0, gc_collect_cycles()); } - public function testCancelPromiseAfterTruncatedResponseShouldNotCreateAnyGarbageReferences() + public function testCancelPromiseAfterTruncatedResponseShouldNotCreateAnyGarbageReferences(): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); @@ -211,7 +223,7 @@ public function testCancelPromiseAfterTruncatedResponseShouldNotCreateAnyGarbage $this->assertEquals(0, gc_collect_cycles()); } - public function testRejectedPromiseAfterTruncatedResponseShouldNotCreateAnyGarbageReferences() + public function testRejectedPromiseAfterTruncatedResponseShouldNotCreateAnyGarbageReferences(): void { $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN); diff --git a/tests/Query/TcpTransportExecutorTest.php b/tests/Query/TcpTransportExecutorTest.php index e65e347d..459d9deb 100644 --- a/tests/Query/TcpTransportExecutorTest.php +++ b/tests/Query/TcpTransportExecutorTest.php @@ -24,7 +24,7 @@ class TcpTransportExecutorTest extends TestCase * @param string $input * @param string $expected */ - public function testCtorShouldAcceptNameserverAddresses($input, $expected) + public function testCtorShouldAcceptNameserverAddresses(string $input, string $expected): void { $loop = $this->createMock(LoopInterface::class); @@ -37,7 +37,10 @@ public function testCtorShouldAcceptNameserverAddresses($input, $expected) $this->assertEquals($expected, $value); } - public static function provideDefaultPortProvider() + /** + * @return iterable + */ + public static function provideDefaultPortProvider(): iterable { yield [ '8.8.8.8', @@ -65,7 +68,7 @@ public static function provideDefaultPortProvider() ]; } - public function testCtorWithoutLoopShouldAssignDefaultLoop() + public function testCtorWithoutLoopShouldAssignDefaultLoop(): void { $executor = new TcpTransportExecutor('127.0.0.1'); @@ -76,7 +79,7 @@ public function testCtorWithoutLoopShouldAssignDefaultLoop() $this->assertInstanceOf(LoopInterface::class, $loop); } - public function testCtorShouldThrowWhenNameserverAddressIsInvalid() + public function testCtorShouldThrowWhenNameserverAddressIsInvalid(): void { $loop = $this->createMock(LoopInterface::class); @@ -84,7 +87,7 @@ public function testCtorShouldThrowWhenNameserverAddressIsInvalid() new TcpTransportExecutor('///', $loop); } - public function testCtorShouldThrowWhenNameserverAddressContainsHostname() + public function testCtorShouldThrowWhenNameserverAddressContainsHostname(): void { $loop = $this->createMock(LoopInterface::class); @@ -92,7 +95,7 @@ public function testCtorShouldThrowWhenNameserverAddressContainsHostname() new TcpTransportExecutor('localhost', $loop); } - public function testCtorShouldThrowWhenNameserverSchemeIsInvalid() + public function testCtorShouldThrowWhenNameserverSchemeIsInvalid(): void { $loop = $this->createMock(LoopInterface::class); @@ -100,7 +103,7 @@ public function testCtorShouldThrowWhenNameserverSchemeIsInvalid() new TcpTransportExecutor('udp://1.2.3.4', $loop); } - public function testQueryRejectsIfMessageExceedsMaximumMessageSize() + public function testQueryRejectsIfMessageExceedsMaximumMessageSize(): void { $loop = $this->createMock(LoopInterface::class); $loop->expects($this->never())->method('addWriteStream'); @@ -120,7 +123,7 @@ public function testQueryRejectsIfMessageExceedsMaximumMessageSize() $this->assertEquals('DNS query for '. $query->name . ' (A) failed: Query too large for TCP transport', $exception->getMessage()); } - public function testQueryRejectsIfServerConnectionFails() + public function testQueryRejectsIfServerConnectionFails(): void { $loop = $this->createMock(LoopInterface::class); $loop->expects($this->never())->method('addWriteStream'); @@ -144,7 +147,7 @@ public function testQueryRejectsIfServerConnectionFails() $this->assertEquals('DNS query for google.com (A) failed: Unable to connect to DNS server /// (Failed to parse address "///")', $exception->getMessage()); } - public function testQueryRejectsOnCancellationWithoutClosingSocketButStartsIdleTimer() + public function testQueryRejectsOnCancellationWithoutClosingSocketButStartsIdleTimer(): void { $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('addWriteStream'); @@ -156,7 +159,9 @@ public function testQueryRejectsOnCancellationWithoutClosingSocketButStartsIdleT $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything())->willReturn($timer); $loop->expects($this->never())->method('cancelTimer'); + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address, $loop); @@ -175,7 +180,7 @@ public function testQueryRejectsOnCancellationWithoutClosingSocketButStartsIdleT $this->assertEquals('DNS query for google.com (A) has been cancelled', $exception->getMessage()); } - public function testTriggerIdleTimerAfterQueryRejectedOnCancellationWillCloseSocket() + public function testTriggerIdleTimerAfterQueryRejectedOnCancellationWillCloseSocket(): void { $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('addWriteStream'); @@ -191,7 +196,9 @@ public function testTriggerIdleTimerAfterQueryRejectedOnCancellationWillCloseSoc }))->willReturn($timer); $loop->expects($this->once())->method('cancelTimer')->with($timer); + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address, $loop); @@ -208,7 +215,7 @@ public function testTriggerIdleTimerAfterQueryRejectedOnCancellationWillCloseSoc $timerCallback(); } - public function testQueryRejectsOnCancellationWithoutClosingSocketAndWithoutStartingIdleTimerWhenOtherQueryIsStillPending() + public function testQueryRejectsOnCancellationWithoutClosingSocketAndWithoutStartingIdleTimerWhenOtherQueryIsStillPending(): void { $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('addWriteStream'); @@ -219,7 +226,9 @@ public function testQueryRejectsOnCancellationWithoutClosingSocketAndWithoutStar $loop->expects($this->never())->method('addTimer'); $loop->expects($this->never())->method('cancelTimer'); + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address, $loop); @@ -233,7 +242,7 @@ public function testQueryRejectsOnCancellationWithoutClosingSocketAndWithoutStar $promise2->then(null, $this->expectCallableOnce()); } - public function testQueryAgainAfterPreviousWasCancelledReusesExistingSocket() + public function testQueryAgainAfterPreviousWasCancelledReusesExistingSocket(): void { $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('addWriteStream'); @@ -241,7 +250,9 @@ public function testQueryAgainAfterPreviousWasCancelledReusesExistingSocket() $loop->expects($this->never())->method('addReadStream'); $loop->expects($this->never())->method('removeReadStream'); + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address, $loop); @@ -253,7 +264,7 @@ public function testQueryAgainAfterPreviousWasCancelledReusesExistingSocket() $executor->query($query); } - public function testQueryRejectsWhenServerIsNotListening() + public function testQueryRejectsWhenServerIsNotListening(): void { $executor = new TcpTransportExecutor('127.0.0.1:1'); @@ -278,7 +289,7 @@ function ($e) use (&$exception) { $this->assertEquals(defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111, $exception->getCode()); } - public function testQueryStaysPendingWhenClientCanNotSendExcessiveMessageInOneChunk() + public function testQueryStaysPendingWhenClientCanNotSendExcessiveMessageInOneChunk(): void { $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('addWriteStream'); @@ -286,8 +297,10 @@ public function testQueryStaysPendingWhenClientCanNotSendExcessiveMessageInOneCh $loop->expects($this->never())->method('removeWriteStream'); $loop->expects($this->never())->method('removeReadStream'); + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address, $loop); @@ -303,17 +316,20 @@ public function testQueryStaysPendingWhenClientCanNotSendExcessiveMessageInOneCh $executor->handleWritable(); - $promise->then(null, 'printf'); + $promise->then(null, static function(\Throwable $error): void { + echo $error; + }); $promise->then($this->expectCallableNever(), $this->expectCallableNever()); $ref = new \ReflectionProperty($executor, 'writePending'); $ref->setAccessible(true); + /** @var bool $writePending */ $writePending = $ref->getValue($executor); $this->assertTrue($writePending); } - public function testQueryStaysPendingWhenClientCanNotSendExcessiveMessageInOneChunkWhenServerClosesSocket() + public function testQueryStaysPendingWhenClientCanNotSendExcessiveMessageInOneChunkWhenServerClosesSocket(): void { if (PHP_OS === 'Darwin') { // Skip on macOS because it exhibits what looks like a kernal race condition when sending excessive data to a socket that is about to shut down (EPROTOTYPE) @@ -329,8 +345,10 @@ public function testQueryStaysPendingWhenClientCanNotSendExcessiveMessageInOneCh $loop->expects($this->never())->method('removeWriteStream'); $loop->expects($this->never())->method('removeReadStream'); + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address, $loop); @@ -341,6 +359,7 @@ public function testQueryStaysPendingWhenClientCanNotSendExcessiveMessageInOneCh $promise = $executor->query($query); } + /** @var resource $client */ $client = stream_socket_accept($server); fclose($client); @@ -350,12 +369,13 @@ public function testQueryStaysPendingWhenClientCanNotSendExcessiveMessageInOneCh $ref = new \ReflectionProperty($executor, 'writePending'); $ref->setAccessible(true); + /** @var bool $writePending */ $writePending = $ref->getValue($executor); $this->assertTrue($writePending); } - public function testQueryRejectsWhenClientKeepsSendingWhenServerClosesSocketWithoutCallingCustomErrorHandler() + public function testQueryRejectsWhenClientKeepsSendingWhenServerClosesSocketWithoutCallingCustomErrorHandler(): void { $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('addWriteStream'); @@ -363,8 +383,10 @@ public function testQueryRejectsWhenClientKeepsSendingWhenServerClosesSocketWith $loop->expects($this->once())->method('removeWriteStream'); $loop->expects($this->once())->method('removeReadStream'); + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address, $loop); @@ -374,17 +396,20 @@ public function testQueryRejectsWhenClientKeepsSendingWhenServerClosesSocketWith $exception = null; for ($i = 0; $i < 2000; ++$i) { $promise = $executor->query($query); - $promise->then(null, function (\Exception $reason) use (&$exception) { + $promise->then(null, function (\Throwable $reason) use (&$exception): void { $exception = $reason; }); } + /** @var resource $client */ $client = stream_socket_accept($server); fclose($client); $error = null; - set_error_handler(function ($_, $errstr) use (&$error) { + set_error_handler(function (int $_, string $errstr) use (&$error): bool { $error = $errstr; + + return true; }); $executor->handleWritable(); @@ -405,18 +430,20 @@ public function testQueryRejectsWhenClientKeepsSendingWhenServerClosesSocketWith $this->assertNull($error); // expect EPIPE (Broken pipe), except for macOS kernel race condition - $this->expectException( - \RuntimeException::class, - 'Unable to send query to DNS server tcp://' . $address . ' (', - defined('SOCKET_EPIPE') ? (PHP_OS !== 'Darwin' || $writePending ? SOCKET_EPIPE : SOCKET_EPROTOTYPE) : null - ); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Unable to send query to DNS server tcp://' . $address . ' ('); + $this->expectExceptionCode(defined('SOCKET_EPIPE') ? (PHP_OS !== 'Darwin' || $writePending ? SOCKET_EPIPE : SOCKET_EPROTOTYPE) : PHP_INT_MIN); + $this->assertNotNull($exception); + throw $exception; } - public function testQueryRejectsWhenServerClosesConnection() + public function testQueryRejectsWhenServerClosesConnection(): void { + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); Loop::addReadStream($server, function ($server) { + /** @var resource $client */ $client = stream_socket_accept($server); fclose($client); @@ -424,6 +451,7 @@ public function testQueryRejectsWhenServerClosesConnection() fclose($server); }); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address); @@ -447,11 +475,13 @@ function ($e) use (&$exception) { $this->assertEquals('DNS query for google.com (A) failed: Connection to DNS server tcp://' . $address . ' lost', $exception->getMessage()); } - public function testQueryKeepsPendingIfServerSendsIncompleteMessageLength() + public function testQueryKeepsPendingIfServerSendsIncompleteMessageLength(): void { $client = null; + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); Loop::addReadStream($server, function ($server) use (&$client) { + /** @var resource $client */ $client = stream_socket_accept($server); Loop::addReadStream($client, function ($client) { Loop::removeReadStream($client); @@ -462,6 +492,7 @@ public function testQueryKeepsPendingIfServerSendsIncompleteMessageLength() fclose($server); }); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address); @@ -483,11 +514,13 @@ function ($e) use (&$wait) { Loop::removeReadStream($client); } - public function testQueryKeepsPendingIfServerSendsIncompleteMessageBody() + public function testQueryKeepsPendingIfServerSendsIncompleteMessageBody(): void { $client = null; + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); Loop::addReadStream($server, function ($server) use (&$client) { + /** @var resource $client */ $client = stream_socket_accept($server); Loop::addReadStream($client, function ($client) { Loop::removeReadStream($client); @@ -498,6 +531,7 @@ public function testQueryKeepsPendingIfServerSendsIncompleteMessageBody() fclose($server); }); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address); @@ -519,10 +553,12 @@ function ($e) use (&$wait) { Loop::removeReadStream($client); } - public function testQueryRejectsWhenServerSendsInvalidMessage() + public function testQueryRejectsWhenServerSendsInvalidMessage(): void { + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); Loop::addReadStream($server, function ($server) { + /** @var resource $client */ $client = stream_socket_accept($server); Loop::addReadStream($client, function ($client) { Loop::removeReadStream($client); @@ -533,6 +569,7 @@ public function testQueryRejectsWhenServerSendsInvalidMessage() fclose($server); }); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address); @@ -556,18 +593,22 @@ function ($e) use (&$exception) { $this->assertEquals('DNS query for google.com (A) failed: Invalid message received from DNS server tcp://' . $address, $exception->getMessage()); } - public function testQueryRejectsWhenServerSendsInvalidId() + public function testQueryRejectsWhenServerSendsInvalidId(): void { $parser = new Parser(); $dumper = new BinaryDumper(); + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); Loop::addReadStream($server, function ($server) use ($parser, $dumper) { + /** @var resource $client */ $client = stream_socket_accept($server); Loop::addReadStream($client, function ($client) use ($parser, $dumper) { Loop::removeReadStream($client); + /** @var string $data */ $data = fread($client, 512); + /** @phpstan-ignore-next-line unpack won't error on this line as our format is correct */ list(, $length) = unpack('n', substr($data, 0, 2)); assert(strlen($data) - 2 === $length); $data = substr($data, 2); @@ -585,6 +626,7 @@ public function testQueryRejectsWhenServerSendsInvalidId() fclose($server); }); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address); @@ -608,18 +650,22 @@ function ($e) use (&$exception) { $this->assertEquals('DNS query for google.com (A) failed: Invalid response message received from DNS server tcp://' . $address, $exception->getMessage()); } - public function testQueryRejectsIfServerSendsTruncatedResponse() + public function testQueryRejectsIfServerSendsTruncatedResponse(): void { $parser = new Parser(); $dumper = new BinaryDumper(); + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); Loop::addReadStream($server, function ($server) use ($parser, $dumper) { + /** @var resource $client */ $client = stream_socket_accept($server); Loop::addReadStream($client, function ($client) use ($parser, $dumper) { Loop::removeReadStream($client); + /** @var string $data */ $data = fread($client, 512); + /** @phpstan-ignore-next-line unpack won't error on this line as our format is correct */ list(, $length) = unpack('n', substr($data, 0, 2)); assert(strlen($data) - 2 === $length); $data = substr($data, 2); @@ -637,6 +683,7 @@ public function testQueryRejectsIfServerSendsTruncatedResponse() fclose($server); }); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address); @@ -660,15 +707,19 @@ function ($e) use (&$exception) { $this->assertEquals('DNS query for google.com (A) failed: Invalid response message received from DNS server tcp://' . $address, $exception->getMessage()); } - public function testQueryResolvesIfServerSendsValidResponse() + public function testQueryResolvesIfServerSendsValidResponse(): void { + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); Loop::addReadStream($server, function ($server) { + /** @var resource $client */ $client = stream_socket_accept($server); Loop::addReadStream($client, function ($client) { Loop::removeReadStream($client); + /** @var string $data */ $data = fread($client, 512); + /** @phpstan-ignore-next-line unpack won't error on this line as our format is correct */ list(, $length) = unpack('n', substr($data, 0, 2)); assert(strlen($data) - 2 === $length); @@ -679,6 +730,7 @@ public function testQueryResolvesIfServerSendsValidResponse() fclose($server); }); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address); @@ -690,7 +742,7 @@ public function testQueryResolvesIfServerSendsValidResponse() $this->assertInstanceOf(Message::class, $response); } - public function testQueryRejectsIfSocketIsClosedAfterPreviousQueryThatWasStillPending() + public function testQueryRejectsIfSocketIsClosedAfterPreviousQueryThatWasStillPending(): void { $loop = $this->createMock(LoopInterface::class); $loop->expects($this->exactly(2))->method('addWriteStream'); @@ -701,7 +753,9 @@ public function testQueryRejectsIfSocketIsClosedAfterPreviousQueryThatWasStillPe $loop->expects($this->never())->method('addTimer'); $loop->expects($this->never())->method('cancelTimer'); + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address, $loop); @@ -709,6 +763,7 @@ public function testQueryRejectsIfSocketIsClosedAfterPreviousQueryThatWasStillPe $promise1 = $executor->query($query); + /** @var resource $client */ $client = stream_socket_accept($server); $executor->handleWritable(); @@ -724,7 +779,7 @@ public function testQueryRejectsIfSocketIsClosedAfterPreviousQueryThatWasStillPe $promise2->then(null, $this->expectCallableOnce()); } - public function testQueryResolvesIfServerSendsBackResponseMessageAndWillStartIdleTimer() + public function testQueryResolvesIfServerSendsBackResponseMessageAndWillStartIdleTimer(): void { $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('addWriteStream'); @@ -735,7 +790,9 @@ public function testQueryResolvesIfServerSendsBackResponseMessageAndWillStartIdl $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything()); $loop->expects($this->never())->method('cancelTimer'); + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address, $loop); @@ -746,8 +803,10 @@ public function testQueryResolvesIfServerSendsBackResponseMessageAndWillStartIdl // use outgoing buffer as response message $ref = new \ReflectionProperty($executor, 'writeBuffer'); $ref->setAccessible(true); + /** @var string $data */ $data = $ref->getValue($executor); + /** @var resource $client */ $client = stream_socket_accept($server); fwrite($client, $data); @@ -757,7 +816,7 @@ public function testQueryResolvesIfServerSendsBackResponseMessageAndWillStartIdl $promise->then($this->expectCallableOnce()); } - public function testQueryResolvesIfServerSendsBackResponseMessageAfterCancellingQueryAndWillStartIdleTimer() + public function testQueryResolvesIfServerSendsBackResponseMessageAfterCancellingQueryAndWillStartIdleTimer(): void { $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('addWriteStream'); @@ -769,7 +828,9 @@ public function testQueryResolvesIfServerSendsBackResponseMessageAfterCancelling $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything())->willReturn($timer); $loop->expects($this->never())->method('cancelTimer'); + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address, $loop); @@ -781,8 +842,10 @@ public function testQueryResolvesIfServerSendsBackResponseMessageAfterCancelling // use outgoing buffer as response message $ref = new \ReflectionProperty($executor, 'writeBuffer'); $ref->setAccessible(true); + /** @var string $data */ $data = $ref->getValue($executor); + /** @var resource $client */ $client = stream_socket_accept($server); fwrite($client, $data); @@ -792,7 +855,7 @@ public function testQueryResolvesIfServerSendsBackResponseMessageAfterCancelling //$promise->then(null, $this->expectCallableOnce()); } - public function testQueryResolvesIfServerSendsBackResponseMessageAfterCancellingOtherQueryAndWillStartIdleTimer() + public function testQueryResolvesIfServerSendsBackResponseMessageAfterCancellingOtherQueryAndWillStartIdleTimer(): void { $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('addWriteStream'); @@ -803,7 +866,9 @@ public function testQueryResolvesIfServerSendsBackResponseMessageAfterCancelling $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything()); $loop->expects($this->never())->method('cancelTimer'); + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address, $loop); @@ -814,8 +879,10 @@ public function testQueryResolvesIfServerSendsBackResponseMessageAfterCancelling // use outgoing buffer as response message $ref = new \ReflectionProperty($executor, 'writeBuffer'); $ref->setAccessible(true); + /** @var string $data */ $data = $ref->getValue($executor); + /** @var resource $client */ $client = stream_socket_accept($server); fwrite($client, $data); @@ -828,7 +895,7 @@ public function testQueryResolvesIfServerSendsBackResponseMessageAfterCancelling $promise->then($this->expectCallableOnce()); } - public function testTriggerIdleTimerAfterPreviousQueryResolvedWillCloseIdleSocketConnection() + public function testTriggerIdleTimerAfterPreviousQueryResolvedWillCloseIdleSocketConnection(): void { $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('addWriteStream'); @@ -844,7 +911,9 @@ public function testTriggerIdleTimerAfterPreviousQueryResolvedWillCloseIdleSocke }))->willReturn($timer); $loop->expects($this->once())->method('cancelTimer')->with($timer); + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address, $loop); @@ -855,8 +924,10 @@ public function testTriggerIdleTimerAfterPreviousQueryResolvedWillCloseIdleSocke // use outgoing buffer as response message $ref = new \ReflectionProperty($executor, 'writeBuffer'); $ref->setAccessible(true); + /** @var string $data */ $data = $ref->getValue($executor); + /** @var resource $client */ $client = stream_socket_accept($server); fwrite($client, $data); @@ -870,7 +941,7 @@ public function testTriggerIdleTimerAfterPreviousQueryResolvedWillCloseIdleSocke $timerCallback(); } - public function testClosingConnectionAfterPreviousQueryResolvedWillCancelIdleTimer() + public function testClosingConnectionAfterPreviousQueryResolvedWillCancelIdleTimer(): void { $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('addWriteStream'); @@ -882,7 +953,9 @@ public function testClosingConnectionAfterPreviousQueryResolvedWillCancelIdleTim $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything())->willReturn($timer); $loop->expects($this->once())->method('cancelTimer')->with($timer); + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address, $loop); @@ -893,8 +966,10 @@ public function testClosingConnectionAfterPreviousQueryResolvedWillCancelIdleTim // use outgoing buffer as response message $ref = new \ReflectionProperty($executor, 'writeBuffer'); $ref->setAccessible(true); + /** @var string $data */ $data = $ref->getValue($executor); + /** @var resource $client */ $client = stream_socket_accept($server); fwrite($client, $data); @@ -908,7 +983,7 @@ public function testClosingConnectionAfterPreviousQueryResolvedWillCancelIdleTim $executor->handleRead(); } - public function testQueryAgainAfterPreviousQueryResolvedWillReuseSocketAndCancelIdleTimer() + public function testQueryAgainAfterPreviousQueryResolvedWillReuseSocketAndCancelIdleTimer(): void { $loop = $this->createMock(LoopInterface::class); $loop->expects($this->exactly(2))->method('addWriteStream'); @@ -920,7 +995,9 @@ public function testQueryAgainAfterPreviousQueryResolvedWillReuseSocketAndCancel $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything())->willReturn($timer); $loop->expects($this->once())->method('cancelTimer')->with($timer); + /** @var resource $server */ $server = stream_socket_server('tcp://127.0.0.1:0'); + /** @var string $address */ $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address, $loop); @@ -931,8 +1008,10 @@ public function testQueryAgainAfterPreviousQueryResolvedWillReuseSocketAndCancel // use outgoing buffer as response message $ref = new \ReflectionProperty($executor, 'writeBuffer'); $ref->setAccessible(true); + /** @var string $data */ $data = $ref->getValue($executor); + /** @var resource $client */ $client = stream_socket_accept($server); fwrite($client, $data); diff --git a/tests/Query/TimeoutExecutorTest.php b/tests/Query/TimeoutExecutorTest.php index 96b355ce..ec4850c8 100644 --- a/tests/Query/TimeoutExecutorTest.php +++ b/tests/Query/TimeoutExecutorTest.php @@ -2,7 +2,9 @@ namespace React\Tests\Dns\Query; +use PHPUnit\Framework\MockObject\MockObject; use React\Dns\Model\Message; +use React\Dns\Model\Record; use React\Dns\Query\CancellationException; use React\Dns\Query\ExecutorInterface; use React\Dns\Query\Query; @@ -17,14 +19,25 @@ class TimeoutExecutorTest extends TestCase { + /** + * @var ExecutorInterface&MockObject + */ private $wrapped; + + /** + * @var ExecutorInterface + */ private $executor; + + /** + * @var LoopInterface&MockObject + */ private $loop; /** * @before */ - public function setUpExecutor() + public function setUpExecutor(): void { $this->wrapped = $this->createMock(ExecutorInterface::class); @@ -33,7 +46,7 @@ public function setUpExecutor() $this->executor = new TimeoutExecutor($this->wrapped, 5.0, $this->loop); } - public function testCtorWithoutLoopShouldAssignDefaultLoop() + public function testCtorWithoutLoopShouldAssignDefaultLoop(): void { $executor = new TimeoutExecutor($this->executor, 5.0); @@ -44,7 +57,7 @@ public function testCtorWithoutLoopShouldAssignDefaultLoop() $this->assertInstanceOf(LoopInterface::class, $loop); } - public function testCancellingPromiseWillCancelWrapped() + public function testCancellingPromiseWillCancelWrapped(): void { $timer = $this->createMock(TimerInterface::class); $this->loop->expects($this->once())->method('addTimer')->with(5.0, $this->anything())->willReturn($timer); @@ -74,7 +87,7 @@ public function testCancellingPromiseWillCancelWrapped() $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); } - public function testResolvesPromiseWithoutStartingTimerWhenWrappedReturnsResolvedPromise() + public function testResolvesPromiseWithoutStartingTimerWhenWrappedReturnsResolvedPromise(): void { $this->loop->expects($this->never())->method('addTimer'); $this->loop->expects($this->never())->method('cancelTimer'); @@ -82,7 +95,7 @@ public function testResolvesPromiseWithoutStartingTimerWhenWrappedReturnsResolve $this->wrapped ->expects($this->once()) ->method('query') - ->willReturn(resolve('0.0.0.0')); + ->willReturn(resolve($this->createStandardResponse())); $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN); $promise = $this->executor->query($query); @@ -90,7 +103,7 @@ public function testResolvesPromiseWithoutStartingTimerWhenWrappedReturnsResolve $promise->then($this->expectCallableOnce(), $this->expectCallableNever()); } - public function testResolvesPromiseAfterCancellingTimerWhenWrappedReturnsPendingPromiseThatResolves() + public function testResolvesPromiseAfterCancellingTimerWhenWrappedReturnsPendingPromiseThatResolves(): void { $timer = $this->createMock(TimerInterface::class); $this->loop->expects($this->once())->method('addTimer')->with(5.0, $this->anything())->willReturn($timer); @@ -105,12 +118,12 @@ public function testResolvesPromiseAfterCancellingTimerWhenWrappedReturnsPending $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN); $promise = $this->executor->query($query); - $deferred->resolve('0.0.0.0'); + $deferred->resolve($this->createStandardResponse()); $promise->then($this->expectCallableOnce(), $this->expectCallableNever()); } - public function testRejectsPromiseWithoutStartingTimerWhenWrappedReturnsRejectedPromise() + public function testRejectsPromiseWithoutStartingTimerWhenWrappedReturnsRejectedPromise(): void { $this->loop->expects($this->never())->method('addTimer'); $this->loop->expects($this->never())->method('cancelTimer'); @@ -126,7 +139,7 @@ public function testRejectsPromiseWithoutStartingTimerWhenWrappedReturnsRejected $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith(new \RuntimeException())); } - public function testRejectsPromiseAfterCancellingTimerWhenWrappedReturnsPendingPromiseThatRejects() + public function testRejectsPromiseAfterCancellingTimerWhenWrappedReturnsPendingPromiseThatRejects(): void { $timer = $this->createMock(TimerInterface::class); $this->loop->expects($this->once())->method('addTimer')->with(5.0, $this->anything())->willReturn($timer); @@ -146,7 +159,7 @@ public function testRejectsPromiseAfterCancellingTimerWhenWrappedReturnsPendingP $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith(new \RuntimeException())); } - public function testRejectsPromiseAndCancelsPendingQueryWhenTimeoutTriggers() + public function testRejectsPromiseAndCancelsPendingQueryWhenTimeoutTriggers(): void { $timerCallback = null; $timer = $this->createMock(TimerInterface::class); @@ -189,4 +202,15 @@ public function testRejectsPromiseAndCancelsPendingQueryWhenTimeoutTriggers() $this->assertInstanceOf(TimeoutException::class, $exception); $this->assertEquals('DNS query for igor.io (A) timed out' , $exception->getMessage()); } + + + protected function createStandardResponse(): Message + { + $response = new Message(); + $response->qr = true; + $response->questions[] = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN); + $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '0.0.0.0'); + + return $response; + } } diff --git a/tests/Query/UdpTransportExecutorTest.php b/tests/Query/UdpTransportExecutorTest.php index 41b27a9a..b4d4907b 100644 --- a/tests/Query/UdpTransportExecutorTest.php +++ b/tests/Query/UdpTransportExecutorTest.php @@ -23,7 +23,7 @@ class UdpTransportExecutorTest extends TestCase * @param string $input * @param string $expected */ - public function testCtorShouldAcceptNameserverAddresses($input, $expected) + public function testCtorShouldAcceptNameserverAddresses(string $input, string $expected): void { $loop = $this->createMock(LoopInterface::class); @@ -36,7 +36,10 @@ public function testCtorShouldAcceptNameserverAddresses($input, $expected) $this->assertEquals($expected, $value); } - public static function provideDefaultPortProvider() + /** + * @return iterable + */ + public static function provideDefaultPortProvider(): iterable { yield [ '8.8.8.8', @@ -64,7 +67,7 @@ public static function provideDefaultPortProvider() ]; } - public function testCtorWithoutLoopShouldAssignDefaultLoop() + public function testCtorWithoutLoopShouldAssignDefaultLoop(): void { $executor = new UdpTransportExecutor('127.0.0.1'); @@ -75,7 +78,7 @@ public function testCtorWithoutLoopShouldAssignDefaultLoop() $this->assertInstanceOf(LoopInterface::class, $loop); } - public function testCtorShouldThrowWhenNameserverAddressIsInvalid() + public function testCtorShouldThrowWhenNameserverAddressIsInvalid(): void { $loop = $this->createMock(LoopInterface::class); @@ -83,7 +86,7 @@ public function testCtorShouldThrowWhenNameserverAddressIsInvalid() new UdpTransportExecutor('///', $loop); } - public function testCtorShouldThrowWhenNameserverAddressContainsHostname() + public function testCtorShouldThrowWhenNameserverAddressContainsHostname(): void { $loop = $this->createMock(LoopInterface::class); @@ -91,7 +94,7 @@ public function testCtorShouldThrowWhenNameserverAddressContainsHostname() new UdpTransportExecutor('localhost', $loop); } - public function testCtorShouldThrowWhenNameserverSchemeIsInvalid() + public function testCtorShouldThrowWhenNameserverSchemeIsInvalid(): void { $loop = $this->createMock(LoopInterface::class); @@ -99,7 +102,7 @@ public function testCtorShouldThrowWhenNameserverSchemeIsInvalid() new UdpTransportExecutor('tcp://1.2.3.4', $loop); } - public function testQueryRejectsIfMessageExceedsUdpSize() + public function testQueryRejectsIfMessageExceedsUdpSize(): void { $loop = $this->createMock(LoopInterface::class); $loop->expects($this->never())->method('addReadStream'); @@ -111,20 +114,14 @@ public function testQueryRejectsIfMessageExceedsUdpSize() $this->assertInstanceOf(PromiseInterface::class, $promise); - $exception = null; - $promise->then(null, function ($reason) use (&$exception) { - $exception = $reason; - }); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('DNS query for ' . $query->name . ' (A) failed: Query too large for UDP transport'); + $this->expectExceptionCode(defined('SOCKET_EMSGSIZE') ? SOCKET_EMSGSIZE : 90); - $this->expectException( - \RuntimeException::class, - 'DNS query for ' . $query->name . ' (A) failed: Query too large for UDP transport', - defined('SOCKET_EMSGSIZE') ? SOCKET_EMSGSIZE : 90 - ); - throw $exception; + await($promise); } - public function testQueryRejectsIfServerConnectionFails() + public function testQueryRejectsIfServerConnectionFails(): void { $loop = $this->createMock(LoopInterface::class); $loop->expects($this->never())->method('addReadStream'); @@ -140,19 +137,13 @@ public function testQueryRejectsIfServerConnectionFails() $this->assertInstanceOf(PromiseInterface::class, $promise); - $exception = null; - $promise->then(null, function ($reason) use (&$exception) { - $exception = $reason; - }); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('DNS query for google.com (A) failed: Unable to connect to DNS server /// (Failed to parse address "///")'); - $this->expectException( - \RuntimeException::class, - 'DNS query for google.com (A) failed: Unable to connect to DNS server /// (Failed to parse address "///")' - ); - throw $exception; + await($promise); } - public function testQueryRejectsIfSendToServerFailsAfterConnectionWithoutCallingCustomErrorHandler() + public function testQueryRejectsIfSendToServerFailsAfterConnectionWithoutCallingCustomErrorHandler(): void { $loop = $this->createMock(LoopInterface::class); $loop->expects($this->never())->method('addReadStream'); @@ -165,8 +156,10 @@ public function testQueryRejectsIfSendToServerFailsAfterConnectionWithoutCalling $ref->setValue($executor, PHP_INT_MAX); $error = null; - set_error_handler(function ($_, $errstr) use (&$error) { + set_error_handler(function (int $_, string $errstr) use (&$error): bool { $error = $errstr; + + return true; }); $query = new Query(str_repeat('a.', 100000) . '.example', Message::TYPE_A, Message::CLASS_IN); @@ -177,20 +170,14 @@ public function testQueryRejectsIfSendToServerFailsAfterConnectionWithoutCalling $this->assertInstanceOf(PromiseInterface::class, $promise); - $exception = null; - $promise->then(null, function ($reason) use (&$exception) { - $exception = $reason; - }); - // ECONNREFUSED (Connection refused) on Linux, EMSGSIZE (Message too long) on macOS - $this->expectException( - \RuntimeException::class, - 'DNS query for ' . $query->name . ' (A) failed: Unable to send query to DNS server udp://0.0.0.0:53 (' - ); - throw $exception; + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('DNS query for ' . $query->name . ' (A) failed: Unable to send query to DNS server udp://0.0.0.0:53 ('); + + await($promise); } - public function testQueryKeepsPendingIfReadFailsBecauseServerRefusesConnection() + public function testQueryKeepsPendingIfReadFailsBecauseServerRefusesConnection(): void { $socket = null; $callback = null; @@ -226,7 +213,7 @@ public function testQueryKeepsPendingIfReadFailsBecauseServerRefusesConnection() /** * @group internet */ - public function testQueryRejectsOnCancellation() + public function testQueryRejectsOnCancellation(): void { $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('addReadStream'); @@ -248,9 +235,12 @@ public function testQueryRejectsOnCancellation() $this->assertEquals('DNS query for google.com (A) has been cancelled', $exception->getMessage()); } - public function testQueryKeepsPendingIfServerSendsInvalidMessage() + public function testQueryKeepsPendingIfServerSendsInvalidMessage(): void { $server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND); + if ($server === false) { + $this->fail('Unable to start UDP server'); + } Loop::addReadStream($server, function ($server) { $data = stream_socket_recvfrom($server, 512, 0, $peer); stream_socket_sendto($server, 'invalid', 0, $peer); @@ -260,6 +250,9 @@ public function testQueryKeepsPendingIfServerSendsInvalidMessage() }); $address = stream_socket_get_name($server, false); + if ($address === false) { + $this->fail('Unable get UDP socket name'); + } $executor = new UdpTransportExecutor($address); $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN); @@ -278,13 +271,17 @@ function ($e) use (&$wait) { $promise->cancel(); } - public function testQueryKeepsPendingIfServerSendsInvalidId() + public function testQueryKeepsPendingIfServerSendsInvalidId(): void { $parser = new Parser(); $dumper = new BinaryDumper(); $server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND); + if ($server === false) { + $this->fail('Unable to start UDP server'); + } Loop::addReadStream($server, function ($server) use ($parser, $dumper) { + /** @var string $data */ $data = stream_socket_recvfrom($server, 512, 0, $peer); $message = $parser->parseMessage($data); @@ -297,6 +294,9 @@ public function testQueryKeepsPendingIfServerSendsInvalidId() }); $address = stream_socket_get_name($server, false); + if ($address === false) { + $this->fail('Unable get UDP socket name'); + } $executor = new UdpTransportExecutor($address); $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN); @@ -315,13 +315,17 @@ function ($e) use (&$wait) { $promise->cancel(); } - public function testQueryRejectsIfServerSendsTruncatedResponse() + public function testQueryRejectsIfServerSendsTruncatedResponse(): void { $parser = new Parser(); $dumper = new BinaryDumper(); $server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND); + if ($server === false) { + $this->fail('Unable to start UDP server'); + } Loop::addReadStream($server, function ($server) use ($parser, $dumper) { + /** @var string $data */ $data = stream_socket_recvfrom($server, 512, 0, $peer); $message = $parser->parseMessage($data); @@ -334,27 +338,33 @@ public function testQueryRejectsIfServerSendsTruncatedResponse() }); $address = stream_socket_get_name($server, false); + if ($address === false) { + $this->fail('Unable get UDP socket name'); + } $executor = new UdpTransportExecutor($address); $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN); $promise = $executor->query($query); - $this->expectException( - \RuntimeException::class, - '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 - ); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('DNS query for google.com (A) failed: The DNS server udp://' . $address . ' returned a truncated result for a UDP query'); + $this->expectExceptionCode(defined('SOCKET_EMSGSIZE') ? SOCKET_EMSGSIZE : 90); + await(timeout($promise, 0.1)); } - public function testQueryResolvesIfServerSendsValidResponse() + public function testQueryResolvesIfServerSendsValidResponse(): void { $parser = new Parser(); $dumper = new BinaryDumper(); $server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND); + if ($server === false) { + $this->fail('Unable to start UDP server'); + } Loop::addReadStream($server, function ($server) use ($parser, $dumper) { + /** @var string $data */ $data = stream_socket_recvfrom($server, 512, 0, $peer); $message = $parser->parseMessage($data); @@ -366,6 +376,9 @@ public function testQueryResolvesIfServerSendsValidResponse() }); $address = stream_socket_get_name($server, false); + if ($address === false) { + $this->fail('Unable get UDP socket name'); + } $executor = new UdpTransportExecutor($address); $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN); diff --git a/tests/Resolver/FactoryTest.php b/tests/Resolver/FactoryTest.php index c50f80f3..43f34848 100644 --- a/tests/Resolver/FactoryTest.php +++ b/tests/Resolver/FactoryTest.php @@ -7,6 +7,7 @@ use React\Dns\Config\Config; use React\Dns\Query\CachingExecutor; use React\Dns\Query\CoopExecutor; +use React\Dns\Query\ExecutorInterface; use React\Dns\Query\HostsFileExecutor; use React\Dns\Query\FallbackExecutor; use React\Dns\Query\RetryExecutor; @@ -22,7 +23,7 @@ class FactoryTest extends TestCase { /** @test */ - public function createShouldCreateResolver() + public function createShouldCreateResolver(): void { $factory = new Factory(); $resolver = $factory->create('8.8.8.8:53'); @@ -31,7 +32,7 @@ public function createShouldCreateResolver() } /** @test */ - public function createWithoutSchemeShouldCreateResolverWithSelectiveUdpAndTcpExecutorStack() + public function createWithoutSchemeShouldCreateResolverWithSelectiveUdpAndTcpExecutorStack(): void { $loop = $this->createMock(LoopInterface::class); @@ -86,7 +87,7 @@ public function createWithoutSchemeShouldCreateResolverWithSelectiveUdpAndTcpExe } /** @test */ - public function createWithUdpSchemeShouldCreateResolverWithUdpExecutorStack() + public function createWithUdpSchemeShouldCreateResolverWithUdpExecutorStack(): void { $loop = $this->createMock(LoopInterface::class); @@ -119,7 +120,7 @@ public function createWithUdpSchemeShouldCreateResolverWithUdpExecutorStack() } /** @test */ - public function createWithTcpSchemeShouldCreateResolverWithTcpExecutorStack() + public function createWithTcpSchemeShouldCreateResolverWithTcpExecutorStack(): void { $loop = $this->createMock(LoopInterface::class); @@ -152,7 +153,7 @@ public function createWithTcpSchemeShouldCreateResolverWithTcpExecutorStack() } /** @test */ - public function createWithConfigWithTcpNameserverSchemeShouldCreateResolverWithTcpExecutorStack() + public function createWithConfigWithTcpNameserverSchemeShouldCreateResolverWithTcpExecutorStack(): void { $loop = $this->createMock(LoopInterface::class); @@ -188,7 +189,7 @@ public function createWithConfigWithTcpNameserverSchemeShouldCreateResolverWithT } /** @test */ - public function createWithConfigWithTwoNameserversWithTcpSchemeShouldCreateResolverWithFallbackExecutorStack() + public function createWithConfigWithTwoNameserversWithTcpSchemeShouldCreateResolverWithFallbackExecutorStack(): void { $loop = $this->createMock(LoopInterface::class); @@ -255,7 +256,7 @@ public function createWithConfigWithTwoNameserversWithTcpSchemeShouldCreateResol } /** @test */ - public function createWithConfigWithThreeNameserversWithTcpSchemeShouldCreateResolverWithNestedFallbackExecutorStack() + public function createWithConfigWithThreeNameserversWithTcpSchemeShouldCreateResolverWithNestedFallbackExecutorStack(): void { $loop = $this->createMock(LoopInterface::class); @@ -347,7 +348,7 @@ public function createWithConfigWithThreeNameserversWithTcpSchemeShouldCreateRes } /** @test */ - public function createShouldThrowWhenNameserverIsInvalid() + public function createShouldThrowWhenNameserverIsInvalid(): void { $loop = $this->createMock(LoopInterface::class); @@ -358,7 +359,7 @@ public function createShouldThrowWhenNameserverIsInvalid() } /** @test */ - public function createShouldThrowWhenConfigHasNoNameservers() + public function createShouldThrowWhenConfigHasNoNameservers(): void { $loop = $this->createMock(LoopInterface::class); @@ -369,7 +370,7 @@ public function createShouldThrowWhenConfigHasNoNameservers() } /** @test */ - public function createShouldThrowWhenConfigHasInvalidNameserver() + public function createShouldThrowWhenConfigHasInvalidNameserver(): void { $loop = $this->createMock(LoopInterface::class); @@ -383,7 +384,7 @@ public function createShouldThrowWhenConfigHasInvalidNameserver() } /** @test */ - public function createCachedShouldCreateResolverWithCachingExecutor() + public function createCachedShouldCreateResolverWithCachingExecutor(): void { $factory = new Factory(); $resolver = $factory->createCached('8.8.8.8:53'); @@ -396,7 +397,7 @@ public function createCachedShouldCreateResolverWithCachingExecutor() } /** @test */ - public function createCachedShouldCreateResolverWithCachingExecutorWithCustomCache() + public function createCachedShouldCreateResolverWithCachingExecutorWithCustomCache(): void { $cache = $this->createMock(CacheInterface::class); $loop = $this->createMock(LoopInterface::class); @@ -411,7 +412,7 @@ public function createCachedShouldCreateResolverWithCachingExecutorWithCustomCac $this->assertSame($cache, $cacheProperty); } - private function getResolverPrivateExecutor($resolver) + private function getResolverPrivateExecutor(Resolver $resolver): ExecutorInterface { $executor = $this->getResolverPrivateMemberValue($resolver, 'executor'); @@ -420,23 +421,29 @@ private function getResolverPrivateExecutor($resolver) $reflector = new \ReflectionProperty(HostsFileExecutor::class, 'fallback'); $reflector->setAccessible(true); + /** @var ExecutorInterface $executor */ $executor = $reflector->getValue($executor); } return $executor; } - private function getResolverPrivateMemberValue($resolver, $field) + private function getResolverPrivateMemberValue(Resolver $resolver, string $field): ExecutorInterface { $reflector = new \ReflectionProperty(Resolver::class, $field); $reflector->setAccessible(true); + /** @var ExecutorInterface */ return $reflector->getValue($resolver); } - private function getCachingExecutorPrivateMemberValue($resolver, $field) + /** + * @return ExecutorInterface|CacheInterface + */ + private function getCachingExecutorPrivateMemberValue(CachingExecutor $resolver, string $field) { $reflector = new \ReflectionProperty(CachingExecutor::class, $field); $reflector->setAccessible(true); + /** @var ExecutorInterface|CacheInterface */ return $reflector->getValue($resolver); } } diff --git a/tests/Resolver/ResolveAliasesTest.php b/tests/Resolver/ResolveAliasesTest.php index 013aa048..5983988a 100644 --- a/tests/Resolver/ResolveAliasesTest.php +++ b/tests/Resolver/ResolveAliasesTest.php @@ -2,6 +2,7 @@ namespace React\Tests\Dns\Resolver; +use PHPUnit\Framework\MockObject\MockObject; use React\Dns\Model\Message; use React\Dns\Model\Record; use React\Dns\Query\ExecutorInterface; @@ -12,9 +13,12 @@ class ResolveAliasesTest extends TestCase { /** + * @param array $expectedAnswers + * @param array $answers + * * @dataProvider provideAliasedAnswers */ - public function testResolveAliases(array $expectedAnswers, array $answers, $name) + public function testResolveAliases(array $expectedAnswers, array $answers, string $name): void { $message = new Message(); foreach ($answers as $answer) { @@ -31,7 +35,10 @@ public function testResolveAliases(array $expectedAnswers, array $answers, $name $answers->then($this->expectCallableOnceWith($expectedAnswers), null); } - public function provideAliasedAnswers() + /** + * @return iterable, array, string}> + */ + public function provideAliasedAnswers(): iterable { yield [ ['178.79.169.131'], @@ -90,6 +97,9 @@ public function provideAliasedAnswers() ]; } + /** + * @return ExecutorInterface&MockObject + */ private function createExecutorMock() { return $this->createMock(ExecutorInterface::class); diff --git a/tests/Resolver/ResolverTest.php b/tests/Resolver/ResolverTest.php index f1df1850..fe793be1 100644 --- a/tests/Resolver/ResolverTest.php +++ b/tests/Resolver/ResolverTest.php @@ -2,19 +2,21 @@ namespace React\Tests\Dns\Resolver; +use PHPUnit\Framework\MockObject\MockObject; use React\Dns\Model\Message; use React\Dns\Model\Record; use React\Dns\Query\ExecutorInterface; use React\Dns\Query\Query; use React\Dns\RecordNotFoundException; use React\Dns\Resolver\Resolver; +use React\Promise\PromiseInterface; use React\Tests\Dns\TestCase; use function React\Promise\resolve; class ResolverTest extends TestCase { /** @test */ - public function resolveShouldQueryARecords() + public function resolveShouldQueryARecords(): void { $executor = $this->createExecutorMock(); $executor @@ -35,7 +37,7 @@ public function resolveShouldQueryARecords() } /** @test */ - public function resolveAllShouldQueryGivenRecords() + public function resolveAllShouldQueryGivenRecords(): void { $executor = $this->createExecutorMock(); $executor @@ -56,7 +58,7 @@ public function resolveAllShouldQueryGivenRecords() } /** @test */ - public function resolveAllShouldIgnoreRecordsWithOtherTypes() + public function resolveAllShouldIgnoreRecordsWithOtherTypes(): void { $executor = $this->createExecutorMock(); $executor @@ -78,7 +80,7 @@ public function resolveAllShouldIgnoreRecordsWithOtherTypes() } /** @test */ - public function resolveAllShouldReturnMultipleValuesForAlias() + public function resolveAllShouldReturnMultipleValuesForAlias(): void { $executor = $this->createExecutorMock(); $executor @@ -103,7 +105,7 @@ public function resolveAllShouldReturnMultipleValuesForAlias() } /** @test */ - public function resolveShouldQueryARecordsAndIgnoreCase() + public function resolveShouldQueryARecordsAndIgnoreCase(): void { $executor = $this->createExecutorMock(); $executor @@ -124,7 +126,7 @@ public function resolveShouldQueryARecordsAndIgnoreCase() } /** @test */ - public function resolveShouldFilterByName() + public function resolveShouldFilterByName(): void { $executor = $this->createExecutorMock(); $executor @@ -149,7 +151,7 @@ public function resolveShouldFilterByName() /** * @test */ - public function resolveWithNoAnswersShouldCallErrbackIfGiven() + public function resolveWithNoAnswersShouldCallErrbackIfGiven(): void { $executor = $this->createExecutorMock(); $executor @@ -172,7 +174,10 @@ public function resolveWithNoAnswersShouldCallErrbackIfGiven() $resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback); } - public function provideRcodeErrors() + /** + * @return iterable + */ + public function provideRcodeErrors(): iterable { yield [ Message::RCODE_FORMAT_ERROR, @@ -204,14 +209,14 @@ public function provideRcodeErrors() * @test * @dataProvider provideRcodeErrors */ - public function resolveWithRcodeErrorShouldCallErrbackIfGiven($code, $expectedMessage) + public function resolveWithRcodeErrorShouldCallErrbackIfGiven(int $code, string $expectedMessage): void { $executor = $this->createExecutorMock(); $executor ->expects($this->once()) ->method('query') ->with($this->isInstanceOf(Query::class)) - ->will($this->returnCallback(function ($query) use ($code) { + ->will($this->returnCallback(static function (Query $query) use ($code): PromiseInterface { $response = new Message(); $response->qr = true; $response->rcode = $code; @@ -220,7 +225,7 @@ public function resolveWithRcodeErrorShouldCallErrbackIfGiven($code, $expectedMe return resolve($response); })); - $errback = $this->expectCallableOnceWith($this->callback(function ($param) use ($code, $expectedMessage) { + $errback = $this->expectCallableOnceWith($this->callback(function ($param) use ($code, $expectedMessage): bool { return ($param instanceof RecordNotFoundException && $param->getCode() === $code && $param->getMessage() === $expectedMessage); })); @@ -228,6 +233,9 @@ public function resolveWithRcodeErrorShouldCallErrbackIfGiven($code, $expectedMe $resolver->resolve('example.com')->then($this->expectCallableNever(), $errback); } + /** + * @return ExecutorInterface&MockObject + */ private function createExecutorMock() { return $this->createMock(ExecutorInterface::class); diff --git a/tests/TestCase.php b/tests/TestCase.php index 23bd860e..fea296fb 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,12 +2,17 @@ namespace React\Tests\Dns; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase as BaseTestCase; abstract class TestCase extends BaseTestCase { - protected function expectCallableOnce() + /** + * @return MockObject&(callable(): void)&\stdClass + */ + protected function expectCallableOnce(): MockObject { + /** @var MockObject&(callable(): void)&\stdClass $mock */ $mock = $this->createCallableMock(); $mock ->expects($this->once()) @@ -16,8 +21,13 @@ protected function expectCallableOnce() return $mock; } - protected function expectCallableOnceWith($value) + /** + * @param mixed $value + * @return MockObject&(callable(): void)&\stdClass + */ + protected function expectCallableOnceWith($value): MockObject { + /** @var MockObject&(callable(): void)&\stdClass $mock */ $mock = $this->createCallableMock(); $mock ->expects($this->once()) @@ -27,8 +37,12 @@ protected function expectCallableOnceWith($value) return $mock; } - protected function expectCallableNever() + /** + * @return MockObject&(callable(): void)&\stdClass + */ + protected function expectCallableNever(): MockObject { + /** @var MockObject&(callable(): void)&\stdClass $mock */ $mock = $this->createCallableMock(); $mock ->expects($this->never()) @@ -37,7 +51,10 @@ protected function expectCallableNever() return $mock; } - protected function createCallableMock() + /** + * @return MockObject&\stdClass + */ + protected function createCallableMock(): MockObject { $builder = $this->getMockBuilder(\stdClass::class); if (method_exists($builder, 'addMethods')) {