]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
wsgi: Avoid touching the asyncio event loop at import time 2437/head
authorBen Darnell <ben@bendarnell.com>
Fri, 6 Jul 2018 18:49:25 +0000 (14:49 -0400)
committerBen Darnell <ben@bendarnell.com>
Fri, 6 Jul 2018 19:02:55 +0000 (15:02 -0400)
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

tornado/test/import_test.py
tornado/wsgi.py

index 0e981482ef92dab1b464f217ab9861f854a7ff72..ecc569eb56dabdd2a2f902a521f5f6bf4b8ef677 100644 (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.
index beda0b9aa36317d2821edbace03fcbd0bdbd3d64..e1230da09d0af648e7bb276ec297494ddac2089e 100644 (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