You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
453 lines
14 KiB
453 lines
14 KiB
from gevent import monkey; monkey.patch_all()
|
|
|
|
import sys
|
|
import os
|
|
import array
|
|
import socket
|
|
import traceback
|
|
import time
|
|
import unittest
|
|
import gevent.testing as greentest
|
|
from functools import wraps
|
|
from gevent.testing import six
|
|
from gevent.testing import LARGE_TIMEOUT
|
|
|
|
# we use threading on purpose so that we can test both regular and gevent sockets with the same code
|
|
from threading import Thread as _Thread
|
|
|
|
errno_types = int
|
|
|
|
def wrap_error(func):
|
|
|
|
@wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
try:
|
|
return func(*args, **kwargs)
|
|
except: # pylint:disable=bare-except
|
|
traceback.print_exc()
|
|
os._exit(2)
|
|
|
|
return wrapper
|
|
|
|
|
|
class Thread(_Thread):
|
|
|
|
def __init__(self, **kwargs):
|
|
target = kwargs.pop('target')
|
|
target = wrap_error(target)
|
|
_Thread.__init__(self, target=target, **kwargs)
|
|
self.start()
|
|
|
|
|
|
class TestTCP(greentest.TestCase):
|
|
|
|
__timeout__ = None
|
|
TIMEOUT_ERROR = socket.timeout
|
|
long_data = ", ".join([str(x) for x in range(20000)])
|
|
if not isinstance(long_data, bytes):
|
|
long_data = long_data.encode('ascii')
|
|
|
|
def setUp(self):
|
|
super(TestTCP, self).setUp()
|
|
self.listener = self._close_on_teardown(self._setup_listener())
|
|
|
|
# XXX: On Windows (at least with libev), if we have a cleanup/tearDown method
|
|
# that does 'del self.listener' AND we haven't sometime
|
|
# previously closed the listener (while the test body was executing)
|
|
# we tend to sometimes see hangs when tests run in succession;
|
|
# notably test_empty_send followed by test_makefile produces a hang
|
|
# in test_makefile when it tries to read from the client_file, because
|
|
# the accept() call in accept_once has not yet returned a new socket to
|
|
# write to.
|
|
|
|
# The cause *seems* to be that the listener socket in both tests gets the
|
|
# same fileno(); or, at least, if we don't del the listener object,
|
|
# we get a different fileno, and that scenario works.
|
|
|
|
# Perhaps our logic is wrong in libev_vfd in the way we use
|
|
# _open_osfhandle and determine we can close it?
|
|
self.port = self.listener.getsockname()[1]
|
|
|
|
def _setup_listener(self):
|
|
listener = socket.socket()
|
|
greentest.bind_and_listen(listener, ('127.0.0.1', 0))
|
|
return listener
|
|
|
|
def create_connection(self, host='127.0.0.1', port=None, timeout=None,
|
|
blocking=None):
|
|
sock = socket.socket()
|
|
sock.connect((host, port or self.port))
|
|
if timeout is not None:
|
|
sock.settimeout(timeout)
|
|
if blocking is not None:
|
|
sock.setblocking(blocking)
|
|
return self._close_on_teardown(sock)
|
|
|
|
def _test_sendall(self, data, match_data=None, client_method='sendall',
|
|
**client_args):
|
|
|
|
read_data = []
|
|
server_exc_info = []
|
|
|
|
def accept_and_read():
|
|
conn = None
|
|
try:
|
|
conn, _ = self.listener.accept()
|
|
r = conn.makefile(mode='rb')
|
|
read_data.append(r.read())
|
|
r.flush()
|
|
r.close()
|
|
except: # pylint:disable=bare-except
|
|
server_exc_info.append(sys.exc_info())
|
|
finally:
|
|
if conn:
|
|
conn.close()
|
|
self.listener.close()
|
|
|
|
server = Thread(target=accept_and_read)
|
|
client = self.create_connection(**client_args)
|
|
|
|
try:
|
|
getattr(client, client_method)(data)
|
|
finally:
|
|
client.shutdown(socket.SHUT_RDWR)
|
|
client.close()
|
|
|
|
server.join()
|
|
if match_data is None:
|
|
match_data = self.long_data
|
|
self.assertEqual(read_data[0], match_data)
|
|
|
|
if server_exc_info:
|
|
six.reraise(*server_exc_info[0])
|
|
|
|
def test_sendall_str(self):
|
|
self._test_sendall(self.long_data)
|
|
|
|
if not six.PY3:
|
|
def test_sendall_unicode(self):
|
|
self._test_sendall(six.text_type(self.long_data))
|
|
|
|
def test_sendall_array(self):
|
|
data = array.array("B", self.long_data)
|
|
self._test_sendall(data)
|
|
|
|
def test_sendall_empty(self):
|
|
data = b''
|
|
self._test_sendall(data, data)
|
|
|
|
def test_sendall_empty_with_timeout(self):
|
|
# Issue 719
|
|
data = b''
|
|
self._test_sendall(data, data, timeout=10)
|
|
|
|
def test_sendall_nonblocking(self):
|
|
# https://github.com/benoitc/gunicorn/issues/1282
|
|
# Even if the socket is non-blocking, we make at least
|
|
# one attempt to send data. Under Py2 before this fix, we
|
|
# would incorrectly immediately raise a timeout error
|
|
data = b'hi\n'
|
|
self._test_sendall(data, data, blocking=False)
|
|
|
|
def test_empty_send(self):
|
|
# Issue 719
|
|
data = b''
|
|
self._test_sendall(data, data, client_method='send')
|
|
|
|
def test_fullduplex(self):
|
|
N = 100000
|
|
|
|
def server():
|
|
(remote_client, _) = self.listener.accept()
|
|
# start reading, then, while reading, start writing. the reader should not hang forever
|
|
|
|
def sendall():
|
|
remote_client.sendall(b't' * N)
|
|
|
|
sender = Thread(target=sendall)
|
|
result = remote_client.recv(1000)
|
|
self.assertEqual(result, b'hello world')
|
|
sender.join()
|
|
remote_client.close()
|
|
self.listener.close()
|
|
|
|
server_thread = Thread(target=server)
|
|
client = self.create_connection()
|
|
client_file = client.makefile()
|
|
client_reader = Thread(target=client_file.read, args=(N, ))
|
|
time.sleep(0.1)
|
|
client.sendall(b'hello world')
|
|
time.sleep(0.1)
|
|
|
|
# close() used to hang
|
|
client_file.close()
|
|
client.close()
|
|
|
|
# this tests "full duplex" bug;
|
|
server_thread.join()
|
|
|
|
client_reader.join()
|
|
|
|
def test_recv_timeout(self):
|
|
client_sock = []
|
|
acceptor = Thread(target=lambda: client_sock.append(self.listener.accept()))
|
|
client = self.create_connection()
|
|
client.settimeout(1)
|
|
start = time.time()
|
|
self.assertRaises(self.TIMEOUT_ERROR, client.recv, 1024)
|
|
took = time.time() - start
|
|
self.assertTimeWithinRange(took, 1 - 0.1, 1 + 0.1)
|
|
acceptor.join()
|
|
client.close()
|
|
client_sock[0][0].close()
|
|
|
|
# Subclasses can disable this
|
|
_test_sendall_timeout_check_time = True
|
|
|
|
# Travis-CI container infrastructure is configured with
|
|
# large socket buffers, at least 2MB, as-of Jun 3, 2015,
|
|
# so we must be sure to send more data than that.
|
|
# In 2018, this needs to be increased *again* as a smaller value was
|
|
# still often being sent.
|
|
_test_sendall_data = b'hello' * 100000000
|
|
|
|
# This doesn't make much sense...why are we really skipping this?
|
|
@greentest.skipOnWindows("On Windows send() accepts whatever is thrown at it")
|
|
def test_sendall_timeout(self):
|
|
client_sock = []
|
|
acceptor = Thread(target=lambda: client_sock.append(self.listener.accept()))
|
|
client = self.create_connection()
|
|
time.sleep(0.1)
|
|
assert client_sock
|
|
client.settimeout(0.1)
|
|
start = time.time()
|
|
try:
|
|
with self.assertRaises(self.TIMEOUT_ERROR):
|
|
client.sendall(self._test_sendall_data)
|
|
if self._test_sendall_timeout_check_time:
|
|
took = time.time() - start
|
|
self.assertTimeWithinRange(took, 0.09, 0.2)
|
|
finally:
|
|
acceptor.join()
|
|
client.close()
|
|
client_sock[0][0].close()
|
|
|
|
def test_makefile(self):
|
|
def accept_once():
|
|
conn, _ = self.listener.accept()
|
|
fd = conn.makefile(mode='wb')
|
|
fd.write(b'hello\n')
|
|
fd.flush()
|
|
fd.close()
|
|
conn.close() # for pypy
|
|
self.listener.close()
|
|
|
|
acceptor = Thread(target=accept_once)
|
|
client = self.create_connection()
|
|
# Closing the socket doesn't close the file
|
|
client_file = client.makefile(mode='rb')
|
|
client.close()
|
|
line = client_file.readline()
|
|
self.assertEqual(line, b'hello\n')
|
|
self.assertEqual(client_file.read(), b'')
|
|
client_file.close()
|
|
acceptor.join()
|
|
|
|
def test_makefile_timeout(self):
|
|
|
|
def accept_once():
|
|
conn, _ = self.listener.accept()
|
|
try:
|
|
time.sleep(0.3)
|
|
finally:
|
|
conn.close() # for pypy
|
|
|
|
acceptor = Thread(target=accept_once)
|
|
client = self.create_connection()
|
|
client.settimeout(0.1)
|
|
fd = client.makefile(mode='rb')
|
|
self.assertRaises(self.TIMEOUT_ERROR, fd.readline)
|
|
client.close()
|
|
fd.close()
|
|
acceptor.join()
|
|
|
|
def test_attributes(self):
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
|
|
self.assertEqual(socket.AF_INET, s.type)
|
|
self.assertEqual(socket.SOCK_DGRAM, s.family)
|
|
self.assertEqual(0, s.proto)
|
|
|
|
if hasattr(socket, 'SOCK_NONBLOCK'):
|
|
s.settimeout(1)
|
|
self.assertEqual(socket.AF_INET, s.type)
|
|
|
|
s.setblocking(0)
|
|
std_socket = monkey.get_original('socket', 'socket')(socket.AF_INET, socket.SOCK_DGRAM, 0)
|
|
try:
|
|
std_socket.setblocking(0)
|
|
self.assertEqual(std_socket.type, s.type)
|
|
finally:
|
|
std_socket.close()
|
|
|
|
s.close()
|
|
|
|
def test_connect_ex_nonblocking_bad_connection(self):
|
|
# Issue 841
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
s.setblocking(False)
|
|
ret = s.connect_ex((greentest.DEFAULT_LOCAL_HOST_ADDR, get_port()))
|
|
self.assertIsInstance(ret, errno_types)
|
|
s.close()
|
|
|
|
def test_connect_ex_gaierror(self):
|
|
# Issue 841
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
with self.assertRaises(socket.gaierror):
|
|
s.connect_ex(('foo.bar.fizzbuzz', get_port()))
|
|
s.close()
|
|
|
|
def test_connect_ex_nonblocking_overflow(self):
|
|
# Issue 841
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
s.setblocking(False)
|
|
with self.assertRaises(OverflowError):
|
|
s.connect_ex((greentest.DEFAULT_LOCAL_HOST_ADDR, 65539))
|
|
s.close()
|
|
|
|
@unittest.skipUnless(hasattr(socket, 'SOCK_CLOEXEC'),
|
|
"Requires SOCK_CLOEXEC")
|
|
def test_connect_with_type_flags_ignored(self):
|
|
# Issue 944
|
|
# If we have SOCK_CLOEXEC or similar, we shouldn't be passing
|
|
# them through to the getaddrinfo call that connect() makes
|
|
SOCK_CLOEXEC = socket.SOCK_CLOEXEC # pylint:disable=no-member
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM | SOCK_CLOEXEC)
|
|
|
|
def accept_once():
|
|
conn, _ = self.listener.accept()
|
|
fd = conn.makefile(mode='wb')
|
|
fd.write(b'hello\n')
|
|
fd.close()
|
|
conn.close()
|
|
|
|
acceptor = Thread(target=accept_once)
|
|
s.connect(('127.0.0.1', self.port))
|
|
fd = s.makefile(mode='rb')
|
|
self.assertEqual(fd.readline(), b'hello\n')
|
|
|
|
fd.close()
|
|
s.close()
|
|
|
|
acceptor.join()
|
|
|
|
def get_port():
|
|
tempsock = socket.socket()
|
|
tempsock.bind(('', 0))
|
|
port = tempsock.getsockname()[1]
|
|
tempsock.close()
|
|
return port
|
|
|
|
|
|
class TestCreateConnection(greentest.TestCase):
|
|
|
|
__timeout__ = LARGE_TIMEOUT
|
|
|
|
def test_refuses(self):
|
|
with self.assertRaises(socket.error) as cm:
|
|
socket.create_connection((greentest.DEFAULT_BIND_ADDR, get_port()),
|
|
timeout=30,
|
|
source_address=('', get_port()))
|
|
ex = cm.exception
|
|
self.assertIn('refused', str(ex).lower())
|
|
|
|
@greentest.ignores_leakcheck
|
|
def test_base_exception(self):
|
|
# such as a GreenletExit or a gevent.timeout.Timeout
|
|
|
|
class E(BaseException):
|
|
pass
|
|
|
|
class MockSocket(object):
|
|
|
|
created = ()
|
|
closed = False
|
|
|
|
def __init__(self, *_):
|
|
MockSocket.created += (self,)
|
|
|
|
def connect(self, _):
|
|
raise E()
|
|
|
|
def close(self):
|
|
self.closed = True
|
|
|
|
def mockgetaddrinfo(*_):
|
|
return [(1, 2, 3, 3, 5),]
|
|
|
|
import gevent.socket as gsocket
|
|
# Make sure we're monkey patched
|
|
self.assertEqual(gsocket.create_connection, socket.create_connection)
|
|
orig_socket = gsocket.socket
|
|
orig_getaddrinfo = gsocket.getaddrinfo
|
|
|
|
try:
|
|
gsocket.socket = MockSocket
|
|
gsocket.getaddrinfo = mockgetaddrinfo
|
|
|
|
with self.assertRaises(E):
|
|
socket.create_connection(('host', 'port'))
|
|
|
|
self.assertEqual(1, len(MockSocket.created))
|
|
self.assertTrue(MockSocket.created[0].closed)
|
|
|
|
finally:
|
|
MockSocket.created = ()
|
|
gsocket.socket = orig_socket
|
|
gsocket.getaddrinfo = orig_getaddrinfo
|
|
|
|
class TestFunctions(greentest.TestCase):
|
|
|
|
@greentest.ignores_leakcheck
|
|
# Creating new types in the function takes a cycle to cleanup.
|
|
def test_wait_timeout(self):
|
|
# Issue #635
|
|
import gevent.socket
|
|
import gevent._socketcommon
|
|
|
|
class io(object):
|
|
callback = None
|
|
|
|
def start(self, *_args):
|
|
gevent.sleep(10)
|
|
|
|
with self.assertRaises(gevent.socket.timeout):
|
|
gevent.socket.wait(io(), timeout=0.01) # pylint:disable=no-member
|
|
|
|
|
|
def test_signatures(self):
|
|
# https://github.com/gevent/gevent/issues/960
|
|
exclude = []
|
|
if greentest.PYPY:
|
|
# Up through at least PyPy 5.7.1, they define these as
|
|
# gethostbyname(host), whereas the official CPython argument name
|
|
# is hostname. But cpython doesn't allow calling with keyword args.
|
|
# Likewise for gethostbyaddr: PyPy uses host, cpython uses ip_address
|
|
exclude.append('gethostbyname')
|
|
exclude.append('gethostbyname_ex')
|
|
exclude.append('gethostbyaddr')
|
|
self.assertMonkeyPatchedFuncSignatures('socket', exclude=exclude)
|
|
|
|
|
|
class TestSocket(greentest.TestCase):
|
|
|
|
def test_shutdown_when_closed(self):
|
|
# https://github.com/gevent/gevent/issues/1089
|
|
# we once raised an AttributeError.
|
|
s = socket.socket()
|
|
s.close()
|
|
with self.assertRaises(socket.error):
|
|
s.shutdown(socket.SHUT_RDWR)
|
|
|
|
if __name__ == '__main__':
|
|
greentest.main()
|