From: Ben Darnell Date: Sun, 27 Jan 2013 22:47:37 +0000 (-0500) Subject: Add decorator for @gen.engine-like tests. X-Git-Tag: v3.0.0~144 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8a9179f2d35e353782aa3611dbdd79c29a5e8185;p=thirdparty%2Ftornado.git 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. --- diff --git a/tornado/gen.py b/tornado/gen.py index 434ad4bdf..320f7da65 100644 --- a/tornado/gen.py +++ b/tornado/gen.py @@ -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 diff --git a/tornado/test/concurrent_test.py b/tornado/test/concurrent_test.py index c06660657..00799b8ee 100644 --- a/tornado/test/concurrent_test.py +++ b/tornado/test/concurrent_test.py @@ -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) diff --git a/tornado/test/testing_test.py b/tornado/test/testing_test.py index 75f07093a..dc9e0866e 100644 --- a/tornado/test/testing_test.py +++ b/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 @@ 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() diff --git a/tornado/testing.py b/tornado/testing.py index 8c8ca0da8..c97c802ae 100644 --- a/tornado/testing.py +++ b/tornado/testing.py @@ -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. diff --git a/website/sphinx/releases/next.rst b/website/sphinx/releases/next.rst index fc5605777..283aa24d4 100644 --- a/website/sphinx/releases/next.rst +++ b/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` ~~~~~~~~~~~~~~