summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Rivera <rivera@joel.mx>2016-03-03 11:08:38 -0600
committerJoel Rivera <rivera@joel.mx>2016-03-03 11:08:38 -0600
commit94d6a3e8fe973169e07d02374de4c997edeb4929 (patch)
tree12d8df440c61073f3bf92a58cc378745ebe749e9
parent305e8cd9c2cac57a2c1b9556f1ecddcdd67004ce (diff)
downloadcherrypy-94d6a3e8fe973169e07d02374de4c997edeb4929.tar.gz
Add new argument to openURL for the testsuite to be able to
raise subclasses of socket.error (since python3.3 OSError) instead of retrying. This fixes the KeyboardError problem for 3.5 by specifying that subclasses of BadStatusLine can be raised. The longer explanation of the fix: From python3.5 a new exception is retuned when the connection ends abruptly: http.client.RemoteDisconnected RemoteDisconnected is a subclass of: (ConnectionResetError, http.client.BadStatusLine) and ConnectionResetError is an indirect subclass of: OSError From python 3.3 an up socket.error is an alias to OSError following PEP-3151, therefore http.client.RemoteDisconnected is considered a socket.error. raise_subcls specifies the classes that are not going to be considered as a socket.error for the retries. Given that RemoteDisconnected is part BadStatusLine we can use the same call for all py3 versions without sideffects. python < 3.5 will raise directly BadStatusLine which is not a subclass for socket.error/OSError. Fixes issue #1406.
-rw-r--r--cherrypy/test/helper.py12
-rw-r--r--cherrypy/test/test_states.py20
-rw-r--r--cherrypy/test/webtest.py33
3 files changed, 52 insertions, 13 deletions
diff --git a/cherrypy/test/helper.py b/cherrypy/test/helper.py
index fd8bb914..adc225df 100644
--- a/cherrypy/test/helper.py
+++ b/cherrypy/test/helper.py
@@ -341,12 +341,18 @@ class CPWebCase(webtest.WebCase):
sys.exit()
def getPage(self, url, headers=None, method="GET", body=None,
- protocol=None):
- """Open the url. Return status, headers, body."""
+ protocol=None, raise_subcls=None):
+ """Open the url. Return status, headers, body.
+
+ `raise_subcls` must be a tuple with the exceptions classes
+ or a single exception class that are not going to be considered
+ a socket.error regardless that they were are subclass of a
+ socket.error and therefore not considered for a connection retry.
+ """
if self.script_name:
url = httputil.urljoin(self.script_name, url)
return webtest.WebCase.getPage(self, url, headers, method, body,
- protocol)
+ protocol, raise_subcls)
def skip(self, msg='skipped '):
raise nose.SkipTest(msg)
diff --git a/cherrypy/test/test_states.py b/cherrypy/test/test_states.py
index d86e5cf5..dc6891bc 100644
--- a/cherrypy/test/test_states.py
+++ b/cherrypy/test/test_states.py
@@ -204,9 +204,25 @@ class ServerStateTests(helper.CPWebCase):
# thread will just die without writing a response.
engine.start()
cherrypy.server.start()
-
+ # From python3.5 a new exception is retuned when the connection
+ # ends abruptly:
+ # http.client.RemoteDisconnected
+ # RemoteDisconnected is a subclass of:
+ # (ConnectionResetError, http.client.BadStatusLine)
+ # and ConnectionResetError is an indirect subclass of:
+ # OSError
+ # From python 3.3 an up socket.error is an alias to OSError
+ # following PEP-3151, therefore http.client.RemoteDisconnected
+ # is considered a socket.error.
+ #
+ # raise_subcls specifies the classes that are not going
+ # to be considered as a socket.error for the retries.
+ # Given that RemoteDisconnected is part BadStatusLine
+ # we can use the same call for all py3 versions without
+ # sideffects. python < 3.5 will raise directly BadStatusLine
+ # which is not a subclass for socket.error/OSError.
try:
- self.getPage("/ctrlc")
+ self.getPage("/ctrlc", raise_subcls=BadStatusLine)
except BadStatusLine:
pass
else:
diff --git a/cherrypy/test/webtest.py b/cherrypy/test/webtest.py
index fc3cab49..c9aeb71d 100644
--- a/cherrypy/test/webtest.py
+++ b/cherrypy/test/webtest.py
@@ -238,8 +238,13 @@ class WebCase(TestCase):
return interface(self.HOST)
def getPage(self, url, headers=None, method="GET", body=None,
- protocol=None):
+ protocol=None, raise_subcls=None):
"""Open the url with debugging support. Return status, headers, body.
+
+ `raise_subcls` must be a tuple with the exceptions classes
+ or a single exception class that are not going to be considered
+ a socket.error regardless that they were are subclass of a
+ socket.error and therefore not considered for a connection retry.
"""
ServerError.on = False
@@ -252,7 +257,8 @@ class WebCase(TestCase):
self.time = None
start = time.time()
result = openURL(url, headers, method, body, self.HOST, self.PORT,
- self.HTTP_CONN, protocol or self.PROTOCOL)
+ self.HTTP_CONN, protocol or self.PROTOCOL,
+ raise_subcls)
self.time = time.time() - start
self.status, self.headers, self.body = result
@@ -492,9 +498,15 @@ def shb(response):
def openURL(url, headers=None, method="GET", body=None,
host="127.0.0.1", port=8000, http_conn=HTTPConnection,
- protocol="HTTP/1.1"):
- """Open the given HTTP resource and return status, headers, and body."""
+ protocol="HTTP/1.1", raise_subcls=None):
+ """
+ Open the given HTTP resource and return status, headers, and body.
+ `raise_subcls` must be a tuple with the exceptions classes
+ or a single exception class that are not going to be considered
+ a socket.error regardless that they were are subclass of a
+ socket.error and therefore not considered for a connection retry.
+ """
headers = cleanHeaders(headers, method, body, host, port)
# Trying 10 times is simply in case of socket errors.
@@ -512,7 +524,6 @@ def openURL(url, headers=None, method="GET", body=None,
if py3k and isinstance(url, bytes):
url = url.decode()
-
conn.putrequest(method.upper(), url, skip_host=True,
skip_accept_encoding=True)
@@ -533,10 +544,16 @@ def openURL(url, headers=None, method="GET", body=None,
conn.close()
return s, h, b
- except socket.error:
- time.sleep(0.5)
- if trial == 9:
+ except socket.error as e:
+ if (raise_subcls is not None and
+ issubclass(e.__class__, raise_subcls)):
raise
+ else:
+ time.sleep(0.5)
+ if trial == 9:
+ raise
+
+
# Add any exceptions which your web framework handles