Commit 9d3f00d7 authored by Jason Madden's avatar Jason Madden

Even more care with socket lifetimes. Getting a clean 3.7, 2.7 and 2.7pypy run locally now.

parent c6cb4a41
...@@ -113,6 +113,11 @@ def get_switch_expected(fullname): ...@@ -113,6 +113,11 @@ def get_switch_expected(fullname):
disabled_tests = [ disabled_tests = [
# The server side takes awhile to shut down # The server side takes awhile to shut down
'test_httplib.HTTPSTest.test_local_bad_hostname', 'test_httplib.HTTPSTest.test_local_bad_hostname',
# These were previously 3.5+ issues (same as above)
# but have been backported.
'test_httplib.HTTPSTest.test_local_good_hostname',
'test_httplib.HTTPSTest.test_local_unknown_cert',
'test_threading.ThreadTests.test_PyThreadState_SetAsyncExc', 'test_threading.ThreadTests.test_PyThreadState_SetAsyncExc',
# uses some internal C API of threads not available when threads are emulated with greenlets # uses some internal C API of threads not available when threads are emulated with greenlets
...@@ -232,14 +237,21 @@ if PY2 and PYPY: ...@@ -232,14 +237,21 @@ if PY2 and PYPY:
'test_httpservers.BaseHTTPServerTestCase.test_head_keep_alive', 'test_httpservers.BaseHTTPServerTestCase.test_head_keep_alive',
'test_httpservers.BaseHTTPServerTestCase.test_send_blank', 'test_httpservers.BaseHTTPServerTestCase.test_send_blank',
'test_httpservers.BaseHTTPServerTestCase.test_send_error', 'test_httpservers.BaseHTTPServerTestCase.test_send_error',
'test_httpservers.BaseHTTPServerTestCase.test_command',
'test_httpservers.BaseHTTPServerTestCase.test_handler',
'test_httpservers.CGIHTTPServerTestcase.test_post', 'test_httpservers.CGIHTTPServerTestcase.test_post',
'test_httpservers.CGIHTTPServerTestCase.test_query_with_continuous_slashes', 'test_httpservers.CGIHTTPServerTestCase.test_query_with_continuous_slashes',
'test_httpservers.CGIHTTPServerTestCase.test_query_with_multiple_question_mark', 'test_httpservers.CGIHTTPServerTestCase.test_query_with_multiple_question_mark',
'test_httpservers.CGIHTTPServerTestCase.test_os_environ_is_not_altered', 'test_httpservers.CGIHTTPServerTestCase.test_os_environ_is_not_altered',
# This is flaxy, apparently a race condition? Began with PyPy 2.7-7 # These are flaxy, apparently a race condition? Began with PyPy 2.7-7
'test_asyncore.TestAPI_UsePoll.test_handle_error', 'test_asyncore.TestAPI_UsePoll.test_handle_error',
'test_asyncore.TestAPI_UsePoll.test_handle_read', 'test_asyncore.TestAPI_UsePoll.test_handle_read',
# This one sometimes results on connection refused
'test_urllib2_localnet.TestUrlopen.test_info',
# Sometimes hangs
'test_ssl.ThreadedTests.test_socketserver',
] ]
if LIBUV: if LIBUV:
...@@ -856,6 +868,9 @@ if PYPY: ...@@ -856,6 +868,9 @@ if PYPY:
'test_httpservers.CGIHTTPServerTestCase.test_invaliduri': _gc_at_end, 'test_httpservers.CGIHTTPServerTestCase.test_invaliduri': _gc_at_end,
'test_httpservers.CGIHTTPServerTestCase.test_issue19435': _gc_at_end, 'test_httpservers.CGIHTTPServerTestCase.test_issue19435': _gc_at_end,
'test_httplib.TunnelTests.test_connect': _gc_at_end,
'test_httplib.SourceAddressTest.testHTTPConnectionSourceAddress': _gc_at_end,
# Unclear # Unclear
'test_urllib2_localnet.ProxyAuthTests.test_proxy_with_bad_password_raises_httperror': _gc_at_end, 'test_urllib2_localnet.ProxyAuthTests.test_proxy_with_bad_password_raises_httperror': _gc_at_end,
'test_urllib2_localnet.ProxyAuthTests.test_proxy_with_no_password_raises_httperror': _gc_at_end, 'test_urllib2_localnet.ProxyAuthTests.test_proxy_with_no_password_raises_httperror': _gc_at_end,
...@@ -898,10 +913,6 @@ if PY35: ...@@ -898,10 +913,6 @@ if PY35:
# it should be found at runtime. # it should be found at runtime.
'test_socket.GeneralModuleTests.test_sock_ioctl', 'test_socket.GeneralModuleTests.test_sock_ioctl',
# See comments for 2.7; these hang
'test_httplib.HTTPSTest.test_local_good_hostname',
'test_httplib.HTTPSTest.test_local_unknown_cert',
# XXX This fails for an unknown reason # XXX This fails for an unknown reason
'test_httplib.HeaderTests.test_parse_all_octets', 'test_httplib.HeaderTests.test_parse_all_octets',
] ]
...@@ -1042,6 +1053,11 @@ if PY37: ...@@ -1042,6 +1053,11 @@ if PY37:
# but it passes when they run it and fails when we do. It's not # but it passes when they run it and fails when we do. It's not
# clear why. # clear why.
'test_ssl.ThreadedTests.test_check_hostname_idn', 'test_ssl.ThreadedTests.test_check_hostname_idn',
# These appear to hang, haven't investigated why
'test_ssl.SimpleBackgroundTests.test_get_server_certificate',
# Probably the same as NetworkConnectionNoServer.test_create_connection_timeout
'test_socket.NetworkConnectionNoServer.test_create_connection',
] ]
if APPVEYOR: if APPVEYOR:
......
...@@ -203,7 +203,6 @@ class TestCase(TestCaseMetaClass("NewBase", ...@@ -203,7 +203,6 @@ class TestCase(TestCaseMetaClass("NewBase",
self.close_on_teardown = [] self.close_on_teardown = []
for x in to_close: for x in to_close:
print("Closing", x)
close = getattr(x, 'close', x) close = getattr(x, 'close', x)
try: try:
close() close()
......
from __future__ import print_function, division from __future__ import print_function, division
from contextlib import contextmanager
import unittest import unittest
import errno import errno
import os import os
...@@ -47,8 +48,8 @@ class Settings(object): ...@@ -47,8 +48,8 @@ class Settings(object):
@staticmethod @staticmethod
def assertAcceptedConnectionError(inst): def assertAcceptedConnectionError(inst):
conn = inst.makefile() with inst.makefile() as conn:
result = conn.read() result = conn.read()
inst.assertFalse(result) inst.assertFalse(result)
assert500 = assertAcceptedConnectionError assert500 = assertAcceptedConnectionError
...@@ -107,6 +108,7 @@ class TestCase(greentest.TestCase): ...@@ -107,6 +108,7 @@ class TestCase(greentest.TestCase):
return server_host, self.server.server_port, family return server_host, self.server.server_port, family
@contextmanager
def makefile(self, timeout=_DEFAULT_SOCKET_TIMEOUT, bufsize=1): def makefile(self, timeout=_DEFAULT_SOCKET_TIMEOUT, bufsize=1):
server_host, server_port, family = self.get_server_host_port_family() server_host, server_port, family = self.get_server_host_port_family()
bufarg = 'buffering' if PY3 else 'bufsize' bufarg = 'buffering' if PY3 else 'bufsize'
...@@ -116,41 +118,35 @@ class TestCase(greentest.TestCase): ...@@ -116,41 +118,35 @@ class TestCase(greentest.TestCase):
# makefile() opened in r, and r+ is not allowed # makefile() opened in r, and r+ is not allowed
makefile_kwargs['mode'] = 'rwb' makefile_kwargs['mode'] = 'rwb'
sock = socket.socket(family=family) with socket.socket(family=family) as sock:
self._close_on_teardown(sock) rconn = None
rconn = None
try:
#print("Connecting to", self.server, self.server.started, server_host, server_port)
sock.connect((server_host, server_port)) sock.connect((server_host, server_port))
sock.settimeout(timeout)
rconn = sock.makefile(**makefile_kwargs) with sock.makefile(**makefile_kwargs) as rconn:
self._close_on_teardown(rconn) # We want the socket to be accessible from the fileobject
if PY3: # XXX: Why do we do this? # we return. On Python 2, natively this is available as
self._close_on_teardown(rconn._sock) # _sock, but Python 3 doesn't have that.
rconn._sock = sock # We emulate it by assigning to a new attribute; note that this
rconn._sock.settimeout(timeout) # (probably) introduces a cycle on Python 3, so we are careful
except Exception: # to clear it.
#print("Failed to connect to", self.server) rconn.gevent_sock = sock
# avoid ResourceWarning under Py3/PyPy try:
sock.close() yield rconn
if rconn is not None: finally:
rconn.close() del rconn.gevent_sock
del rconn
del sock
raise
sock.close()
return rconn
def send_request(self, url='/', timeout=_DEFAULT_SOCKET_TIMEOUT, bufsize=1): def send_request(self, url='/', timeout=_DEFAULT_SOCKET_TIMEOUT, bufsize=1):
conn = self.makefile(timeout=timeout, bufsize=bufsize) with self.makefile(timeout=timeout, bufsize=bufsize) as conn:
conn.write(('GET %s HTTP/1.0\r\n\r\n' % url).encode('latin-1')) self.send_request_to_fd(conn, url)
conn.flush()
return conn 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): def assertConnectionRefused(self):
with self.assertRaises(socket.error) as exc: with self.assertRaises(socket.error) as exc:
conn = self.makefile() with self.makefile() as conn:
conn.close() conn.close()
ex = exc.exception ex = exc.exception
self.assertIn(ex.args[0], (errno.ECONNREFUSED, errno.EADDRNOTAVAIL), ex) self.assertIn(ex.args[0], (errno.ECONNREFUSED, errno.EADDRNOTAVAIL), ex)
...@@ -168,31 +164,28 @@ class TestCase(greentest.TestCase): ...@@ -168,31 +164,28 @@ class TestCase(greentest.TestCase):
self.Settings.assertPoolFull(self) self.Settings.assertPoolFull(self)
def assertNotAccepted(self): def assertNotAccepted(self):
conn = self.makefile() with self.makefile() as conn:
conn.write(b'GET / HTTP/1.0\r\n\r\n') conn.write(b'GET / HTTP/1.0\r\n\r\n')
conn.flush() conn.flush()
result = b'' result = b''
try: try:
while True: while True:
data = conn._sock.recv(1) data = conn.gevent_sock.recv(1)
if not data: if not data:
break break
result += data result += data
except socket.timeout: except socket.timeout:
self.assertFalse(result) self.assertFalse(result)
return return
finally:
conn.close()
self.assertTrue(result.startswith(b'HTTP/1.0 500 Internal Server Error'), repr(result)) self.assertTrue(result.startswith(b'HTTP/1.0 500 Internal Server Error'), repr(result))
def assertRequestSucceeded(self, timeout=_DEFAULT_SOCKET_TIMEOUT): def assertRequestSucceeded(self, timeout=_DEFAULT_SOCKET_TIMEOUT):
conn = self.makefile(timeout=timeout) with self.makefile(timeout=timeout) as conn:
try:
conn.write(b'GET /ping HTTP/1.0\r\n\r\n') conn.write(b'GET /ping HTTP/1.0\r\n\r\n')
result = conn.read() result = conn.read()
finally:
conn.close()
self.assertTrue(result.endswith(b'\r\n\r\nPONG'), repr(result)) self.assertTrue(result.endswith(b'\r\n\r\nPONG'), repr(result))
def start_server(self): def start_server(self):
...@@ -330,22 +323,21 @@ class TestDefaultSpawn(TestCase): ...@@ -330,22 +323,21 @@ class TestDefaultSpawn(TestCase):
def test_server_closes_client_sockets(self): def test_server_closes_client_sockets(self):
self.server = self.ServerClass((greentest.DEFAULT_BIND_ADDR, 0), lambda *args: []) self.server = self.ServerClass((greentest.DEFAULT_BIND_ADDR, 0), lambda *args: [])
self.server.start() self.server.start()
conn = self.send_request() with self.makefile() as conn:
# use assert500 below? self.send_request_to_fd(conn)
with gevent.Timeout._start_new_or_dummy(1): # use assert500 below?
try: with gevent.Timeout._start_new_or_dummy(1):
result = conn.read() try:
if result: result = conn.read()
assert result.startswith('HTTP/1.0 500 Internal Server Error'), repr(result) if result:
except socket.error as ex: assert result.startswith('HTTP/1.0 500 Internal Server Error'), repr(result)
if ex.args[0] == 10053: except socket.error as ex:
pass # "established connection was aborted by the software in your host machine" if ex.args[0] == 10053:
elif ex.args[0] == errno.ECONNRESET: pass # "established connection was aborted by the software in your host machine"
pass elif ex.args[0] == errno.ECONNRESET:
else: pass
raise else:
finally: raise
conn.close()
self.stop_server() self.stop_server()
...@@ -403,21 +395,20 @@ class TestPoolSpawn(TestDefaultSpawn): ...@@ -403,21 +395,20 @@ class TestPoolSpawn(TestDefaultSpawn):
"requests in the pool be full.") "requests in the pool be full.")
def test_pool_full(self): def test_pool_full(self):
self.init_server() self.init_server()
short_request = self.send_request('/short') with self.makefile() as long_request:
long_request = self.send_request('/long') with self.makefile() as short_request:
# keep long_request in scope, otherwise the connection will be closed self.send_request_to_fd(short_request, '/short')
gevent.get_hub().loop.update_now() self.send_request_to_fd(long_request, '/long')
gevent.sleep(_DEFAULT_SOCKET_TIMEOUT / 10.0)
self.assertPoolFull() # keep long_request in scope, otherwise the connection will be closed
self.assertPoolFull() gevent.get_hub().loop.update_now()
# XXX Not entirely clear why this fails (timeout) on appveyor; gevent.sleep(_DEFAULT_SOCKET_TIMEOUT / 10.0)
# underlying socket timeout causing the long_request to close? self.assertPoolFull()
self.assertPoolFull() self.assertPoolFull()
short_request._sock.close() # XXX Not entirely clear why this fails (timeout) on appveyor;
if PY3: # underlying socket timeout causing the long_request to close?
# We use two makefiles to simulate reading/writing self.assertPoolFull()
# under py3
short_request.close()
# gevent.http and gevent.wsgi cannot detect socket close, so sleep a little # gevent.http and gevent.wsgi cannot detect socket close, so sleep a little
# to let /short request finish # to let /short request finish
gevent.sleep(_DEFAULT_SOCKET_TIMEOUT) gevent.sleep(_DEFAULT_SOCKET_TIMEOUT)
...@@ -429,8 +420,6 @@ class TestPoolSpawn(TestDefaultSpawn): ...@@ -429,8 +420,6 @@ class TestPoolSpawn(TestDefaultSpawn):
except socket.timeout: except socket.timeout:
greentest.reraiseFlakyTestTimeout() greentest.reraiseFlakyTestTimeout()
del long_request
test_pool_full.error_fatal = False test_pool_full.error_fatal = False
......
...@@ -4,7 +4,7 @@ import gevent.testing as greentest ...@@ -4,7 +4,7 @@ import gevent.testing as greentest
import gevent import gevent
from gevent import pywsgi from gevent import pywsgi
import test__server from gevent.tests import test__server
def application(environ, start_response): def application(environ, start_response):
...@@ -51,26 +51,20 @@ class Settings(test__server.Settings): ...@@ -51,26 +51,20 @@ class Settings(test__server.Settings):
@staticmethod @staticmethod
def assert500(inst): def assert500(inst):
conn = inst.makefile() with inst.makefile() as conn:
try:
conn.write(b'GET / HTTP/1.0\r\n\r\n') conn.write(b'GET / HTTP/1.0\r\n\r\n')
result = conn.read() result = conn.read()
inst.assertTrue(result.startswith(internal_error_start), inst.assertTrue(result.startswith(internal_error_start),
(result, internal_error_start)) (result, internal_error_start))
inst.assertTrue(result.endswith(internal_error_end), inst.assertTrue(result.endswith(internal_error_end),
(result, internal_error_end)) (result, internal_error_end))
finally:
conn.close()
@staticmethod @staticmethod
def assert503(inst): def assert503(inst):
conn = inst.makefile() with inst.makefile() as conn:
try:
conn.write(b'GET / HTTP/1.0\r\n\r\n') conn.write(b'GET / HTTP/1.0\r\n\r\n')
result = conn.read() result = conn.read()
inst.assertEqual(result, internal_error503) inst.assertEqual(result, internal_error503)
finally:
conn.close()
@staticmethod @staticmethod
def assertPoolFull(inst): def assertPoolFull(inst):
...@@ -79,12 +73,9 @@ class Settings(test__server.Settings): ...@@ -79,12 +73,9 @@ class Settings(test__server.Settings):
@staticmethod @staticmethod
def assertAcceptedConnectionError(inst): def assertAcceptedConnectionError(inst):
conn = inst.makefile() with inst.makefile() as conn:
try:
result = conn.read() result = conn.read()
inst.assertFalse(result) inst.assertFalse(result)
finally:
conn.close()
@staticmethod @staticmethod
def fill_default_server_args(inst, kwargs): def fill_default_server_args(inst, kwargs):
......
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