From 63e869f4e4ba3fdfd6e6bc3715349519215c73f7 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sat, 20 May 2017 13:44:08 -0400 Subject: [PATCH 01/14] options: Report redefinition errors when underscores are used Fixes #2020 --- tornado/options.py | 6 +++--- tornado/test/options_test.py | 20 +++++++++++++++++++- tornado/test/util.py | 13 +++++++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/tornado/options.py b/tornado/options.py index 0a72cc65..707fbd35 100644 --- a/tornado/options.py +++ b/tornado/options.py @@ -223,9 +223,10 @@ class OptionParser(object): override options set earlier on the command line, but can be overridden by later flags. """ - if name in self._options: + normalized = self._normalize_name(name) + if normalized in self._options: raise Error("Option %r already defined in %s" % - (name, self._options[name].file_name)) + (normalized, self._options[normalized].file_name)) frame = sys._getframe(0) options_file = frame.f_code.co_filename @@ -247,7 +248,6 @@ class OptionParser(object): group_name = group else: group_name = file_name - normalized = self._normalize_name(name) option = _Option(name, file_name=file_name, default=default, type=type, help=help, metavar=metavar, multiple=multiple, diff --git a/tornado/test/options_test.py b/tornado/test/options_test.py index bafeea6f..1a0ac8fb 100644 --- a/tornado/test/options_test.py +++ b/tornado/test/options_test.py @@ -7,7 +7,7 @@ import sys from tornado.options import OptionParser, Error from tornado.util import basestring_type, PY3 -from tornado.test.util import unittest +from tornado.test.util import unittest, subTest if PY3: from io import StringIO @@ -232,6 +232,24 @@ class OptionsTest(unittest.TestCase): self.assertRegexpMatches(str(cm.exception), 'Option.*foo.*already defined') + def test_error_redefine_underscore(self): + # Ensure that the dash/underscore normalization doesn't + # interfere with the redefinition error. + tests = [ + ('foo-bar', 'foo-bar'), + ('foo_bar', 'foo_bar'), + ('foo-bar', 'foo_bar'), + ('foo_bar', 'foo-bar'), + ] + for a, b in tests: + with subTest(self, a=a, b=b): + options = OptionParser() + options.define(a) + with self.assertRaises(Error) as cm: + options.define(b) + self.assertRegexpMatches(str(cm.exception), + 'Option.*foo.bar.*already defined') + def test_dash_underscore_cli(self): # Dashes and underscores should be interchangeable. for defined_name in ['foo-bar', 'foo_bar']: diff --git a/tornado/test/util.py b/tornado/test/util.py index 6c032da6..5f534e84 100644 --- a/tornado/test/util.py +++ b/tornado/test/util.py @@ -1,5 +1,6 @@ from __future__ import absolute_import, division, print_function +import contextlib import os import platform import socket @@ -94,3 +95,15 @@ def is_coverage_running(): except AttributeError: return False return mod.startswith('coverage') + + +def subTest(test, *args, **kwargs): + """Compatibility shim for unittest.TestCase.subTest. + + Usage: ``with tornado.test.util.subTest(self, x=x):`` + """ + try: + subTest = test.subTest # py34+ + except AttributeError: + subTest = contextlib.contextmanager(lambda *a, **kw: (yield)) + return subTest(*args, **kwargs) From d67d4916a362a57fd05f8f73851efe6fe1b407a1 Mon Sep 17 00:00:00 2001 From: Sebastien Boving Date: Tue, 27 Jun 2017 09:35:55 -0700 Subject: [PATCH 02/14] Do not send/expect Content-Length on 1xx --- tornado/http1connection.py | 7 ++++--- tornado/web.py | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tornado/http1connection.py b/tornado/http1connection.py index 53744ece..20d98e43 100644 --- a/tornado/http1connection.py +++ b/tornado/http1connection.py @@ -349,10 +349,11 @@ class HTTP1Connection(httputil.HTTPConnection): # self._request_start_line.version or # start_line.version? self._request_start_line.version == 'HTTP/1.1' and - # 304 responses have no body (not even a zero-length body), and so - # should not have either Content-Length or Transfer-Encoding. - # headers. + # 1xx, 204 and 304 responses have no body (not even a zero-length + # body), and so should not have either Content-Length or + # Transfer-Encoding headers. start_line.code not in (204, 304) and + (start_line.code < 100 or start_line.code >= 200) and # No need to chunk the output if a Content-Length is specified. 'Content-Length' not in headers and # Applications are discouraged from touching Transfer-Encoding, diff --git a/tornado/web.py b/tornado/web.py index d79889fa..338fe0f0 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -977,7 +977,8 @@ class RequestHandler(object): if self._status_code in (204, 304): assert not self._write_buffer, "Cannot send body with %s" % self._status_code self._clear_headers_for_304() - elif "Content-Length" not in self._headers: + elif ("Content-Length" not in self._headers and + (self._status_code < 100 or self._status_code >= 200)): content_length = sum(len(part) for part in self._write_buffer) self.set_header("Content-Length", content_length) From 7a23296100b7a796ae42df9b0c8e250b9a7a5cf1 Mon Sep 17 00:00:00 2001 From: Sebastien Boving Date: Wed, 5 Jul 2017 10:04:33 -0700 Subject: [PATCH 03/14] Handle 1xx's along 204/304's --- tornado/web.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tornado/web.py b/tornado/web.py index 338fe0f0..e8d102b5 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -974,11 +974,11 @@ class RequestHandler(object): if self.check_etag_header(): self._write_buffer = [] self.set_status(304) - if self._status_code in (204, 304): + if (self._status_code in (204, 304) or + (self._status_code >= 100 and self._status_code < 200)): assert not self._write_buffer, "Cannot send body with %s" % self._status_code self._clear_headers_for_304() - elif ("Content-Length" not in self._headers and - (self._status_code < 100 or self._status_code >= 200)): + elif "Content-Length" not in self._headers: content_length = sum(len(part) for part in self._write_buffer) self.set_header("Content-Length", content_length) From 8851634dca72d76df847947a40603681b27de44e Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 11 Jul 2017 12:07:34 +0200 Subject: [PATCH 04/14] cast curl debug messages to str since they come in as bytes on Python 3 --- tornado/curl_httpclient.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tornado/curl_httpclient.py b/tornado/curl_httpclient.py index eef4a17a..8558d65c 100644 --- a/tornado/curl_httpclient.py +++ b/tornado/curl_httpclient.py @@ -493,6 +493,7 @@ class CurlAsyncHTTPClient(AsyncHTTPClient): def _curl_debug(self, debug_type, debug_msg): debug_types = ('I', '<', '>', '<', '>') + debug_msg = native_str(debug_msg) if debug_type == 0: curl_log.debug('%s', debug_msg.strip()) elif debug_type in (1, 2): From b7f9f0b9116ddb09915e7e783fb9cbc82546b301 Mon Sep 17 00:00:00 2001 From: Jungkook Park Date: Fri, 22 Sep 2017 02:14:51 +0900 Subject: [PATCH 05/14] fix websocket handshake failure causes an exception --- tornado/test/websocket_test.py | 7 +++++++ tornado/websocket.py | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/tornado/test/websocket_test.py b/tornado/test/websocket_test.py index d47a74e6..95a5ecd4 100644 --- a/tornado/test/websocket_test.py +++ b/tornado/test/websocket_test.py @@ -189,6 +189,13 @@ class WebSocketTest(WebSocketBaseTestCase): response = self.fetch('/echo') self.assertEqual(response.code, 400) + def test_missing_websocket_key(self): + response = self.fetch('/echo', + headers={'Connection': 'Upgrade', + 'Upgrade': 'WebSocket', + 'Sec-WebSocket-Version': '13'}) + self.assertEqual(response.code, 400) + def test_bad_websocket_version(self): response = self.fetch('/echo', headers={'Connection': 'Upgrade', diff --git a/tornado/websocket.py b/tornado/websocket.py index 69437ee4..0e9d339f 100644 --- a/tornado/websocket.py +++ b/tornado/websocket.py @@ -616,6 +616,14 @@ class WebSocketProtocol13(WebSocketProtocol): def accept_connection(self): try: self._handle_websocket_headers() + except ValueError: + self.handler.set_status(400) + log_msg = "Missing/Invalid WebSocket headers" + self.handler.finish(log_msg) + gen_log.debug(log_msg) + return + + try: self._accept_connection() except ValueError: gen_log.debug("Malformed WebSocket request received", From fdc577f15f329303d2d5b3fdb6eb811eb6f0caf2 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Sat, 21 Oct 2017 20:47:29 +0200 Subject: [PATCH 06/14] Release memoryviews explicitly See https://github.com/tornadoweb/tornado/pull/2008 for reasons why this might be necessary. --- tornado/iostream.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/tornado/iostream.py b/tornado/iostream.py index a1619c49..784ef5e1 100644 --- a/tornado/iostream.py +++ b/tornado/iostream.py @@ -83,6 +83,15 @@ if hasattr(errno, "WSAEINPROGRESS"): _ERRNO_INPROGRESS += (errno.WSAEINPROGRESS,) # type: ignore _WINDOWS = sys.platform.startswith('win') +_PY3 = sys.version_info[0] >= 3 + +if _PY3: + # We'd like to release memoryviews explicitly, see + # https://github.com/tornadoweb/tornado/pull/2008 + _release_memoryview = memoryview.release +else: + def _release_memoryview(m): + pass class StreamClosedError(IOError): @@ -858,8 +867,14 @@ class BaseIOStream(object): size = 128 * 1024 else: size = self._write_buffer_size - num_bytes = self.write_to_fd( - memoryview(self._write_buffer)[start:start + size]) + mem = memoryview(self._write_buffer) + mem2 = mem[start:start + size] + try: + num_bytes = self.write_to_fd(mem2) + finally: + _release_memoryview(mem2) + _release_memoryview(mem) + del mem, mem2 if num_bytes == 0: self._got_empty_write(size) break @@ -907,9 +922,14 @@ class BaseIOStream(object): return b"" assert loc <= self._read_buffer_size # Slice the bytearray buffer into bytes, without intermediate copying - b = (memoryview(self._read_buffer) - [self._read_buffer_pos:self._read_buffer_pos + loc] - ).tobytes() + mem = memoryview(self._read_buffer) + mem2 = mem[self._read_buffer_pos:self._read_buffer_pos + loc] + try: + b = mem2.tobytes() + finally: + _release_memoryview(mem2) + _release_memoryview(mem) + del mem, mem2 self._read_buffer_pos += loc self._read_buffer_size -= loc # Amortized O(1) shrink From a91106195905a9cfb1c40cb2cdcc1c94bf46cde9 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Sat, 21 Oct 2017 22:05:19 +0200 Subject: [PATCH 07/14] Don't keep any reference to memoryviews See https://github.com/tornadoweb/tornado/pull/2008 for reasons why this might be necessary. --- tornado/iostream.py | 48 ++++++++++++++++++++------------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/tornado/iostream.py b/tornado/iostream.py index 784ef5e1..639ed508 100644 --- a/tornado/iostream.py +++ b/tornado/iostream.py @@ -83,15 +83,6 @@ if hasattr(errno, "WSAEINPROGRESS"): _ERRNO_INPROGRESS += (errno.WSAEINPROGRESS,) # type: ignore _WINDOWS = sys.platform.startswith('win') -_PY3 = sys.version_info[0] >= 3 - -if _PY3: - # We'd like to release memoryviews explicitly, see - # https://github.com/tornadoweb/tornado/pull/2008 - _release_memoryview = memoryview.release -else: - def _release_memoryview(m): - pass class StreamClosedError(IOError): @@ -867,14 +858,8 @@ class BaseIOStream(object): size = 128 * 1024 else: size = self._write_buffer_size - mem = memoryview(self._write_buffer) - mem2 = mem[start:start + size] - try: - num_bytes = self.write_to_fd(mem2) - finally: - _release_memoryview(mem2) - _release_memoryview(mem) - del mem, mem2 + num_bytes = self.write_to_fd( + memoryview(self._write_buffer)[start:start + size]) if num_bytes == 0: self._got_empty_write(size) break @@ -922,14 +907,9 @@ class BaseIOStream(object): return b"" assert loc <= self._read_buffer_size # Slice the bytearray buffer into bytes, without intermediate copying - mem = memoryview(self._read_buffer) - mem2 = mem[self._read_buffer_pos:self._read_buffer_pos + loc] - try: - b = mem2.tobytes() - finally: - _release_memoryview(mem2) - _release_memoryview(mem) - del mem, mem2 + b = (memoryview(self._read_buffer) + [self._read_buffer_pos:self._read_buffer_pos + loc] + ).tobytes() self._read_buffer_pos += loc self._read_buffer_size -= loc # Amortized O(1) shrink @@ -1081,7 +1061,12 @@ class IOStream(BaseIOStream): return chunk def write_to_fd(self, data): - return self.socket.send(data) + try: + return self.socket.send(data) + finally: + # Avoid keeping to data, which can be a memoryview. + # See https://github.com/tornadoweb/tornado/pull/2008 + del data def connect(self, address, callback=None, server_hostname=None): """Connects the socket to a remote address without blocking. @@ -1491,6 +1476,10 @@ class SSLIOStream(IOStream): # simply return 0 bytes written. return 0 raise + finally: + # Avoid keeping to data, which can be a memoryview. + # See https://github.com/tornadoweb/tornado/pull/2008 + del data def read_from_fd(self): if self._ssl_accepting: @@ -1548,7 +1537,12 @@ class PipeIOStream(BaseIOStream): os.close(self.fd) def write_to_fd(self, data): - return os.write(self.fd, data) + try: + return os.write(self.fd, data) + finally: + # Avoid keeping to data, which can be a memoryview. + # See https://github.com/tornadoweb/tornado/pull/2008 + del data def read_from_fd(self): try: From 17210e382571b87eb1bd80972a79c9f2f4e92adc Mon Sep 17 00:00:00 2001 From: Pierce Lopez Date: Mon, 25 Dec 2017 22:11:26 -0500 Subject: [PATCH 08/14] http: read final crlf of chunked requests otherwise a subsequent request on the same connection will fail to be parsed thanks to @eeelin for the bug report --- tornado/http1connection.py | 3 +++ tornado/test/httpserver_test.py | 7 +++++-- tornado/test/web_test.py | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tornado/http1connection.py b/tornado/http1connection.py index 20d98e43..9b33c94e 100644 --- a/tornado/http1connection.py +++ b/tornado/http1connection.py @@ -593,6 +593,9 @@ class HTTP1Connection(httputil.HTTPConnection): chunk_len = yield self.stream.read_until(b"\r\n", max_bytes=64) chunk_len = int(chunk_len.strip(), 16) if chunk_len == 0: + crlf = yield self.stream.read_bytes(2) + if crlf != b'\r\n': + raise HTTPInputError("improperly terminated chunked request") return total_size += chunk_len if total_size > self._max_body_size: diff --git a/tornado/test/httpserver_test.py b/tornado/test/httpserver_test.py index 11cb7231..59eb6fd1 100644 --- a/tornado/test/httpserver_test.py +++ b/tornado/test/httpserver_test.py @@ -786,9 +786,12 @@ class KeepAliveTest(AsyncHTTPTestCase): def test_keepalive_chunked(self): self.http_version = b'HTTP/1.0' self.connect() - self.stream.write(b'POST / HTTP/1.0\r\nConnection: keep-alive\r\n' + self.stream.write(b'POST / HTTP/1.0\r\n' + b'Connection: keep-alive\r\n' b'Transfer-Encoding: chunked\r\n' - b'\r\n0\r\n') + b'\r\n' + b'0\r\n' + b'\r\n') self.read_response() self.assertEqual(self.headers['Connection'], 'Keep-Alive') self.stream.write(b'GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n') diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py index d79ea52c..d83446e4 100644 --- a/tornado/test/web_test.py +++ b/tornado/test/web_test.py @@ -2134,7 +2134,7 @@ class StreamingRequestBodyTest(WebTestCase): stream.write(b"4\r\nqwer\r\n") data = yield self.data self.assertEquals(data, b"qwer") - stream.write(b"0\r\n") + stream.write(b"0\r\n\r\n") yield self.finished data = yield gen.Task(stream.read_until_close) # This would ideally use an HTTP1Connection to read the response. From f1f7d2ef8a5428ad64877191a4efafa47b9fe752 Mon Sep 17 00:00:00 2001 From: Pierce Lopez Date: Sat, 30 Dec 2017 18:34:32 -0500 Subject: [PATCH 09/14] fix HTTPInputError reference for improperly terminated chunked request bug introduced in #2225 --- tornado/http1connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tornado/http1connection.py b/tornado/http1connection.py index 9b33c94e..32bed6c9 100644 --- a/tornado/http1connection.py +++ b/tornado/http1connection.py @@ -595,7 +595,7 @@ class HTTP1Connection(httputil.HTTPConnection): if chunk_len == 0: crlf = yield self.stream.read_bytes(2) if crlf != b'\r\n': - raise HTTPInputError("improperly terminated chunked request") + raise httputil.HTTPInputError("improperly terminated chunked request") return total_size += chunk_len if total_size > self._max_body_size: From 84bb2e285e15415bb86cbdf2326b19f0debb80fd Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sun, 5 Nov 2017 10:32:10 -0500 Subject: [PATCH 10/14] testing: Connect to 127.0.0.1 instead of localhost The servers used in tests are only listening for ipv4, not ipv6 (to avoid spurious firewall prompts on macos). In what is apparently a recent macos change, connecting to `localhost` when the ipv6 port is unbound now incurs a 200ms delay, slowing a full test run down by a factor of 20. --- tornado/test/simple_httpclient_test.py | 2 +- tornado/test/web_test.py | 2 +- tornado/testing.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tornado/test/simple_httpclient_test.py b/tornado/test/simple_httpclient_test.py index 02d57c5f..451942d2 100644 --- a/tornado/test/simple_httpclient_test.py +++ b/tornado/test/simple_httpclient_test.py @@ -327,7 +327,7 @@ class SimpleHTTPClientTestMixin(object): self.assertNotIn("Content-Length", response.headers) def test_host_header(self): - host_re = re.compile(b"^localhost:[0-9]+$") + host_re = re.compile(b"^127.0.0.1:[0-9]+$") response = self.fetch("/host_echo") self.assertTrue(host_re.match(response.body)) diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py index d83446e4..013c2ac2 100644 --- a/tornado/test/web_test.py +++ b/tornado/test/web_test.py @@ -356,7 +356,7 @@ class AuthRedirectTest(WebTestCase): response = self.wait() self.assertEqual(response.code, 302) self.assertTrue(re.match( - 'http://example.com/login\?next=http%3A%2F%2Flocalhost%3A[0-9]+%2Fabsolute', + 'http://example.com/login\?next=http%3A%2F%2F127.0.0.1%3A[0-9]+%2Fabsolute', response.headers['Location']), response.headers['Location']) diff --git a/tornado/testing.py b/tornado/testing.py index 74d04b60..82a3b937 100644 --- a/tornado/testing.py +++ b/tornado/testing.py @@ -423,7 +423,7 @@ class AsyncHTTPTestCase(AsyncTestCase): def get_url(self, path): """Returns an absolute url for the given path on the test server.""" - return '%s://localhost:%s%s' % (self.get_protocol(), + return '%s://127.0.0.1:%s%s' % (self.get_protocol(), self.get_http_port(), path) def tearDown(self): From b525e423c3d143f3e67dba26fea2f1103b9bb6d4 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sun, 5 Nov 2017 10:38:03 -0500 Subject: [PATCH 11/14] test: More robust ipv6 detection There are many ways ipv6 can be disabled or broken (and it just stopped working on travis-ci), so instead of checking flags like socket.has_ipv6, attempt to actually bind to an ipv6 address. Closes #2185 --- tornado/test/simple_httpclient_test.py | 13 +++---------- tornado/test/util.py | 23 ++++++++++++++++++++--- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/tornado/test/simple_httpclient_test.py b/tornado/test/simple_httpclient_test.py index 451942d2..0e75e530 100644 --- a/tornado/test/simple_httpclient_test.py +++ b/tornado/test/simple_httpclient_test.py @@ -272,16 +272,9 @@ class SimpleHTTPClientTestMixin(object): @skipIfNoIPv6 def test_ipv6(self): - try: - [sock] = bind_sockets(None, '::1', family=socket.AF_INET6) - port = sock.getsockname()[1] - self.http_server.add_socket(sock) - except socket.gaierror as e: - if e.args[0] == socket.EAI_ADDRFAMILY: - # python supports ipv6, but it's not configured on the network - # interface, so skip this test. - return - raise + [sock] = bind_sockets(None, '::1', family=socket.AF_INET6) + port = sock.getsockname()[1] + self.http_server.add_socket(sock) url = '%s://[::1]:%d/hello' % (self.get_protocol(), port) # ipv6 is currently enabled by default but can be disabled diff --git a/tornado/test/util.py b/tornado/test/util.py index 5f534e84..7958d92b 100644 --- a/tornado/test/util.py +++ b/tornado/test/util.py @@ -35,15 +35,32 @@ skipOnAppEngine = unittest.skipIf('APPENGINE_RUNTIME' in os.environ, skipIfNoNetwork = unittest.skipIf('NO_NETWORK' in os.environ, 'network access disabled') -skipIfNoIPv6 = unittest.skipIf(not socket.has_ipv6, 'ipv6 support not present') - - skipBefore33 = unittest.skipIf(sys.version_info < (3, 3), 'PEP 380 (yield from) not available') skipBefore35 = unittest.skipIf(sys.version_info < (3, 5), 'PEP 492 (async/await) not available') skipNotCPython = unittest.skipIf(platform.python_implementation() != 'CPython', 'Not CPython implementation') +def _detect_ipv6(): + if not socket.has_ipv6: + # socket.has_ipv6 check reports whether ipv6 was present at compile + # time. It's usually true even when ipv6 doesn't work for other reasons. + return False + sock = None + try: + sock = socket.socket(socket.AF_INET6) + sock.bind(('::1', 0)) + except socket.error: + return False + finally: + if sock is not None: + sock.close() + return True + + +skipIfNoIPv6 = unittest.skipIf(not _detect_ipv6(), 'ipv6 support not present') + + def refusing_port(): """Returns a local port number that will refuse all connections. From 716ab7d509a44bb00c5cbfec37ad3d16cdcd2618 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Fri, 5 Jan 2018 09:59:00 -0500 Subject: [PATCH 12/14] Update travis-ci python versions to match master. 2.7.8 is no longer available and "pypy3" is old and has other issues. --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4b2ac2bc..e3b33c04 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ # https://travis-ci.org/tornadoweb/tornado language: python python: - - 2.7.8 - 2.7 - pypy - 3.3 @@ -9,7 +8,7 @@ python: - 3.5 - 3.6 - nightly - - pypy3 + - pypy3.5-5.8.0 install: - if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then travis_retry pip install futures mock monotonic trollius; fi From 7be09af39047c8e528946e3dea10f1309be78953 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sun, 27 Aug 2017 22:38:14 -0400 Subject: [PATCH 13/14] Get tests passing on latest version of pypy3 --- tornado/test/iostream_test.py | 6 ++++-- tornado/test/util.py | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/tornado/test/iostream_test.py b/tornado/test/iostream_test.py index 91bc7bf6..56fffe60 100644 --- a/tornado/test/iostream_test.py +++ b/tornado/test/iostream_test.py @@ -9,7 +9,7 @@ from tornado.netutil import ssl_wrap_socket from tornado.stack_context import NullContext from tornado.tcpserver import TCPServer from tornado.testing import AsyncHTTPTestCase, AsyncHTTPSTestCase, AsyncTestCase, bind_unused_port, ExpectLog, gen_test -from tornado.test.util import unittest, skipIfNonUnix, refusing_port +from tornado.test.util import unittest, skipIfNonUnix, refusing_port, skipPypy3V58 from tornado.web import RequestHandler, Application import errno import logging @@ -539,6 +539,7 @@ class TestIOStreamMixin(object): client.close() @skipIfNonUnix + @skipPypy3V58 def test_inline_read_error(self): # An error on an inline read is raised without logging (on the # assumption that it will eventually be noticed or logged further @@ -557,6 +558,7 @@ class TestIOStreamMixin(object): server.close() client.close() + @skipPypy3V58 def test_async_read_error_logging(self): # Socket errors on asynchronous reads should be logged (but only # once). @@ -993,7 +995,7 @@ class TestIOStreamStartTLS(AsyncTestCase): server_future = self.server_start_tls(_server_ssl_options()) client_future = self.client_start_tls( ssl.create_default_context(), - server_hostname=b'127.0.0.1') + server_hostname='127.0.0.1') with ExpectLog(gen_log, "SSL Error"): with self.assertRaises(ssl.SSLError): # The client fails to connect with an SSL error. diff --git a/tornado/test/util.py b/tornado/test/util.py index 7958d92b..90a9c7b8 100644 --- a/tornado/test/util.py +++ b/tornado/test/util.py @@ -40,6 +40,14 @@ skipBefore35 = unittest.skipIf(sys.version_info < (3, 5), 'PEP 492 (async/await) skipNotCPython = unittest.skipIf(platform.python_implementation() != 'CPython', 'Not CPython implementation') +# Used for tests affected by +# https://bitbucket.org/pypy/pypy/issues/2616/incomplete-error-handling-in +# TODO: remove this after pypy3 5.8 is obsolete. +skipPypy3V58 = unittest.skipIf(platform.python_implementation() == 'PyPy' and + sys.version_info > (3,) and + sys.pypy_version_info < (5, 9), + 'pypy3 5.8 has buggy ssl module') + def _detect_ipv6(): if not socket.has_ipv6: From 09c63c55e05674a51f2cce86d5fda090843afb3a Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Thu, 4 Jan 2018 22:44:17 -0500 Subject: [PATCH 14/14] Release notes for 4.5.3 --- docs/releases.rst | 1 + docs/releases/v4.5.3.rst | 49 ++++++++++++++++++++++++++++++++++++++++ setup.py | 2 +- tornado/__init__.py | 4 ++-- 4 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 docs/releases/v4.5.3.rst diff --git a/docs/releases.rst b/docs/releases.rst index 3a9ef777..128c7603 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -4,6 +4,7 @@ Release notes .. toctree:: :maxdepth: 2 + releases/v4.5.3 releases/v4.5.2 releases/v4.5.1 releases/v4.5.0 diff --git a/docs/releases/v4.5.3.rst b/docs/releases/v4.5.3.rst new file mode 100644 index 00000000..b1102459 --- /dev/null +++ b/docs/releases/v4.5.3.rst @@ -0,0 +1,49 @@ +What's new in Tornado 4.5.2 +=========================== + +Aug 27, 2017 +------------ + +`tornado.curl_httpclient` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Improved debug logging on Python 3. + +`tornado.httpserver` +~~~~~~~~~~~~~~~~~~~~ + +- ``Content-Length`` and ``Transfer-Encoding`` headers are no longer + sent with 1xx or 204 responses (this was already true of 304 + responses). +- Reading chunked requests no longer leaves the connection in a broken + state. + +`tornado.iostream` +~~~~~~~~~~~~~~~~~~ + +- Writing a `memoryview` can no longer result in "BufferError: + Existing exports of data: object cannot be re-sized". + +`tornado.options` +~~~~~~~~~~~~~~~~~ + +- Duplicate option names are now detected properly whether they use + hyphens or underscores. + +`tornado.testing` +~~~~~~~~~~~~~~~~~ + +- `.AsyncHTTPTestCase.fetch` now uses ``127.0.0.1`` instead of + ``localhost``, improving compatibility with systems that have + partially-working ipv6 stacks. + +`tornado.web` +~~~~~~~~~~~~~ + +- It is no longer allowed to send a body with 1xx or 204 responses. + +`tornado.websocket` +~~~~~~~~~~~~~~~~~~~ + +- Requests with invalid websocket headers now get a response with + status code 400 instead of a closed connection. diff --git a/setup.py b/setup.py index 66d846be..a1feea67 100644 --- a/setup.py +++ b/setup.py @@ -103,7 +103,7 @@ http://api.mongodb.org/python/current/installation.html#osx kwargs = {} -version = "4.5.2" +version = "4.5.3" with open('README.rst') as f: kwargs['long_description'] = f.read() diff --git a/tornado/__init__.py b/tornado/__init__.py index 3eaa57b8..fa71bf61 100644 --- a/tornado/__init__.py +++ b/tornado/__init__.py @@ -25,5 +25,5 @@ from __future__ import absolute_import, division, print_function # is zero for an official release, positive for a development branch, # or negative for a release candidate or beta (after the base version # number has been incremented) -version = "4.5.2" -version_info = (4, 5, 2, 0) +version = "4.5.3" +version_info = (4, 5, 3, 0)