wsgi: Avoid touching the asyncio event loop at import time

Rewrite import_test. Its original purpose of augmenting our once-poor
test coverage, but it can now be useful for ensuring the absence of
import-time side effects.

Fixes #2426
This commit is contained in:
Ben Darnell 2018-07-06 14:49:25 -04:00
parent d68e63f55b
commit ccc89025f2
2 changed files with 64 additions and 44 deletions

View File

@ -1,50 +1,68 @@
# flake8: noqa
from __future__ import absolute_import, division, print_function
import subprocess
import sys
from tornado.test.util import unittest
_import_everything = b"""
# The event loop is not fork-safe, and it's easy to initialize an asyncio.Future
# at startup, which in turn creates the default event loop and prevents forking.
# Explicitly disallow the default event loop so that an error will be raised
# if something tries to touch it.
try:
import asyncio
except ImportError:
pass
else:
asyncio.set_event_loop(None)
import tornado.auth
import tornado.autoreload
import tornado.concurrent
import tornado.escape
import tornado.gen
import tornado.http1connection
import tornado.httpclient
import tornado.httpserver
import tornado.httputil
import tornado.ioloop
import tornado.iostream
import tornado.locale
import tornado.log
import tornado.netutil
import tornado.options
import tornado.process
import tornado.simple_httpclient
import tornado.stack_context
import tornado.tcpserver
import tornado.tcpclient
import tornado.template
import tornado.testing
import tornado.util
import tornado.web
import tornado.websocket
import tornado.wsgi
try:
import pycurl
except ImportError:
pass
else:
import tornado.curl_httpclient
"""
class ImportTest(unittest.TestCase):
def test_import_everything(self):
# Some of our modules are not otherwise tested. Import them
# all (unless they have external dependencies) here to at
# least ensure that there are no syntax errors.
import tornado.auth
import tornado.autoreload
import tornado.concurrent
import tornado.escape
import tornado.gen
import tornado.http1connection
import tornado.httpclient
import tornado.httpserver
import tornado.httputil
import tornado.ioloop
import tornado.iostream
import tornado.locale
import tornado.log
import tornado.netutil
import tornado.options
import tornado.process
import tornado.simple_httpclient
import tornado.stack_context
import tornado.tcpserver
import tornado.tcpclient
import tornado.template
import tornado.testing
import tornado.util
import tornado.web
import tornado.websocket
import tornado.wsgi
# for modules with dependencies, if those dependencies can be loaded,
# load them too.
def test_import_pycurl(self):
try:
import pycurl # type: ignore
except ImportError:
pass
else:
import tornado.curl_httpclient
# Test that all Tornado modules can be imported without side effects,
# specifically without initializing the default asyncio event loop.
# Since we can't tell which modules may have already beein imported
# in our process, do it in a subprocess for a clean slate.
proc = subprocess.Popen([sys.executable], stdin=subprocess.PIPE)
proc.communicate(_import_everything)
self.assertEqual(proc.returncode, 0)
def test_import_aliases(self):
# Ensure we don't delete formerly-documented aliases accidentally.

View File

@ -85,8 +85,10 @@ class WSGIApplication(web.Application):
# WSGI has no facilities for flow control, so just return an already-done
# Future when the interface requires it.
_dummy_future = Future()
_dummy_future.set_result(None)
def _dummy_future():
f = Future()
f.set_result(None)
return f
class _WSGIConnection(httputil.HTTPConnection):
@ -118,7 +120,7 @@ class _WSGIConnection(httputil.HTTPConnection):
self.write(chunk, callback)
elif callback is not None:
callback()
return _dummy_future
return _dummy_future()
def write(self, chunk, callback=None):
if self._expected_content_remaining is not None:
@ -130,7 +132,7 @@ class _WSGIConnection(httputil.HTTPConnection):
self._write_buffer.append(chunk)
if callback is not None:
callback()
return _dummy_future
return _dummy_future()
def finish(self):
if (self._expected_content_remaining is not None and