testing.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  1. """Support classes for automated testing.
  2. * `AsyncTestCase` and `AsyncHTTPTestCase`: Subclasses of unittest.TestCase
  3. with additional support for testing asynchronous (`.IOLoop`-based) code.
  4. * `ExpectLog`: Make test logs less spammy.
  5. * `main()`: A simple test runner (wrapper around unittest.main()) with support
  6. for the tornado.autoreload module to rerun the tests when code changes.
  7. """
  8. import asyncio
  9. from collections.abc import Generator
  10. import functools
  11. import inspect
  12. import logging
  13. import os
  14. import re
  15. import signal
  16. import socket
  17. import sys
  18. import unittest
  19. from tornado import gen
  20. from tornado.httpclient import AsyncHTTPClient, HTTPResponse
  21. from tornado.httpserver import HTTPServer
  22. from tornado.ioloop import IOLoop, TimeoutError
  23. from tornado import netutil
  24. from tornado.platform.asyncio import AsyncIOMainLoop
  25. from tornado.process import Subprocess
  26. from tornado.log import app_log
  27. from tornado.util import raise_exc_info, basestring_type
  28. from tornado.web import Application
  29. import typing
  30. from typing import Tuple, Any, Callable, Type, Dict, Union, Optional
  31. from types import TracebackType
  32. if typing.TYPE_CHECKING:
  33. # Coroutine wasn't added to typing until 3.5.3, so only import it
  34. # when mypy is running and use forward references.
  35. from typing import Coroutine # noqa: F401
  36. _ExcInfoTuple = Tuple[
  37. Optional[Type[BaseException]], Optional[BaseException], Optional[TracebackType]
  38. ]
  39. _NON_OWNED_IOLOOPS = AsyncIOMainLoop
  40. def bind_unused_port(reuse_port: bool = False) -> Tuple[socket.socket, int]:
  41. """Binds a server socket to an available port on localhost.
  42. Returns a tuple (socket, port).
  43. .. versionchanged:: 4.4
  44. Always binds to ``127.0.0.1`` without resolving the name
  45. ``localhost``.
  46. """
  47. sock = netutil.bind_sockets(
  48. 0, "127.0.0.1", family=socket.AF_INET, reuse_port=reuse_port
  49. )[0]
  50. port = sock.getsockname()[1]
  51. return sock, port
  52. def get_async_test_timeout() -> float:
  53. """Get the global timeout setting for async tests.
  54. Returns a float, the timeout in seconds.
  55. .. versionadded:: 3.1
  56. """
  57. env = os.environ.get("ASYNC_TEST_TIMEOUT")
  58. if env is not None:
  59. try:
  60. return float(env)
  61. except ValueError:
  62. pass
  63. return 5
  64. class _TestMethodWrapper(object):
  65. """Wraps a test method to raise an error if it returns a value.
  66. This is mainly used to detect undecorated generators (if a test
  67. method yields it must use a decorator to consume the generator),
  68. but will also detect other kinds of return values (these are not
  69. necessarily errors, but we alert anyway since there is no good
  70. reason to return a value from a test).
  71. """
  72. def __init__(self, orig_method: Callable) -> None:
  73. self.orig_method = orig_method
  74. def __call__(self, *args: Any, **kwargs: Any) -> None:
  75. result = self.orig_method(*args, **kwargs)
  76. if isinstance(result, Generator) or inspect.iscoroutine(result):
  77. raise TypeError(
  78. "Generator and coroutine test methods should be"
  79. " decorated with tornado.testing.gen_test"
  80. )
  81. elif result is not None:
  82. raise ValueError("Return value from test method ignored: %r" % result)
  83. def __getattr__(self, name: str) -> Any:
  84. """Proxy all unknown attributes to the original method.
  85. This is important for some of the decorators in the `unittest`
  86. module, such as `unittest.skipIf`.
  87. """
  88. return getattr(self.orig_method, name)
  89. class AsyncTestCase(unittest.TestCase):
  90. """`~unittest.TestCase` subclass for testing `.IOLoop`-based
  91. asynchronous code.
  92. The unittest framework is synchronous, so the test must be
  93. complete by the time the test method returns. This means that
  94. asynchronous code cannot be used in quite the same way as usual
  95. and must be adapted to fit. To write your tests with coroutines,
  96. decorate your test methods with `tornado.testing.gen_test` instead
  97. of `tornado.gen.coroutine`.
  98. This class also provides the (deprecated) `stop()` and `wait()`
  99. methods for a more manual style of testing. The test method itself
  100. must call ``self.wait()``, and asynchronous callbacks should call
  101. ``self.stop()`` to signal completion.
  102. By default, a new `.IOLoop` is constructed for each test and is available
  103. as ``self.io_loop``. If the code being tested requires a
  104. global `.IOLoop`, subclasses should override `get_new_ioloop` to return it.
  105. The `.IOLoop`'s ``start`` and ``stop`` methods should not be
  106. called directly. Instead, use `self.stop <stop>` and `self.wait
  107. <wait>`. Arguments passed to ``self.stop`` are returned from
  108. ``self.wait``. It is possible to have multiple ``wait``/``stop``
  109. cycles in the same test.
  110. Example::
  111. # This test uses coroutine style.
  112. class MyTestCase(AsyncTestCase):
  113. @tornado.testing.gen_test
  114. def test_http_fetch(self):
  115. client = AsyncHTTPClient()
  116. response = yield client.fetch("http://www.tornadoweb.org")
  117. # Test contents of response
  118. self.assertIn("FriendFeed", response.body)
  119. # This test uses argument passing between self.stop and self.wait.
  120. class MyTestCase2(AsyncTestCase):
  121. def test_http_fetch(self):
  122. client = AsyncHTTPClient()
  123. client.fetch("http://www.tornadoweb.org/", self.stop)
  124. response = self.wait()
  125. # Test contents of response
  126. self.assertIn("FriendFeed", response.body)
  127. """
  128. def __init__(self, methodName: str = "runTest") -> None:
  129. super(AsyncTestCase, self).__init__(methodName)
  130. self.__stopped = False
  131. self.__running = False
  132. self.__failure = None # type: Optional[_ExcInfoTuple]
  133. self.__stop_args = None # type: Any
  134. self.__timeout = None # type: Optional[object]
  135. # It's easy to forget the @gen_test decorator, but if you do
  136. # the test will silently be ignored because nothing will consume
  137. # the generator. Replace the test method with a wrapper that will
  138. # make sure it's not an undecorated generator.
  139. setattr(self, methodName, _TestMethodWrapper(getattr(self, methodName)))
  140. # Not used in this class itself, but used by @gen_test
  141. self._test_generator = None # type: Optional[Union[Generator, Coroutine]]
  142. def setUp(self) -> None:
  143. super(AsyncTestCase, self).setUp()
  144. self.io_loop = self.get_new_ioloop()
  145. self.io_loop.make_current()
  146. def tearDown(self) -> None:
  147. # Native coroutines tend to produce warnings if they're not
  148. # allowed to run to completion. It's difficult to ensure that
  149. # this always happens in tests, so cancel any tasks that are
  150. # still pending by the time we get here.
  151. asyncio_loop = self.io_loop.asyncio_loop # type: ignore
  152. if hasattr(asyncio, "all_tasks"): # py37
  153. tasks = asyncio.all_tasks(asyncio_loop) # type: ignore
  154. else:
  155. tasks = asyncio.Task.all_tasks(asyncio_loop)
  156. # Tasks that are done may still appear here and may contain
  157. # non-cancellation exceptions, so filter them out.
  158. tasks = [t for t in tasks if not t.done()]
  159. for t in tasks:
  160. t.cancel()
  161. # Allow the tasks to run and finalize themselves (which means
  162. # raising a CancelledError inside the coroutine). This may
  163. # just transform the "task was destroyed but it is pending"
  164. # warning into a "uncaught CancelledError" warning, but
  165. # catching CancelledErrors in coroutines that may leak is
  166. # simpler than ensuring that no coroutines leak.
  167. if tasks:
  168. done, pending = self.io_loop.run_sync(lambda: asyncio.wait(tasks))
  169. assert not pending
  170. # If any task failed with anything but a CancelledError, raise it.
  171. for f in done:
  172. try:
  173. f.result()
  174. except asyncio.CancelledError:
  175. pass
  176. # Clean up Subprocess, so it can be used again with a new ioloop.
  177. Subprocess.uninitialize()
  178. self.io_loop.clear_current()
  179. if not isinstance(self.io_loop, _NON_OWNED_IOLOOPS):
  180. # Try to clean up any file descriptors left open in the ioloop.
  181. # This avoids leaks, especially when tests are run repeatedly
  182. # in the same process with autoreload (because curl does not
  183. # set FD_CLOEXEC on its file descriptors)
  184. self.io_loop.close(all_fds=True)
  185. super(AsyncTestCase, self).tearDown()
  186. # In case an exception escaped or the StackContext caught an exception
  187. # when there wasn't a wait() to re-raise it, do so here.
  188. # This is our last chance to raise an exception in a way that the
  189. # unittest machinery understands.
  190. self.__rethrow()
  191. def get_new_ioloop(self) -> IOLoop:
  192. """Returns the `.IOLoop` to use for this test.
  193. By default, a new `.IOLoop` is created for each test.
  194. Subclasses may override this method to return
  195. `.IOLoop.current()` if it is not appropriate to use a new
  196. `.IOLoop` in each tests (for example, if there are global
  197. singletons using the default `.IOLoop`) or if a per-test event
  198. loop is being provided by another system (such as
  199. ``pytest-asyncio``).
  200. """
  201. return IOLoop()
  202. def _handle_exception(
  203. self, typ: Type[Exception], value: Exception, tb: TracebackType
  204. ) -> bool:
  205. if self.__failure is None:
  206. self.__failure = (typ, value, tb)
  207. else:
  208. app_log.error(
  209. "multiple unhandled exceptions in test", exc_info=(typ, value, tb)
  210. )
  211. self.stop()
  212. return True
  213. def __rethrow(self) -> None:
  214. if self.__failure is not None:
  215. failure = self.__failure
  216. self.__failure = None
  217. raise_exc_info(failure)
  218. def run(self, result: unittest.TestResult = None) -> unittest.TestCase:
  219. ret = super(AsyncTestCase, self).run(result)
  220. # As a last resort, if an exception escaped super.run() and wasn't
  221. # re-raised in tearDown, raise it here. This will cause the
  222. # unittest run to fail messily, but that's better than silently
  223. # ignoring an error.
  224. self.__rethrow()
  225. return ret
  226. def stop(self, _arg: Any = None, **kwargs: Any) -> None:
  227. """Stops the `.IOLoop`, causing one pending (or future) call to `wait()`
  228. to return.
  229. Keyword arguments or a single positional argument passed to `stop()` are
  230. saved and will be returned by `wait()`.
  231. .. deprecated:: 5.1
  232. `stop` and `wait` are deprecated; use ``@gen_test`` instead.
  233. """
  234. assert _arg is None or not kwargs
  235. self.__stop_args = kwargs or _arg
  236. if self.__running:
  237. self.io_loop.stop()
  238. self.__running = False
  239. self.__stopped = True
  240. def wait(
  241. self, condition: Callable[..., bool] = None, timeout: float = None
  242. ) -> None:
  243. """Runs the `.IOLoop` until stop is called or timeout has passed.
  244. In the event of a timeout, an exception will be thrown. The
  245. default timeout is 5 seconds; it may be overridden with a
  246. ``timeout`` keyword argument or globally with the
  247. ``ASYNC_TEST_TIMEOUT`` environment variable.
  248. If ``condition`` is not ``None``, the `.IOLoop` will be restarted
  249. after `stop()` until ``condition()`` returns ``True``.
  250. .. versionchanged:: 3.1
  251. Added the ``ASYNC_TEST_TIMEOUT`` environment variable.
  252. .. deprecated:: 5.1
  253. `stop` and `wait` are deprecated; use ``@gen_test`` instead.
  254. """
  255. if timeout is None:
  256. timeout = get_async_test_timeout()
  257. if not self.__stopped:
  258. if timeout:
  259. def timeout_func() -> None:
  260. try:
  261. raise self.failureException(
  262. "Async operation timed out after %s seconds" % timeout
  263. )
  264. except Exception:
  265. self.__failure = sys.exc_info()
  266. self.stop()
  267. self.__timeout = self.io_loop.add_timeout(
  268. self.io_loop.time() + timeout, timeout_func
  269. )
  270. while True:
  271. self.__running = True
  272. self.io_loop.start()
  273. if self.__failure is not None or condition is None or condition():
  274. break
  275. if self.__timeout is not None:
  276. self.io_loop.remove_timeout(self.__timeout)
  277. self.__timeout = None
  278. assert self.__stopped
  279. self.__stopped = False
  280. self.__rethrow()
  281. result = self.__stop_args
  282. self.__stop_args = None
  283. return result
  284. class AsyncHTTPTestCase(AsyncTestCase):
  285. """A test case that starts up an HTTP server.
  286. Subclasses must override `get_app()`, which returns the
  287. `tornado.web.Application` (or other `.HTTPServer` callback) to be tested.
  288. Tests will typically use the provided ``self.http_client`` to fetch
  289. URLs from this server.
  290. Example, assuming the "Hello, world" example from the user guide is in
  291. ``hello.py``::
  292. import hello
  293. class TestHelloApp(AsyncHTTPTestCase):
  294. def get_app(self):
  295. return hello.make_app()
  296. def test_homepage(self):
  297. response = self.fetch('/')
  298. self.assertEqual(response.code, 200)
  299. self.assertEqual(response.body, 'Hello, world')
  300. That call to ``self.fetch()`` is equivalent to ::
  301. self.http_client.fetch(self.get_url('/'), self.stop)
  302. response = self.wait()
  303. which illustrates how AsyncTestCase can turn an asynchronous operation,
  304. like ``http_client.fetch()``, into a synchronous operation. If you need
  305. to do other asynchronous operations in tests, you'll probably need to use
  306. ``stop()`` and ``wait()`` yourself.
  307. """
  308. def setUp(self) -> None:
  309. super(AsyncHTTPTestCase, self).setUp()
  310. sock, port = bind_unused_port()
  311. self.__port = port
  312. self.http_client = self.get_http_client()
  313. self._app = self.get_app()
  314. self.http_server = self.get_http_server()
  315. self.http_server.add_sockets([sock])
  316. def get_http_client(self) -> AsyncHTTPClient:
  317. return AsyncHTTPClient()
  318. def get_http_server(self) -> HTTPServer:
  319. return HTTPServer(self._app, **self.get_httpserver_options())
  320. def get_app(self) -> Application:
  321. """Should be overridden by subclasses to return a
  322. `tornado.web.Application` or other `.HTTPServer` callback.
  323. """
  324. raise NotImplementedError()
  325. def fetch(
  326. self, path: str, raise_error: bool = False, **kwargs: Any
  327. ) -> HTTPResponse:
  328. """Convenience method to synchronously fetch a URL.
  329. The given path will be appended to the local server's host and
  330. port. Any additional keyword arguments will be passed directly to
  331. `.AsyncHTTPClient.fetch` (and so could be used to pass
  332. ``method="POST"``, ``body="..."``, etc).
  333. If the path begins with http:// or https://, it will be treated as a
  334. full URL and will be fetched as-is.
  335. If ``raise_error`` is ``True``, a `tornado.httpclient.HTTPError` will
  336. be raised if the response code is not 200. This is the same behavior
  337. as the ``raise_error`` argument to `.AsyncHTTPClient.fetch`, but
  338. the default is ``False`` here (it's ``True`` in `.AsyncHTTPClient`)
  339. because tests often need to deal with non-200 response codes.
  340. .. versionchanged:: 5.0
  341. Added support for absolute URLs.
  342. .. versionchanged:: 5.1
  343. Added the ``raise_error`` argument.
  344. .. deprecated:: 5.1
  345. This method currently turns any exception into an
  346. `.HTTPResponse` with status code 599. In Tornado 6.0,
  347. errors other than `tornado.httpclient.HTTPError` will be
  348. passed through, and ``raise_error=False`` will only
  349. suppress errors that would be raised due to non-200
  350. response codes.
  351. """
  352. if path.lower().startswith(("http://", "https://")):
  353. url = path
  354. else:
  355. url = self.get_url(path)
  356. return self.io_loop.run_sync(
  357. lambda: self.http_client.fetch(url, raise_error=raise_error, **kwargs),
  358. timeout=get_async_test_timeout(),
  359. )
  360. def get_httpserver_options(self) -> Dict[str, Any]:
  361. """May be overridden by subclasses to return additional
  362. keyword arguments for the server.
  363. """
  364. return {}
  365. def get_http_port(self) -> int:
  366. """Returns the port used by the server.
  367. A new port is chosen for each test.
  368. """
  369. return self.__port
  370. def get_protocol(self) -> str:
  371. return "http"
  372. def get_url(self, path: str) -> str:
  373. """Returns an absolute url for the given path on the test server."""
  374. return "%s://127.0.0.1:%s%s" % (self.get_protocol(), self.get_http_port(), path)
  375. def tearDown(self) -> None:
  376. self.http_server.stop()
  377. self.io_loop.run_sync(
  378. self.http_server.close_all_connections, timeout=get_async_test_timeout()
  379. )
  380. self.http_client.close()
  381. del self.http_server
  382. del self._app
  383. super(AsyncHTTPTestCase, self).tearDown()
  384. class AsyncHTTPSTestCase(AsyncHTTPTestCase):
  385. """A test case that starts an HTTPS server.
  386. Interface is generally the same as `AsyncHTTPTestCase`.
  387. """
  388. def get_http_client(self) -> AsyncHTTPClient:
  389. return AsyncHTTPClient(force_instance=True, defaults=dict(validate_cert=False))
  390. def get_httpserver_options(self) -> Dict[str, Any]:
  391. return dict(ssl_options=self.get_ssl_options())
  392. def get_ssl_options(self) -> Dict[str, Any]:
  393. """May be overridden by subclasses to select SSL options.
  394. By default includes a self-signed testing certificate.
  395. """
  396. return AsyncHTTPSTestCase.default_ssl_options()
  397. @staticmethod
  398. def default_ssl_options() -> Dict[str, Any]:
  399. # Testing keys were generated with:
  400. # openssl req -new -keyout tornado/test/test.key \
  401. # -out tornado/test/test.crt -nodes -days 3650 -x509
  402. module_dir = os.path.dirname(__file__)
  403. return dict(
  404. certfile=os.path.join(module_dir, "test", "test.crt"),
  405. keyfile=os.path.join(module_dir, "test", "test.key"),
  406. )
  407. def get_protocol(self) -> str:
  408. return "https"
  409. @typing.overload
  410. def gen_test(
  411. *, timeout: float = None
  412. ) -> Callable[[Callable[..., Union[Generator, "Coroutine"]]], Callable[..., None]]:
  413. pass
  414. @typing.overload # noqa: F811
  415. def gen_test(func: Callable[..., Union[Generator, "Coroutine"]]) -> Callable[..., None]:
  416. pass
  417. def gen_test( # noqa: F811
  418. func: Callable[..., Union[Generator, "Coroutine"]] = None, timeout: float = None
  419. ) -> Union[
  420. Callable[..., None],
  421. Callable[[Callable[..., Union[Generator, "Coroutine"]]], Callable[..., None]],
  422. ]:
  423. """Testing equivalent of ``@gen.coroutine``, to be applied to test methods.
  424. ``@gen.coroutine`` cannot be used on tests because the `.IOLoop` is not
  425. already running. ``@gen_test`` should be applied to test methods
  426. on subclasses of `AsyncTestCase`.
  427. Example::
  428. class MyTest(AsyncHTTPTestCase):
  429. @gen_test
  430. def test_something(self):
  431. response = yield self.http_client.fetch(self.get_url('/'))
  432. By default, ``@gen_test`` times out after 5 seconds. The timeout may be
  433. overridden globally with the ``ASYNC_TEST_TIMEOUT`` environment variable,
  434. or for each test with the ``timeout`` keyword argument::
  435. class MyTest(AsyncHTTPTestCase):
  436. @gen_test(timeout=10)
  437. def test_something_slow(self):
  438. response = yield self.http_client.fetch(self.get_url('/'))
  439. Note that ``@gen_test`` is incompatible with `AsyncTestCase.stop`,
  440. `AsyncTestCase.wait`, and `AsyncHTTPTestCase.fetch`. Use ``yield
  441. self.http_client.fetch(self.get_url())`` as shown above instead.
  442. .. versionadded:: 3.1
  443. The ``timeout`` argument and ``ASYNC_TEST_TIMEOUT`` environment
  444. variable.
  445. .. versionchanged:: 4.0
  446. The wrapper now passes along ``*args, **kwargs`` so it can be used
  447. on functions with arguments.
  448. """
  449. if timeout is None:
  450. timeout = get_async_test_timeout()
  451. def wrap(f: Callable[..., Union[Generator, "Coroutine"]]) -> Callable[..., None]:
  452. # Stack up several decorators to allow us to access the generator
  453. # object itself. In the innermost wrapper, we capture the generator
  454. # and save it in an attribute of self. Next, we run the wrapped
  455. # function through @gen.coroutine. Finally, the coroutine is
  456. # wrapped again to make it synchronous with run_sync.
  457. #
  458. # This is a good case study arguing for either some sort of
  459. # extensibility in the gen decorators or cancellation support.
  460. @functools.wraps(f)
  461. def pre_coroutine(self, *args, **kwargs):
  462. # type: (AsyncTestCase, *Any, **Any) -> Union[Generator, Coroutine]
  463. # Type comments used to avoid pypy3 bug.
  464. result = f(self, *args, **kwargs)
  465. if isinstance(result, Generator) or inspect.iscoroutine(result):
  466. self._test_generator = result
  467. else:
  468. self._test_generator = None
  469. return result
  470. if inspect.iscoroutinefunction(f):
  471. coro = pre_coroutine
  472. else:
  473. coro = gen.coroutine(pre_coroutine)
  474. @functools.wraps(coro)
  475. def post_coroutine(self, *args, **kwargs):
  476. # type: (AsyncTestCase, *Any, **Any) -> None
  477. try:
  478. return self.io_loop.run_sync(
  479. functools.partial(coro, self, *args, **kwargs), timeout=timeout
  480. )
  481. except TimeoutError as e:
  482. # run_sync raises an error with an unhelpful traceback.
  483. # If the underlying generator is still running, we can throw the
  484. # exception back into it so the stack trace is replaced by the
  485. # point where the test is stopped. The only reason the generator
  486. # would not be running would be if it were cancelled, which means
  487. # a native coroutine, so we can rely on the cr_running attribute.
  488. if self._test_generator is not None and getattr(
  489. self._test_generator, "cr_running", True
  490. ):
  491. self._test_generator.throw(type(e), e)
  492. # In case the test contains an overly broad except
  493. # clause, we may get back here.
  494. # Coroutine was stopped or didn't raise a useful stack trace,
  495. # so re-raise the original exception which is better than nothing.
  496. raise
  497. return post_coroutine
  498. if func is not None:
  499. # Used like:
  500. # @gen_test
  501. # def f(self):
  502. # pass
  503. return wrap(func)
  504. else:
  505. # Used like @gen_test(timeout=10)
  506. return wrap
  507. # Without this attribute, nosetests will try to run gen_test as a test
  508. # anywhere it is imported.
  509. gen_test.__test__ = False # type: ignore
  510. class ExpectLog(logging.Filter):
  511. """Context manager to capture and suppress expected log output.
  512. Useful to make tests of error conditions less noisy, while still
  513. leaving unexpected log entries visible. *Not thread safe.*
  514. The attribute ``logged_stack`` is set to ``True`` if any exception
  515. stack trace was logged.
  516. Usage::
  517. with ExpectLog('tornado.application', "Uncaught exception"):
  518. error_response = self.fetch("/some_page")
  519. .. versionchanged:: 4.3
  520. Added the ``logged_stack`` attribute.
  521. """
  522. def __init__(
  523. self,
  524. logger: Union[logging.Logger, basestring_type],
  525. regex: str,
  526. required: bool = True,
  527. ) -> None:
  528. """Constructs an ExpectLog context manager.
  529. :param logger: Logger object (or name of logger) to watch. Pass
  530. an empty string to watch the root logger.
  531. :param regex: Regular expression to match. Any log entries on
  532. the specified logger that match this regex will be suppressed.
  533. :param required: If true, an exception will be raised if the end of
  534. the ``with`` statement is reached without matching any log entries.
  535. """
  536. if isinstance(logger, basestring_type):
  537. logger = logging.getLogger(logger)
  538. self.logger = logger
  539. self.regex = re.compile(regex)
  540. self.required = required
  541. self.matched = False
  542. self.logged_stack = False
  543. def filter(self, record: logging.LogRecord) -> bool:
  544. if record.exc_info:
  545. self.logged_stack = True
  546. message = record.getMessage()
  547. if self.regex.match(message):
  548. self.matched = True
  549. return False
  550. return True
  551. def __enter__(self) -> "ExpectLog":
  552. self.logger.addFilter(self)
  553. return self
  554. def __exit__(
  555. self,
  556. typ: "Optional[Type[BaseException]]",
  557. value: Optional[BaseException],
  558. tb: Optional[TracebackType],
  559. ) -> None:
  560. self.logger.removeFilter(self)
  561. if not typ and self.required and not self.matched:
  562. raise Exception("did not get expected log message")
  563. def main(**kwargs: Any) -> None:
  564. """A simple test runner.
  565. This test runner is essentially equivalent to `unittest.main` from
  566. the standard library, but adds support for Tornado-style option
  567. parsing and log formatting. It is *not* necessary to use this
  568. `main` function to run tests using `AsyncTestCase`; these tests
  569. are self-contained and can run with any test runner.
  570. The easiest way to run a test is via the command line::
  571. python -m tornado.testing tornado.test.web_test
  572. See the standard library ``unittest`` module for ways in which
  573. tests can be specified.
  574. Projects with many tests may wish to define a test script like
  575. ``tornado/test/runtests.py``. This script should define a method
  576. ``all()`` which returns a test suite and then call
  577. `tornado.testing.main()`. Note that even when a test script is
  578. used, the ``all()`` test suite may be overridden by naming a
  579. single test on the command line::
  580. # Runs all tests
  581. python -m tornado.test.runtests
  582. # Runs one test
  583. python -m tornado.test.runtests tornado.test.web_test
  584. Additional keyword arguments passed through to ``unittest.main()``.
  585. For example, use ``tornado.testing.main(verbosity=2)``
  586. to show many test details as they are run.
  587. See http://docs.python.org/library/unittest.html#unittest.main
  588. for full argument list.
  589. .. versionchanged:: 5.0
  590. This function produces no output of its own; only that produced
  591. by the `unittest` module (previously it would add a PASS or FAIL
  592. log message).
  593. """
  594. from tornado.options import define, options, parse_command_line
  595. define(
  596. "exception_on_interrupt",
  597. type=bool,
  598. default=True,
  599. help=(
  600. "If true (default), ctrl-c raises a KeyboardInterrupt "
  601. "exception. This prints a stack trace but cannot interrupt "
  602. "certain operations. If false, the process is more reliably "
  603. "killed, but does not print a stack trace."
  604. ),
  605. )
  606. # support the same options as unittest's command-line interface
  607. define("verbose", type=bool)
  608. define("quiet", type=bool)
  609. define("failfast", type=bool)
  610. define("catch", type=bool)
  611. define("buffer", type=bool)
  612. argv = [sys.argv[0]] + parse_command_line(sys.argv)
  613. if not options.exception_on_interrupt:
  614. signal.signal(signal.SIGINT, signal.SIG_DFL)
  615. if options.verbose is not None:
  616. kwargs["verbosity"] = 2
  617. if options.quiet is not None:
  618. kwargs["verbosity"] = 0
  619. if options.failfast is not None:
  620. kwargs["failfast"] = True
  621. if options.catch is not None:
  622. kwargs["catchbreak"] = True
  623. if options.buffer is not None:
  624. kwargs["buffer"] = True
  625. if __name__ == "__main__" and len(argv) == 1:
  626. print("No tests specified", file=sys.stderr)
  627. sys.exit(1)
  628. # In order to be able to run tests by their fully-qualified name
  629. # on the command line without importing all tests here,
  630. # module must be set to None. Python 3.2's unittest.main ignores
  631. # defaultTest if no module is given (it tries to do its own
  632. # test discovery, which is incompatible with auto2to3), so don't
  633. # set module if we're not asking for a specific test.
  634. if len(argv) > 1:
  635. unittest.main(module=None, argv=argv, **kwargs) # type: ignore
  636. else:
  637. unittest.main(defaultTest="all", argv=argv, **kwargs)
  638. if __name__ == "__main__":
  639. main()