Commit 80e45b1c authored by Jason Madden's avatar Jason Madden

Refactor the resolver classes into a package and add one for dnspython. Fixes #580

parent cecd236d
...@@ -13,8 +13,8 @@ src/gevent/libev/corecext.c ...@@ -13,8 +13,8 @@ src/gevent/libev/corecext.c
src/gevent/libev/corecext.h src/gevent/libev/corecext.h
src/gevent/libev/_corecffi.c src/gevent/libev/_corecffi.c
src/gevent/libev/_corecffi.o src/gevent/libev/_corecffi.o
src/gevent/ares.c src/gevent/resolver/cares.c
src/gevent/ares.h
Makefile.ext Makefile.ext
MANIFEST MANIFEST
*_flymake.py *_flymake.py
......
...@@ -32,6 +32,9 @@ ...@@ -32,6 +32,9 @@
- Travis CI tests on Python 3.7.0a4 and PyPy 2.7 5.10.0 and PyPy 3.5 - Travis CI tests on Python 3.7.0a4 and PyPy 2.7 5.10.0 and PyPy 3.5
5.10.1. 5.10.1.
- Add the ``dnspython`` resolver as a lightweight alternative to
c-ares.
1.3a1 (2018-01-27) 1.3a1 (2018-01-27)
================== ==================
......
...@@ -14,7 +14,7 @@ export LC_ALL=C.UTF-8 ...@@ -14,7 +14,7 @@ export LC_ALL=C.UTF-8
clean: clean:
rm -f src/gevent/libev/corecext.c src/gevent/libev/corecext.h rm -f src/gevent/libev/corecext.c src/gevent/libev/corecext.h
rm -f src/gevent/ares.c src/gevent/ares.h rm -f src/gevent/resolver/cares.c src/gevent/resolver/cares.h
rm -f src/gevent/_semaphore.c src/gevent/_semaphore.h rm -f src/gevent/_semaphore.c src/gevent/_semaphore.h
rm -f src/gevent/local.c src/gevent/local.h rm -f src/gevent/local.c src/gevent/local.h
rm -f src/gevent/*.so src/gevent/*.pyd src/gevent/libev/*.so src/gevent/libuv/*.so src/gevent/libev/*.pyd src/gevent/libuv/*.pyd rm -f src/gevent/*.so src/gevent/*.pyd src/gevent/libev/*.so src/gevent/libuv/*.so src/gevent/libev/*.pyd src/gevent/libuv/*.pyd
...@@ -33,7 +33,7 @@ doc: ...@@ -33,7 +33,7 @@ doc:
cd doc && PYTHONPATH=.. make html cd doc && PYTHONPATH=.. make html
whitespace: whitespace:
! find . -not -path "*.pem" -not -path "./.eggs/*" -not -path "./src/greentest/htmlcov/*" -not -path "./src/greentest/.coverage.*" -not -path "./.tox/*" -not -path "*/__pycache__/*" -not -path "*.so" -not -path "*.pyc" -not -path "./.git/*" -not -path "./build/*" -not -path "./src/gevent/libev/*" -not -path "./src/gevent.egg-info/*" -not -path "./dist/*" -not -path "./.DS_Store" -not -path "./deps/*" -not -path "./src/gevent/libev/corecext.*.[ch]" -not -path "./src/gevent/ares.*" -not -path "./doc/_build/*" -not -path "./doc/mytheme/static/*" -type f | xargs egrep -l " $$" ! find . -not -path "*.pem" -not -path "./.eggs/*" -not -path "./src/greentest/htmlcov/*" -not -path "./src/greentest/.coverage.*" -not -path "./.tox/*" -not -path "*/__pycache__/*" -not -path "*.so" -not -path "*.pyc" -not -path "./.git/*" -not -path "./build/*" -not -path "./src/gevent/libev/*" -not -path "./src/gevent.egg-info/*" -not -path "./dist/*" -not -path "./.DS_Store" -not -path "./deps/*" -not -path "./src/gevent/libev/corecext.*.[ch]" -not -path "./src/gevent/resolver/cares.*" -not -path "./doc/_build/*" -not -path "./doc/mytheme/static/*" -type f | xargs egrep -l " $$"
prospector: prospector:
which prospector which prospector
...@@ -66,6 +66,9 @@ alltest: basictest ...@@ -66,6 +66,9 @@ alltest: basictest
${PYTHON} scripts/travis.py fold_start ares "Running c-ares tests" ${PYTHON} scripts/travis.py fold_start ares "Running c-ares tests"
cd src/greentest && GEVENT_RESOLVER=ares GEVENTARES_SERVERS=8.8.8.8 ${PYTHON} testrunner.py --config known_failures.py --ignore tests_that_dont_use_resolver.txt --quiet cd src/greentest && GEVENT_RESOLVER=ares GEVENTARES_SERVERS=8.8.8.8 ${PYTHON} testrunner.py --config known_failures.py --ignore tests_that_dont_use_resolver.txt --quiet
${PYTHON} scripts/travis.py fold_end ares ${PYTHON} scripts/travis.py fold_end ares
${PYTHON} scripts/travis.py fold_start dnspython "Running dnspython tests"
cd src/greentest && GEVENT_RESOLVER=dnspython ${PYTHON} testrunner.py --config known_failures.py --ignore tests_that_dont_use_resolver.txt,tests_that_dont_monkeypatch.txt --quiet
${PYTHON} scripts/travis.py fold_end dnspython
# In the past, we included all test files that had a reference to 'subprocess'' somewhere in their # In the past, we included all test files that had a reference to 'subprocess'' somewhere in their
# text. The monkey-patched stdlib tests were specifically included here. # text. The monkey-patched stdlib tests were specifically included here.
# However, we now always also test on AppVeyor (Windows) which only has GEVENT_FILE=thread, # However, we now always also test on AppVeyor (Windows) which only has GEVENT_FILE=thread,
......
...@@ -79,13 +79,13 @@ def configure_ares(bext, ext): ...@@ -79,13 +79,13 @@ def configure_ares(bext, ext):
os.chdir(cwd) os.chdir(cwd)
ARES = Extension(name='gevent.ares', ARES = Extension(name='gevent.resolver.cares',
sources=['src/gevent/ares.pyx'], sources=['src/gevent/resolver/cares.pyx'],
include_dirs=['src/gevent'] + [dep_abspath('c-ares')] if CARES_EMBED else [], include_dirs=['src/gevent/resolver'] + [dep_abspath('c-ares')] if CARES_EMBED else [],
libraries=list(LIBRARIES), libraries=list(LIBRARIES),
define_macros=list(DEFINE_MACROS), define_macros=list(DEFINE_MACROS),
depends=glob_many('src/gevent/dnshelper.c', depends=glob_many('src/gevent/resolver/dnshelper.c',
'src/gevent/cares_*.[ch]')) 'src/gevent/resolver/cares_*.[ch]'))
ARES.optional = not RUNNING_ON_CI ARES.optional = not RUNNING_ON_CI
......
...@@ -15,6 +15,8 @@ coveralls>=1.0 ...@@ -15,6 +15,8 @@ coveralls>=1.0
# See version requirements in setup.py # See version requirements in setup.py
cffi cffi
futures futures
dnspython
idna
# Makes tests faster # Makes tests faster
psutil psutil
# For viewing README.rst (restview --long-description), # For viewing README.rst (restview --long-description),
......
...@@ -22,19 +22,21 @@ A resolver implements the 5 standandard functions from the ...@@ -22,19 +22,21 @@ A resolver implements the 5 standandard functions from the
Configuration Configuration
============= =============
gevent includes three implementations of resolvers, and applications gevent includes four implementations of resolvers, and applications
can provide their own implementation. By default, gevent uses can provide their own implementation. By default, gevent uses
:class:`gevent.resolver_thread.Resolver`. :class:`gevent.resolver.thread.Resolver`.
Configuration can be done through the ``GEVENT_RESOLVER`` environment Configuration can be done through the ``GEVENT_RESOLVER`` environment
variable. Specify ``ares``, ``thread``, or ``block`` to use the variable. Specify ``ares``, ``thread``, ``dnspython``, or ``block`` to use the
:class:`gevent.resolver_ares.Resolver`, :class:`gevent.resolver.ares.Resolver`,
:class:`gevent.resolver_thread.Resolver`, or :class:`gevent.resolver.thread.Resolver`, or
:class:`gevent.socket.BlockingResolver`, respectively, or set it to :class:`gevent.resolver.dnspython.Resolver`, or
:class:`gevent.resolver.blocking.Resolver`, respectively, or set it to
the fully-qualified name of an implementation of the standard the fully-qualified name of an implementation of the standard
functions. functions.
.. toctree:: .. toctree::
gevent.resolver_thread gevent.resolver.thread
gevent.resolver_ares gevent.resolver.ares
gevent.resolver.dnspython
...@@ -169,6 +169,12 @@ def run_setup(ext_modules, run_make): ...@@ -169,6 +169,12 @@ def run_setup(ext_modules, run_make):
cmdclass=dict(build_ext=ConfiguringBuildExt), cmdclass=dict(build_ext=ConfiguringBuildExt),
install_requires=install_requires, install_requires=install_requires,
setup_requires=setup_requires, setup_requires=setup_requires,
extras_require={
'dnspython': [
'dnspython',
'idna',
],
},
# It's always safe to pass the CFFI keyword, even if # It's always safe to pass the CFFI keyword, even if
# cffi is not installed: it's just ignored in that case. # cffi is not installed: it's just ignored in that case.
cffi_modules=cffi_modules, cffi_modules=cffi_modules,
......
...@@ -238,20 +238,6 @@ def cancel_wait(watcher, error=cancel_wait_ex): ...@@ -238,20 +238,6 @@ def cancel_wait(watcher, error=cancel_wait_ex):
get_hub().cancel_wait(watcher, error) get_hub().cancel_wait(watcher, error)
class BlockingResolver(object):
def __init__(self, hub=None):
pass
def close(self):
pass
for method in ['gethostbyname',
'gethostbyname_ex',
'getaddrinfo',
'gethostbyaddr',
'getnameinfo']:
locals()[method] = staticmethod(getattr(_socket, method))
def gethostbyname(hostname): def gethostbyname(hostname):
......
"""Backwards compatibility alias for :mod:`gevent.resolver.cares`.
.. deprecated:: 1.3
Use :mod:`gevent.resolver.cares`
"""
from gevent.resolver.cares import * # pylint:disable=wildcard-import,unused-wildcard-import
import gevent.resolver.cares as _cares
__all__ = _cares.__all__
del _cares
...@@ -450,9 +450,12 @@ def resolver_config(default, envvar): ...@@ -450,9 +450,12 @@ def resolver_config(default, envvar):
return [_resolvers.get(x, x) for x in result] return [_resolvers.get(x, x) for x in result]
_resolvers = {'ares': 'gevent.resolver_ares.Resolver', _resolvers = {
'thread': 'gevent.resolver_thread.Resolver', 'ares': 'gevent.resolver.ares.Resolver',
'block': 'gevent.socket.BlockingResolver'} 'thread': 'gevent.resolver.thread.Resolver',
'block': 'gevent.resolver.blocking.Resolver',
'dnspython': 'gevent.resolver.dnspython.Resolver',
}
_DEFAULT_LOOP_CLASS = 'gevent.core.loop' _DEFAULT_LOOP_CLASS = 'gevent.core.loop'
...@@ -494,9 +497,12 @@ class Hub(RawGreenlet): ...@@ -494,9 +497,12 @@ class Hub(RawGreenlet):
if loop_class == [_DEFAULT_LOOP_CLASS]: if loop_class == [_DEFAULT_LOOP_CLASS]:
loop_class = [_import(loop_class)] loop_class = [_import(loop_class)]
resolver_class = ['gevent.resolver_thread.Resolver', resolver_class = [
'gevent.resolver_ares.Resolver', 'gevent.resolver.thread.Resolver',
'gevent.socket.BlockingResolver'] 'gevent.resolver.dnspython.Resolver',
'gevent.resolver.ares.Resolver',
'gevent.resolver.blacking.Resolver',
]
#: The class or callable object, or the name of a factory function or class, #: The class or callable object, or the name of a factory function or class,
#: that will be used to create :attr:`resolver`. By default, configured according to #: that will be used to create :attr:`resolver`. By default, configured according to
#: :doc:`dns`. If a list, a list of objects in preference order. #: :doc:`dns`. If a list, a list of objects in preference order.
......
# Copyright (c) 2018 gevent contributors. See LICENSE for details.
from _socket import gaierror
from _socket import error
from _socket import getservbyname
from _socket import getaddrinfo
from gevent._compat import string_types
from gevent._compat import integer_types
from gevent.socket import SOCK_STREAM
from gevent.socket import SOCK_DGRAM
from gevent.socket import SOL_TCP
from gevent.socket import AI_CANONNAME
from gevent.socket import EAI_SERVICE
from gevent.socket import AF_INET
from gevent.socket import AI_PASSIVE
def _lookup_port(port, socktype):
# pylint:disable=too-many-branches
socktypes = []
if isinstance(port, string_types):
try:
port = int(port)
except ValueError:
try:
if socktype == 0:
origport = port
try:
port = getservbyname(port, 'tcp')
socktypes.append(SOCK_STREAM)
except error:
port = getservbyname(port, 'udp')
socktypes.append(SOCK_DGRAM)
else:
try:
if port == getservbyname(origport, 'udp'):
socktypes.append(SOCK_DGRAM)
except error:
pass
elif socktype == SOCK_STREAM:
port = getservbyname(port, 'tcp')
elif socktype == SOCK_DGRAM:
port = getservbyname(port, 'udp')
else:
raise gaierror(EAI_SERVICE, 'Servname not supported for ai_socktype')
except error as ex:
if 'not found' in str(ex):
raise gaierror(EAI_SERVICE, 'Servname not supported for ai_socktype')
else:
raise gaierror(str(ex))
except UnicodeEncodeError:
raise error('Int or String expected', port)
elif port is None:
port = 0
elif isinstance(port, integer_types):
pass
else:
raise error('Int or String expected', port, type(port))
port = int(port % 65536)
if not socktypes and socktype:
socktypes.append(socktype)
return port, socktypes
def _resolve_special(hostname, family):
if hostname == '':
result = getaddrinfo(None, 0, family, SOCK_DGRAM, 0, AI_PASSIVE)
if len(result) != 1:
raise error('wildcard resolved to multiple address')
return result[0][4][0]
return hostname
class AbstractResolver(object):
def gethostbyname(self, hostname, family=AF_INET):
hostname = _resolve_special(hostname, family)
return self.gethostbyname_ex(hostname, family)[-1][0]
def gethostbyname_ex(self, hostname, family=AF_INET):
aliases = []
addresses = []
tuples = self.getaddrinfo(hostname, 0, family,
SOCK_STREAM,
SOL_TCP, AI_CANONNAME)
canonical = tuples[0][3]
for item in tuples:
addresses.append(item[4][0])
# XXX we just ignore aliases
return (canonical, aliases, addresses)
def getaddrinfo(self, host, port, family=0, socktype=0, proto=0, flags=0):
raise NotImplementedError()
This diff is collapsed.
# Copyright (c) 2018 gevent contributors. See LICENSE for details.
import _socket
class Resolver(object):
"""
A resolver that directly uses the system's resolver functions.
"""
def __init__(self, hub=None):
pass
def close(self):
pass
for method in (
'gethostbyname',
'gethostbyname_ex',
'getaddrinfo',
'gethostbyaddr',
'getnameinfo'
):
locals()[method] = staticmethod(getattr(_socket, method))
# Copyright (c) 2018 gevent contributors. See LICENSE for details.
import _socket
from socket import AI_NUMERICHOST
from socket import gaierror
from . import AbstractResolver
from dns import resolver
from dns.resolver import _getaddrinfo
from gevent import Timeout
def _safe_getaddrinfo(*args, **kwargs):
try:
return _getaddrinfo(*args, **kwargs)
except gaierror as ex:
if isinstance(getattr(ex, '__context__', None),
(Timeout, KeyboardInterrupt, SystemExit)):
raise ex.__context__
raise
resolver._getaddrinfo = _safe_getaddrinfo
__all__ = [
'Resolver',
]
class Resolver(AbstractResolver):
"""
A resolver that uses dnspython.
This completely ignores the contents of ``/etc/hosts``, but it is
configured by ``/etc/resolv.conf`` (on Unix) or the registry (on
Windows).
This uses thread locks and sockets, so it only functions if the system
has been monkey-patched. Otherwise it will raise a ``ValueError``.
This can cause timeouts to be lost: there is a bare `except:` clause
in the dnspython code that will catch all timeout exceptions gevent raises and
translate them into socket errors. On Python 3 we can detect this, but
on Python 2 we cannot.
.. versionadded:: 1.3a2
"""
def __init__(self, hub=None): # pylint: disable=unused-argument
from gevent import monkey
if not all(monkey.is_module_patched(m) for m in ['threading', 'socket', 'select']):
raise ValueError("Can only be used when monkey-patched")
if resolver._resolver is None:
resolver._resolver = resolver.get_default_resolver()
def close(self):
pass
def getaddrinfo(self, host, port, family=0, socktype=0, proto=0, flags=0):
if ((host == u'localhost' or host == b'localhost')
or not isinstance(host, str) or (flags & AI_NUMERICHOST)):
# this handles cases which do not require network access
# 1) host is None
# 2) host is of an invalid type
# 3) AI_NUMERICHOST flag is set
return _socket.getaddrinfo(host, port, family, socktype, proto, flags)
return resolver._getaddrinfo(host, port, family, socktype, proto, flags)
def getnameinfo(self, sockaddr, flags):
if sockaddr and isinstance(sockaddr, (list, tuple)) and sockaddr[0] in ('::1', '127.0.0.1'):
return _socket.getnameinfo(sockaddr, flags)
return resolver._getnameinfo(sockaddr, flags)
def gethostbyaddr(self, ip_address):
if ip_address in (u'127.0.0.1', u'::1',
b'127.0.0.1', b'::1',
'localhost'):
return _socket.gethostbyaddr(ip_address)
return resolver._gethostbyaddr(ip_address)
# Copyright (c) 2012-2015 Denis Bilenko. See LICENSE for details.
"""
Native thread-based hostname resolver.
"""
import _socket
from gevent.hub import get_hub
__all__ = ['Resolver']
# trigger import of encodings.idna to avoid https://github.com/gevent/gevent/issues/349
u'foo'.encode('idna')
class Resolver(object):
"""
Implementation of the resolver API using native threads and native resolution
functions.
Using the native resolution mechanisms ensures the highest
compatibility with what a non-gevent program would return
including good support for platform specific configuration
mechanisms. The use of native (non-greenlet) threads ensures that
a caller doesn't block other greenlets.
This implementation also has the benefit of being very simple in comparison to
:class:`gevent.resolver_ares.Resolver`.
.. tip::
Most users find this resolver to be quite reliable in a
properly monkey-patched environment. However, there have been
some reports of long delays, slow performance or even hangs,
particularly in long-lived programs that make many, many DNS
requests. If you suspect that may be happening to you, try the
ares resolver (and submit a bug report).
"""
def __init__(self, hub=None):
if hub is None:
hub = get_hub()
self.pool = hub.threadpool
if _socket.gaierror not in hub.NOT_ERROR:
# Do not cause lookup failures to get printed by the default
# error handler. This can be very noisy.
hub.NOT_ERROR += (_socket.gaierror, _socket.herror)
def __repr__(self):
return '<gevent.resolver_thread.Resolver at 0x%x pool=%r>' % (id(self), self.pool)
def close(self):
pass
# from briefly reading socketmodule.c, it seems that all of the functions
# below are thread-safe in Python, even if they are not thread-safe in C.
def gethostbyname(self, *args):
return self.pool.apply(_socket.gethostbyname, args)
def gethostbyname_ex(self, *args):
return self.pool.apply(_socket.gethostbyname_ex, args)
def getaddrinfo(self, *args, **kwargs):
return self.pool.apply(_socket.getaddrinfo, args, kwargs)
def gethostbyaddr(self, *args, **kwargs):
return self.pool.apply(_socket.gethostbyaddr, args, kwargs)
def getnameinfo(self, *args, **kwargs):
return self.pool.apply(_socket.getnameinfo, args, kwargs)
This diff is collapsed.
# Copyright (c) 2012-2015 Denis Bilenko. See LICENSE for details. """Backwards compatibility alias for :mod:`gevent.resolver.thread`.
"""
Native thread-based hostname resolver.
"""
import _socket
from gevent.hub import get_hub
__all__ = ['Resolver']
# trigger import of encodings.idna to avoid https://github.com/gevent/gevent/issues/349
u'foo'.encode('idna')
class Resolver(object):
"""
Implementation of the resolver API using native threads and native resolution
functions.
Using the native resolution mechanisms ensures the highest
compatibility with what a non-gevent program would return
including good support for platform specific configuration
mechanisms. The use of native (non-greenlet) threads ensures that
a caller doesn't block other greenlets.
This implementation also has the benefit of being very simple in comparison to .. deprecated:: 1.3
:class:`gevent.resolver_ares.Resolver`. Use :mod:`gevent.resolver.cares`
"""
.. tip::
Most users find this resolver to be quite reliable in a
properly monkey-patched environment. However, there have been
some reports of long delays, slow performance or even hangs,
particularly in long-lived programs that make many, many DNS
requests. If you suspect that may be happening to you, try the
ares resolver (and submit a bug report).
"""
def __init__(self, hub=None):
if hub is None:
hub = get_hub()
self.pool = hub.threadpool
if _socket.gaierror not in hub.NOT_ERROR:
# Do not cause lookup failures to get printed by the default
# error handler. This can be very noisy.
hub.NOT_ERROR += (_socket.gaierror, _socket.herror)
def __repr__(self):
return '<gevent.resolver_thread.Resolver at 0x%x pool=%r>' % (id(self), self.pool)
def close(self):
pass
# from briefly reading socketmodule.c, it seems that all of the functions
# below are thread-safe in Python, even if they are not thread-safe in C.
def gethostbyname(self, *args):
return self.pool.apply(_socket.gethostbyname, args)
def gethostbyname_ex(self, *args):
return self.pool.apply(_socket.gethostbyname_ex, args)
def getaddrinfo(self, *args, **kwargs):
return self.pool.apply(_socket.getaddrinfo, args, kwargs)
def gethostbyaddr(self, *args, **kwargs):
return self.pool.apply(_socket.gethostbyaddr, args, kwargs)
def getnameinfo(self, *args, **kwargs): from gevent.resolver.thread import * # pylint:disable=wildcard-import,unused-wildcard-import
return self.pool.apply(_socket.getnameinfo, args, kwargs) import gevent.resolver.thread as _thread
__all__ = _thread.__all__
del _thread
...@@ -15,7 +15,7 @@ import re ...@@ -15,7 +15,7 @@ import re
from greentest.sysinfo import RUNNING_ON_APPVEYOR as APPVEYOR from greentest.sysinfo import RUNNING_ON_APPVEYOR as APPVEYOR
from greentest.sysinfo import RUNNING_ON_TRAVIS as TRAVIS from greentest.sysinfo import RUNNING_ON_TRAVIS as TRAVIS
from greentest.sysinfo import RESOLVER_ARES as ARES from greentest.sysinfo import RESOLVER_NOT_SYSTEM as ARES
from greentest.sysinfo import RUN_COVERAGE from greentest.sysinfo import RUN_COVERAGE
......
...@@ -126,3 +126,6 @@ CONN_ABORTED_ERRORS.append(ECONNRESET) ...@@ -126,3 +126,6 @@ CONN_ABORTED_ERRORS.append(ECONNRESET)
CONN_ABORTED_ERRORS = frozenset(CONN_ABORTED_ERRORS) CONN_ABORTED_ERRORS = frozenset(CONN_ABORTED_ERRORS)
RESOLVER_ARES = os.getenv('GEVENT_RESOLVER') == 'ares' RESOLVER_ARES = os.getenv('GEVENT_RESOLVER') == 'ares'
RESOLVER_DNSPYTHON = os.getenv('GEVENT_RESOLVER') == 'dnspython'
RESOLVER_NOT_SYSTEM = RESOLVER_ARES or RESOLVER_DNSPYTHON
...@@ -141,7 +141,10 @@ def run_many(tests, expected=(), failfast=False, quiet=False): ...@@ -141,7 +141,10 @@ def run_many(tests, expected=(), failfast=False, quiet=False):
def discover(tests=None, ignore=(), coverage=False): def discover(tests=None, ignore=(), coverage=False):
if isinstance(ignore, six.string_types): if isinstance(ignore, six.string_types):
ignore = set(load_list_from_file(ignore)) ignore_files = ignore.split(',')
ignore = set()
for f in ignore_files:
ignore.update(set(load_list_from_file(f)))
ignore = set(ignore or ()) ignore = set(ignore or ())
if coverage: if coverage:
......
...@@ -10,7 +10,7 @@ from greentest.sysinfo import RUNNING_ON_APPVEYOR as APPVEYOR ...@@ -10,7 +10,7 @@ from greentest.sysinfo import RUNNING_ON_APPVEYOR as APPVEYOR
from greentest.sysinfo import RUNNING_ON_TRAVIS as TRAVIS from greentest.sysinfo import RUNNING_ON_TRAVIS as TRAVIS
from greentest.sysinfo import RUN_LEAKCHECKS as LEAKTEST from greentest.sysinfo import RUN_LEAKCHECKS as LEAKTEST
from greentest.sysinfo import RUN_COVERAGE as COVERAGE from greentest.sysinfo import RUN_COVERAGE as COVERAGE
from greentest.sysinfo import RESOLVER_ARES from greentest.sysinfo import RESOLVER_NOT_SYSTEM
from greentest.sysinfo import PYPY from greentest.sysinfo import PYPY
from greentest.sysinfo import PY3 from greentest.sysinfo import PY3
...@@ -145,7 +145,7 @@ if PYPY: ...@@ -145,7 +145,7 @@ if PYPY:
'FLAKY test__backdoor.py', 'FLAKY test__backdoor.py',
] ]
if RESOLVER_ARES: if RESOLVER_NOT_SYSTEM:
FAILING_TESTS += [ FAILING_TESTS += [
# A few errors and differences: # A few errors and differences:
......
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import gevent
from gevent import monkey
if ['gevent.resolver.dnspython.Resolver'] == gevent.get_hub().resolver_class:
# dnspython requires monkey-patching
monkey.patch_all()
import re import re
import greentest import greentest
import unittest import unittest
import socket import socket
from time import time from time import time
import gevent
import gevent.socket as gevent_socket import gevent.socket as gevent_socket
from greentest.util import log from greentest.util import log
from greentest import six from greentest import six
...@@ -19,8 +26,10 @@ log('Resolver: %s', resolver) ...@@ -19,8 +26,10 @@ log('Resolver: %s', resolver)
if getattr(resolver, 'pool', None) is not None: if getattr(resolver, 'pool', None) is not None:
resolver.pool.size = 1 resolver.pool.size = 1
from greentest.sysinfo import RESOLVER_NOT_SYSTEM
from greentest.sysinfo import RESOLVER_DNSPYTHON
from greentest.sysinfo import PY2
RESOLVER_IS_ARES = 'ares' in gevent.get_hub().resolver_class.__module__
assert gevent_socket.gaierror is socket.gaierror assert gevent_socket.gaierror is socket.gaierror
assert gevent_socket.error is socket.error assert gevent_socket.error is socket.error
...@@ -146,8 +155,11 @@ def relaxed_is_equal(a, b): ...@@ -146,8 +155,11 @@ def relaxed_is_equal(a, b):
return True return True
if isinstance(a, six.string_types): if isinstance(a, six.string_types):
return compare_relaxed(a, b) return compare_relaxed(a, b)
try:
if len(a) != len(b): if len(a) != len(b):
return False return False
except TypeError:
return False
if contains_5tuples(a) and contains_5tuples(b): if contains_5tuples(a) and contains_5tuples(b):
# getaddrinfo results # getaddrinfo results
a = sorted(a) a = sorted(a)
...@@ -219,7 +231,7 @@ class TestCase(greentest.TestCase): ...@@ -219,7 +231,7 @@ class TestCase(greentest.TestCase):
def _test(self, func, *args): def _test(self, func, *args):
gevent_func = getattr(gevent_socket, func) gevent_func = getattr(gevent_socket, func)
real_func = getattr(socket, func) real_func = monkey.get_original('socket', func)
real_result, time_real = run(real_func, *args) real_result, time_real = run(real_func, *args)
gevent_result, time_gevent = run(gevent_func, *args) gevent_result, time_gevent = run(gevent_func, *args)
if not DEBUG and self.should_log_results(real_result, gevent_result): if not DEBUG and self.should_log_results(real_result, gevent_result):
...@@ -251,7 +263,7 @@ class TestCase(greentest.TestCase): ...@@ -251,7 +263,7 @@ class TestCase(greentest.TestCase):
# can be in different orders if we're hitting different servers, # can be in different orders if we're hitting different servers,
# or using the native and ares resolvers due to load-balancing techniques. # or using the native and ares resolvers due to load-balancing techniques.
# We sort them. # We sort them.
if not RESOLVER_IS_ARES or isinstance(result, BaseException): if not RESOLVER_NOT_SYSTEM or isinstance(result, BaseException):
return result return result
# result[1].sort() # we wind up discarding this # result[1].sort() # we wind up discarding this
...@@ -275,7 +287,7 @@ class TestCase(greentest.TestCase): ...@@ -275,7 +287,7 @@ class TestCase(greentest.TestCase):
return (result[0], [], ips) return (result[0], [], ips)
def _normalize_result_getaddrinfo(self, result): def _normalize_result_getaddrinfo(self, result):
if not RESOLVER_IS_ARES: if not RESOLVER_NOT_SYSTEM:
return result return result
# On Python 3, the builtin resolver can return SOCK_RAW results, but # On Python 3, the builtin resolver can return SOCK_RAW results, but
# c-ares doesn't do that. So we remove those if we find them. # c-ares doesn't do that. So we remove those if we find them.
...@@ -286,7 +298,7 @@ class TestCase(greentest.TestCase): ...@@ -286,7 +298,7 @@ class TestCase(greentest.TestCase):
return result return result
def _normalize_result_gethostbyaddr(self, result): def _normalize_result_gethostbyaddr(self, result):
if not RESOLVER_IS_ARES: if not RESOLVER_NOT_SYSTEM:
return result return result
if isinstance(result, tuple): if isinstance(result, tuple):
...@@ -316,7 +328,7 @@ class TestCase(greentest.TestCase): ...@@ -316,7 +328,7 @@ class TestCase(greentest.TestCase):
# If we're using the ares resolver, allow the real resolver to generate an # If we're using the ares resolver, allow the real resolver to generate an
# error that the ares resolver actually gets an answer to. # error that the ares resolver actually gets an answer to.
if (RESOLVER_IS_ARES if (RESOLVER_NOT_SYSTEM
and isinstance(real_result, errors) and isinstance(real_result, errors)
and not isinstance(gevent_result, errors)): and not isinstance(gevent_result, errors)):
return return
...@@ -334,6 +346,8 @@ add(TestTypeError, None) ...@@ -334,6 +346,8 @@ add(TestTypeError, None)
add(TestTypeError, 25) add(TestTypeError, 25)
@unittest.skipIf(RESOLVER_DNSPYTHON,
"This commonly needs /etc/hosts to function, and dnspython doesn't do that.")
class TestHostname(TestCase): class TestHostname(TestCase):
pass pass
...@@ -348,13 +362,13 @@ class TestLocalhost(TestCase): ...@@ -348,13 +362,13 @@ class TestLocalhost(TestCase):
# anymore. # anymore.
def _normalize_result_getaddrinfo(self, result): def _normalize_result_getaddrinfo(self, result):
if RESOLVER_IS_ARES: if RESOLVER_NOT_SYSTEM:
# We see that some impls (OS X) return extra results # We see that some impls (OS X) return extra results
# like DGRAM that ares does not. # like DGRAM that ares does not.
return () return ()
return super(TestLocalhost, self)._normalize_result_getaddrinfo(result) return super(TestLocalhost, self)._normalize_result_getaddrinfo(result)
if greentest.RUNNING_ON_TRAVIS and greentest.PY2 and RESOLVER_IS_ARES: if greentest.RUNNING_ON_TRAVIS and greentest.PY2 and RESOLVER_NOT_SYSTEM:
def _normalize_result_gethostbyaddr(self, result): def _normalize_result_gethostbyaddr(self, result):
# Beginning in November 2017 after an upgrade to Travis, # Beginning in November 2017 after an upgrade to Travis,
# we started seeing ares return ::1 for localhost, but # we started seeing ares return ::1 for localhost, but
...@@ -398,8 +412,8 @@ if not greentest.RUNNING_ON_TRAVIS: ...@@ -398,8 +412,8 @@ if not greentest.RUNNING_ON_TRAVIS:
class TestBroadcast(TestCase): class TestBroadcast(TestCase):
switch_expected = False switch_expected = False
if RESOLVER_IS_ARES: if RESOLVER_NOT_SYSTEM:
# ares raises errors for broadcasthost/255.255.255.255 # ares and dnspython raises errors for broadcasthost/255.255.255.255
@unittest.skip('ares raises errors for broadcasthost/255.255.255.255') @unittest.skip('ares raises errors for broadcasthost/255.255.255.255')
def test__broadcast__gethostbyaddr(self): def test__broadcast__gethostbyaddr(self):
...@@ -410,6 +424,7 @@ class TestBroadcast(TestCase): ...@@ -410,6 +424,7 @@ class TestBroadcast(TestCase):
add(TestBroadcast, '<broadcast>') add(TestBroadcast, '<broadcast>')
@unittest.skipIf(RESOLVER_DNSPYTHON, "/etc/hosts completely ignored under dnspython")
class TestEtcHosts(TestCase): class TestEtcHosts(TestCase):
pass pass
...@@ -420,7 +435,7 @@ except IOError: ...@@ -420,7 +435,7 @@ except IOError:
etc_hosts = '' etc_hosts = ''
for ip, host in re.findall(r'^\s*(\d+\.\d+\.\d+\.\d+)\s+([^\s]+)', etc_hosts, re.M)[:10]: for ip, host in re.findall(r'^\s*(\d+\.\d+\.\d+\.\d+)\s+([^\s]+)', etc_hosts, re.M)[:10]:
if (RESOLVER_IS_ARES if (RESOLVER_NOT_SYSTEM
and (host.endswith('local') # ignore bonjour, ares can't find them and (host.endswith('local') # ignore bonjour, ares can't find them
# ignore common aliases that ares can't find # ignore common aliases that ares can't find
or ip == '255.255.255.255' or ip == '255.255.255.255'
...@@ -521,6 +536,8 @@ class Test_getaddrinfo(TestCase): ...@@ -521,6 +536,8 @@ class Test_getaddrinfo(TestCase):
def test2(self): def test2(self):
return self._test_getaddrinfo(TestGeventOrg.HOSTNAME, 53, socket.AF_INET, socket.SOCK_DGRAM, 17) return self._test_getaddrinfo(TestGeventOrg.HOSTNAME, 53, socket.AF_INET, socket.SOCK_DGRAM, 17)
@unittest.skipIf(RESOLVER_DNSPYTHON,
"dnspython only returns some of the possibilities")
def test3(self): def test3(self):
return self._test_getaddrinfo('google.com', 'http', socket.AF_INET6) return self._test_getaddrinfo('google.com', 'http', socket.AF_INET6)
...@@ -532,7 +549,8 @@ add(TestInternational, u'президент.рф', 'russian') ...@@ -532,7 +549,8 @@ add(TestInternational, u'президент.рф', 'russian')
add(TestInternational, u'президент.рф'.encode('idna'), 'idna') add(TestInternational, u'президент.рф'.encode('idna'), 'idna')
@unittest.skipIf(RESOLVER_DNSPYTHON and PY2,
"dnspython has a bare except and we can't workaround it on Python 2.")
class TestInterrupted_gethostbyname(greentest.GenericWaitTestCase): class TestInterrupted_gethostbyname(greentest.GenericWaitTestCase):
# There are refs to a Waiter in the C code that don't go # There are refs to a Waiter in the C code that don't go
...@@ -544,6 +562,7 @@ class TestInterrupted_gethostbyname(greentest.GenericWaitTestCase): ...@@ -544,6 +562,7 @@ class TestInterrupted_gethostbyname(greentest.GenericWaitTestCase):
def wait(self, timeout): def wait(self, timeout):
with gevent.Timeout(timeout, False): with gevent.Timeout(timeout, False):
for index in xrange(1000000): for index in xrange(1000000):
print("Resolving", index)
try: try:
gevent_socket.gethostbyname('www.x%s.com' % index) gevent_socket.gethostbyname('www.x%s.com' % index)
except socket.error: except socket.error:
...@@ -604,7 +623,6 @@ add(TestBadIP, '1.2.3.400') ...@@ -604,7 +623,6 @@ add(TestBadIP, '1.2.3.400')
class Test_getnameinfo_127001(TestCase): class Test_getnameinfo_127001(TestCase):
def test(self): def test(self):
assert gevent_socket.getnameinfo is not socket.getnameinfo
self._test('getnameinfo', ('127.0.0.1', 80), 0) self._test('getnameinfo', ('127.0.0.1', 80), 0)
def test_DGRAM(self): def test_DGRAM(self):
......
...@@ -2,9 +2,12 @@ ...@@ -2,9 +2,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import greentest import greentest
import socket import socket
from test__socket_dns import TestCase, add, RESOLVER_IS_ARES from test__socket_dns import TestCase, add
if not greentest.RUNNING_ON_CI: from greentest.sysinfo import RESOLVER_NOT_SYSTEM
from greentest.sysinfo import RESOLVER_DNSPYTHON
if not greentest.RUNNING_ON_CI and not RESOLVER_DNSPYTHON:
# We can't control the DNS servers we use there # We can't control the DNS servers we use there
...@@ -46,7 +49,7 @@ if not greentest.RUNNING_ON_CI: ...@@ -46,7 +49,7 @@ if not greentest.RUNNING_ON_CI:
host = 'ipv6.google.com' host = 'ipv6.google.com'
def _normalize_result_getnameinfo(self, result): def _normalize_result_getnameinfo(self, result):
if greentest.RUNNING_ON_CI and RESOLVER_IS_ARES: if greentest.RUNNING_ON_CI and RESOLVER_NOT_SYSTEM:
# Disabled, there are multiple possibilities # Disabled, there are multiple possibilities
# and we can get different ones, rarely. # and we can get different ones, rarely.
return () return ()
......
test___example_servers.py
test__backdoor.py
test__example_echoserver.py
test__example_udp_client.py
test__getaddrinfo_import.py
test__example_portforwarder.py
test__pywsgi.py
test__server.py
test__server_pywsgi.py
test__socket_close.py
test__socket_dns6.py
test__socket_errors.py
test__socket_send_memoryview.py
test__socket_timeout.py
test__examples.py
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment