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:
parent
d68e63f55b
commit
ccc89025f2
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue