Repository: tornadoweb/tornado
Pull request #2002 (2017-04-04 19:51:42)

Fetch absolute URL in AsyncHTTPTestCase
Extends the `AsyncHTTPTestCase.fetch` method to allow for the fetching of a path as-is instead of appending it to the Tornado instance. Useful when testing against external services.

bdarnell: You can tell whether a url is absolute by looking at it (i.e. does it start with `http:` or `https:`), so I don't think we need an explicit parameter for this.

This seems reasonable, but I've actually been thinking about going in the other direction: deprecating stop/wait and fetch, and using `await self.http_client.fetch(...)` to make test code more like application code. We just need some nicer interface for getting a URL to the local test server (and then fetching an absolute URL doesn't need anything special). 

Patch:
diff --git a/.travis.yml b/.travis.yml
index 4b2ac2bc..761dc2d7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -23,13 +23,11 @@ install:
     # On travis the extension should always be built
     - if [[ $TRAVIS_PYTHON_VERSION != 'pypy'* ]]; then export TORNADO_EXTENSION=1; fi
     - travis_retry python setup.py install
-    - travis_retry pip install codecov virtualenv
+    - travis_retry pip install codecov
     # Create a separate no-dependencies virtualenv to make sure all imports
-    # of optional-dependencies are guarded. (skipped on pypy3 because
-    # virtualenv no longer supports py32)
-    - if [[ $TRAVIS_PYTHON_VERSION != 'pypy3' ]]; then virtualenv ./nodeps; fi
-    - if [[ $TRAVIS_PYTHON_VERSION != 'pypy3' ]]; then ./nodeps/bin/python -VV; fi
-    - if [[ $TRAVIS_PYTHON_VERSION != 'pypy3' ]]; then ./nodeps/bin/python setup.py install; fi
+    # of optional-dependencies are guarded.
+    - virtualenv ./nodeps
+    - ./nodeps/bin/python setup.py install
     - curl-config --version; pip freeze
 
 script:
@@ -68,7 +66,7 @@ script:
     - if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then python $TARGET --resolver=tornado.platform.twisted.TwistedResolver; fi
     #- if [[ $TRAVIS_PYTHON_VERSION != pypy* ]]; then python $TARGET --resolver=tornado.platform.caresresolver.CaresResolver; fi
     - if [[ $TRAVIS_PYTHON_VERSION == 3* ]]; then python $TARGET --ioloop_time_monotonic; fi
-    - if [[ $TRAVIS_PYTHON_VERSION != 'pypy3' ]]; then ../nodeps/bin/python -m tornado.test.runtests; fi
+    - ../nodeps/bin/python -m tornado.test.runtests
     # make coverage reports for Codecov to find
     - if [[ $TRAVIS_PYTHON_VERSION != nightly ]]; then coverage xml; fi
     - export TORNADO_EXTENSION=0
diff --git a/demos/file_upload/file_uploader.py b/demos/file_upload/file_uploader.py
index 5f13fcb6..025c2159 100644
--- a/demos/file_upload/file_uploader.py
+++ b/demos/file_upload/file_uploader.py
@@ -9,7 +9,6 @@ single file without encoding.
 See also file_receiver.py in this directory, a server that receives uploads.
 """
 
-from __future__ import print_function
 import mimetypes
 import os
 import sys
diff --git a/docs/conf.py b/docs/conf.py
index 5bc6635b..18a75ff5 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -70,9 +70,6 @@ coverage_ignore_functions = [
     # parse_qs_bytes should probably be documented but it's complicated by
     # having different implementations between py2 and py3.
     "parse_qs_bytes",
-
-    # tornado.gen
-    "Multi",
 ]
 
 html_favicon = 'favicon.ico'
diff --git a/docs/releases.rst b/docs/releases.rst
index 8fb67c8e..ebf0978a 100644
--- a/docs/releases.rst
+++ b/docs/releases.rst
@@ -4,7 +4,6 @@ Release notes
 .. toctree::
    :maxdepth: 2
 
-   releases/v4.5.1
    releases/v4.5.0
    releases/v4.4.3
    releases/v4.4.2
diff --git a/docs/releases/v4.5.0.rst b/docs/releases/v4.5.0.rst
index 9cdf0ad5..1632d525 100644
--- a/docs/releases/v4.5.0.rst
+++ b/docs/releases/v4.5.0.rst
@@ -1,7 +1,7 @@
 What's new in Tornado 4.5
 =========================
 
-Apr 16, 2017
+Xxx XX, 2017
 ------------
 
 Backwards-compatibility warning
@@ -22,19 +22,6 @@ General changes
 - Reduced the number of circular references, reducing memory usage and
   improving performance.
 
-`tornado.auth`
-~~~~~~~~~~~~~~
-
-* The `tornado.auth` module has been updated for compatibility with `a
-  change to Facebook's access_token endpoint
-  <https://github.com/tornadoweb/tornado/pull/1977>`_. This includes both
-  the changes initially released in Tornado 4.4.3 and an additional change
-  to support the ```session_expires`` field in the new format.
-  The ``session_expires`` field is currently a string; it should be accessed
-  as ``int(user['session_expires'])`` because it will change from a string to
-  an int in Tornado 5.0.
-
-
 `tornado.autoreload`
 ~~~~~~~~~~~~~~~~~~~~
 
diff --git a/docs/releases/v4.5.1.rst b/docs/releases/v4.5.1.rst
deleted file mode 100644
index 0f1f3fcf..00000000
--- a/docs/releases/v4.5.1.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-What's new in Tornado 4.5.1
-===========================
-
-Apr 20, 2017
-------------
-
-`tornado.log`
-~~~~~~~~~~~~~
-
-- Improved detection of libraries for colorized logging.
-
-`tornado.httputil`
-~~~~~~~~~~~~~~~~~~
-
-- `.url_concat` once again treats None as equivalent to an empty sequence.
diff --git a/docs/websocket.rst b/docs/websocket.rst
index 96255589..b3596d4c 100644
--- a/docs/websocket.rst
+++ b/docs/websocket.rst
@@ -16,7 +16,6 @@
    .. automethod:: WebSocketHandler.on_message
    .. automethod:: WebSocketHandler.on_close
    .. automethod:: WebSocketHandler.select_subprotocol
-   .. automethod:: WebSocketHandler.on_ping
 
    Output
    ------
diff --git a/maint/requirements.txt b/maint/requirements.txt
index eccf2d9a..1a7bf45d 100644
--- a/maint/requirements.txt
+++ b/maint/requirements.txt
@@ -1,40 +1,30 @@
-alabaster==0.7.10
-appdirs==1.4.3
-args==0.1.0
-attrs==16.3.0
-Automat==0.5.0
-autopep8==1.3.1
-Babel==2.4.0
-clint==0.5.1
-constantly==15.1.0
-coverage==4.3.4
-docutils==0.13.1
-flake8==3.3.0
-imagesize==0.7.1
-incremental==16.10.1
-Jinja2==2.9.6
-MarkupSafe==1.0
-mccabe==0.6.1
-packaging==16.8
+alabaster==0.7.7
+autopep8==1.2.2
+Babel==2.3.2
+coverage==4.0.3
+docutils==0.12
+flake8==2.5.4
+imagesize==0.7.0
+Jinja2==2.8
+MarkupSafe==0.23
+mccabe==0.4.0
 pep8==1.7.0
-pkginfo==1.4.1
-pluggy==0.4.0
-py==1.4.33
-pycares==2.1.1
-pycodestyle==2.3.1
+pkginfo==1.2.1
+pluggy==0.3.1
+py==1.4.31
+pycares==1.0.0
 pycurl==7.43.0
-pyflakes==1.5.0
-Pygments==2.2.0
-pyparsing==2.2.0
-pytz==2017.2
-requests==2.13.0
-requests-toolbelt==0.7.1
+pyflakes==1.1.0
+Pygments==2.1.3
+pytz==2016.3
+requests==2.9.1
+requests-toolbelt==0.6.0
 six==1.10.0
 snowballstemmer==1.2.1
-Sphinx==1.5.5
-sphinx-rtd-theme==0.2.4
-tox==2.7.0
-twine==1.8.1
-Twisted==17.1.0
-virtualenv==15.1.0
-zope.interface==4.3.3
+Sphinx==1.4
+sphinx-rtd-theme==0.1.9
+tox==2.3.1
+twine==1.6.5
+Twisted==16.1.0
+virtualenv==15.0.1
+zope.interface==4.1.3
diff --git a/setup.py b/setup.py
index 4a94ed72..14caff8c 100644
--- a/setup.py
+++ b/setup.py
@@ -103,7 +103,7 @@ http://api.mongodb.org/python/current/installation.html#osx
 
 kwargs = {}
 
-version = "5.0.dev1"
+version = "4.5b1"
 
 with open('README.rst') as f:
     kwargs['long_description'] = f.read()
diff --git a/tornado/__init__.py b/tornado/__init__.py
index 3c191c0d..548c6272 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 = "5.0.dev1"
-version_info = (5, 0, 0, -100)
+version = "4.5b1"
+version_info = (4, 5, 0, -99)
diff --git a/tornado/auth.py b/tornado/auth.py
index f02d2898..554db592 100644
--- a/tornado/auth.py
+++ b/tornado/auth.py
@@ -954,20 +954,6 @@ class FacebookGraphMixin(OAuth2Mixin):
         .. testoutput::
            :hide:
 
-        This method returns a dictionary which may contain the following fields:
-
-        * ``access_token``, a string which may be passed to `facebook_request`
-        * ``session_expires``, an integer encoded as a string representing
-          the time until the access token expires in seconds. This field should
-          be used like ``int(user['session_expires'])``; in a future version of
-          Tornado it will change from a string to an integer.
-        * ``id``, ``name``, ``first_name``, ``last_name``, ``locale``, ``picture``,
-          ``link``, plus any fields named in the ``extra_fields`` argument. These
-          fields are copied from the Facebook graph API `user object <https://developers.facebook.com/docs/graph-api/reference/user>`_
-
-        .. versionchanged:: 4.5
-           The ``session_expires`` field was updated to support changes made to the
-           Facebook API in March 2017.
         """
         http = self.get_auth_http_client()
         args = {
@@ -995,7 +981,7 @@ class FacebookGraphMixin(OAuth2Mixin):
         args = escape.json_decode(response.body)
         session = {
             "access_token": args.get("access_token"),
-            "expires_in": args.get("expires_in")
+            "expires": args.get("expires")
         }
 
         self.facebook_request(
@@ -1018,12 +1004,7 @@ class FacebookGraphMixin(OAuth2Mixin):
         for field in fields:
             fieldmap[field] = user.get(field)
 
-        # session_expires is converted to str for compatibility with
-        # older versions in which the server used url-encoding and
-        # this code simply returned the string verbatim.
-        # This should change in Tornado 5.0.
-        fieldmap.update({"access_token": session["access_token"],
-                         "session_expires": str(session.get("expires_in"))})
+        fieldmap.update({"access_token": session["access_token"], "session_expires": session.get("expires")})
         future.set_result(fieldmap)
 
     @_auth_return_future
diff --git a/tornado/autoreload.py b/tornado/autoreload.py
index 60571efe..5b50ce93 100644
--- a/tornado/autoreload.py
+++ b/tornado/autoreload.py
@@ -243,7 +243,6 @@ def _reload():
             # unwind, so just exit uncleanly.
             os._exit(0)
 
-
 _USAGE = """\
 Usage:
   python -m tornado.autoreload -m module.to.run [args...]
diff --git a/tornado/concurrent.py b/tornado/concurrent.py
index 667e6b17..312fdf4f 100644
--- a/tornado/concurrent.py
+++ b/tornado/concurrent.py
@@ -343,7 +343,6 @@ class Future(object):
             app_log.error('Future %r exception was never retrieved: %s',
                           self, ''.join(tb).rstrip())
 
-
 TracebackFuture = Future
 
 if futures is None:
@@ -368,7 +367,6 @@ class DummyExecutor(object):
     def shutdown(self, wait=True):
         pass
 
-
 dummy_executor = DummyExecutor()
 
 
diff --git a/tornado/curl_httpclient.py b/tornado/curl_httpclient.py
index 554710ee..ab54bc0b 100644
--- a/tornado/curl_httpclient.py
+++ b/tornado/curl_httpclient.py
@@ -74,12 +74,6 @@ class CurlAsyncHTTPClient(AsyncHTTPClient):
         self._multi.close()
         super(CurlAsyncHTTPClient, self).close()
 
-        # Set below properties to None to reduce the reference count of current
-        # instance, because those properties hold some methods of current
-        # instance that will case circular reference.
-        self._force_timeout_callback = None
-        self._multi = None
-
     def fetch_impl(self, request, callback):
         self._requests.append((request, callback))
         self._process_queue()
@@ -284,9 +278,9 @@ class CurlAsyncHTTPClient(AsyncHTTPClient):
         if curl_log.isEnabledFor(logging.DEBUG):
             curl.setopt(pycurl.VERBOSE, 1)
             curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)
-        if hasattr(pycurl, 'PROTOCOLS'):  # PROTOCOLS first appeared in pycurl 7.19.5 (2014-07-12)
-            curl.setopt(pycurl.PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)
-            curl.setopt(pycurl.REDIR_PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)
+        if hasattr(pycurl,'PROTOCOLS'): # PROTOCOLS first appeared in pycurl 7.19.5 (2014-07-12)
+            curl.setopt(pycurl.PROTOCOLS, pycurl.PROTO_HTTP|pycurl.PROTO_HTTPS)
+            curl.setopt(pycurl.REDIR_PROTOCOLS, pycurl.PROTO_HTTP|pycurl.PROTO_HTTPS)
         return curl
 
     def _curl_setup_request(self, curl, request, buffer, headers):
diff --git a/tornado/escape.py b/tornado/escape.py
index 2ca3fe3f..c4b2fa3b 100644
--- a/tornado/escape.py
+++ b/tornado/escape.py
@@ -199,7 +199,6 @@ def utf8(value):
         )
     return value.encode("utf-8")
 
-
 _TO_UNICODE_TYPES = (unicode_type, type(None))
 
 
@@ -217,7 +216,6 @@ def to_unicode(value):
         )
     return value.decode("utf-8")
 
-
 # to_unicode was previously named _unicode not because it was private,
 # but to avoid conflicts with the built-in unicode() function/type
 _unicode = to_unicode
@@ -266,7 +264,6 @@ def recursive_unicode(obj):
     else:
         return obj
 
-
 # I originally used the regex from
 # http://daringfireball.net/2010/07/improved_regex_for_matching_urls
 # but it gets all exponential on certain patterns (such as too many trailing
@@ -394,5 +391,4 @@ def _build_unicode_map():
         unicode_map[name] = unichr(value)
     return unicode_map
 
-
 _HTML_UNICODE_MAP = _build_unicode_map()
diff --git a/tornado/gen.py b/tornado/gen.py
index 99f91066..9ac1d6e3 100644
--- a/tornado/gen.py
+++ b/tornado/gen.py
@@ -245,7 +245,6 @@ def coroutine(func, replace_callback=True):
     """
     return _make_coroutine_wrapper(func, replace_callback=True)
 
-
 # Ties lifetime of runners to their result futures. Github Issue #1769
 # Generators, like any object in Python, must be strong referenced
 # in order to not be cleaned up by the garbage collector. When using
@@ -265,7 +264,6 @@ def coroutine(func, replace_callback=True):
 # Runner alive.
 _futures_to_runners = weakref.WeakKeyDictionary()
 
-
 def _make_coroutine_wrapper(func, replace_callback):
     """The inner workings of ``@gen.coroutine`` and ``@gen.engine``.
 
@@ -720,7 +718,6 @@ def multi(children, quiet_exceptions=()):
     else:
         return multi_future(children, quiet_exceptions=quiet_exceptions)
 
-
 Multi = multi
 
 
@@ -1147,8 +1144,7 @@ class Runner(object):
 
         if not self.future.done() or self.future is moment:
             def inner(f):
-                # Break a reference cycle to speed GC.
-                f = None # noqa
+                f = None
                 self.run()
             self.io_loop.add_future(
                 self.future, inner)
@@ -1173,7 +1169,6 @@ class Runner(object):
             self.stack_context_deactivate()
             self.stack_context_deactivate = None
 
-
 Arguments = collections.namedtuple('Arguments', ['args', 'kwargs'])
 
 
@@ -1193,7 +1188,6 @@ def _argument_adapter(callback):
             callback(None)
     return wrapper
 
-
 # Convert Awaitables into Futures. It is unfortunately possible
 # to have infinite recursion here if those Awaitables assume that
 # we're using a different coroutine runner and yield objects
@@ -1282,7 +1276,6 @@ def convert_yielded(yielded):
     else:
         raise BadYieldError("yielded unknown object %r" % (yielded,))
 
-
 if singledispatch is not None:
     convert_yielded = singledispatch(convert_yielded)
 
diff --git a/tornado/httpclient.py b/tornado/httpclient.py
index 8436ece4..6f45367c 100644
--- a/tornado/httpclient.py
+++ b/tornado/httpclient.py
@@ -673,6 +673,5 @@ def main():
             print(native_str(response.body))
     client.close()
 
-
 if __name__ == "__main__":
     main()
diff --git a/tornado/httpserver.py b/tornado/httpserver.py
index d757be18..6ae32e6f 100644
--- a/tornado/httpserver.py
+++ b/tornado/httpserver.py
@@ -321,5 +321,4 @@ class _ProxyAdapter(httputil.HTTPMessageDelegate):
     def _cleanup(self):
         self.connection.context._unapply_xheaders()
 
-
 HTTPRequest = httputil.HTTPServerRequest
diff --git a/tornado/httputil.py b/tornado/httputil.py
index 818ea914..39a27f78 100644
--- a/tornado/httputil.py
+++ b/tornado/httputil.py
@@ -99,7 +99,6 @@ class _NormalizedHeaderCache(dict):
             del self[old_key]
         return normalized
 
-
 _normalized_headers = _NormalizedHeaderCache(1000)
 
 
@@ -603,8 +602,6 @@ def url_concat(url, args):
     >>> url_concat("http://example.com/foo?a=b", [("c", "d"), ("c", "d2")])
     'http://example.com/foo?a=b&c=d&c=d2'
     """
-    if args is None:
-        return url
     parsed_url = urlparse(url)
     if isinstance(args, dict):
         parsed_query = parse_qsl(parsed_url.query, keep_blank_values=True)
@@ -939,12 +936,10 @@ def split_host_and_port(netloc):
         port = None
     return (host, port)
 
-
 _OctalPatt = re.compile(r"\\[0-3][0-7][0-7]")
 _QuotePatt = re.compile(r"[\\].")
 _nulljoin = ''.join
 
-
 def _unquote_cookie(str):
     """Handle double quotes and escaping in cookie values.
 
@@ -986,11 +981,11 @@ def _unquote_cookie(str):
             k = q_match.start(0)
         if q_match and (not o_match or k < j):     # QuotePatt matched
             res.append(str[i:k])
-            res.append(str[k + 1])
+            res.append(str[k+1])
             i = k + 2
         else:                                      # OctalPatt matched
             res.append(str[i:j])
-            res.append(chr(int(str[j + 1:j + 4], 8)))
+            res.append(chr(int(str[j+1:j+4], 8)))
             i = j + 4
     return _nulljoin(res)
 
diff --git a/tornado/iostream.py b/tornado/iostream.py
index a1619c49..691251ad 100644
--- a/tornado/iostream.py
+++ b/tornado/iostream.py
@@ -908,8 +908,8 @@ class BaseIOStream(object):
         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()
+                       [self._read_buffer_pos:self._read_buffer_pos + loc]
+                       ).tobytes()
         self._read_buffer_pos += loc
         self._read_buffer_size -= loc
         # Amortized O(1) shrink
diff --git a/tornado/log.py b/tornado/log.py
index 654afc02..f870da4a 100644
--- a/tornado/log.py
+++ b/tornado/log.py
@@ -32,6 +32,7 @@ from __future__ import absolute_import, division, print_function
 
 import logging
 import logging.handlers
+import os
 import sys
 
 from tornado.escape import _unicode
@@ -54,21 +55,18 @@ gen_log = logging.getLogger("tornado.general")
 
 
 def _stderr_supports_color():
-    try:
-        if hasattr(sys.stderr, 'isatty') and sys.stderr.isatty():
-            if curses:
+    color = False
+    if hasattr(sys.stderr, 'isatty') and sys.stderr.isatty():
+        if curses:
+            try:
                 curses.setupterm()
                 if curses.tigetnum("colors") > 0:
-                    return True
-            elif colorama:
-                if sys.stderr is getattr(colorama.initialise, 'wrapped_stderr',
-                                         object()):
-                    return True
-    except Exception:
-        # Very broad exception handling because it's always better to
-        # fall back to non-colored logs than to break at startup.
-        pass
-    return False
+                    color = True
+            except Exception:
+                pass
+        elif colorama:
+            color = True
+    return color
 
 
 def _safe_unicode(s):
@@ -149,12 +147,12 @@ class LogFormatter(logging.Formatter):
                 for levelno, code in colors.items():
                     self._colors[levelno] = unicode_type(curses.tparm(fg_color, code), "ascii")
                 self._normal = unicode_type(curses.tigetstr("sgr0"), "ascii")
-            else:
-                # If curses is not present (currently we'll only get here for
-                # colorama on windows), assume hard-coded ANSI color codes.
+            elif sys.stderr is getattr(colorama, 'wrapped_stderr', object()):
                 for levelno, code in colors.items():
                     self._colors[levelno] = '\033[2;3%dm' % code
                 self._normal = '\033[0m'
+            else:
+                raise RuntimeError("No supported color terminal library")
         else:
             self._normal = ''
 
diff --git a/tornado/netutil.py b/tornado/netutil.py
index c34c8c8b..9653421f 100644
--- a/tornado/netutil.py
+++ b/tornado/netutil.py
@@ -200,7 +200,6 @@ def bind_sockets(port, address=None, family=socket.AF_UNSPEC,
         sockets.append(sock)
     return sockets
 
-
 if hasattr(socket, 'AF_UNIX'):
     def bind_unix_socket(file, mode=0o600, backlog=_DEFAULT_BACKLOG):
         """Creates a listening unix socket.
diff --git a/tornado/platform/asyncio.py b/tornado/platform/asyncio.py
index 830ee1f3..549a1cc0 100644
--- a/tornado/platform/asyncio.py
+++ b/tornado/platform/asyncio.py
@@ -30,7 +30,7 @@ from tornado import stack_context
 try:
     # Import the real asyncio module for py33+ first.  Older versions of the
     # trollius backport also use this name.
-    import asyncio  # type: ignore
+    import asyncio # type: ignore
 except ImportError as e:
     # Asyncio itself isn't available; see if trollius is (backport to py26+).
     try:
@@ -217,6 +217,5 @@ def to_asyncio_future(tornado_future):
     tornado.concurrent.chain_future(tornado_future, af)
     return af
 
-
 if hasattr(convert_yielded, 'register'):
     convert_yielded.register(asyncio.Future, to_tornado_future)  # type: ignore
diff --git a/tornado/platform/common.py b/tornado/platform/common.py
index a73f8db7..f51f49ac 100644
--- a/tornado/platform/common.py
+++ b/tornado/platform/common.py
@@ -8,7 +8,6 @@ import time
 from tornado.platform import interface
 from tornado.util import errno_from_exception
 
-
 def try_close(f):
     # Avoid issue #875 (race condition when using the file in another
     # thread).
diff --git a/tornado/platform/interface.py b/tornado/platform/interface.py
index c0ef2905..e4d92736 100644
--- a/tornado/platform/interface.py
+++ b/tornado/platform/interface.py
@@ -62,6 +62,5 @@ class Waker(object):
         """Closes the waker's file descriptor(s)."""
         raise NotImplementedError()
 
-
 def monotonic_time():
     raise NotImplementedError()
diff --git a/tornado/platform/twisted.py b/tornado/platform/twisted.py
index 0f9787e8..ec269413 100644
--- a/tornado/platform/twisted.py
+++ b/tornado/platform/twisted.py
@@ -574,7 +574,6 @@ class TwistedResolver(Resolver):
         ]
         raise gen.Return(result)
 
-
 if hasattr(gen.convert_yielded, 'register'):
     @gen.convert_yielded.register(Deferred)  # type: ignore
     def _(d):
diff --git a/tornado/speedups.c b/tornado/speedups.c
index b714268a..c59bda00 100644
--- a/tornado/speedups.c
+++ b/tornado/speedups.c
@@ -1,12 +1,9 @@
 #define PY_SSIZE_T_CLEAN
 #include <Python.h>
-#include <stdint.h>
 
 static PyObject* websocket_mask(PyObject* self, PyObject* args) {
     const char* mask;
     Py_ssize_t mask_len;
-    uint32_t uint32_mask;
-    uint64_t uint64_mask;
     const char* data;
     Py_ssize_t data_len;
     Py_ssize_t i;
@@ -17,35 +14,13 @@ static PyObject* websocket_mask(PyObject* self, PyObject* args) {
         return NULL;
     }
 
-    uint32_mask = ((uint32_t*)mask)[0];
-
     result = PyBytes_FromStringAndSize(NULL, data_len);
     if (!result) {
         return NULL;
     }
     buf = PyBytes_AsString(result);
-
-    if (sizeof(size_t) >= 8) {
-        uint64_mask = uint32_mask;
-        uint64_mask = (uint64_mask << 32) | uint32_mask;
-
-        while (data_len >= 8) {
-            ((uint64_t*)buf)[0] = ((uint64_t*)data)[0] ^ uint64_mask;
-            data += 8;
-            buf += 8;
-            data_len -= 8;
-        }
-    }
-
-    while (data_len >= 4) {
-        ((uint32_t*)buf)[0] = ((uint32_t*)data)[0] ^ uint32_mask;
-        data += 4;
-        buf += 4;
-        data_len -= 4;
-    }
-
     for (i = 0; i < data_len; i++) {
-        buf[i] = data[i] ^ mask[i];
+        buf[i] = data[i] ^ mask[i % 4];
     }
 
     return result;
diff --git a/tornado/stack_context.py b/tornado/stack_context.py
index 61ae51f4..74e39385 100644
--- a/tornado/stack_context.py
+++ b/tornado/stack_context.py
@@ -82,8 +82,6 @@ class StackContextInconsistentError(Exception):
 class _State(threading.local):
     def __init__(self):
         self.contexts = (tuple(), None)
-
-
 _state = _State()
 
 
diff --git a/tornado/util.py b/tornado/util.py
index 981b94c8..b9960cf7 100644
--- a/tornado/util.py
+++ b/tornado/util.py
@@ -236,7 +236,6 @@ def _re_unescape_replacement(match):
         raise ValueError("cannot unescape '\\\\%s'" % group[0])
     return group
 
-
 _re_unescape_pattern = re.compile(r'\\(.)', re.DOTALL)
 
 
@@ -455,7 +454,6 @@ def _websocket_mask_python(mask, data):
     else:
         return unmasked_arr.tostring()
 
-
 if (os.environ.get('TORNADO_NO_EXTENSION') or
         os.environ.get('TORNADO_EXTENSION') == '0'):
     # These environment variables exist to make it easier to do performance
diff --git a/tornado/web.py b/tornado/web.py
index 132f42dd..6d6ce92e 100644
--- a/tornado/web.py
+++ b/tornado/web.py
@@ -319,7 +319,10 @@ class RequestHandler(object):
         if reason is not None:
             self._reason = escape.native_str(reason)
         else:
-            self._reason = httputil.responses.get(status_code, "Unknown")
+            try:
+                self._reason = httputil.responses[status_code]
+            except KeyError:
+                raise ValueError("unknown status code %d" % status_code)
 
     def get_status(self):
         """Returns the status code for our response."""
@@ -990,9 +993,6 @@ class RequestHandler(object):
         self._log()
         self._finished = True
         self.on_finish()
-        self._break_cycles()
-
-    def _break_cycles(self):
         # Break up a reference cycle between this handler and the
         # _ui_module closures to allow for faster GC on CPython.
         self.ui = None
@@ -1558,7 +1558,11 @@ class RequestHandler(object):
             # send a response.
             return
         if isinstance(e, HTTPError):
-            self.send_error(e.status_code, exc_info=sys.exc_info())
+            if e.status_code not in httputil.responses and not e.reason:
+                gen_log.error("Bad HTTP status code: %d", e.status_code)
+                self.send_error(500, exc_info=sys.exc_info())
+            else:
+                self.send_error(e.status_code, exc_info=sys.exc_info())
         else:
             self.send_error(500, exc_info=sys.exc_info())
 
@@ -3115,7 +3119,6 @@ def create_signed_value(secret, name, value, version=None, clock=None,
     else:
         raise ValueError("Unsupported version %d" % version)
 
-
 # A leading version number in decimal
 # with no leading zeros, followed by a pipe.
 _signed_value_version_re = re.compile(br"^([1-9][0-9]*)\|(.*)$")
diff --git a/tornado/websocket.py b/tornado/websocket.py
index 69437ee4..0af9e8f8 100644
--- a/tornado/websocket.py
+++ b/tornado/websocket.py
@@ -285,7 +285,7 @@ class WebSocketHandler(tornado.web.RequestHandler):
 
            Added ``compression_level`` and ``mem_level``.
         """
-        # TODO: Add wbits option.
+        #TODO: Add wbits option.
         return None
 
     def open(self, *args, **kwargs):
@@ -434,16 +434,6 @@ class WebSocketHandler(tornado.web.RequestHandler):
         if not self._on_close_called:
             self._on_close_called = True
             self.on_close()
-            self._break_cycles()
-
-    def _break_cycles(self):
-        # WebSocketHandlers call finish() early, but we don't want to
-        # break up reference cycles (which makes it impossible to call
-        # self.render_string) until after we've really closed the
-        # connection (if it was established in the first place,
-        # indicated by status code 101).
-        if self.get_status() != 101 or self._on_close_called:
-            super(WebSocketHandler, self)._break_cycles()
 
     def send_error(self, *args, **kwargs):
         if self.stream is None:
@@ -523,12 +513,12 @@ class _PerMessageDeflateCompressor(object):
                              max_wbits, zlib.MAX_WBITS)
         self._max_wbits = max_wbits
 
-        if compression_options is None or 'compression_level' not in compression_options:
+        if compression_options is None or not 'compression_level' in compression_options:
             self._compression_level = tornado.web.GZipContentEncoding.GZIP_LEVEL
         else:
             self._compression_level = compression_options['compression_level']
 
-        if compression_options is None or 'mem_level' not in compression_options:
+        if compression_options is None or not 'mem_level' in compression_options:
             self._mem_level = 8
         else:
             self._mem_level = compression_options['mem_level']
@@ -613,6 +603,7 @@ class WebSocketProtocol13(WebSocketProtocol):
         self.last_ping = 0
         self.last_pong = 0
 
+
     def accept_connection(self):
         try:
             self._handle_websocket_headers()
@@ -648,6 +639,7 @@ class WebSocketProtocol13(WebSocketProtocol):
             self.request.headers.get("Sec-Websocket-Key"))
 
     def _accept_connection(self):
+        subprotocol_header = ''
         subprotocols = self.request.headers.get("Sec-WebSocket-Protocol", '')
         subprotocols = [s.strip() for s in subprotocols.split(',')]
         if subprotocols:
@@ -656,6 +648,7 @@ class WebSocketProtocol13(WebSocketProtocol):
                 assert selected in subprotocols
                 self.handler.set_header("Sec-WebSocket-Protocol", selected)
 
+        extension_header = ''
         extensions = self._parse_extensions_header(self.request.headers)
         for ext in extensions:
             if (ext[0] == 'permessage-deflate' and
@@ -833,7 +826,7 @@ class WebSocketProtocol13(WebSocketProtocol):
         new_len = self._frame_length
         if self._fragmented_message_buffer is not None:
             new_len += len(self._fragmented_message_buffer)
-        if new_len > (self.handler.max_message_size or 10 * 1024 * 1024):
+        if new_len > (self.handler.max_message_size or 10*1024*1024):
             self.close(1009, "message too big")
             return
         self.stream.read_bytes(
@@ -1004,7 +997,7 @@ class WebSocketProtocol13(WebSocketProtocol):
         if self.ping_interval > 0:
             self.last_ping = self.last_pong = IOLoop.current().time()
             self.ping_callback = PeriodicCallback(
-                self.periodic_ping, self.ping_interval * 1000)
+                self.periodic_ping, self.ping_interval*1000)
             self.ping_callback.start()
 
     def periodic_ping(self):
@@ -1022,7 +1015,7 @@ class WebSocketProtocol13(WebSocketProtocol):
         now = IOLoop.current().time()
         since_last_pong = now - self.last_pong
         since_last_ping = now - self.last_ping
-        if (since_last_ping < 2 * self.ping_interval and
+        if (since_last_ping < 2*self.ping_interval and
                 since_last_pong > self.ping_timeout):
             self.close()
             return


Test patch:
diff --git a/tornado/test/auth_test.py b/tornado/test/auth_test.py
index 400fc4f4..14dd2f62 100644
--- a/tornado/test/auth_test.py
+++ b/tornado/test/auth_test.py
@@ -149,7 +149,7 @@ class FacebookClientLoginHandler(RequestHandler, FacebookGraphMixin):
 
 class FacebookServerAccessTokenHandler(RequestHandler):
     def get(self):
-        self.write(dict(access_token="asdf", expires_in=3600))
+        self.write(dict(access_token="asdf"))
 
 
 class FacebookServerMeHandler(RequestHandler):
@@ -401,9 +401,6 @@ class AuthTest(AsyncHTTPTestCase):
         self.assertTrue('/facebook/server/authorize?' in response.headers['Location'])
         response = self.fetch('/facebook/client/login?code=1234', follow_redirects=False)
         self.assertEqual(response.code, 200)
-        user = json_decode(response.body)
-        self.assertEqual(user['access_token'], 'asdf')
-        self.assertEqual(user['session_expires'], '3600')
 
     def base_twitter_redirect(self, url):
         # Same as test_oauth10a_redirect
diff --git a/tornado/test/concurrent_test.py b/tornado/test/concurrent_test.py
index 4d89f572..fd0f4a67 100644
--- a/tornado/test/concurrent_test.py
+++ b/tornado/test/concurrent_test.py
@@ -178,7 +178,7 @@ class ReturnFutureTest(AsyncTestCase):
         @gen.coroutine
         def f():
             yield gen.moment
-            1 / 0
+            1/0
 
         g = f()
 
diff --git a/tornado/test/gen_test.py b/tornado/test/gen_test.py
index fea4c644..de803a8b 100644
--- a/tornado/test/gen_test.py
+++ b/tornado/test/gen_test.py
@@ -26,7 +26,6 @@ try:
 except ImportError:
     futures = None
 
-
 class GenEngineTest(AsyncTestCase):
     def setUp(self):
         super(GenEngineTest, self).setUp()
@@ -1445,7 +1444,6 @@ class RunnerGCTest(AsyncTestCase):
         """Runners shouldn't GC if future is alive"""
         # Create the weakref
         weakref_scope = [None]
-
         def callback():
             gc.collect(2)
             weakref_scope[0]().set_result(123)
@@ -1462,6 +1460,5 @@ class RunnerGCTest(AsyncTestCase):
             tester()
         )
 
-
 if __name__ == '__main__':
     unittest.main()
diff --git a/tornado/test/httputil_test.py b/tornado/test/httputil_test.py
index d1278567..5249eadd 100644
--- a/tornado/test/httputil_test.py
+++ b/tornado/test/httputil_test.py
@@ -66,13 +66,6 @@ class TestUrlConcat(unittest.TestCase):
         )
         self.assertEqual(url, "https://localhost/path?r=1&t=2")
 
-    def test_url_concat_none_params(self):
-        url = url_concat(
-            "https://localhost/path?r=1&t=2",
-            None,
-        )
-        self.assertEqual(url, "https://localhost/path?r=1&t=2")
-
     def test_url_concat_with_frag(self):
         url = url_concat(
             "https://localhost/path#tab",
diff --git a/tornado/test/runtests.py b/tornado/test/runtests.py
index b81c5f22..81ae9a27 100644
--- a/tornado/test/runtests.py
+++ b/tornado/test/runtests.py
@@ -185,6 +185,5 @@ def main():
                           log_counter.warning_count, log_counter.error_count)
             sys.exit(1)
 
-
 if __name__ == '__main__':
     main()
diff --git a/tornado/test/stack_context_test.py b/tornado/test/stack_context_test.py
index 59d25474..d55e0ee3 100644
--- a/tornado/test/stack_context_test.py
+++ b/tornado/test/stack_context_test.py
@@ -284,6 +284,5 @@ class StackContextTest(AsyncTestCase):
             f1)
         self.assertEqual(self.active_contexts, [])
 
-
 if __name__ == '__main__':
     unittest.main()
diff --git a/tornado/test/tcpserver_test.py b/tornado/test/tcpserver_test.py
index 9afb5420..18473a55 100644
--- a/tornado/test/tcpserver_test.py
+++ b/tornado/test/tcpserver_test.py
@@ -68,3 +68,4 @@ class TCPServerTest(AsyncTestCase):
         server.add_socket(sock)
         server.stop()
         server.stop()
+
diff --git a/tornado/test/testing_test.py b/tornado/test/testing_test.py
index b3d6d8c5..21ae7833 100644
--- a/tornado/test/testing_test.py
+++ b/tornado/test/testing_test.py
@@ -4,8 +4,9 @@ from __future__ import absolute_import, division, print_function
 
 from tornado import gen, ioloop
 from tornado.log import app_log
-from tornado.testing import AsyncTestCase, gen_test, ExpectLog
 from tornado.test.util import unittest, skipBefore35, exec_test
+from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, bind_unused_port, gen_test, ExpectLog
+from tornado.web import Application
 import contextlib
 import os
 import traceback
@@ -75,6 +76,40 @@ class AsyncTestCaseTest(AsyncTestCase):
         self.assertEqual(str(cm.exception), "error one")
 
 
+class AsyncHTTPTestCaseTest(AsyncHTTPTestCase):
+    @classmethod
+    def setUpClass(cls):
+        super(AsyncHTTPTestCaseTest, cls).setUpClass()
+        # An unused port is bound so we can make requests upon it without
+        # impacting a real local web server.
+        cls.external_sock, cls.external_port = bind_unused_port()
+
+    def get_app(self):
+        return Application()
+
+    def test_fetch_segment(self):
+        path = '/path'
+        response = self.fetch(path)
+        self.assertEqual(response.request.url, self.get_url(path))
+
+    def test_fetch_full_http_url(self):
+        path = 'http://localhost:%d/path' % self.external_port
+
+        response = self.fetch(path, request_timeout=0.1)
+        self.assertEqual(response.request.url, path)
+
+    def test_fetch_full_https_url(self):
+        path = 'https://localhost:%d/path' % self.external_port
+
+        response = self.fetch(path, request_timeout=0.1)
+        self.assertEqual(response.request.url, path)
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.external_sock.close()
+        super(AsyncHTTPTestCaseTest, cls).tearDownClass()
+
+
 class AsyncTestCaseWrapperTest(unittest.TestCase):
     def test_undecorated_generator(self):
         class Test(AsyncTestCase):
diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py
index de26c42e..913818f9 100644
--- a/tornado/test/web_test.py
+++ b/tornado/test/web_test.py
@@ -917,10 +917,6 @@ class ErrorResponseTest(WebTestCase):
             self.assertEqual(response.code, 503)
             self.assertTrue(b"503: Service Unavailable" in response.body)
 
-            response = self.fetch("/default?status=435")
-            self.assertEqual(response.code, 435)
-            self.assertTrue(b"435: Unknown" in response.body)
-
     def test_write_error(self):
         with ExpectLog(app_log, "Uncaught exception"):
             response = self.fetch("/write_error")
@@ -1493,9 +1489,9 @@ class StatusReasonTest(SimpleHandlerTestCase):
         response = self.fetch("/?code=682&reason=Bar")
         self.assertEqual(response.code, 682)
         self.assertEqual(response.reason, "Bar")
-        response = self.fetch("/?code=682")
-        self.assertEqual(response.code, 682)
-        self.assertEqual(response.reason, "Unknown")
+        with ExpectLog(app_log, 'Uncaught exception'):
+            response = self.fetch("/?code=682")
+        self.assertEqual(response.code, 500)
 
 
 @wsgi_safe
@@ -1578,6 +1574,7 @@ class GzipTestCase(SimpleHandlerTestCase):
                 response.headers.get('X-Consumed-Content-Encoding')),
             'gzip')
 
+
     def test_gzip(self):
         response = self.fetch('/')
         self.assert_compressed(response)
@@ -1608,7 +1605,6 @@ class GzipTestCase(SimpleHandlerTestCase):
         self.assertEqual([s.strip() for s in response.headers['Vary'].split(',')],
                          ['Accept-Language', 'Cookie', 'Accept-Encoding'])
 
-
 @wsgi_safe
 class PathArgsInPrepareTest(WebTestCase):
     class Handler(RequestHandler):
diff --git a/tornado/test/websocket_test.py b/tornado/test/websocket_test.py
index d47a74e6..7bdca877 100644
--- a/tornado/test/websocket_test.py
+++ b/tornado/test/websocket_test.py
@@ -8,7 +8,6 @@ from tornado.concurrent import Future
 from tornado import gen
 from tornado.httpclient import HTTPError, HTTPRequest
 from tornado.log import gen_log, app_log
-from tornado.template import DictLoader
 from tornado.testing import AsyncHTTPTestCase, gen_test, bind_unused_port, ExpectLog
 from tornado.test.util import unittest, skipBefore35, exec_test
 from tornado.web import Application, RequestHandler
@@ -131,11 +130,6 @@ class CoroutineOnMessageHandler(TestWebSocketHandler):
         self.write_message(message)
 
 
-class RenderMessageHandler(TestWebSocketHandler):
-    def on_message(self, message):
-        self.write_message(self.render_string('message.html', message=message))
-
-
 class WebSocketBaseTestCase(AsyncHTTPTestCase):
     @gen.coroutine
     def ws_connect(self, path, **kwargs):
@@ -174,15 +168,7 @@ class WebSocketTest(WebSocketBaseTestCase):
              dict(close_future=self.close_future)),
             ('/coroutine', CoroutineOnMessageHandler,
              dict(close_future=self.close_future)),
-            ('/render', RenderMessageHandler,
-             dict(close_future=self.close_future)),
-        ], template_loader=DictLoader({
-            'message.html': '<b>{{ message }}</b>',
-        }))
-
-    def tearDown(self):
-        super(WebSocketTest, self).tearDown()
-        RequestHandler._template_loaders.clear()
+        ])
 
     def test_http_request(self):
         # WS server, HTTP client.
@@ -233,14 +219,6 @@ class WebSocketTest(WebSocketBaseTestCase):
         self.assertEqual(response, u'hello \u00e9')
         yield self.close(ws)
 
-    @gen_test
-    def test_render_message(self):
-        ws = yield self.ws_connect('/render')
-        ws.write_message('hello')
-        response = yield ws.read_message()
-        self.assertEqual(response, '<b>hello</b>')
-        yield self.close(ws)
-
     @gen_test
     def test_error_in_on_message(self):
         ws = yield self.ws_connect('/error_in_on_message')
diff --git a/tornado/test/windows_test.py b/tornado/test/windows_test.py
index e5cb3381..f136c8aa 100644
--- a/tornado/test/windows_test.py
+++ b/tornado/test/windows_test.py
@@ -8,7 +8,6 @@ from tornado.platform.auto import set_close_exec
 
 skipIfNonWindows = unittest.skipIf(os.name != 'nt', 'non-windows platform')
 
-
 @skipIfNonWindows
 class WindowsTest(unittest.TestCase):
     def test_set_close_exec(self):
diff --git a/tornado/test/wsgi_test.py b/tornado/test/wsgi_test.py
index e6ccc82a..78da7853 100644
--- a/tornado/test/wsgi_test.py
+++ b/tornado/test/wsgi_test.py
@@ -83,8 +83,6 @@ def wrap_web_tests_application():
                 return WSGIContainer(validator(self.app))
         result["WSGIApplication_" + cls.__name__] = WSGIApplicationWrappedTest
     return result
-
-
 globals().update(wrap_web_tests_application())
 
 
@@ -98,6 +96,4 @@ def wrap_web_tests_adapter():
                 return WSGIContainer(validator(WSGIAdapter(self.app)))
         result["WSGIAdapter_" + cls.__name__] = WSGIAdapterWrappedTest
     return result
-
-
 globals().update(wrap_web_tests_adapter())
diff --git a/tornado/testing.py b/tornado/testing.py
index 74d04b60..2456f216 100644
--- a/tornado/testing.py
+++ b/tornado/testing.py
@@ -395,14 +395,20 @@ class AsyncHTTPTestCase(AsyncTestCase):
         raise NotImplementedError()
 
     def fetch(self, path, **kwargs):
-        """Convenience method to synchronously fetch a url.
+        """Convenience method to synchronously fetch a URL.
 
         The given path will be appended to the local server's host and
         port.  Any additional kwargs will be passed directly to
         `.AsyncHTTPClient.fetch` (and so could be used to pass
         ``method="POST"``, ``body="..."``, etc).
+
+        If the path begins with http:// or https://, it will be treated as a
+        full URL and will be fetched as-is.
         """
-        self.http_client.fetch(self.get_url(path), self.stop, **kwargs)
+        if path.lower().startswith(('http://', 'https://')):
+            self.http_client.fetch(path, self.stop, **kwargs)
+        else:
+            self.http_client.fetch(self.get_url(path), self.stop, **kwargs)
         return self.wait()
 
     def get_httpserver_options(self):
@@ -737,6 +743,5 @@ def main(**kwargs):
             gen_log.error('FAIL')
         raise
 
-
 if __name__ == '__main__':
     main()