You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

505 lines
17 KiB
Python

5 years ago
from __future__ import print_function, division
from contextlib import contextmanager
import unittest
import errno
import os
import gevent.testing as greentest
from gevent.testing import PY3
from gevent.testing import DEFAULT_SOCKET_TIMEOUT as _DEFAULT_SOCKET_TIMEOUT
from gevent.testing.sockets import tcp_listener
from gevent import socket
import gevent
from gevent.server import StreamServer
class SimpleStreamServer(StreamServer):
def handle(self, client_socket, _address): # pylint:disable=method-hidden
fd = client_socket.makefile()
try:
request_line = fd.readline()
if not request_line:
return
try:
_method, path, _rest = request_line.split(' ', 3)
except Exception:
print('Failed to parse request line: %r' % (request_line, ))
raise
if path == '/ping':
client_socket.sendall(b'HTTP/1.0 200 OK\r\n\r\nPONG')
elif path in ['/long', '/short']:
client_socket.sendall(b'hello')
while True:
data = client_socket.recv(1)
if not data:
break
else:
client_socket.sendall(b'HTTP/1.0 404 WTF?\r\n\r\n')
finally:
fd.close()
class Settings(object):
ServerClass = StreamServer
ServerSubClass = SimpleStreamServer
restartable = True
close_socket_detected = True
@staticmethod
def assertAcceptedConnectionError(inst):
with inst.makefile() as conn:
result = conn.read()
inst.assertFalse(result)
assert500 = assertAcceptedConnectionError
@staticmethod
def assert503(inst):
# regular reads timeout
inst.assert500()
# attempt to send anything reset the connection
try:
inst.send_request()
except socket.error as ex:
if ex.args[0] not in greentest.CONN_ABORTED_ERRORS:
raise
@staticmethod
def assertPoolFull(inst):
with inst.assertRaises(socket.timeout):
inst.assertRequestSucceeded(timeout=0.01)
@staticmethod
def fill_default_server_args(inst, kwargs):
kwargs.setdefault('spawn', inst.get_spawn())
return kwargs
class TestCase(greentest.TestCase):
# pylint: disable=too-many-public-methods
__timeout__ = greentest.LARGE_TIMEOUT
Settings = Settings
server = None
def cleanup(self):
if getattr(self, 'server', None) is not None:
self.server.stop()
self.server = None
def get_listener(self):
return self._close_on_teardown(tcp_listener(backlog=5))
def get_server_host_port_family(self):
server_host = self.server.server_host
if not server_host:
server_host = greentest.DEFAULT_LOCAL_HOST_ADDR
elif server_host == '::':
server_host = greentest.DEFAULT_LOCAL_HOST_ADDR6
try:
family = self.server.socket.family
except AttributeError:
# server deletes socket when closed
family = socket.AF_INET
return server_host, self.server.server_port, family
@contextmanager
def makefile(self, timeout=_DEFAULT_SOCKET_TIMEOUT, bufsize=1, include_raw_socket=False):
server_host, server_port, family = self.get_server_host_port_family()
bufarg = 'buffering' if PY3 else 'bufsize'
makefile_kwargs = {bufarg: bufsize}
if PY3:
# Under Python3, you can't read and write to the same
# makefile() opened in r, and r+ is not allowed
makefile_kwargs['mode'] = 'rwb'
with socket.socket(family=family) as sock:
rconn = None
# We want the socket to be accessible from the fileobject
# we return. On Python 2, natively this is available as
# _sock, but Python 3 doesn't have that.
sock.connect((server_host, server_port))
sock.settimeout(timeout)
with sock.makefile(**makefile_kwargs) as rconn:
result = rconn if not include_raw_socket else (rconn, sock)
yield result
def send_request(self, url='/', timeout=_DEFAULT_SOCKET_TIMEOUT, bufsize=1):
with self.makefile(timeout=timeout, bufsize=bufsize) as conn:
self.send_request_to_fd(conn, url)
def send_request_to_fd(self, fd, url='/'):
fd.write(('GET %s HTTP/1.0\r\n\r\n' % url).encode('latin-1'))
fd.flush()
def assertConnectionRefused(self):
with self.assertRaises(socket.error) as exc:
with self.makefile() as conn:
conn.close()
ex = exc.exception
self.assertIn(ex.args[0],
(errno.ECONNREFUSED, errno.EADDRNOTAVAIL,
errno.ECONNRESET, errno.ECONNABORTED),
(ex, ex.args))
def assert500(self):
self.Settings.assert500(self)
def assert503(self):
self.Settings.assert503(self)
def assertAcceptedConnectionError(self):
self.Settings.assertAcceptedConnectionError(self)
def assertPoolFull(self):
self.Settings.assertPoolFull(self)
def assertNotAccepted(self):
with self.makefile(include_raw_socket=True) as (conn, sock):
conn.write(b'GET / HTTP/1.0\r\n\r\n')
conn.flush()
result = b''
try:
while True:
data = sock.recv(1)
if not data:
break
result += data
except socket.timeout:
self.assertFalse(result)
return
self.assertTrue(result.startswith(b'HTTP/1.0 500 Internal Server Error'), repr(result))
def assertRequestSucceeded(self, timeout=_DEFAULT_SOCKET_TIMEOUT):
with self.makefile(timeout=timeout) as conn:
conn.write(b'GET /ping HTTP/1.0\r\n\r\n')
result = conn.read()
self.assertTrue(result.endswith(b'\r\n\r\nPONG'), repr(result))
def start_server(self):
self.server.start()
self.assertRequestSucceeded()
self.assertRequestSucceeded()
def stop_server(self):
self.server.stop()
self.assertConnectionRefused()
def report_netstat(self, _msg):
# At one point this would call 'sudo netstat -anp | grep PID'
# with os.system. We can probably do better with psutil.
return
def _create_server(self):
return self.ServerSubClass((greentest.DEFAULT_BIND_ADDR, 0))
def init_server(self):
self.server = self._create_server()
self.server.start()
gevent.sleep()
@property
def socket(self):
return self.server.socket
def _test_invalid_callback(self):
try:
self.server = self.ServerClass((greentest.DEFAULT_BIND_ADDR, 0), lambda: None)
self.server.start()
self.expect_one_error()
self.assert500()
self.assert_error(TypeError)
finally:
self.server.stop()
# XXX: There's something unreachable (with a traceback?)
# We need to clear it to make the leak checks work on Travis;
# so far I can't reproduce it locally on OS X.
import gc; gc.collect()
def fill_default_server_args(self, kwargs):
return self.Settings.fill_default_server_args(self, kwargs)
def ServerClass(self, *args, **kwargs):
return self.Settings.ServerClass(*args,
**self.fill_default_server_args(kwargs))
def ServerSubClass(self, *args, **kwargs):
return self.Settings.ServerSubClass(*args,
**self.fill_default_server_args(kwargs))
def get_spawn(self):
return None
class TestDefaultSpawn(TestCase):
def get_spawn(self):
return gevent.spawn
def _test_server_start_stop(self, restartable):
self.report_netstat('before start')
self.start_server()
self.report_netstat('after start')
if restartable and self.Settings.restartable:
self.server.stop_accepting()
self.report_netstat('after stop_accepting')
self.assertNotAccepted()
self.server.start_accepting()
self.report_netstat('after start_accepting')
self.assertRequestSucceeded()
self.stop_server()
self.report_netstat('after stop')
def test_backlog_is_not_accepted_for_socket(self):
self.switch_expected = False
with self.assertRaises(TypeError):
self.ServerClass(self.get_listener(), backlog=25, handle=False)
def test_backlog_is_accepted_for_address(self):
self.server = self.ServerSubClass((greentest.DEFAULT_BIND_ADDR, 0), backlog=25)
self.assertConnectionRefused()
self._test_server_start_stop(restartable=False)
def test_subclass_just_create(self):
self.server = self.ServerSubClass(self.get_listener())
self.assertNotAccepted()
def test_subclass_with_socket(self):
self.server = self.ServerSubClass(self.get_listener())
# the connection won't be refused, because there exists a
# listening socket, but it won't be handled also
self.assertNotAccepted()
self._test_server_start_stop(restartable=True)
def test_subclass_with_address(self):
self.server = self.ServerSubClass((greentest.DEFAULT_BIND_ADDR, 0))
self.assertConnectionRefused()
self._test_server_start_stop(restartable=True)
def test_invalid_callback(self):
self._test_invalid_callback()
@greentest.reraises_flaky_timeout(socket.timeout)
def _test_serve_forever(self):
g = gevent.spawn(self.server.serve_forever)
try:
gevent.sleep(0.01)
self.assertRequestSucceeded()
self.server.stop()
self.assertFalse(self.server.started)
self.assertConnectionRefused()
finally:
g.kill()
g.get()
self.server.stop()
def test_serve_forever(self):
self.server = self.ServerSubClass((greentest.DEFAULT_BIND_ADDR, 0))
self.assertFalse(self.server.started)
self.assertConnectionRefused()
self._test_serve_forever()
def test_serve_forever_after_start(self):
self.server = self.ServerSubClass((greentest.DEFAULT_BIND_ADDR, 0))
self.assertConnectionRefused()
self.assertFalse(self.server.started)
self.server.start()
self.assertTrue(self.server.started)
self._test_serve_forever()
def test_server_closes_client_sockets(self):
self.server = self.ServerClass((greentest.DEFAULT_BIND_ADDR, 0), lambda *args: [])
self.server.start()
with self.makefile() as conn:
self.send_request_to_fd(conn)
# use assert500 below?
with gevent.Timeout._start_new_or_dummy(1):
try:
result = conn.read()
if result:
assert result.startswith('HTTP/1.0 500 Internal Server Error'), repr(result)
except socket.error as ex:
if ex.args[0] == 10053:
pass # "established connection was aborted by the software in your host machine"
elif ex.args[0] == errno.ECONNRESET:
pass
else:
raise
self.stop_server()
@property
def socket(self):
return self.server.socket
def test_error_in_spawn(self):
self.init_server()
self.assertTrue(self.server.started)
error = ExpectedError('test_error_in_spawn')
self.server._spawn = lambda *args: gevent.getcurrent().throw(error)
self.expect_one_error()
self.assertAcceptedConnectionError()
self.assert_error(ExpectedError, error)
def test_server_repr_when_handle_is_instancemethod(self):
# PR 501
self.init_server()
assert self.server.started
self.assertIn('Server', repr(self.server))
self.server.set_handle(self.server.handle)
self.assertIn('handle=<bound method', repr(self.server))
self.assertIn('of self>', repr(self.server))
self.server.set_handle(self.test_server_repr_when_handle_is_instancemethod)
self.assertIn('test_server_repr_when_handle_is_instancemethod', repr(self.server))
def handle():
pass
self.server.set_handle(handle)
self.assertIn('handle=<function', repr(self.server))
class TestRawSpawn(TestDefaultSpawn):
def get_spawn(self):
return gevent.spawn_raw
class TestPoolSpawn(TestDefaultSpawn):
def get_spawn(self):
return 2
@greentest.skipIf(greentest.EXPECT_POOR_TIMER_RESOLUTION,
"If we have bad timer resolution and hence increase timeouts, "
"it can be hard to sleep for a correct amount of time that lets "
"requests in the pool be full.")
def test_pool_full(self):
self.init_server()
with self.makefile() as long_request:
with self.makefile() as short_request:
self.send_request_to_fd(short_request, '/short')
self.send_request_to_fd(long_request, '/long')
# keep long_request in scope, otherwise the connection will be closed
gevent.get_hub().loop.update_now()
gevent.sleep(_DEFAULT_SOCKET_TIMEOUT / 10.0)
self.assertPoolFull()
self.assertPoolFull()
# XXX Not entirely clear why this fails (timeout) on appveyor;
# underlying socket timeout causing the long_request to close?
self.assertPoolFull()
# gevent.http and gevent.wsgi cannot detect socket close, so sleep a little
# to let /short request finish
gevent.sleep(_DEFAULT_SOCKET_TIMEOUT)
# XXX: This tends to timeout. Which is weird, because what would have
# been the third call to assertPoolFull() DID NOT timeout, hence why it
# was removed.
try:
self.assertRequestSucceeded()
except socket.timeout:
greentest.reraiseFlakyTestTimeout()
test_pool_full.error_fatal = False
class TestNoneSpawn(TestCase):
def get_spawn(self):
return None
def test_invalid_callback(self):
self._test_invalid_callback()
def test_assertion_in_blocking_func(self):
def sleep(*_args):
gevent.sleep(0)
self.server = self.Settings.ServerClass((greentest.DEFAULT_BIND_ADDR, 0), sleep, spawn=None)
self.server.start()
self.expect_one_error()
self.assert500()
self.assert_error(AssertionError, 'Impossible to call blocking function in the event loop callback')
class ExpectedError(Exception):
pass
class TestSSLSocketNotAllowed(TestCase):
switch_expected = False
def get_spawn(self):
return gevent.spawn
@unittest.skipUnless(hasattr(socket, 'ssl'), "Uses socket.ssl")
def test(self):
from gevent.socket import ssl
listener = self._close_on_teardown(tcp_listener(backlog=5))
listener = ssl(listener)
self.assertRaises(TypeError, self.ServerSubClass, listener)
def _file(name, here=os.path.dirname(__file__)):
return os.path.abspath(os.path.join(here, name))
class BadWrapException(BaseException):
pass
class TestSSLGetCertificate(TestCase):
def _create_server(self):
return self.ServerSubClass((greentest.DEFAULT_BIND_ADDR, 0),
keyfile=_file('server.key'),
certfile=_file('server.crt'))
def get_spawn(self):
return gevent.spawn
def test_certificate(self):
# Issue 801
from gevent import monkey, ssl
# only broken if *not* monkey patched
self.assertFalse(monkey.is_module_patched('ssl'))
self.assertFalse(monkey.is_module_patched('socket'))
self.init_server()
server_host, server_port, _family = self.get_server_host_port_family()
ssl.get_server_certificate((server_host, server_port)) # pylint:disable=no-member
def test_wrap_socket_and_handle_wrap_failure(self):
# A failure to wrap the socket doesn't have follow on effects
# like failing with a UnboundLocalError.
# See https://github.com/gevent/gevent/issues/1236
self.init_server()
def bad_wrap(_client_socket, **_wrap_args):
raise BadWrapException()
self.server.wrap_socket = bad_wrap
with self.assertRaises(BadWrapException):
self.server._handle(None, None)
# test non-socket.error exception in accept call: fatal
# test error in spawn(): non-fatal
# test error in spawned handler: non-fatal
if __name__ == '__main__':
greentest.main()