runtests.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. from __future__ import absolute_import, division, print_function
  2. import gc
  3. import io
  4. import locale # system locale module, not tornado.locale
  5. import logging
  6. import operator
  7. import textwrap
  8. import sys
  9. from tornado.httpclient import AsyncHTTPClient
  10. from tornado.httpserver import HTTPServer
  11. from tornado.ioloop import IOLoop
  12. from tornado.netutil import Resolver
  13. from tornado.options import define, options, add_parse_callback
  14. from tornado.test.util import unittest
  15. try:
  16. reduce # py2
  17. except NameError:
  18. from functools import reduce # py3
  19. TEST_MODULES = [
  20. 'tornado.httputil.doctests',
  21. 'tornado.iostream.doctests',
  22. 'tornado.util.doctests',
  23. 'tornado.test.asyncio_test',
  24. 'tornado.test.auth_test',
  25. 'tornado.test.autoreload_test',
  26. 'tornado.test.concurrent_test',
  27. 'tornado.test.curl_httpclient_test',
  28. 'tornado.test.escape_test',
  29. 'tornado.test.gen_test',
  30. 'tornado.test.http1connection_test',
  31. 'tornado.test.httpclient_test',
  32. 'tornado.test.httpserver_test',
  33. 'tornado.test.httputil_test',
  34. 'tornado.test.import_test',
  35. 'tornado.test.ioloop_test',
  36. 'tornado.test.iostream_test',
  37. 'tornado.test.locale_test',
  38. 'tornado.test.locks_test',
  39. 'tornado.test.netutil_test',
  40. 'tornado.test.log_test',
  41. 'tornado.test.options_test',
  42. 'tornado.test.process_test',
  43. 'tornado.test.queues_test',
  44. 'tornado.test.routing_test',
  45. 'tornado.test.simple_httpclient_test',
  46. 'tornado.test.stack_context_test',
  47. 'tornado.test.tcpclient_test',
  48. 'tornado.test.tcpserver_test',
  49. 'tornado.test.template_test',
  50. 'tornado.test.testing_test',
  51. 'tornado.test.twisted_test',
  52. 'tornado.test.util_test',
  53. 'tornado.test.web_test',
  54. 'tornado.test.websocket_test',
  55. 'tornado.test.windows_test',
  56. 'tornado.test.wsgi_test',
  57. ]
  58. def all():
  59. return unittest.defaultTestLoader.loadTestsFromNames(TEST_MODULES)
  60. def test_runner_factory(stderr):
  61. class TornadoTextTestRunner(unittest.TextTestRunner):
  62. def __init__(self, *args, **kwargs):
  63. super(TornadoTextTestRunner, self).__init__(*args, stream=stderr, **kwargs)
  64. def run(self, test):
  65. result = super(TornadoTextTestRunner, self).run(test)
  66. if result.skipped:
  67. skip_reasons = set(reason for (test, reason) in result.skipped)
  68. self.stream.write(textwrap.fill(
  69. "Some tests were skipped because: %s" %
  70. ", ".join(sorted(skip_reasons))))
  71. self.stream.write("\n")
  72. return result
  73. return TornadoTextTestRunner
  74. class LogCounter(logging.Filter):
  75. """Counts the number of WARNING or higher log records."""
  76. def __init__(self, *args, **kwargs):
  77. # Can't use super() because logging.Filter is an old-style class in py26
  78. logging.Filter.__init__(self, *args, **kwargs)
  79. self.info_count = self.warning_count = self.error_count = 0
  80. def filter(self, record):
  81. if record.levelno >= logging.ERROR:
  82. self.error_count += 1
  83. elif record.levelno >= logging.WARNING:
  84. self.warning_count += 1
  85. elif record.levelno >= logging.INFO:
  86. self.info_count += 1
  87. return True
  88. class CountingStderr(io.IOBase):
  89. def __init__(self, real):
  90. self.real = real
  91. self.byte_count = 0
  92. def write(self, data):
  93. self.byte_count += len(data)
  94. return self.real.write(data)
  95. def flush(self):
  96. return self.real.flush()
  97. def main():
  98. # The -W command-line option does not work in a virtualenv with
  99. # python 3 (as of virtualenv 1.7), so configure warnings
  100. # programmatically instead.
  101. import warnings
  102. # Be strict about most warnings. This also turns on warnings that are
  103. # ignored by default, including DeprecationWarnings and
  104. # python 3.2's ResourceWarnings.
  105. warnings.filterwarnings("error")
  106. # setuptools sometimes gives ImportWarnings about things that are on
  107. # sys.path even if they're not being used.
  108. warnings.filterwarnings("ignore", category=ImportWarning)
  109. # Tornado generally shouldn't use anything deprecated, but some of
  110. # our dependencies do (last match wins).
  111. warnings.filterwarnings("ignore", category=DeprecationWarning)
  112. warnings.filterwarnings("error", category=DeprecationWarning,
  113. module=r"tornado\..*")
  114. warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
  115. warnings.filterwarnings("error", category=PendingDeprecationWarning,
  116. module=r"tornado\..*")
  117. # The unittest module is aggressive about deprecating redundant methods,
  118. # leaving some without non-deprecated spellings that work on both
  119. # 2.7 and 3.2
  120. warnings.filterwarnings("ignore", category=DeprecationWarning,
  121. message="Please use assert.* instead")
  122. warnings.filterwarnings("ignore", category=PendingDeprecationWarning,
  123. message="Please use assert.* instead")
  124. # Twisted 15.0.0 triggers some warnings on py3 with -bb.
  125. warnings.filterwarnings("ignore", category=BytesWarning,
  126. module=r"twisted\..*")
  127. if (3,) < sys.version_info < (3, 6):
  128. # Prior to 3.6, async ResourceWarnings were rather noisy
  129. # and even
  130. # `python3.4 -W error -c 'import asyncio; asyncio.get_event_loop()'`
  131. # would generate a warning.
  132. warnings.filterwarnings("ignore", category=ResourceWarning, # noqa: F821
  133. module=r"asyncio\..*")
  134. logging.getLogger("tornado.access").setLevel(logging.CRITICAL)
  135. define('httpclient', type=str, default=None,
  136. callback=lambda s: AsyncHTTPClient.configure(
  137. s, defaults=dict(allow_ipv6=False)))
  138. define('httpserver', type=str, default=None,
  139. callback=HTTPServer.configure)
  140. define('ioloop', type=str, default=None)
  141. define('ioloop_time_monotonic', default=False)
  142. define('resolver', type=str, default=None,
  143. callback=Resolver.configure)
  144. define('debug_gc', type=str, multiple=True,
  145. help="A comma-separated list of gc module debug constants, "
  146. "e.g. DEBUG_STATS or DEBUG_COLLECTABLE,DEBUG_OBJECTS",
  147. callback=lambda values: gc.set_debug(
  148. reduce(operator.or_, (getattr(gc, v) for v in values))))
  149. define('locale', type=str, default=None,
  150. callback=lambda x: locale.setlocale(locale.LC_ALL, x))
  151. def configure_ioloop():
  152. kwargs = {}
  153. if options.ioloop_time_monotonic:
  154. from tornado.platform.auto import monotonic_time
  155. if monotonic_time is None:
  156. raise RuntimeError("monotonic clock not found")
  157. kwargs['time_func'] = monotonic_time
  158. if options.ioloop or kwargs:
  159. IOLoop.configure(options.ioloop, **kwargs)
  160. add_parse_callback(configure_ioloop)
  161. log_counter = LogCounter()
  162. add_parse_callback(
  163. lambda: logging.getLogger().handlers[0].addFilter(log_counter))
  164. # Certain errors (especially "unclosed resource" errors raised in
  165. # destructors) go directly to stderr instead of logging. Count
  166. # anything written by anything but the test runner as an error.
  167. orig_stderr = sys.stderr
  168. sys.stderr = CountingStderr(orig_stderr)
  169. import tornado.testing
  170. kwargs = {}
  171. if sys.version_info >= (3, 2):
  172. # HACK: unittest.main will make its own changes to the warning
  173. # configuration, which may conflict with the settings above
  174. # or command-line flags like -bb. Passing warnings=False
  175. # suppresses this behavior, although this looks like an implementation
  176. # detail. http://bugs.python.org/issue15626
  177. kwargs['warnings'] = False
  178. kwargs['testRunner'] = test_runner_factory(orig_stderr)
  179. try:
  180. tornado.testing.main(**kwargs)
  181. finally:
  182. # The tests should run clean; consider it a failure if they
  183. # logged anything at info level or above.
  184. if (log_counter.info_count > 0 or
  185. log_counter.warning_count > 0 or
  186. log_counter.error_count > 0 or
  187. sys.stderr.byte_count > 0):
  188. logging.error("logged %d infos, %d warnings, %d errors, and %d bytes to stderr",
  189. log_counter.info_count, log_counter.warning_count,
  190. log_counter.error_count, sys.stderr.byte_count)
  191. sys.exit(1)
  192. if __name__ == '__main__':
  193. main()