]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
testing: Don't close the event loop if we didn't create it 2327/head
authorBen Darnell <ben@bendarnell.com>
Sun, 25 Mar 2018 15:50:09 +0000 (11:50 -0400)
committerBen Darnell <ben@bendarnell.com>
Sun, 25 Mar 2018 15:50:09 +0000 (11:50 -0400)
This improves compatibility with asyncio test frameworks like
pytest-asyncio.

Fixes #2324

tornado/test/testing_test.py
tornado/testing.py

index 9905c9272f742ce139c218855aa9291c392cdfe3..3744946680ee2e3382c00359ec99aa00f6345d77 100644 (file)
@@ -11,6 +11,11 @@ import platform
 import traceback
 import warnings
 
+try:
+    import asyncio
+except ImportError:
+    asyncio = None
+
 
 @contextlib.contextmanager
 def set_environ(name, value):
@@ -307,5 +312,30 @@ class GenTest(AsyncTestCase):
             self.finished = True
 
 
+@unittest.skipIf(asyncio is None, "asyncio module not present")
+class GetNewIOLoopTest(AsyncTestCase):
+    def get_new_ioloop(self):
+        # Use the current loop instead of creating a new one here.
+        return ioloop.IOLoop.current()
+
+    def setUp(self):
+        # This simulates the effect of an asyncio test harness like
+        # pytest-asyncio.
+        self.orig_loop = asyncio.get_event_loop()
+        self.new_loop = asyncio.new_event_loop()
+        asyncio.set_event_loop(self.new_loop)
+        super(GetNewIOLoopTest, self).setUp()
+
+    def tearDown(self):
+        super(GetNewIOLoopTest, self).tearDown()
+        # AsyncTestCase must not affect the existing asyncio loop.
+        self.assertFalse(asyncio.get_event_loop().is_closed())
+        asyncio.set_event_loop(self.orig_loop)
+        self.new_loop.close()
+
+    def test_loop(self):
+        self.assertIs(self.io_loop.asyncio_loop, self.new_loop)
+
+
 if __name__ == '__main__':
     unittest.main()
index 400dd3e4c34692113fdc1c3858b25a88b733861b..19bc5a946f13c70dd8656c54f5242bc0e60c5071 100644 (file)
@@ -74,6 +74,12 @@ else:
         import unittest  # type: ignore
 
 
+if asyncio is None:
+    _NON_OWNED_IOLOOPS = ()
+else:
+    import tornado.platform.asyncio
+    _NON_OWNED_IOLOOPS = tornado.platform.asyncio.AsyncIOMainLoop
+
 def bind_unused_port(reuse_port=False):
     """Binds a server socket to an available port on localhost.
 
@@ -216,11 +222,12 @@ class AsyncTestCase(unittest.TestCase):
         # Clean up Subprocess, so it can be used again with a new ioloop.
         Subprocess.uninitialize()
         self.io_loop.clear_current()
-        # Try to clean up any file descriptors left open in the ioloop.
-        # This avoids leaks, especially when tests are run repeatedly
-        # in the same process with autoreload (because curl does not
-        # set FD_CLOEXEC on its file descriptors)
-        self.io_loop.close(all_fds=True)
+        if not isinstance(self.io_loop, _NON_OWNED_IOLOOPS):
+            # Try to clean up any file descriptors left open in the ioloop.
+            # This avoids leaks, especially when tests are run repeatedly
+            # in the same process with autoreload (because curl does not
+            # set FD_CLOEXEC on its file descriptors)
+            self.io_loop.close(all_fds=True)
         super(AsyncTestCase, self).tearDown()
         # In case an exception escaped or the StackContext caught an exception
         # when there wasn't a wait() to re-raise it, do so here.
@@ -229,9 +236,15 @@ class AsyncTestCase(unittest.TestCase):
         self.__rethrow()
 
     def get_new_ioloop(self):
-        """Creates a new `.IOLoop` for this test.  May be overridden in
-        subclasses for tests that require a specific `.IOLoop` (usually
-        the singleton `.IOLoop.instance()`).
+        """Returns the `.IOLoop` to use for this test.
+
+        By default, a new `.IOLoop` is created for each test.
+        Subclasses may override this method to return
+        `.IOLoop.current()` if it is not appropriate to use a new
+        `.IOLoop` in each tests (for example, if there are global
+        singletons using the default `.IOLoop`) or if a per-test event
+        loop is being provided by another system (such as
+        ``pytest-asyncio``).
         """
         return IOLoop()