00001
00002 """Support classes for automated testing.
00003
00004 * `AsyncTestCase` and `AsyncHTTPTestCase`: Subclasses of unittest.TestCase
00005 with additional support for testing asynchronous (`.IOLoop` based) code.
00006
00007 * `ExpectLog` and `LogTrapTestCase`: Make test logs less spammy.
00008
00009 * `main()`: A simple test runner (wrapper around unittest.main()) with support
00010 for the tornado.autoreload module to rerun the tests when code changes.
00011 """
00012
00013 from __future__ import absolute_import, division, print_function, with_statement
00014
00015 try:
00016 from tornado import gen
00017 from tornado.httpclient import AsyncHTTPClient
00018 from tornado.httpserver import HTTPServer
00019 from tornado.simple_httpclient import SimpleAsyncHTTPClient
00020 from tornado.ioloop import IOLoop, TimeoutError
00021 from tornado import netutil
00022 except ImportError:
00023
00024
00025 AsyncHTTPClient = None
00026 gen = None
00027 HTTPServer = None
00028 IOLoop = None
00029 netutil = None
00030 SimpleAsyncHTTPClient = None
00031 from tornado.log import gen_log
00032 from tornado.stack_context import ExceptionStackContext
00033 from tornado.util import raise_exc_info, basestring_type
00034 import functools
00035 import logging
00036 import os
00037 import re
00038 import signal
00039 import socket
00040 import sys
00041 import types
00042
00043 try:
00044 from cStringIO import StringIO
00045 except ImportError:
00046 from io import StringIO
00047
00048
00049
00050
00051
00052 if sys.version_info >= (3,):
00053
00054
00055 import unittest
00056 else:
00057
00058 try:
00059 import unittest2 as unittest
00060 except ImportError:
00061 import unittest
00062
00063 _next_port = 10000
00064
00065
00066 def get_unused_port():
00067 """Returns a (hopefully) unused port number.
00068
00069 This function does not guarantee that the port it returns is available,
00070 only that a series of get_unused_port calls in a single process return
00071 distinct ports.
00072
00073 .. deprecated::
00074 Use bind_unused_port instead, which is guaranteed to find an unused port.
00075 """
00076 global _next_port
00077 port = _next_port
00078 _next_port = _next_port + 1
00079 return port
00080
00081
00082 def bind_unused_port():
00083 """Binds a server socket to an available port on localhost.
00084
00085 Returns a tuple (socket, port).
00086 """
00087 [sock] = netutil.bind_sockets(None, 'localhost', family=socket.AF_INET)
00088 port = sock.getsockname()[1]
00089 return sock, port
00090
00091
00092 def get_async_test_timeout():
00093 """Get the global timeout setting for async tests.
00094
00095 Returns a float, the timeout in seconds.
00096
00097 .. versionadded:: 3.1
00098 """
00099 try:
00100 return float(os.environ.get('ASYNC_TEST_TIMEOUT'))
00101 except (ValueError, TypeError):
00102 return 5
00103
00104
00105 class _TestMethodWrapper(object):
00106 """Wraps a test method to raise an error if it returns a value.
00107
00108 This is mainly used to detect undecorated generators (if a test
00109 method yields it must use a decorator to consume the generator),
00110 but will also detect other kinds of return values (these are not
00111 necessarily errors, but we alert anyway since there is no good
00112 reason to return a value from a test.
00113 """
00114 def __init__(self, orig_method):
00115 self.orig_method = orig_method
00116
00117 def __call__(self, *args, **kwargs):
00118 result = self.orig_method(*args, **kwargs)
00119 if isinstance(result, types.GeneratorType):
00120 raise TypeError("Generator test methods should be decorated with "
00121 "tornado.testing.gen_test")
00122 elif result is not None:
00123 raise ValueError("Return value from test method ignored: %r" %
00124 result)
00125
00126 def __getattr__(self, name):
00127 """Proxy all unknown attributes to the original method.
00128
00129 This is important for some of the decorators in the `unittest`
00130 module, such as `unittest.skipIf`.
00131 """
00132 return getattr(self.orig_method, name)
00133
00134
00135 class AsyncTestCase(unittest.TestCase):
00136 """`~unittest.TestCase` subclass for testing `.IOLoop`-based
00137 asynchronous code.
00138
00139 The unittest framework is synchronous, so the test must be
00140 complete by the time the test method returns. This means that
00141 asynchronous code cannot be used in quite the same way as usual.
00142 To write test functions that use the same ``yield``-based patterns
00143 used with the `tornado.gen` module, decorate your test methods
00144 with `tornado.testing.gen_test` instead of
00145 `tornado.gen.coroutine`. This class also provides the `stop()`
00146 and `wait()` methods for a more manual style of testing. The test
00147 method itself must call ``self.wait()``, and asynchronous
00148 callbacks should call ``self.stop()`` to signal completion.
00149
00150 By default, a new `.IOLoop` is constructed for each test and is available
00151 as ``self.io_loop``. This `.IOLoop` should be used in the construction of
00152 HTTP clients/servers, etc. If the code being tested requires a
00153 global `.IOLoop`, subclasses should override `get_new_ioloop` to return it.
00154
00155 The `.IOLoop`'s ``start`` and ``stop`` methods should not be
00156 called directly. Instead, use `self.stop <stop>` and `self.wait
00157 <wait>`. Arguments passed to ``self.stop`` are returned from
00158 ``self.wait``. It is possible to have multiple ``wait``/``stop``
00159 cycles in the same test.
00160
00161 Example::
00162
00163 # This test uses coroutine style.
00164 class MyTestCase(AsyncTestCase):
00165 @tornado.testing.gen_test
00166 def test_http_fetch(self):
00167 client = AsyncHTTPClient(self.io_loop)
00168 response = yield client.fetch("http://www.tornadoweb.org")
00169 # Test contents of response
00170 self.assertIn("FriendFeed", response.body)
00171
00172 # This test uses argument passing between self.stop and self.wait.
00173 class MyTestCase2(AsyncTestCase):
00174 def test_http_fetch(self):
00175 client = AsyncHTTPClient(self.io_loop)
00176 client.fetch("http://www.tornadoweb.org/", self.stop)
00177 response = self.wait()
00178 # Test contents of response
00179 self.assertIn("FriendFeed", response.body)
00180
00181 # This test uses an explicit callback-based style.
00182 class MyTestCase3(AsyncTestCase):
00183 def test_http_fetch(self):
00184 client = AsyncHTTPClient(self.io_loop)
00185 client.fetch("http://www.tornadoweb.org/", self.handle_fetch)
00186 self.wait()
00187
00188 def handle_fetch(self, response):
00189 # Test contents of response (failures and exceptions here
00190 # will cause self.wait() to throw an exception and end the
00191 # test).
00192 # Exceptions thrown here are magically propagated to
00193 # self.wait() in test_http_fetch() via stack_context.
00194 self.assertIn("FriendFeed", response.body)
00195 self.stop()
00196 """
00197 def __init__(self, methodName='runTest', **kwargs):
00198 super(AsyncTestCase, self).__init__(methodName, **kwargs)
00199 self.__stopped = False
00200 self.__running = False
00201 self.__failure = None
00202 self.__stop_args = None
00203 self.__timeout = None
00204
00205
00206
00207
00208
00209 setattr(self, methodName, _TestMethodWrapper(getattr(self, methodName)))
00210
00211 def setUp(self):
00212 super(AsyncTestCase, self).setUp()
00213 self.io_loop = self.get_new_ioloop()
00214 self.io_loop.make_current()
00215
00216 def tearDown(self):
00217 self.io_loop.clear_current()
00218 if (not IOLoop.initialized() or
00219 self.io_loop is not IOLoop.instance()):
00220
00221
00222
00223
00224 self.io_loop.close(all_fds=True)
00225 super(AsyncTestCase, self).tearDown()
00226
00227
00228
00229
00230 self.__rethrow()
00231
00232 def get_new_ioloop(self):
00233 """Creates a new `.IOLoop` for this test. May be overridden in
00234 subclasses for tests that require a specific `.IOLoop` (usually
00235 the singleton `.IOLoop.instance()`).
00236 """
00237 return IOLoop()
00238
00239 def _handle_exception(self, typ, value, tb):
00240 self.__failure = (typ, value, tb)
00241 self.stop()
00242 return True
00243
00244 def __rethrow(self):
00245 if self.__failure is not None:
00246 failure = self.__failure
00247 self.__failure = None
00248 raise_exc_info(failure)
00249
00250 def run(self, result=None):
00251 with ExceptionStackContext(self._handle_exception):
00252 super(AsyncTestCase, self).run(result)
00253
00254
00255
00256
00257 self.__rethrow()
00258
00259 def stop(self, _arg=None, **kwargs):
00260 """Stops the `.IOLoop`, causing one pending (or future) call to `wait()`
00261 to return.
00262
00263 Keyword arguments or a single positional argument passed to `stop()` are
00264 saved and will be returned by `wait()`.
00265 """
00266 assert _arg is None or not kwargs
00267 self.__stop_args = kwargs or _arg
00268 if self.__running:
00269 self.io_loop.stop()
00270 self.__running = False
00271 self.__stopped = True
00272
00273 def wait(self, condition=None, timeout=None):
00274 """Runs the `.IOLoop` until stop is called or timeout has passed.
00275
00276 In the event of a timeout, an exception will be thrown. The
00277 default timeout is 5 seconds; it may be overridden with a
00278 ``timeout`` keyword argument or globally with the
00279 ``ASYNC_TEST_TIMEOUT`` environment variable.
00280
00281 If ``condition`` is not None, the `.IOLoop` will be restarted
00282 after `stop()` until ``condition()`` returns true.
00283
00284 .. versionchanged:: 3.1
00285 Added the ``ASYNC_TEST_TIMEOUT`` environment variable.
00286 """
00287 if timeout is None:
00288 timeout = get_async_test_timeout()
00289
00290 if not self.__stopped:
00291 if timeout:
00292 def timeout_func():
00293 try:
00294 raise self.failureException(
00295 'Async operation timed out after %s seconds' %
00296 timeout)
00297 except Exception:
00298 self.__failure = sys.exc_info()
00299 self.stop()
00300 self.__timeout = self.io_loop.add_timeout(self.io_loop.time() + timeout, timeout_func)
00301 while True:
00302 self.__running = True
00303 self.io_loop.start()
00304 if (self.__failure is not None or
00305 condition is None or condition()):
00306 break
00307 if self.__timeout is not None:
00308 self.io_loop.remove_timeout(self.__timeout)
00309 self.__timeout = None
00310 assert self.__stopped
00311 self.__stopped = False
00312 self.__rethrow()
00313 result = self.__stop_args
00314 self.__stop_args = None
00315 return result
00316
00317
00318 class AsyncHTTPTestCase(AsyncTestCase):
00319 """A test case that starts up an HTTP server.
00320
00321 Subclasses must override `get_app()`, which returns the
00322 `tornado.web.Application` (or other `.HTTPServer` callback) to be tested.
00323 Tests will typically use the provided ``self.http_client`` to fetch
00324 URLs from this server.
00325
00326 Example::
00327
00328 class MyHTTPTest(AsyncHTTPTestCase):
00329 def get_app(self):
00330 return Application([('/', MyHandler)...])
00331
00332 def test_homepage(self):
00333 # The following two lines are equivalent to
00334 # response = self.fetch('/')
00335 # but are shown in full here to demonstrate explicit use
00336 # of self.stop and self.wait.
00337 self.http_client.fetch(self.get_url('/'), self.stop)
00338 response = self.wait()
00339 # test contents of response
00340 """
00341 def setUp(self):
00342 super(AsyncHTTPTestCase, self).setUp()
00343 sock, port = bind_unused_port()
00344 self.__port = port
00345
00346 self.http_client = self.get_http_client()
00347 self._app = self.get_app()
00348 self.http_server = self.get_http_server()
00349 self.http_server.add_sockets([sock])
00350
00351 def get_http_client(self):
00352 return AsyncHTTPClient(io_loop=self.io_loop)
00353
00354 def get_http_server(self):
00355 return HTTPServer(self._app, io_loop=self.io_loop,
00356 **self.get_httpserver_options())
00357
00358 def get_app(self):
00359 """Should be overridden by subclasses to return a
00360 `tornado.web.Application` or other `.HTTPServer` callback.
00361 """
00362 raise NotImplementedError()
00363
00364 def fetch(self, path, **kwargs):
00365 """Convenience method to synchronously fetch a url.
00366
00367 The given path will be appended to the local server's host and
00368 port. Any additional kwargs will be passed directly to
00369 `.AsyncHTTPClient.fetch` (and so could be used to pass
00370 ``method="POST"``, ``body="..."``, etc).
00371 """
00372 self.http_client.fetch(self.get_url(path), self.stop, **kwargs)
00373 return self.wait()
00374
00375 def get_httpserver_options(self):
00376 """May be overridden by subclasses to return additional
00377 keyword arguments for the server.
00378 """
00379 return {}
00380
00381 def get_http_port(self):
00382 """Returns the port used by the server.
00383
00384 A new port is chosen for each test.
00385 """
00386 return self.__port
00387
00388 def get_protocol(self):
00389 return 'http'
00390
00391 def get_url(self, path):
00392 """Returns an absolute url for the given path on the test server."""
00393 return '%s://localhost:%s%s' % (self.get_protocol(),
00394 self.get_http_port(), path)
00395
00396 def tearDown(self):
00397 self.http_server.stop()
00398 self.io_loop.run_sync(self.http_server.close_all_connections)
00399 if (not IOLoop.initialized() or
00400 self.http_client.io_loop is not IOLoop.instance()):
00401 self.http_client.close()
00402 super(AsyncHTTPTestCase, self).tearDown()
00403
00404
00405 class AsyncHTTPSTestCase(AsyncHTTPTestCase):
00406 """A test case that starts an HTTPS server.
00407
00408 Interface is generally the same as `AsyncHTTPTestCase`.
00409 """
00410 def get_http_client(self):
00411
00412
00413 return SimpleAsyncHTTPClient(io_loop=self.io_loop, force_instance=True,
00414 defaults=dict(validate_cert=False))
00415
00416 def get_httpserver_options(self):
00417 return dict(ssl_options=self.get_ssl_options())
00418
00419 def get_ssl_options(self):
00420 """May be overridden by subclasses to select SSL options.
00421
00422 By default includes a self-signed testing certificate.
00423 """
00424
00425
00426 module_dir = os.path.dirname(__file__)
00427 return dict(
00428 certfile=os.path.join(module_dir, 'test', 'test.crt'),
00429 keyfile=os.path.join(module_dir, 'test', 'test.key'))
00430
00431 def get_protocol(self):
00432 return 'https'
00433
00434
00435 def gen_test(func=None, timeout=None):
00436 """Testing equivalent of ``@gen.coroutine``, to be applied to test methods.
00437
00438 ``@gen.coroutine`` cannot be used on tests because the `.IOLoop` is not
00439 already running. ``@gen_test`` should be applied to test methods
00440 on subclasses of `AsyncTestCase`.
00441
00442 Example::
00443
00444 class MyTest(AsyncHTTPTestCase):
00445 @gen_test
00446 def test_something(self):
00447 response = yield gen.Task(self.fetch('/'))
00448
00449 By default, ``@gen_test`` times out after 5 seconds. The timeout may be
00450 overridden globally with the ``ASYNC_TEST_TIMEOUT`` environment variable,
00451 or for each test with the ``timeout`` keyword argument::
00452
00453 class MyTest(AsyncHTTPTestCase):
00454 @gen_test(timeout=10)
00455 def test_something_slow(self):
00456 response = yield gen.Task(self.fetch('/'))
00457
00458 .. versionadded:: 3.1
00459 The ``timeout`` argument and ``ASYNC_TEST_TIMEOUT`` environment
00460 variable.
00461
00462 .. versionchanged:: 4.0
00463 The wrapper now passes along ``*args, **kwargs`` so it can be used
00464 on functions with arguments.
00465 """
00466 if timeout is None:
00467 timeout = get_async_test_timeout()
00468
00469 def wrap(f):
00470
00471
00472
00473
00474
00475
00476
00477
00478 @functools.wraps(f)
00479 def pre_coroutine(self, *args, **kwargs):
00480 result = f(self, *args, **kwargs)
00481 if isinstance(result, types.GeneratorType):
00482 self._test_generator = result
00483 else:
00484 self._test_generator = None
00485 return result
00486
00487 coro = gen.coroutine(pre_coroutine)
00488
00489 @functools.wraps(coro)
00490 def post_coroutine(self, *args, **kwargs):
00491 try:
00492 return self.io_loop.run_sync(
00493 functools.partial(coro, self, *args, **kwargs),
00494 timeout=timeout)
00495 except TimeoutError as e:
00496
00497
00498
00499 self._test_generator.throw(e)
00500
00501
00502
00503 raise
00504 return post_coroutine
00505
00506 if func is not None:
00507
00508
00509
00510
00511 return wrap(func)
00512 else:
00513
00514 return wrap
00515
00516
00517
00518
00519 gen_test.__test__ = False
00520
00521
00522 class LogTrapTestCase(unittest.TestCase):
00523 """A test case that captures and discards all logging output
00524 if the test passes.
00525
00526 Some libraries can produce a lot of logging output even when
00527 the test succeeds, so this class can be useful to minimize the noise.
00528 Simply use it as a base class for your test case. It is safe to combine
00529 with AsyncTestCase via multiple inheritance
00530 (``class MyTestCase(AsyncHTTPTestCase, LogTrapTestCase):``)
00531
00532 This class assumes that only one log handler is configured and
00533 that it is a `~logging.StreamHandler`. This is true for both
00534 `logging.basicConfig` and the "pretty logging" configured by
00535 `tornado.options`. It is not compatible with other log buffering
00536 mechanisms, such as those provided by some test runners.
00537 """
00538 def run(self, result=None):
00539 logger = logging.getLogger()
00540 if not logger.handlers:
00541 logging.basicConfig()
00542 handler = logger.handlers[0]
00543 if (len(logger.handlers) > 1 or
00544 not isinstance(handler, logging.StreamHandler)):
00545
00546
00547 super(LogTrapTestCase, self).run(result)
00548 return
00549 old_stream = handler.stream
00550 try:
00551 handler.stream = StringIO()
00552 gen_log.info("RUNNING TEST: " + str(self))
00553 old_error_count = len(result.failures) + len(result.errors)
00554 super(LogTrapTestCase, self).run(result)
00555 new_error_count = len(result.failures) + len(result.errors)
00556 if new_error_count != old_error_count:
00557 old_stream.write(handler.stream.getvalue())
00558 finally:
00559 handler.stream = old_stream
00560
00561
00562 class ExpectLog(logging.Filter):
00563 """Context manager to capture and suppress expected log output.
00564
00565 Useful to make tests of error conditions less noisy, while still
00566 leaving unexpected log entries visible. *Not thread safe.*
00567
00568 Usage::
00569
00570 with ExpectLog('tornado.application', "Uncaught exception"):
00571 error_response = self.fetch("/some_page")
00572 """
00573 def __init__(self, logger, regex, required=True):
00574 """Constructs an ExpectLog context manager.
00575
00576 :param logger: Logger object (or name of logger) to watch. Pass
00577 an empty string to watch the root logger.
00578 :param regex: Regular expression to match. Any log entries on
00579 the specified logger that match this regex will be suppressed.
00580 :param required: If true, an exeption will be raised if the end of
00581 the ``with`` statement is reached without matching any log entries.
00582 """
00583 if isinstance(logger, basestring_type):
00584 logger = logging.getLogger(logger)
00585 self.logger = logger
00586 self.regex = re.compile(regex)
00587 self.required = required
00588 self.matched = False
00589
00590 def filter(self, record):
00591 message = record.getMessage()
00592 if self.regex.match(message):
00593 self.matched = True
00594 return False
00595 return True
00596
00597 def __enter__(self):
00598 self.logger.addFilter(self)
00599
00600 def __exit__(self, typ, value, tb):
00601 self.logger.removeFilter(self)
00602 if not typ and self.required and not self.matched:
00603 raise Exception("did not get expected log message")
00604
00605
00606 def main(**kwargs):
00607 """A simple test runner.
00608
00609 This test runner is essentially equivalent to `unittest.main` from
00610 the standard library, but adds support for tornado-style option
00611 parsing and log formatting.
00612
00613 The easiest way to run a test is via the command line::
00614
00615 python -m tornado.testing tornado.test.stack_context_test
00616
00617 See the standard library unittest module for ways in which tests can
00618 be specified.
00619
00620 Projects with many tests may wish to define a test script like
00621 ``tornado/test/runtests.py``. This script should define a method
00622 ``all()`` which returns a test suite and then call
00623 `tornado.testing.main()`. Note that even when a test script is
00624 used, the ``all()`` test suite may be overridden by naming a
00625 single test on the command line::
00626
00627 # Runs all tests
00628 python -m tornado.test.runtests
00629 # Runs one test
00630 python -m tornado.test.runtests tornado.test.stack_context_test
00631
00632 Additional keyword arguments passed through to ``unittest.main()``.
00633 For example, use ``tornado.testing.main(verbosity=2)``
00634 to show many test details as they are run.
00635 See http://docs.python.org/library/unittest.html#unittest.main
00636 for full argument list.
00637 """
00638 from tornado.options import define, options, parse_command_line
00639
00640 define('exception_on_interrupt', type=bool, default=True,
00641 help=("If true (default), ctrl-c raises a KeyboardInterrupt "
00642 "exception. This prints a stack trace but cannot interrupt "
00643 "certain operations. If false, the process is more reliably "
00644 "killed, but does not print a stack trace."))
00645
00646
00647 define('verbose', type=bool)
00648 define('quiet', type=bool)
00649 define('failfast', type=bool)
00650 define('catch', type=bool)
00651 define('buffer', type=bool)
00652
00653 argv = [sys.argv[0]] + parse_command_line(sys.argv)
00654
00655 if not options.exception_on_interrupt:
00656 signal.signal(signal.SIGINT, signal.SIG_DFL)
00657
00658 if options.verbose is not None:
00659 kwargs['verbosity'] = 2
00660 if options.quiet is not None:
00661 kwargs['verbosity'] = 0
00662 if options.failfast is not None:
00663 kwargs['failfast'] = True
00664 if options.catch is not None:
00665 kwargs['catchbreak'] = True
00666 if options.buffer is not None:
00667 kwargs['buffer'] = True
00668
00669 if __name__ == '__main__' and len(argv) == 1:
00670 print("No tests specified", file=sys.stderr)
00671 sys.exit(1)
00672 try:
00673
00674
00675
00676
00677
00678
00679 if len(argv) > 1:
00680 unittest.main(module=None, argv=argv, **kwargs)
00681 else:
00682 unittest.main(defaultTest="all", argv=argv, **kwargs)
00683 except SystemExit as e:
00684 if e.code == 0:
00685 gen_log.info('PASS')
00686 else:
00687 gen_log.error('FAIL')
00688 raise
00689
00690 if __name__ == '__main__':
00691 main()