Commit c9da45d0 authored by Jason Madden's avatar Jason Madden

Basic tests pass locally on 3.9.

Begin trying to install on Travis.
parent 99c9620e
...@@ -61,6 +61,7 @@ env: ...@@ -61,6 +61,7 @@ env:
- TRAVIS_PYTHON_VERSION=3.6 - TRAVIS_PYTHON_VERSION=3.6
- TRAVIS_PYTHON_VERSION=3.7 - TRAVIS_PYTHON_VERSION=3.7
- TRAVIS_PYTHON_VERSION=3.8 - TRAVIS_PYTHON_VERSION=3.8
- TRAVIS_PYTHON_VERSION=3.9
- TRAVIS_PYTHON_VERSION=pypy2.7 - TRAVIS_PYTHON_VERSION=pypy2.7
- TRAVIS_PYTHON_VERSION=pypy3.6 - TRAVIS_PYTHON_VERSION=pypy3.6
- TRAVIS_PYTHON_VERSION=2.7 GEVENTSETUP_EMBED=0 GEVENTSETUP_EV_VERIFY=3 - TRAVIS_PYTHON_VERSION=2.7 GEVENTSETUP_EMBED=0 GEVENTSETUP_EV_VERIFY=3
...@@ -153,6 +154,8 @@ jobs: ...@@ -153,6 +154,8 @@ jobs:
exclude: exclude:
- os: osx - os: osx
env: TRAVIS_PYTHON_VERSION=3.5 env: TRAVIS_PYTHON_VERSION=3.5
- os: osx
env: TRAVIS_PYTHON_VERSION=3.9
- os: osx - os: osx
env: TRAVIS_PYTHON_VERSION=pypy2.7 env: TRAVIS_PYTHON_VERSION=pypy2.7
- os: osx - os: osx
...@@ -174,7 +177,7 @@ jobs: ...@@ -174,7 +177,7 @@ jobs:
# First, the build dependencies (see setup.cfg) # First, the build dependencies (see setup.cfg)
# so that we don't have to use build isolation and can better use the cache; # so that we don't have to use build isolation and can better use the cache;
# Note that we can't use -U for cffi and greenlet on PyPy. # Note that we can't use -U for cffi and greenlet on PyPy.
- &build-gevent-deps pip install -U setuptools wheel twine && pip install -U 'faulthandler; python_version == "2.7" and platform_python_implementation == "CPython"' 'cffi;platform_python_implementation=="CPython"' 'cython<3' 'greenlet;platform_python_implementation=="CPython"' - &build-gevent-deps pip install -U setuptools wheel twine && pip install -U 'faulthandler; python_version == "2.7" and platform_python_implementation == "CPython"' 'cffi;platform_python_implementation=="CPython"' 'cython<3' 'greenlet;platform_python_implementation=="CPython" and python_version <= "3.8"' 'git+https://github.com/python-greenlet/greenlet.git#egg=greenlet ; platform_python_implementation == "CPython" and python_version >= "3.9.0b1"'
# Next, build the wheel *in place*. This helps ccache, and also lets us cache the configure # Next, build the wheel *in place*. This helps ccache, and also lets us cache the configure
# output (pip install uses a random temporary directory, making this difficult) # output (pip install uses a random temporary directory, making this difficult)
- python setup.py bdist_wheel - python setup.py bdist_wheel
...@@ -194,6 +197,8 @@ jobs: ...@@ -194,6 +197,8 @@ jobs:
env: TRAVIS_PYTHON_VERSION=3.6 env: TRAVIS_PYTHON_VERSION=3.6
- <<: *build-gevent - <<: *build-gevent
env: TRAVIS_PYTHON_VERSION=3.7 env: TRAVIS_PYTHON_VERSION=3.7
- <<: *build-gevent
env: TRAVIS_PYTHON_VERSION=3.9
- <<: *build-gevent - <<: *build-gevent
env: TRAVIS_PYTHON_VERSION=pypy2.7 env: TRAVIS_PYTHON_VERSION=pypy2.7
- <<: *build-gevent - <<: *build-gevent
...@@ -324,18 +329,9 @@ jobs: ...@@ -324,18 +329,9 @@ jobs:
# For the CPython interpreters, unless we have reason to expect # For the CPython interpreters, unless we have reason to expect
# different behaviour across the versions (e.g., as measured by coverage) # different behaviour across the versions (e.g., as measured by coverage)
# it's sufficient to test the alternate backend library for non-current versions; # it's sufficient to run the full suite on the current version.
# run the full suite on the current version.
# 3.6 # XXX: Move these to 3.9 once the basic tests get worked out.
- <<: *test-libuv-jobs
env: TRAVIS_PYTHON_VERSION=3.6
name: libuv36
# 3.7
- <<: *test-libuv-jobs
env: TRAVIS_PYTHON_VERSION=3.7
name: libuv37
# 3.8 # 3.8
- <<: *test-libuv-jobs - <<: *test-libuv-jobs
......
...@@ -20,4 +20,8 @@ zest.releaser[recommended] ...@@ -20,4 +20,8 @@ zest.releaser[recommended]
# benchmarks use this # benchmarks use this
pyperf >= 1.6.1 pyperf >= 1.6.1
# Python 3.9+ need a version of greenlet that
# is currently unreleased.
git+https://github.com/python-greenlet/greenlet.git#egg=greenlet ; platform_python_implementation == 'CPython' and python_version >= '3.9.0b1'
-e .[test,docs] -e .[test,docs]
Add support for Python 3.9. Add support for Python 3.9.
No binary wheels are available yet, however. No binary wheels are available yet, however, and one must use a
greenlet built from current git master.
...@@ -54,12 +54,15 @@ def application(env, start_response): ...@@ -54,12 +54,15 @@ def application(env, start_response):
if env['QUERY_STRING']: if env['QUERY_STRING']:
path += '?' + env['QUERY_STRING'] path += '?' + env['QUERY_STRING']
path = path.lstrip('/') path = path.lstrip('/')
if (method, path) == ('GET', ''): if (method, path) == ('GET', ''):
start_response('200 OK', [('Content-Type', 'text/html')]) start_response('200 OK', [('Content-Type', 'text/html')])
return [FORM] return [FORM]
elif method == 'GET':
if method == 'GET':
return proxy(path, start_response, proxy_url) return proxy(path, start_response, proxy_url)
elif (method, path) == ('POST', ''):
if (method, path) == ('POST', ''):
key, value = env['wsgi.input'].read().strip().split(b'=') key, value = env['wsgi.input'].read().strip().split(b'=')
assert key == b'url', repr(key) assert key == b'url', repr(key)
value = _as_str(value) value = _as_str(value)
...@@ -75,13 +78,16 @@ def proxy(path, start_response, proxy_url): ...@@ -75,13 +78,16 @@ def proxy(path, start_response, proxy_url):
# pylint:disable=too-many-locals # pylint:disable=too-many-locals
if '://' not in path: if '://' not in path:
path = 'http://' + path path = 'http://' + path
try: try:
try: try:
response = urllib2.urlopen(path) response = urllib2.urlopen(path)
except urllib2.HTTPError as ex: except urllib2.HTTPError as ex:
response = ex response = ex
print('%s: %s %s' % (path, response.code, response.msg)) print('%s: %s %s' % (path, response.code, response.msg))
headers = [(k, v) for (k, v) in response.headers.items() if k not in drop_headers] # Beginning in Python 3.8, headers aren't guaranteed to arrive in
# lowercase; we must do so ourself.
headers = [(k, v) for (k, v) in response.headers.items() if k.lower() not in DROP_HEADERS]
scheme, netloc, path, _params, _query, _fragment = urlparse(path) scheme, netloc, path, _params, _query, _fragment = urlparse(path)
host = (scheme or 'http') + '://' + netloc host = (scheme or 'http') + '://' + netloc
except Exception as ex: # pylint:disable=broad-except except Exception as ex: # pylint:disable=broad-except
...@@ -94,6 +100,7 @@ def proxy(path, start_response, proxy_url): ...@@ -94,6 +100,7 @@ def proxy(path, start_response, proxy_url):
error_str = '<h1>%s</h1><h2>%s</h2><pre>%s</pre>' % (error_str, escape(path), escape(tb)) error_str = '<h1>%s</h1><h2>%s</h2><pre>%s</pre>' % (error_str, escape(path), escape(tb))
return [_as_bytes(error_str)] return [_as_bytes(error_str)]
else: else:
print("Returning", headers)
start_response('%s %s' % (response.code, response.msg), headers) start_response('%s %s' % (response.code, response.msg), headers)
data = response.read() data = response.read()
data = fix_links(data, proxy_url, host) data = fix_links(data, proxy_url, host)
...@@ -110,7 +117,8 @@ def join(url1, *rest): ...@@ -110,7 +117,8 @@ def join(url1, *rest):
if url2.startswith(b'/'): if url2.startswith(b'/'):
return join(url1 + url2[1:], *rest) return join(url1 + url2[1:], *rest)
return join(url1 + url2, *rest) return join(url1 + url2, *rest)
elif url2.startswith(b'/'):
if url2.startswith(b'/'):
return join(url1 + url2, *rest) return join(url1 + url2, *rest)
return join(url1 + b'/' + url2, *rest) return join(url1 + b'/' + url2, *rest)
...@@ -136,7 +144,11 @@ def fix_links(data, proxy_url, host_url): ...@@ -136,7 +144,11 @@ def fix_links(data, proxy_url, host_url):
_link_re_1 = re.compile(br'''(?P<before>(href|src|action)\s*=\s*)(?P<quote>['"])(?P<url>[^#].*?)(?P=quote)''') _link_re_1 = re.compile(br'''(?P<before>(href|src|action)\s*=\s*)(?P<quote>['"])(?P<url>[^#].*?)(?P=quote)''')
_link_re_2 = re.compile(br'''(?P<before>(href|src|action)\s*=\s*)(?P<url>[^'"#>][^ >]*)''') _link_re_2 = re.compile(br'''(?P<before>(href|src|action)\s*=\s*)(?P<url>[^'"#>][^ >]*)''')
drop_headers = ['transfer-encoding', 'set-cookie'] # The lowercase names of headers that we will *NOT* forward.
DROP_HEADERS = {
'transfer-encoding',
'set-cookie'
}
FORM = b"""<html><head> FORM = b"""<html><head>
<title>Web Proxy - gevent example</title></head><body> <title>Web Proxy - gevent example</title></head><body>
......
...@@ -22,7 +22,12 @@ requires = [ ...@@ -22,7 +22,12 @@ requires = [
# See version requirements in setup.py # See version requirements in setup.py
"cffi >= 1.12.3 ; platform_python_implementation == 'CPython'", "cffi >= 1.12.3 ; platform_python_implementation == 'CPython'",
# Python 3.7 requires at least 0.4.14, which is ABI incompatible with earlier # Python 3.7 requires at least 0.4.14, which is ABI incompatible with earlier
"greenlet>=0.4.14 ; platform_python_implementation == 'CPython'", # releases. Python 3.9 and 3.10 require 0.4.16, which has not been released yet.
# Listing it here fails to find a distro on PyPI; and this setting won't accept
# git+https:// VCS urls, it seems. If we list it in dev-requirements.txt, we seem to
# get what we need.
"greenlet>=0.4.14 ; platform_python_implementation == 'CPython' and python_version <= '3.8'",
#"greenlet > 0.4.15 ; platform_python_implementation == 'CPython' and python_version >= '3.9.0b1'",
] ]
[tool.towncrier] [tool.towncrier]
......
...@@ -114,6 +114,9 @@ for var in "$@"; do ...@@ -114,6 +114,9 @@ for var in "$@"; do
3.8) 3.8)
install 3.8.2 python3.8 3.8.d install 3.8.2 python3.8 3.8.d
;; ;;
3.9)
install 3.9-dev python3.9 3.9.d
;;
pypy2.7) pypy2.7)
install pypy2.7-7.3.1 pypy2.7 pypy2.7.d install pypy2.7-7.3.1 pypy2.7 pypy2.7.d
;; ;;
......
...@@ -433,6 +433,8 @@ def run_setup(ext_modules): ...@@ -433,6 +433,8 @@ def run_setup(ext_modules):
"Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python :: Implementation :: PyPy",
"Operating System :: MacOS :: MacOS X", "Operating System :: MacOS :: MacOS X",
......
...@@ -377,6 +377,17 @@ class AbstractLinkable(object): ...@@ -377,6 +377,17 @@ class AbstractLinkable(object):
gotit = self._wait_core(timeout) gotit = self._wait_core(timeout)
return self._wait_return_value(True, gotit) return self._wait_return_value(True, gotit)
def _at_fork_reinit(self):
"""
This method was added in Python 3.9 and is called by logging.py
``_after_at_fork_child_reinit_locks`` on Lock objects.
It is also called from threading.py, ``_after_fork`` in
``_reset_internal_locks``, and that can hit ``Event`` objects.
Do we need to do anything?
"""
def _init(): def _init():
greenlet_init() # pylint:disable=undefined-variable greenlet_init() # pylint:disable=undefined-variable
......
...@@ -18,6 +18,7 @@ PY35 = sys.version_info[:2] >= (3, 5) ...@@ -18,6 +18,7 @@ PY35 = sys.version_info[:2] >= (3, 5)
PY36 = sys.version_info[:2] >= (3, 6) PY36 = sys.version_info[:2] >= (3, 6)
PY37 = sys.version_info[:2] >= (3, 7) PY37 = sys.version_info[:2] >= (3, 7)
PY38 = sys.version_info[:2] >= (3, 8) PY38 = sys.version_info[:2] >= (3, 8)
PY39 = sys.version_info[:2] >= (3, 9)
PYPY = hasattr(sys, 'pypy_version_info') PYPY = hasattr(sys, 'pypy_version_info')
WIN = sys.platform.startswith("win") WIN = sys.platform.startswith("win")
LINUX = sys.platform.startswith('linux') LINUX = sys.platform.startswith('linux')
......
...@@ -73,6 +73,7 @@ import time ...@@ -73,6 +73,7 @@ import time
from gevent._hub_local import get_hub_noargs as get_hub from gevent._hub_local import get_hub_noargs as get_hub
from gevent._compat import string_types, integer_types, PY3 from gevent._compat import string_types, integer_types, PY3
from gevent._compat import PY38 from gevent._compat import PY38
from gevent._compat import PY39
from gevent._compat import WIN as is_windows from gevent._compat import WIN as is_windows
from gevent._compat import OSX as is_macos from gevent._compat import OSX as is_macos
from gevent._util import copy_globals from gevent._util import copy_globals
...@@ -83,6 +84,12 @@ if PY38: ...@@ -83,6 +84,12 @@ if PY38:
'has_dualstack_ipv6', 'has_dualstack_ipv6',
]) ])
if PY39:
__imports__.extend([
'recv_fds',
'send_fds',
])
# pylint:disable=no-name-in-module,unused-import # pylint:disable=no-name-in-module,unused-import
if is_windows: if is_windows:
# no such thing as WSAEPERM or error code 10001 according to winsock.h or MSDN # no such thing as WSAEPERM or error code 10001 according to winsock.h or MSDN
......
...@@ -6,9 +6,6 @@ For the documentation, refer to :mod:`ssl` module manual. ...@@ -6,9 +6,6 @@ For the documentation, refer to :mod:`ssl` module manual.
This module implements cooperative SSL socket wrappers. This module implements cooperative SSL socket wrappers.
""" """
# Our import magic sadly makes this warning useless
# pylint: disable=undefined-variable
# pylint:disable=no-member
from __future__ import absolute_import from __future__ import absolute_import
import ssl as __ssl__ import ssl as __ssl__
...@@ -16,6 +13,8 @@ import ssl as __ssl__ ...@@ -16,6 +13,8 @@ import ssl as __ssl__
_ssl = __ssl__._ssl _ssl = __ssl__._ssl
import errno import errno
import sys
from gevent.socket import socket, timeout_default from gevent.socket import socket, timeout_default
from gevent.socket import error as socket_error from gevent.socket import error as socket_error
from gevent.socket import timeout as _socket_timeout from gevent.socket import timeout as _socket_timeout
...@@ -31,9 +30,35 @@ __implements__ = [ ...@@ -31,9 +30,35 @@ __implements__ = [
'get_server_certificate', 'get_server_certificate',
] ]
# Manually import things we use so we get better linting.
# Also, in the past (adding 3.9 support) it turned out we were
# relying on certain global variables being defined in the ssl module
# that weren't required to be there, e.g., AF_INET, which should be imported
# from socket
from socket import AF_INET
from socket import SOCK_STREAM
from socket import SO_TYPE
from socket import SOL_SOCKET
from ssl import SSLWantReadError
from ssl import SSLWantWriteError
from ssl import CERT_NONE
from ssl import SSLError
from ssl import SSL_ERROR_EOF
from ssl import SSL_ERROR_WANT_READ
from ssl import SSL_ERROR_WANT_WRITE
from ssl import PROTOCOL_SSLv23
from ssl import SSLObject
from ssl import match_hostname
from ssl import CHANNEL_BINDING_TYPES
from ssl import CERT_REQUIRED
from ssl import DER_cert_to_PEM_cert
from ssl import create_connection
# Import all symbols from Python's ssl.py, except those that we are implementing # Import all symbols from Python's ssl.py, except those that we are implementing
# and "private" symbols. # and "private" symbols.
__imports__ = copy_globals(__ssl__, globals(), __imports__ = copy_globals(
__ssl__, globals(),
# SSLSocket *must* subclass gevent.socket.socket; see issue 597 # SSLSocket *must* subclass gevent.socket.socket; see issue 597
names_to_ignore=__implements__ + ['socket'], names_to_ignore=__implements__ + ['socket'],
dunder_names_to_keep=()) dunder_names_to_keep=())
...@@ -143,7 +168,7 @@ class SSLContext(orig_SSLContext): ...@@ -143,7 +168,7 @@ class SSLContext(orig_SSLContext):
__ssl__.SSLContext = orig_SSLContext __ssl__.SSLContext = orig_SSLContext
try: try:
super(SSLContext, SSLContext)._msg_callback.__set__(self, value) super(SSLContext, SSLContext)._msg_callback.__set__(self, value) # pylint:disable=no-member
finally: finally:
__ssl__.SSLContext = SSLContext __ssl__.SSLContext = SSLContext
...@@ -153,7 +178,7 @@ class SSLContext(orig_SSLContext): ...@@ -153,7 +178,7 @@ class SSLContext(orig_SSLContext):
def sni_callback(self): def sni_callback(self):
result = super().sni_callback result = super().sni_callback
if isinstance(result, _Callback): if isinstance(result, _Callback):
result = result.user_function result = result.user_function # pylint:disable=no-member
return result return result
@sni_callback.setter @sni_callback.setter
def sni_callback(self, value): def sni_callback(self, value):
......
...@@ -733,19 +733,21 @@ class WSGIHandler(object): ...@@ -733,19 +733,21 @@ class WSGIHandler(object):
else: else:
self._sendall(data) self._sendall(data)
ApplicationError = AssertionError
def write(self, data): def write(self, data):
# The write() callable we return from start_response. # The write() callable we return from start_response.
# https://www.python.org/dev/peps/pep-3333/#the-write-callable # https://www.python.org/dev/peps/pep-3333/#the-write-callable
# Supposed to do pretty much the same thing as yielding values # Supposed to do pretty much the same thing as yielding values
# from the application's return. # from the application's return.
if self.code in (304, 204) and data: if self.code in (304, 204) and data:
raise AssertionError('The %s response must have no body' % self.code) raise self.ApplicationError('The %s response must have no body' % self.code)
if self.headers_sent: if self.headers_sent:
self._write(data) self._write(data)
else: else:
if not self.status: if not self.status:
raise AssertionError("The application did not call start_response()") raise self.ApplicationError("The application did not call start_response()")
self._write_with_headers(data) self._write_with_headers(data)
def _write_with_headers(self, data): def _write_with_headers(self, data):
...@@ -870,7 +872,7 @@ class WSGIHandler(object): ...@@ -870,7 +872,7 @@ class WSGIHandler(object):
msg = 'Invalid Content-Length for %s response: %r (must be absent or zero)' % (self.code, self.provided_content_length) msg = 'Invalid Content-Length for %s response: %r (must be absent or zero)' % (self.code, self.provided_content_length)
if PY3: if PY3:
msg = msg.encode('latin-1') msg = msg.encode('latin-1')
raise AssertionError(msg) raise self.ApplicationError(msg)
return self.write return self.write
...@@ -1017,7 +1019,7 @@ class WSGIHandler(object): ...@@ -1017,7 +1019,7 @@ class WSGIHandler(object):
def handle_error(self, t, v, tb): def handle_error(self, t, v, tb):
# Called for internal, unexpected errors, NOT invalid client input # Called for internal, unexpected errors, NOT invalid client input
self._log_error(t, v, tb) self._log_error(t, v, tb)
del tb t = v = tb = None
self._send_error_response_if_possible(500) self._send_error_response_if_possible(500)
def _handle_client_error(self, ex): def _handle_client_error(self, ex):
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
# THE SOFTWARE. # THE SOFTWARE.
from __future__ import absolute_import, print_function, division from __future__ import absolute_import, print_function, division
from contextlib import contextmanager
from gevent.hub import Hub from gevent.hub import Hub
from .exception import ExpectedException from .exception import ExpectedException
...@@ -29,12 +29,37 @@ class QuietHub(Hub): ...@@ -29,12 +29,37 @@ class QuietHub(Hub):
_threadpool = None _threadpool = None
EXPECTED_TEST_ERROR = (ExpectedException,) EXPECTED_TEST_ERROR = (ExpectedException,)
IGNORE_EXPECTED_TEST_ERROR = False
@contextmanager
def ignoring_expected_test_error(self):
"""
Code in the body of this context manager will ignore
``EXPECTED_TEST_ERROR`` objects reported to ``handle_error``;
they will not get a chance to go to the hub's parent.
This completely changes the semantics of normal error handling
by avoiding some switches (to the main greenlet, and eventually
once a callback is processed, back to the hub). This should be used
in narrow ways for test compatibility for tests that assume
``ExpectedException`` objects behave this way.
"""
old = self.IGNORE_EXPECTED_TEST_ERROR
self.IGNORE_EXPECTED_TEST_ERROR = True
try:
yield
finally:
self.IGNORE_EXPECTED_TEST_ERROR = old
def handle_error(self, context, type, value, tb): def handle_error(self, context, type, value, tb):
type, value, tb = self._normalize_exception(type, value, tb) type, value, tb = self._normalize_exception(type, value, tb)
# If we check that the ``type`` is a subclass of ``EXPECTED_TEST_ERROR``,
# and return, we completely change the semantics: We avoid raising
# this error in the main greenlet, which cuts out several switches.
# Overall, not good.
if issubclass(type, self.EXPECTED_TEST_ERROR): if self.IGNORE_EXPECTED_TEST_ERROR and issubclass(type, self.EXPECTED_TEST_ERROR):
# Don't print these to cut down on the noise in the test logs # Don't pass these up; avoid switches
return return
return Hub.handle_error(self, context, type, value, tb) return Hub.handle_error(self, context, type, value, tb)
......
...@@ -91,6 +91,9 @@ class ResultCollector(object): ...@@ -91,6 +91,9 @@ class ResultCollector(object):
return self return self
class FailFast(Exception):
pass
class Runner(object): class Runner(object):
TIME_WAIT_REAP = 0.1 TIME_WAIT_REAP = 0.1
...@@ -125,7 +128,11 @@ class Runner(object): ...@@ -125,7 +128,11 @@ class Runner(object):
kwargs['quiet'] = self._quiet kwargs['quiet'] = self._quiet
result = util.run(cmd, **kwargs) result = util.run(cmd, **kwargs)
if not result and self._failfast: if not result and self._failfast:
sys.exit(1) # Under Python 3.9 (maybe older versions?), raising the
# SystemExit here (a background thread belonging to the
# pool) doesn't seem to work well. It gets stuck waiting
# for a lock? The job never shows up as finished.
raise FailFast(cmd)
self.results += result self.results += result
def _reap(self): def _reap(self):
...@@ -141,7 +148,10 @@ class Runner(object): ...@@ -141,7 +148,10 @@ class Runner(object):
return len(self._running_jobs) return len(self._running_jobs)
def _reap_all(self): def _reap_all(self):
while self._reap() > 0: util.log("Reaping %d jobs", len(self._running_jobs), color="debug")
while self._running_jobs:
if not self._reap():
break
util.sleep(self.TIME_WAIT_REAP) util.sleep(self.TIME_WAIT_REAP)
def _spawn(self, pool, cmd, options): def _spawn(self, pool, cmd, options):
......
from __future__ import print_function from __future__ import print_function
# This file makes this directory into a runnable package. # This file makes this directory into a runnable package.
# it exists to test 'python -m gevent.monkey monkey_package' # it exists to test 'python -m gevent.monkey monkey_package'
print(__file__) # Note that the __file__ may differ slightly; starting with
# Python 3.9, directly running it gets an abspath, but
# using ``runpy`` doesn't.
import os.path
print(os.path.abspath(__file__))
print(__name__) print(__name__)
from __future__ import print_function from __future__ import print_function
import socket import socket
import sys import sys
import os.path
if sys.argv[1] == 'patched': if sys.argv[1] == 'patched':
print('gevent' in repr(socket.socket)) print('gevent' in repr(socket.socket))
else: else:
assert sys.argv[1] == 'stdlib' assert sys.argv[1] == 'stdlib'
print('gevent' not in repr(socket.socket)) print('gevent' not in repr(socket.socket))
print(__file__) print(os.path.abspath(__file__))
if sys.version_info[:2] == (2, 7): if sys.version_info[:2] == (2, 7):
# Prior to gevent 1.3, 'python -m gevent.monkey' guaranteed this to be # Prior to gevent 1.3, 'python -m gevent.monkey' guaranteed this to be
......
...@@ -14,6 +14,7 @@ def use_import(): ...@@ -14,6 +14,7 @@ def use_import():
return dedent(" text") return dedent(" text")
if __name__ == '__main__': if __name__ == '__main__':
print(__file__) import os.path
print(os.path.abspath(__file__))
print(__name__) print(__name__)
print(use_import()) print(use_import())
...@@ -29,13 +29,15 @@ class TestKillWithException(greentest.TestCase): ...@@ -29,13 +29,15 @@ class TestKillWithException(greentest.TestCase):
assert isinstance(g.exception, ExpectedError) assert isinstance(g.exception, ExpectedError)
def test_kill_with_exception_after_started(self): def test_kill_with_exception_after_started(self):
with gevent.get_hub().ignoring_expected_test_error():
g = gevent.spawn(f) g = gevent.spawn(f)
g.join(0) g.join(0)
g.kill(ExpectedError) g.kill(ExpectedError)
assert not g.successful()
self.assertFalse(g.successful())
self.assertRaises(ExpectedError, g.get) self.assertRaises(ExpectedError, g.get)
assert g.value is None self.assertIsNone(g.value)
assert isinstance(g.exception, ExpectedError) self.assertIsInstance(g.exception, ExpectedError)
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -86,12 +86,12 @@ class TestRun(greentest.TestCase): ...@@ -86,12 +86,12 @@ class TestRun(greentest.TestCase):
self._run_package(module=True) self._run_package(module=True)
def test_issue_302(self): def test_issue_302(self):
lines = self._run(os.path.join('monkey_package', 'issue302monkey.py')) monkey_lines = self._run(os.path.join('monkey_package', 'issue302monkey.py'))
self.assertEqual(lines[0].strip(), u'True') self.assertEqual(monkey_lines[0].strip(), u'True')
lines[1] = lines[1].replace(u'\\', u'/') # windows path monkey_lines[1] = monkey_lines[1].replace(u'\\', u'/') # windows path
self.assertEqual(lines[1].strip(), u'monkey_package/issue302monkey.py') self.assertTrue(monkey_lines[1].strip().endswith(u'monkey_package/issue302monkey.py'))
self.assertEqual(lines[2].strip(), u'True', lines) self.assertEqual(monkey_lines[2].strip(), u'True', monkey_lines)
# These three tests all sometimes fail on Py2 on CI, writing # These three tests all sometimes fail on Py2 on CI, writing
# to stderr: # to stderr:
......
...@@ -47,11 +47,15 @@ from wsgiref.validate import validator ...@@ -47,11 +47,15 @@ from wsgiref.validate import validator
import gevent.testing as greentest import gevent.testing as greentest
import gevent import gevent
from gevent.testing import PY3, PYPY from gevent.testing import PY3, PYPY
from gevent.testing.exception import ExpectedException
from gevent import socket from gevent import socket
from gevent import pywsgi from gevent import pywsgi
from gevent.pywsgi import Input from gevent.pywsgi import Input
class ExpectedAssertionError(ExpectedException, AssertionError):
"""An expected assertion error"""
CONTENT_LENGTH = 'Content-Length' CONTENT_LENGTH = 'Content-Length'
CONN_ABORTED_ERRORS = greentest.CONN_ABORTED_ERRORS CONN_ABORTED_ERRORS = greentest.CONN_ABORTED_ERRORS
...@@ -90,11 +94,7 @@ def iread_chunks(fd): ...@@ -90,11 +94,7 @@ def iread_chunks(fd):
while True: while True:
line = fd.readline() line = fd.readline()
chunk_size = line.strip() chunk_size = line.strip()
try:
chunk_size = int(chunk_size, 16) chunk_size = int(chunk_size, 16)
except:
print('Failed to parse chunk size: %r' % line)
raise
if chunk_size == 0: if chunk_size == 0:
crlf = fd.read(2) crlf = fd.read(2)
assert crlf == b'\r\n', repr(crlf) assert crlf == b'\r\n', repr(crlf)
...@@ -173,7 +173,7 @@ class Response(object): ...@@ -173,7 +173,7 @@ class Response(object):
if isinstance(content_length, int): if isinstance(content_length, int):
content_length = str(content_length) content_length = str(content_length)
self.assertHeader('Content-Length', content_length) self.assertHeader('Content-Length', content_length)
try:
if 'chunked' in headers.get('Transfer-Encoding', ''): if 'chunked' in headers.get('Transfer-Encoding', ''):
if CONTENT_LENGTH in headers: if CONTENT_LENGTH in headers:
print("WARNING: server used chunked transfer-encoding despite having Content-Length header (libevent 1.x's bug)") print("WARNING: server used chunked transfer-encoding despite having Content-Length header (libevent 1.x's bug)")
...@@ -184,10 +184,7 @@ class Response(object): ...@@ -184,10 +184,7 @@ class Response(object):
self.body = fd.read(num) self.body = fd.read(num)
else: else:
self.body = fd.read() self.body = fd.read()
except:
print('Response.read failed to read the body:\n%s' % self)
import traceback; traceback.print_exc()
raise
if body is not None: if body is not None:
self.assertBody(body) self.assertBody(body)
if chunks is not None: if chunks is not None:
...@@ -210,15 +207,23 @@ class TestCase(greentest.TestCase): ...@@ -210,15 +207,23 @@ class TestCase(greentest.TestCase):
# So use the hostname. # So use the hostname.
connect_addr = greentest.DEFAULT_LOCAL_HOST_ADDR connect_addr = greentest.DEFAULT_LOCAL_HOST_ADDR
class handler_class(pywsgi.WSGIHandler):
ApplicationError = ExpectedAssertionError
def init_logger(self): def init_logger(self):
import logging import logging
logger = logging.getLogger('gevent.pywsgi') logger = logging.getLogger('gevent.tests.pywsgi')
logger.setLevel(logging.CRITICAL)
return logger return logger
def init_server(self, application): def init_server(self, application):
logger = self.logger = self.init_logger() logger = self.logger = self.init_logger()
self.server = pywsgi.WSGIServer((self.listen_addr, 0), application, self.server = pywsgi.WSGIServer(
log=logger, error_log=logger) (self.listen_addr, 0),
application,
log=logger, error_log=logger,
handler_class=self.handler_class,
)
def setUp(self): def setUp(self):
application = self.application application = self.application
...@@ -1125,12 +1130,10 @@ class TestBody304(TestCase): ...@@ -1125,12 +1130,10 @@ class TestBody304(TestCase):
def test_err(self): def test_err(self):
with self.makefile() as fd: with self.makefile() as fd:
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') fd.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
try: with self.assertRaises(AssertionError) as exc:
read_http(fd) read_http(fd)
except AssertionError as ex: ex = exc.exception
self.assertEqual(str(ex), 'The 304 response must have no body') self.assertEqual(str(ex), 'The 304 response must have no body')
else:
raise AssertionError('AssertionError must be raised')
class TestWrite304(TestCase): class TestWrite304(TestCase):
...@@ -1142,9 +1145,9 @@ class TestWrite304(TestCase): ...@@ -1142,9 +1145,9 @@ class TestWrite304(TestCase):
self.error_raised = False self.error_raised = False
try: try:
write('body') write('body')
except AssertionError: except AssertionError as ex:
self.error_raised = True self.error_raised = True
raise raise ExpectedAssertionError(*ex.args)
def test_err(self): def test_err(self):
with self.makefile() as fd: with self.makefile() as fd:
...@@ -1465,7 +1468,8 @@ class TestInvalidEnviron(TestCase): ...@@ -1465,7 +1468,8 @@ class TestInvalidEnviron(TestCase):
for key, value in environ.items(): for key, value in environ.items():
if key in ('CONTENT_LENGTH', 'CONTENT_TYPE') or key.startswith('HTTP_'): if key in ('CONTENT_LENGTH', 'CONTENT_TYPE') or key.startswith('HTTP_'):
if key != 'HTTP_HOST': if key != 'HTTP_HOST':
raise AssertionError('Unexpected environment variable: %s=%r' % (key, value)) raise ExpectedAssertionError('Unexpected environment variable: %s=%r' % (
key, value))
start_response('200 OK', []) start_response('200 OK', [])
return [] return []
...@@ -1493,7 +1497,11 @@ class TestInvalidHeadersDropped(TestCase): ...@@ -1493,7 +1497,11 @@ class TestInvalidHeadersDropped(TestCase):
read_http(fd) read_http(fd)
class Handler(pywsgi.WSGIHandler): class TestHandlerSubclass(TestCase):
validator = None
class handler_class(TestCase.handler_class):
def read_requestline(self): def read_requestline(self):
data = self.rfile.read(7) data = self.rfile.read(7)
...@@ -1510,20 +1518,10 @@ class Handler(pywsgi.WSGIHandler): ...@@ -1510,20 +1518,10 @@ class Handler(pywsgi.WSGIHandler):
return None return None
return data + self.rfile.readline() return data + self.rfile.readline()
class TestHandlerSubclass(TestCase):
validator = None
def application(self, environ, start_response): def application(self, environ, start_response):
start_response('200 OK', []) start_response('200 OK', [])
return [] return []
def init_server(self, application):
self.server = pywsgi.WSGIServer((self.listen_addr, 0),
application,
handler_class=Handler)
def test(self): def test(self):
with self.makefile() as fd: with self.makefile() as fd:
fd.write(b'<policy-file-request/>\x00') fd.write(b'<policy-file-request/>\x00')
...@@ -1645,14 +1643,12 @@ class TestInputRaw(greentest.BaseTestCase): ...@@ -1645,14 +1643,12 @@ class TestInputRaw(greentest.BaseTestCase):
def test_32bit_overflow(self): def test_32bit_overflow(self):
# https://github.com/gevent/gevent/issues/289 # https://github.com/gevent/gevent/issues/289
# Should not raise an OverflowError on Python 2 # Should not raise an OverflowError on Python 2
print("BEGIN 32bit")
data = b'asdf\nghij\n' data = b'asdf\nghij\n'
long_data = b'a' * (pywsgi.MAX_REQUEST_LINE + 10) long_data = b'a' * (pywsgi.MAX_REQUEST_LINE + 10)
long_data += b'\n' long_data += b'\n'
data = data + long_data data = data + long_data
partial_data = b'qjk\n' # Note terminating \n partial_data = b'qjk\n' # Note terminating \n
n = 25 * 1000000000 n = 25 * 1000000000
print("N", n, "Data len", len(data))
if hasattr(n, 'bit_length'): if hasattr(n, 'bit_length'):
self.assertEqual(n.bit_length(), 35) self.assertEqual(n.bit_length(), 35)
if not PY3 and not PYPY: if not PY3 and not PYPY:
......
...@@ -381,6 +381,10 @@ class ThreadTests(unittest.TestCase): ...@@ -381,6 +381,10 @@ class ThreadTests(unittest.TestCase):
# ignored. # ignored.
# self.assertEqual(stderr, "") # self.assertEqual(stderr, "")
@greentest.skipIf(
not(hasattr(sys, 'getcheckinterval')),
"Needs sys.getcheckinterval"
)
def test_enumerate_after_join(self): def test_enumerate_after_join(self):
# Try hard to trigger #1703448: a thread is still returned in # Try hard to trigger #1703448: a thread is still returned in
# threading.enumerate() after it has been join()ed. # threading.enumerate() after it has been join()ed.
...@@ -388,7 +392,8 @@ class ThreadTests(unittest.TestCase): ...@@ -388,7 +392,8 @@ class ThreadTests(unittest.TestCase):
import warnings import warnings
with warnings.catch_warnings(): with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning) warnings.simplefilter('ignore', DeprecationWarning)
# get/set checkinterval are deprecated in Python 3 # get/set checkinterval are deprecated in Python 3,
# and removed in Python 3.9
old_interval = sys.getcheckinterval() old_interval = sys.getcheckinterval()
try: try:
for i in xrange(1, 100): for i in xrange(1, 100):
......
...@@ -152,6 +152,7 @@ class TestTree(greentest.TestCase): ...@@ -152,6 +152,7 @@ class TestTree(greentest.TestCase):
@greentest.ignores_leakcheck @greentest.ignores_leakcheck
def test_tree(self): def test_tree(self):
with gevent.get_hub().ignoring_expected_test_error():
tree, str_tree, tree_format = self._build_tree() tree, str_tree, tree_format = self._build_tree()
self.assertTrue(tree.root) self.assertTrue(tree.root)
...@@ -191,9 +192,9 @@ class TestTree(greentest.TestCase): ...@@ -191,9 +192,9 @@ class TestTree(greentest.TestCase):
@greentest.ignores_leakcheck @greentest.ignores_leakcheck
def test_tree_no_track(self): def test_tree_no_track(self):
gevent.config.track_greenlet_tree = False gevent.config.track_greenlet_tree = False
with gevent.get_hub().ignoring_expected_test_error():
self._build_tree() self._build_tree()
@greentest.ignores_leakcheck @greentest.ignores_leakcheck
def test_forest_fake_parent(self): def test_forest_fake_parent(self):
from greenlet import greenlet as RawGreenlet from greenlet import greenlet as RawGreenlet
......
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