Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add decorator for @gen.engine-like tests.

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.
  • Loading branch information...
commit 8a9179f2d35e353782aa3611dbdd79c29a5e8185 1 parent 3a428c0
@bdarnell bdarnell authored
View
11 tornado/gen.py
@@ -66,7 +66,6 @@ def get(self):
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 @@ def run(self):
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
View
7 tornado/test/concurrent_test.py
@@ -24,7 +24,7 @@
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 @@ def test_async_future(self):
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)
View
23 tornado/test/testing_test.py
@@ -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 @@ def test(self):
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()
View
33 tornado/testing.py
@@ -34,15 +34,18 @@
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 @@ def get_protocol(self):
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.
View
3  website/sphinx/releases/next.rst
@@ -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`
~~~~~~~~~~~~~~

0 comments on commit 8a9179f

Please sign in to comment.
Something went wrong with that request. Please try again.