]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add decorator for @gen.engine-like tests.
authorBen Darnell <ben@bendarnell.com>
Sun, 27 Jan 2013 22:47:37 +0000 (17:47 -0500)
committerBen Darnell <ben@bendarnell.com>
Sun, 27 Jan 2013 22:54:08 +0000 (17:54 -0500)
Futures are awkward to use in stop/wait tests, and this makes it possible
to be consistent between tests and regular code.

Uses the (now-renamed) deactivate_stack_context callback
to detect when a test finishes cleanly.

tornado/gen.py
tornado/test/concurrent_test.py
tornado/test/testing_test.py
tornado/testing.py
website/sphinx/releases/next.rst

index 434ad4bdf792945aeae90b9f8eb89ebde2fb6bdc..320f7da65df6b7e0fb75185eabc708db9748f771 100644 (file)
@@ -66,7 +66,6 @@ from __future__ import absolute_import, division, print_function, with_statement
 
 import collections
 import functools
-import operator
 import sys
 import types
 
@@ -306,10 +305,12 @@ class Runner(object):
     """Internal implementation of `tornado.gen.engine`.
 
     Maintains information about pending callbacks and their results.
+
+    ``final_callback`` is run after the generator exits.
     """
-    def __init__(self, gen, deactivate_stack_context):
+    def __init__(self, gen, final_callback):
         self.gen = gen
-        self.deactivate_stack_context = deactivate_stack_context
+        self.final_callback = final_callback
         self.yield_point = _NullYieldPoint()
         self.pending_callbacks = set()
         self.results = {}
@@ -374,8 +375,8 @@ class Runner(object):
                         raise LeakedCallbackError(
                             "finished without waiting for callbacks %r" %
                             self.pending_callbacks)
-                    self.deactivate_stack_context()
-                    self.deactivate_stack_context = None
+                    self.final_callback()
+                    self.final_callback = None
                     return
                 except Exception:
                     self.finished = True
index c0666065746c406abdf6d8007c6536d11d8dacf3..00799b8ee87b6894a33b9e59505cc2492f52f01f 100644 (file)
@@ -24,7 +24,7 @@ from tornado.escape import utf8, to_unicode
 from tornado import gen
 from tornado.iostream import IOStream
 from tornado.tcpserver import TCPServer
-from tornado.testing import AsyncTestCase, LogTrapTestCase, get_unused_port
+from tornado.testing import AsyncTestCase, LogTrapTestCase, get_unused_port, gen_test
 
 
 class ReturnFutureTest(AsyncTestCase):
@@ -83,6 +83,11 @@ class ReturnFutureTest(AsyncTestCase):
         self.assertIs(future, future2)
         self.assertEqual(future.result(), 42)
 
+    @gen_test
+    def test_async_future_gen(self):
+        result = yield self.async_future()
+        self.assertEqual(result, 42)
+
     def test_delayed_failure(self):
         future = self.delayed_failure()
         self.io_loop.add_future(future, self.stop)
index 75f07093a8ac57811db3bd49365ab6698cc16be3..dc9e0866ed4c56b8b65528d350a64bea48bf5415 100644 (file)
@@ -1,7 +1,9 @@
 #!/usr/bin/env python
 
 from __future__ import absolute_import, division, print_function, with_statement
-from tornado.testing import AsyncTestCase
+
+from tornado import gen
+from tornado.testing import AsyncTestCase, gen_test
 from tornado.test.util import unittest
 
 
@@ -53,5 +55,24 @@ class SetUpTearDownTest(unittest.TestCase):
         expected = ['setUp', 'test', 'tearDown']
         self.assertEqual(expected, events)
 
+
+class GenTest(AsyncTestCase):
+    def setUp(self):
+        super(GenTest, self).setUp()
+        self.finished = False
+
+    def tearDown(self):
+        self.assertTrue(self.finished)
+        super(GenTest, self).tearDown()
+
+    @gen_test
+    def test_sync(self):
+        self.finished = True
+
+    @gen_test
+    def test_async(self):
+        yield gen.Task(self.io_loop.add_callback)
+        self.finished = True
+
 if __name__ == '__main__':
     unittest.main()
index 8c8ca0da866203e22ef7ee814aaa7d2b641ced3a..c97c802ae7fc658aaac576fff1073344531cfefe 100644 (file)
@@ -34,15 +34,18 @@ except ImportError:
     IOLoop = None
     netutil = None
     SimpleAsyncHTTPClient = None
+from tornado import gen
 from tornado.log import gen_log
 from tornado.stack_context import ExceptionStackContext
 from tornado.util import raise_exc_info, basestring_type
+import functools
 import logging
 import os
 import re
 import signal
 import socket
 import sys
+import types
 
 try:
     from io import StringIO  # py3
@@ -352,6 +355,36 @@ class AsyncHTTPSTestCase(AsyncHTTPTestCase):
         return 'https'
 
 
+def gen_test(f):
+    """Testing equivalent of ``@gen.engine``, to be applied to test methods.
+
+    ``@gen.engine`` cannot be used on tests because the `IOLoop` is not
+    already running.  ``@gen_test`` should be applied to test methods
+    on subclasses of `AsyncTestCase`.
+
+    Note that unlike most uses of ``@gen.engine``, ``@gen_test`` can
+    detect automatically when the function finishes cleanly so there
+    is no need to run a callback to signal completion.
+
+    Example::
+        class MyTest(AsyncHTTPTestCase):
+            @gen_test
+            def test_something(self):
+                response = yield gen.Task(self.fetch('/'))
+
+    """
+    @functools.wraps(f)
+    def wrapper(self, *args, **kwargs):
+        result = f(self, *args, **kwargs)
+        if result is None:
+            return
+        assert isinstance(result, types.GeneratorType)
+        runner = gen.Runner(result, self.stop)
+        runner.run()
+        self.wait()
+    return wrapper
+
+
 class LogTrapTestCase(unittest.TestCase):
     """A test case that captures and discards all logging output
     if the test passes.
index fc5605777e5039f06b2c9d0deadbe50efc7196ba..283aa24d4fd90df4ad57a41a29285cb902c837ff 100644 (file)
@@ -281,6 +281,9 @@ General
   instead of putting all possible options in `tornado.testing.main`.
 * `AsyncHTTPTestCase` no longer calls `AsyncHTTPClient.close` for tests
   that use the singleton `IOLoop.instance`.
+* New decorator `tornado.testing.gen_test` can be used to allow for
+  yielding `tornado.gen` objects in tests, as an alternative to the
+  ``stop`` and ``wait`` methods of `AsyncTestCase`.
 
 `tornado.util`
 ~~~~~~~~~~~~~~