From 0b31285ee08800c6a11610fcc7082cbc52538b23 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 15 Mar 2016 18:45:16 +0100 Subject: [PATCH] [PhpUnitBridge] Mock DNS functions --- src/Symfony/Bridge/PhpUnit/ClockMock.php | 2 +- src/Symfony/Bridge/PhpUnit/DnsMock.php | 224 ++++++++++++++++++ .../Bridge/PhpUnit/SymfonyTestsListener.php | 48 +++- .../Bridge/PhpUnit/Tests/DnsMockTest.php | 149 ++++++++++++ 4 files changed, 415 insertions(+), 8 deletions(-) create mode 100644 src/Symfony/Bridge/PhpUnit/DnsMock.php create mode 100644 src/Symfony/Bridge/PhpUnit/Tests/DnsMockTest.php diff --git a/src/Symfony/Bridge/PhpUnit/ClockMock.php b/src/Symfony/Bridge/PhpUnit/ClockMock.php index fe5cd851258d2..6bd5724f2590e 100644 --- a/src/Symfony/Bridge/PhpUnit/ClockMock.php +++ b/src/Symfony/Bridge/PhpUnit/ClockMock.php @@ -74,7 +74,7 @@ public static function register($class) $self = get_called_class(); $mockedNs = array(substr($class, 0, strrpos($class, '\\'))); - if (strpos($class, '\\Tests\\')) { + if (0 < strpos($class, '\\Tests\\')) { $ns = str_replace('\\Tests\\', '\\', $class); $mockedNs[] = substr($ns, 0, strrpos($ns, '\\')); } diff --git a/src/Symfony/Bridge/PhpUnit/DnsMock.php b/src/Symfony/Bridge/PhpUnit/DnsMock.php new file mode 100644 index 0000000000000..3bce1cbbf5b91 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/DnsMock.php @@ -0,0 +1,224 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit; + +/** + * @author Nicolas Grekas + */ +class DnsMock +{ + private static $hosts = array(); + private static $dnsTypes = array( + 'A' => DNS_A, + 'MX' => DNS_MX, + 'NS' => DNS_NS, + 'SOA' => DNS_SOA, + 'PTR' => DNS_PTR, + 'CNAME' => DNS_CNAME, + 'AAAA' => DNS_AAAA, + 'A6' => DNS_A6, + 'SRV' => DNS_SRV, + 'NAPTR' => DNS_NAPTR, + 'TXT' => DNS_TXT, + 'HINFO' => DNS_HINFO, + ); + + /** + * Configures the mock values for DNS queries. + * + * @param array $hosts Mocked hosts as keys, arrays of DNS records as returned by dns_get_record() as values. + */ + public static function withMockedHosts(array $hosts) + { + self::$hosts = $hosts; + } + + public static function checkdnsrr($hostname, $type = 'MX') + { + if (!self::$hosts) { + return \checkdnsrr($hostname, $type); + } + if (isset(self::$hosts[$hostname])) { + $type = strtoupper($type); + + foreach (self::$hosts[$hostname] as $record) { + if ($record['type'] === $type) { + return true; + } + if ('ANY' === $type && isset(self::$dnsTypes[$record['type']]) && 'HINFO' !== $record['type']) { + return true; + } + } + } + + return false; + } + + public static function getmxrr($hostname, &$mxhosts, &$weight = null) + { + if (!self::$hosts) { + return \getmxrr($hostname, $mxhosts, $weight); + } + $mxhosts = $weight = array(); + + if (isset(self::$hosts[$hostname])) { + foreach (self::$hosts[$hostname] as $record) { + if ('MX' === $record['type']) { + $mxhosts[] = $record['host']; + $weight[] = $record['pri']; + } + } + } + + return (bool) $mxhosts; + } + + public static function gethostbyaddr($ipAddress) + { + if (!self::$hosts) { + return \gethostbyaddr($ipAddress); + } + foreach (self::$hosts as $hostname => $records) { + foreach ($records as $record) { + if ('A' === $record['type'] && $ipAddress === $record['ip']) { + return $hostname; + } + if ('AAAA' === $record['type'] && $ipAddress === $record['ipv6']) { + return $hostname; + } + } + } + + return $ipAddress; + } + + public static function gethostbyname($hostname) + { + if (!self::$hosts) { + return \gethostbyname($hostname); + } + if (isset(self::$hosts[$hostname])) { + foreach (self::$hosts[$hostname] as $record) { + if ('A' === $record['type']) { + return $record['ip']; + } + } + } + + return $hostname; + } + + public static function gethostbynamel($hostname) + { + if (!self::$hosts) { + return \gethostbynamel($hostname); + } + $ips = false; + + if (isset(self::$hosts[$hostname])) { + $ips = array(); + + foreach (self::$hosts[$hostname] as $record) { + if ('A' === $record['type']) { + $ips[] = $record['ip']; + } + } + } + + return $ips; + } + + public static function dns_get_record($hostname, $type = DNS_ANY, &$authns = null, &$addtl = null, $raw = false) + { + if (!self::$hosts) { + return \dns_get_record($hostname, $type, $authns, $addtl, $raw); + } + + $records = false; + + if (isset(self::$hosts[$hostname])) { + if (DNS_ANY === $type) { + $type = DNS_ALL; + } + $records = array(); + + foreach (self::$hosts[$hostname] as $record) { + if (isset(self::$dnsTypes[$record['type']]) && (self::$dnsTypes[$record['type']] & $type)) { + $records[] = array_merge(array('host' => $hostname, 'class' => 'IN', 'ttl' => 1, 'type' => $record['type']), $record); + } + } + } + + return $records; + } + + public static function register($class) + { + $self = get_called_class(); + + $mockedNs = array(substr($class, 0, strrpos($class, '\\'))); + if (0 < strpos($class, '\\Tests\\')) { + $ns = str_replace('\\Tests\\', '\\', $class); + $mockedNs[] = substr($ns, 0, strrpos($ns, '\\')); + } + foreach ($mockedNs as $ns) { + if (function_exists($ns.'\checkdnsrr')) { + continue; + } + eval(<< $namespaces) { + if (!is_array($namespaces)) { + $namespaces = array($namespaces); + } + if (is_int($type)) { + // @deprecated BC with v2.8 to v3.0 + $type = 'time-sensitive'; + $warn = true; + } + if ('time-sensitive' === $type) { + foreach ($namespaces as $ns) { + ClockMock::register($ns.'\DummyClass'); + } + } + if ('dns-sensitive' === $type) { + foreach ($namespaces as $ns) { + DnsMock::register($ns.'\DummyClass'); + } } } if (self::$globallyEnabled) { $this->state = -2; } else { self::$globallyEnabled = true; + if ($warn) { + echo "Clock-mocked namespaces for SymfonyTestsListener need to be nested in a \"time-sensitive\" key. This will be enforced in Symfony 4.0.\n"; + } } } @@ -75,10 +97,16 @@ public function startTestSuite(\PHPUnit_Framework_TestSuite $suite) for ($i = 0; isset($testSuites[$i]); ++$i) { foreach ($testSuites[$i]->tests() as $test) { if ($test instanceof \PHPUnit_Framework_TestSuite) { - if (class_exists($test->getName(), false) && in_array('time-sensitive', \PHPUnit_Util_Test::getGroups($test->getName()), true)) { - ClockMock::register($test->getName()); - } else { + if (!class_exists($test->getName(), false)) { $testSuites[] = $test; + continue; + } + $groups = \PHPUnit_Util_Test::getGroups($test->getName()); + if (in_array('time-sensitive', $groups, true)) { + ClockMock::register($test->getName()); + } + if (in_array('dns-sensitive', $groups, true)) { + DnsMock::register($test->getName()); } } } @@ -120,6 +148,9 @@ public function startTest(\PHPUnit_Framework_Test $test) ClockMock::register(get_class($test)); ClockMock::withClockMock(true); } + if (in_array('dns-sensitive', $groups, true)) { + DnsMock::register(get_class($test)); + } } } @@ -131,6 +162,9 @@ public function endTest(\PHPUnit_Framework_Test $test, $time) if (in_array('time-sensitive', $groups, true)) { ClockMock::withClockMock(false); } + if (in_array('dns-sensitive', $groups, true)) { + DnsMock::withMockedHosts(array()); + } } } } diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DnsMockTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DnsMockTest.php new file mode 100644 index 0000000000000..8da1283324a44 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DnsMockTest.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Tests; + +use Symfony\Bridge\PhpUnit\DnsMock; + +require_once __DIR__.'/../DnsMock.php'; + +class DnsMockTest extends \PHPUnit_Framework_TestCase +{ + protected function tearDown() + { + DnsMock::withMockedHosts(array()); + } + + public function testCheckdnsrr() + { + DnsMock::withMockedHosts(array('example.com' => array(array('type' => 'MX')))); + $this->assertTrue(DnsMock::checkdnsrr('example.com')); + + DnsMock::withMockedHosts(array('example.com' => array(array('type' => 'A')))); + $this->assertFalse(DnsMock::checkdnsrr('example.com')); + $this->assertTrue(DnsMock::checkdnsrr('example.com', 'a')); + $this->assertTrue(DnsMock::checkdnsrr('example.com', 'any')); + $this->assertFalse(DnsMock::checkdnsrr('foobar.com', 'ANY')); + } + + public function testGetmxrr() + { + DnsMock::withMockedHosts(array( + 'example.com' => array(array( + 'type' => 'MX', + 'host' => 'mx.example.com', + 'pri' => 10, + )), + )); + + $this->assertFalse(DnsMock::getmxrr('foobar.com', $mxhosts, $weight)); + $this->assertTrue(DnsMock::getmxrr('example.com', $mxhosts, $weight)); + $this->assertSame(array('mx.example.com'), $mxhosts); + $this->assertSame(array(10), $weight); + } + + public function testGethostbyaddr() + { + DnsMock::withMockedHosts(array( + 'example.com' => array( + array( + 'type' => 'A', + 'ip' => '1.2.3.4', + ), + array( + 'type' => 'AAAA', + 'ipv6' => '::12', + ), + ), + )); + + $this->assertSame('::21', DnsMock::gethostbyaddr('::21')); + $this->assertSame('example.com', DnsMock::gethostbyaddr('::12')); + $this->assertSame('example.com', DnsMock::gethostbyaddr('1.2.3.4')); + } + + public function testGethostbyname() + { + DnsMock::withMockedHosts(array( + 'example.com' => array( + array( + 'type' => 'AAAA', + 'ipv6' => '::12', + ), + array( + 'type' => 'A', + 'ip' => '1.2.3.4', + ), + ), + )); + + $this->assertSame('foobar.com', DnsMock::gethostbyname('foobar.com')); + $this->assertSame('1.2.3.4', DnsMock::gethostbyname('example.com')); + } + + public function testGethostbynamel() + { + DnsMock::withMockedHosts(array( + 'example.com' => array( + array( + 'type' => 'A', + 'ip' => '1.2.3.4', + ), + array( + 'type' => 'A', + 'ip' => '2.3.4.5', + ), + ), + )); + + $this->assertFalse(DnsMock::gethostbynamel('foobar.com')); + $this->assertSame(array('1.2.3.4', '2.3.4.5'), DnsMock::gethostbynamel('example.com')); + } + + public function testDnsGetRecord() + { + DnsMock::withMockedHosts(array( + 'example.com' => array( + array( + 'type' => 'A', + 'ip' => '1.2.3.4', + ), + array( + 'type' => 'PTR', + 'ip' => '2.3.4.5', + ), + ), + )); + + $records = array( + array( + 'host' => 'example.com', + 'class' => 'IN', + 'ttl' => 1, + 'type' => 'A', + 'ip' => '1.2.3.4', + ), + $ptr = array( + 'host' => 'example.com', + 'class' => 'IN', + 'ttl' => 1, + 'type' => 'PTR', + 'ip' => '2.3.4.5', + ), + ); + + $this->assertFalse(DnsMock::dns_get_record('foobar.com')); + $this->assertSame($records, DnsMock::dns_get_record('example.com')); + $this->assertSame($records, DnsMock::dns_get_record('example.com', DNS_ALL)); + $this->assertSame($records, DnsMock::dns_get_record('example.com', DNS_A | DNS_PTR)); + $this->assertSame(array($ptr), DnsMock::dns_get_record('example.com', DNS_PTR)); + } +}