From f525768167acf198b120a59ce9ac2f286a1677cb Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Thu, 11 Jul 2024 15:21:09 -0400 Subject: [PATCH 1/5] test: Remove skipOnTravis We haven't used Travis for CI in a long time, and our current CI system is much less overloaded so these timing-sensitive tests are no longer flaky. --- tornado/test/gen_test.py | 3 +-- tornado/test/httpclient_test.py | 3 +-- tornado/test/httpserver_test.py | 3 +-- tornado/test/ioloop_test.py | 3 --- tornado/test/netutil_test.py | 2 -- tornado/test/simple_httpclient_test.py | 3 --- tornado/test/tcpclient_test.py | 2 -- tornado/test/util.py | 8 +------- 8 files changed, 4 insertions(+), 23 deletions(-) diff --git a/tornado/test/gen_test.py b/tornado/test/gen_test.py index 87f6ab7fa4..1a365dbd72 100644 --- a/tornado/test/gen_test.py +++ b/tornado/test/gen_test.py @@ -11,7 +11,7 @@ from tornado.concurrent import Future from tornado.log import app_log from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, ExpectLog, gen_test -from tornado.test.util import skipOnTravis, skipNotCPython +from tornado.test.util import skipNotCPython from tornado.web import Application, RequestHandler, HTTPError from tornado import gen @@ -139,7 +139,6 @@ def f(): self.io_loop.run_sync(f) - @skipOnTravis @gen_test def test_multi_performance(self): # Yielding a list used to have quadratic performance; make diff --git a/tornado/test/httpclient_test.py b/tornado/test/httpclient_test.py index 40a29d0b9e..f6e7dbbe7f 100644 --- a/tornado/test/httpclient_test.py +++ b/tornado/test/httpclient_test.py @@ -28,7 +28,7 @@ from tornado.log import gen_log, app_log from tornado import netutil from tornado.testing import AsyncHTTPTestCase, bind_unused_port, gen_test, ExpectLog -from tornado.test.util import skipOnTravis, ignore_deprecation +from tornado.test.util import ignore_deprecation from tornado.web import Application, RequestHandler, url from tornado.httputil import format_timestamp, HTTPHeaders @@ -191,7 +191,6 @@ def test_patch_receives_payload(self): self.assertEqual(response.code, 200) self.assertEqual(response.body, body) - @skipOnTravis def test_hello_world(self): response = self.fetch("/hello") self.assertEqual(response.code, 200) diff --git a/tornado/test/httpserver_test.py b/tornado/test/httpserver_test.py index 3a90440dfc..7b411ed6e4 100644 --- a/tornado/test/httpserver_test.py +++ b/tornado/test/httpserver_test.py @@ -27,7 +27,7 @@ ExpectLog, gen_test, ) -from tornado.test.util import skipOnTravis, abstract_base_test +from tornado.test.util import abstract_base_test from tornado.web import Application, RequestHandler, stream_request_body from contextlib import closing @@ -1267,7 +1267,6 @@ def test_large_headers(self): self.assertIn(e.response.code, (431, 599)) -@skipOnTravis class IdleTimeoutTest(AsyncHTTPTestCase): def get_app(self): return Application([("/", HelloWorldRequestHandler)]) diff --git a/tornado/test/ioloop_test.py b/tornado/test/ioloop_test.py index 2123939283..047b3614eb 100644 --- a/tornado/test/ioloop_test.py +++ b/tornado/test/ioloop_test.py @@ -28,7 +28,6 @@ from tornado.test.util import ( ignore_deprecation, skipIfNonUnix, - skipOnTravis, ) from tornado.concurrent import Future @@ -58,7 +57,6 @@ def add_callback(self, callback, *args, **kwargs): loop.start() self.assertLess(self.calls, 10) - @skipOnTravis def test_add_callback_wakeup(self): # Make sure that add_callback from inside a running IOLoop # wakes up the IOLoop immediately instead of waiting for a timeout. @@ -77,7 +75,6 @@ def schedule_callback(): self.assertAlmostEqual(time.time(), self.start_time, places=2) self.assertTrue(self.called) - @skipOnTravis def test_add_callback_wakeup_other_thread(self): def target(): # sleep a bit to let the ioloop go into its poll loop diff --git a/tornado/test/netutil_test.py b/tornado/test/netutil_test.py index ef82cba85f..2334727873 100644 --- a/tornado/test/netutil_test.py +++ b/tornado/test/netutil_test.py @@ -188,8 +188,6 @@ def test_is_valid_ip(self): class TestPortAllocation(unittest.TestCase): def test_same_port_allocation(self): - if "TRAVIS" in os.environ: - self.skipTest("dual-stack servers often have port conflicts on travis") sockets = bind_sockets(0, "localhost") try: port = sockets[0].getsockname()[1] diff --git a/tornado/test/simple_httpclient_test.py b/tornado/test/simple_httpclient_test.py index 07f04ae938..c1cff83c9a 100644 --- a/tornado/test/simple_httpclient_test.py +++ b/tornado/test/simple_httpclient_test.py @@ -41,7 +41,6 @@ ) from tornado.test.util import ( abstract_base_test, - skipOnTravis, skipIfNoIPv6, refusing_port, ) @@ -288,7 +287,6 @@ def test_see_other_redirect(self): # request is the original request, is a POST still self.assertEqual("POST", response.request.method) - @skipOnTravis @gen_test def test_connect_timeout(self): timeout = 0.1 @@ -317,7 +315,6 @@ async def resolve(self, *args, **kwargs): cleanup_event.set() yield gen.sleep(0.2) - @skipOnTravis def test_request_timeout(self): timeout = 0.1 if os.name == "nt": diff --git a/tornado/test/tcpclient_test.py b/tornado/test/tcpclient_test.py index 1636a649b8..73b383cbab 100644 --- a/tornado/test/tcpclient_test.py +++ b/tornado/test/tcpclient_test.py @@ -64,8 +64,6 @@ def setUp(self): self.client = TCPClient() def start_server(self, family): - if family == socket.AF_UNSPEC and "TRAVIS" in os.environ: - self.skipTest("dual-stack servers often have port conflicts on travis") self.server = TestTCPServer(family) return self.server.port diff --git a/tornado/test/util.py b/tornado/test/util.py index 40ff9d65e6..2e8bc3e5f1 100644 --- a/tornado/test/util.py +++ b/tornado/test/util.py @@ -16,12 +16,6 @@ os.name != "posix" or sys.platform == "cygwin", "non-unix platform" ) -# travis-ci.org runs our tests in an overworked virtual machine, which makes -# timing-related tests unreliable. -skipOnTravis = unittest.skipIf( - "TRAVIS" in os.environ, "timing tests unreliable on travis" -) - # Set the environment variable NO_NETWORK=1 to disable any tests that # depend on an external network. skipIfNoNetwork = unittest.skipIf("NO_NETWORK" in os.environ, "network access disabled") @@ -67,7 +61,7 @@ def refusing_port(): Return value is (cleanup_func, port); the cleanup function must be called to free the port to be reused. """ - # On travis-ci, port numbers are reassigned frequently. To avoid + # On travis-ci port numbers are reassigned frequently. To avoid # collisions with other tests, we use an open client-side socket's # ephemeral port number to ensure that nothing can listen on that # port. From 1808129122f81b0e78120c29233381be5c37b0b8 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Thu, 11 Jul 2024 15:26:14 -0400 Subject: [PATCH 2/5] test: Remove some obsolete checks for old python versions --- tornado/test/gen_test.py | 3 --- tornado/test/iostream_test.py | 3 --- tornado/test/netutil_test.py | 1 - tornado/test/tcpclient_test.py | 1 - tornado/test/util.py | 10 ---------- 5 files changed, 18 deletions(-) diff --git a/tornado/test/gen_test.py b/tornado/test/gen_test.py index 1a365dbd72..ec3a6c8e69 100644 --- a/tornado/test/gen_test.py +++ b/tornado/test/gen_test.py @@ -572,9 +572,6 @@ def inner(iteration): self.finished = True @skipNotCPython - @unittest.skipIf( - (3,) < sys.version_info < (3, 6), "asyncio.Future has reference cycles" - ) def test_coroutine_refcounting(self): # On CPython, tasks and their arguments should be released immediately # without waiting for garbage collection. diff --git a/tornado/test/iostream_test.py b/tornado/test/iostream_test.py index 200b08303f..09191ecfef 100644 --- a/tornado/test/iostream_test.py +++ b/tornado/test/iostream_test.py @@ -27,7 +27,6 @@ from tornado.test.util import ( skipIfNonUnix, refusing_port, - skipPypy3V58, ignore_deprecation, abstract_base_test, ) @@ -828,7 +827,6 @@ def test_read_until_close_with_error(self): client.close() @skipIfNonUnix - @skipPypy3V58 @gen_test def test_inline_read_error(self): # An error on an inline read is raised without logging (on the @@ -862,7 +860,6 @@ def test_inline_read_error(self): server.close() client.close() - @skipPypy3V58 @gen_test def test_async_read_error_logging(self): # Socket errors on asynchronous reads should be logged (but only diff --git a/tornado/test/netutil_test.py b/tornado/test/netutil_test.py index 2334727873..117a9d7f99 100644 --- a/tornado/test/netutil_test.py +++ b/tornado/test/netutil_test.py @@ -1,5 +1,4 @@ import errno -import os import signal import socket from subprocess import Popen diff --git a/tornado/test/tcpclient_test.py b/tornado/test/tcpclient_test.py index 73b383cbab..5638559cec 100644 --- a/tornado/test/tcpclient_test.py +++ b/tornado/test/tcpclient_test.py @@ -14,7 +14,6 @@ # under the License. from contextlib import closing import getpass -import os import socket import unittest diff --git a/tornado/test/util.py b/tornado/test/util.py index 2e8bc3e5f1..ed5fd5d3f6 100644 --- a/tornado/test/util.py +++ b/tornado/test/util.py @@ -24,16 +24,6 @@ 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), # type: ignore - "pypy3 5.8 has buggy ssl module", -) - def _detect_ipv6(): if not socket.has_ipv6: From 00110e1602c516d2dcb371c5b9aa92b522ee7c48 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Thu, 11 Jul 2024 15:43:32 -0400 Subject: [PATCH 3/5] *: Remove references to old python versions Mainly in docs that say things like "you can only use this if you're using python 3.2". --- tornado/gen.py | 15 ++++++--------- tornado/iostream.py | 9 +-------- tornado/netutil.py | 18 ++++++------------ tornado/options.py | 17 ++++++----------- tornado/tcpserver.py | 2 +- tornado/test/__main__.py | 2 -- tornado/test/gen_test.py | 1 - tornado/test/iostream_test.py | 2 -- tornado/test/web_test.py | 10 +++------- 9 files changed, 23 insertions(+), 53 deletions(-) diff --git a/tornado/gen.py b/tornado/gen.py index 51fa4c19ae..d12f32fbd8 100644 --- a/tornado/gen.py +++ b/tornado/gen.py @@ -275,6 +275,10 @@ def is_coroutine_function(func: Any) -> bool: class Return(Exception): """Special exception to return a value from a `coroutine`. + This exception exists for compatibility with older versions of + Python (before 3.3). In newer code use the ``return`` statement + instead. + If this exception is raised, its value argument is used as the result of the coroutine:: @@ -283,14 +287,7 @@ def fetch_json(url): response = yield AsyncHTTPClient().fetch(url) raise gen.Return(json_decode(response.body)) - In Python 3.3, this exception is no longer necessary: the ``return`` - statement can be used directly to return a value (previously - ``yield`` and ``return`` with a value could not be combined in the - same function). - - By analogy with the return statement, the value argument is optional, - but it is never necessary to ``raise gen.Return()``. The ``return`` - statement can be used with no arguments instead. + By analogy with the return statement, the value argument is optional. """ def __init__(self, value: Any = None) -> None: @@ -337,7 +334,7 @@ class WaitIterator: arguments were used in the construction of the `WaitIterator`, ``current_index`` will use the corresponding keyword). - On Python 3.5, `WaitIterator` implements the async iterator + `WaitIterator` implements the async iterator protocol, so it can be used with the ``async for`` statement (note that in this version the entire iteration is aborted if any value raises an exception, while the previous example can continue past diff --git a/tornado/iostream.py b/tornado/iostream.py index 40b4f8d4e7..dd2111e3b8 100644 --- a/tornado/iostream.py +++ b/tornado/iostream.py @@ -1147,8 +1147,7 @@ class is recommended instead of calling this method directly. In SSL mode, the ``server_hostname`` parameter will be used for certificate validation (unless disabled in the - ``ssl_options``) and SNI (if supported; requires Python - 2.7.9+). + ``ssl_options``) and SNI. Note that it is safe to call `IOStream.write ` while the connection is pending, in @@ -1381,12 +1380,6 @@ def _do_ssl_handshake(self) -> None: ) return self.close(exc_info=err) raise - except ssl.CertificateError as err: - # CertificateError can happen during handshake (hostname - # verification) and should be passed to user. Starting - # in Python 3.7, this error is a subclass of SSLError - # and will be handled by the previous block instead. - return self.close(exc_info=err) except OSError as err: # Some port scans (e.g. nmap in -sT mode) have been known # to cause do_handshake to raise EBADF and ENOTCONN, so make diff --git a/tornado/netutil.py b/tornado/netutil.py index b6772086af..3ec76af77c 100644 --- a/tornado/netutil.py +++ b/tornado/netutil.py @@ -501,10 +501,6 @@ def initialize(self) -> None: # type: ignore class ThreadedResolver(ExecutorResolver): """Multithreaded non-blocking `Resolver` implementation. - Requires the `concurrent.futures` package to be installed - (available in the standard library since Python 3.2, - installable with ``pip install futures`` in older versions). - The thread pool size can be configured with:: Resolver.configure('tornado.netutil.ThreadedResolver', @@ -598,17 +594,15 @@ def ssl_options_to_context( """Try to convert an ``ssl_options`` dictionary to an `~ssl.SSLContext` object. - The ``ssl_options`` dictionary contains keywords to be passed to - ``ssl.SSLContext.wrap_socket``. In Python 2.7.9+, `ssl.SSLContext` objects can - be used instead. This function converts the dict form to its - `~ssl.SSLContext` equivalent, and may be used when a component which - accepts both forms needs to upgrade to the `~ssl.SSLContext` version - to use features like SNI or NPN. + The ``ssl_options`` argument may be either an `ssl.SSLContext` object or a dictionary containing + keywords to be passed to ``ssl.SSLContext.wrap_socket``. This function converts the dict form + to its `~ssl.SSLContext` equivalent, and may be used when a component which accepts both forms + needs to upgrade to the `~ssl.SSLContext` version to use features like SNI or ALPN. .. versionchanged:: 6.2 - Added server_side argument. Omitting this argument will - result in a DeprecationWarning on Python 3.10. + Added server_side argument. Omitting this argument will result in a DeprecationWarning on + Python 3.10. """ if isinstance(ssl_options, ssl.SSLContext): diff --git a/tornado/options.py b/tornado/options.py index d02a730c6a..b6578bb901 100644 --- a/tornado/options.py +++ b/tornado/options.py @@ -482,15 +482,11 @@ def run_parse_callbacks(self) -> None: def mockable(self) -> "_Mockable": """Returns a wrapper around self that is compatible with - `mock.patch `. + `unittest.mock.patch`. - The `mock.patch ` function (included in - the standard library `unittest.mock` package since Python 3.3, - or in the third-party ``mock`` package for older versions of - Python) is incompatible with objects like ``options`` that - override ``__getattr__`` and ``__setattr__``. This function - returns an object that can be used with `mock.patch.object - ` to modify option values:: + The `unittest.mock.patch` function is incompatible with objects like ``options`` that + override ``__getattr__`` and ``__setattr__``. This function returns an object that can be + used with `mock.patch.object ` to modify option values:: with mock.patch.object(options.mockable(), 'name', value): assert options.name == value @@ -664,9 +660,8 @@ def _parse_timedelta(self, value: str) -> datetime.timedelta: num = float(m.group(1)) units = m.group(2) or "seconds" units = self._TIMEDELTA_ABBREV_DICT.get(units, units) - # This line confuses mypy when setup.py sets python_version=3.6 - # https://github.com/python/mypy/issues/9676 - sum += datetime.timedelta(**{units: num}) # type: ignore + + sum += datetime.timedelta(**{units: num}) start = m.end() return sum except Exception: diff --git a/tornado/tcpserver.py b/tornado/tcpserver.py index a9ae88def7..6d74224b76 100644 --- a/tornado/tcpserver.py +++ b/tornado/tcpserver.py @@ -339,7 +339,7 @@ def handle_stream( def _handle_connection(self, connection: socket.socket, address: Any) -> None: if self.ssl_options is not None: - assert ssl, "Python 2.6+ and OpenSSL required for SSL" + assert ssl, "OpenSSL required for SSL" try: connection = ssl_wrap_socket( connection, diff --git a/tornado/test/__main__.py b/tornado/test/__main__.py index 890bd505b4..65b794d9be 100644 --- a/tornado/test/__main__.py +++ b/tornado/test/__main__.py @@ -1,6 +1,4 @@ """Shim to allow python -m tornado.test. - -This only works in python 2.7+. """ from tornado.test.runtests import all, main diff --git a/tornado/test/gen_test.py b/tornado/test/gen_test.py index ec3a6c8e69..71fdceb1c9 100644 --- a/tornado/test/gen_test.py +++ b/tornado/test/gen_test.py @@ -441,7 +441,6 @@ def f(): @gen_test def test_async_return_no_value(self): - # Without a return value we don't need python 3.3. @gen.coroutine def f(): yield gen.moment diff --git a/tornado/test/iostream_test.py b/tornado/test/iostream_test.py index 09191ecfef..d55485c70d 100644 --- a/tornado/test/iostream_test.py +++ b/tornado/test/iostream_test.py @@ -1078,8 +1078,6 @@ def test_handshake_fail(self): @gen_test def test_check_hostname(self): # Test that server_hostname parameter to start_tls is being used. - # The check_hostname functionality is only available in python 2.7 and - # up and in python 3.4 and up. server_future = self.server_start_tls(_server_ssl_options()) with ExpectLog(gen_log, "SSL Error"): client_future = self.client_start_tls( diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py index 32a12bad99..7e1278e6c3 100644 --- a/tornado/test/web_test.py +++ b/tornado/test/web_test.py @@ -367,7 +367,7 @@ def test_cookie_special_char(self): self.assertEqual(len(headers), 3) self.assertEqual(headers[0], 'equals="a=b"; Path=/') self.assertEqual(headers[1], 'quote="a\\"b"; Path=/') - # python 2.7 octal-escapes the semicolon; older versions leave it alone + # Semicolons are octal-escaped self.assertIn( headers[2], ('semicolon="a;b"; Path=/', 'semicolon="a\\073b"; Path=/'), @@ -1888,12 +1888,8 @@ def test_clear_all_cookies(self): response = self.fetch("/", headers={"Cookie": "foo=bar; baz=xyzzy"}) set_cookies = sorted(response.headers.get_list("Set-Cookie")) # Python 3.5 sends 'baz="";'; older versions use 'baz=;' - self.assertTrue( - set_cookies[0].startswith("baz=;") or set_cookies[0].startswith('baz="";') - ) - self.assertTrue( - set_cookies[1].startswith("foo=;") or set_cookies[1].startswith('foo="";') - ) + self.assertTrue(set_cookies[0].startswith('baz="";')) + self.assertTrue(set_cookies[1].startswith('foo="";')) class PermissionError(Exception): From ef8c6ab9bb14a4a966f052c27b9b63a7064e7f3f Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Thu, 11 Jul 2024 15:46:50 -0400 Subject: [PATCH 4/5] web_test: Remove obsolete compatibility with python 3.4 --- tornado/test/web_test.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py index 7e1278e6c3..d43ea78dd2 100644 --- a/tornado/test/web_test.py +++ b/tornado/test/web_test.py @@ -417,12 +417,10 @@ def test_set_cookie_expires_days(self): def test_set_cookie_false_flags(self): response = self.fetch("/set_falsy_flags") headers = sorted(response.headers.get_list("Set-Cookie")) - # The secure and httponly headers are capitalized in py35 and - # lowercase in older versions. - self.assertEqual(headers[0].lower(), "a=1; path=/; secure") - self.assertEqual(headers[1].lower(), "b=1; path=/") - self.assertEqual(headers[2].lower(), "c=1; httponly; path=/") - self.assertEqual(headers[3].lower(), "d=1; path=/") + self.assertEqual(headers[0], "a=1; Path=/; Secure") + self.assertEqual(headers[1], "b=1; Path=/") + self.assertEqual(headers[2], "c=1; HttpOnly; Path=/") + self.assertEqual(headers[3], "d=1; Path=/") def test_set_cookie_deprecated(self): with ignore_deprecation(): From 77e0a4f9cf8ccc4654afa4ef769fb1c5e4c12133 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Thu, 11 Jul 2024 15:50:21 -0400 Subject: [PATCH 5/5] test.util: Remove our private compatibility shim for subTests --- tornado/test/options_test.py | 3 +-- tornado/test/util.py | 12 ------------ 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/tornado/test/options_test.py b/tornado/test/options_test.py index 99c3c12c80..67d89c250e 100644 --- a/tornado/test/options_test.py +++ b/tornado/test/options_test.py @@ -7,7 +7,6 @@ from tornado.options import OptionParser, Error from tornado.util import basestring_type -from tornado.test.util import subTest import typing @@ -277,7 +276,7 @@ def test_error_redefine_underscore(self): ("foo_bar", "foo-bar"), ] for a, b in tests: - with subTest(self, a=a, b=b): + with self.subTest(self, a=a, b=b): options = OptionParser() options.define(a) with self.assertRaises(Error) as cm: diff --git a/tornado/test/util.py b/tornado/test/util.py index ed5fd5d3f6..25dd2000fd 100644 --- a/tornado/test/util.py +++ b/tornado/test/util.py @@ -80,18 +80,6 @@ def exec_test(caller_globals, caller_locals, s): return local_namespace -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) - - @contextlib.contextmanager def ignore_deprecation(): """Context manager to ignore deprecation warnings."""