runtests.py 7.3 KB

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