printer.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. # Copyright (C) 2012 Google, Inc.
  2. # Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org)
  3. #
  4. # Redistribution and use in source and binary forms, with or without
  5. # modification, are permitted provided that the following conditions
  6. # are met:
  7. # 1. Redistributions of source code must retain the above copyright
  8. # notice, this list of conditions and the following disclaimer.
  9. # 2. Redistributions in binary form must reproduce the above copyright
  10. # notice, this list of conditions and the following disclaimer in the
  11. # documentation and/or other materials provided with the distribution.
  12. #
  13. # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
  14. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  15. # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  16. # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
  17. # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  18. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  19. # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  20. # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  21. # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  22. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  23. import logging
  24. import StringIO
  25. from webkitpy.common.system.systemhost import SystemHost
  26. from webkitpy.layout_tests.views.metered_stream import MeteredStream
  27. _log = logging.getLogger(__name__)
  28. class Printer(object):
  29. def __init__(self, stream, options=None):
  30. self.stream = stream
  31. self.meter = None
  32. self.options = options
  33. self.num_tests = 0
  34. self.num_started = 0
  35. self.num_errors = 0
  36. self.num_failures = 0
  37. self.running_tests = []
  38. self.completed_tests = []
  39. if options:
  40. self.configure(options)
  41. def configure(self, options):
  42. self.options = options
  43. if options.timing:
  44. # --timing implies --verbose
  45. options.verbose = max(options.verbose, 1)
  46. log_level = logging.INFO
  47. if options.quiet:
  48. log_level = logging.WARNING
  49. elif options.verbose == 2:
  50. log_level = logging.DEBUG
  51. self.meter = MeteredStream(self.stream, (options.verbose == 2),
  52. number_of_columns=SystemHost().platform.terminal_width())
  53. handler = logging.StreamHandler(self.stream)
  54. # We constrain the level on the handler rather than on the root
  55. # logger itself. This is probably better because the handler is
  56. # configured and known only to this module, whereas the root logger
  57. # is an object shared (and potentially modified) by many modules.
  58. # Modifying the handler, then, is less intrusive and less likely to
  59. # interfere with modifications made by other modules (e.g. in unit
  60. # tests).
  61. handler.name = __name__
  62. handler.setLevel(log_level)
  63. formatter = logging.Formatter("%(message)s")
  64. handler.setFormatter(formatter)
  65. logger = logging.getLogger()
  66. logger.addHandler(handler)
  67. logger.setLevel(logging.NOTSET)
  68. # Filter out most webkitpy messages.
  69. #
  70. # Messages can be selectively re-enabled for this script by updating
  71. # this method accordingly.
  72. def filter_records(record):
  73. """Filter out autoinstall and non-third-party webkitpy messages."""
  74. # FIXME: Figure out a way not to use strings here, for example by
  75. # using syntax like webkitpy.test.__name__. We want to be
  76. # sure not to import any non-Python 2.4 code, though, until
  77. # after the version-checking code has executed.
  78. if (record.name.startswith("webkitpy.common.system.autoinstall") or
  79. record.name.startswith("webkitpy.test")):
  80. return True
  81. if record.name.startswith("webkitpy"):
  82. return False
  83. return True
  84. testing_filter = logging.Filter()
  85. testing_filter.filter = filter_records
  86. # Display a message so developers are not mystified as to why
  87. # logging does not work in the unit tests.
  88. _log.info("Suppressing most webkitpy logging while running unit tests.")
  89. handler.addFilter(testing_filter)
  90. if self.options.pass_through:
  91. # FIXME: Can't import at top of file, as outputcapture needs unittest2
  92. from webkitpy.common.system import outputcapture
  93. outputcapture.OutputCapture.stream_wrapper = _CaptureAndPassThroughStream
  94. def write_update(self, msg):
  95. self.meter.write_update(msg)
  96. def print_started_test(self, source, test_name):
  97. self.running_tests.append(test_name)
  98. if len(self.running_tests) > 1:
  99. suffix = ' (+%d)' % (len(self.running_tests) - 1)
  100. else:
  101. suffix = ''
  102. if self.options.verbose:
  103. write = self.meter.write_update
  104. else:
  105. write = self.meter.write_throttled_update
  106. write(self._test_line(self.running_tests[0], suffix))
  107. def print_finished_test(self, source, test_name, test_time, failures, errors):
  108. write = self.meter.writeln
  109. if failures:
  110. lines = failures[0].splitlines() + ['']
  111. suffix = ' failed:'
  112. self.num_failures += 1
  113. elif errors:
  114. lines = errors[0].splitlines() + ['']
  115. suffix = ' erred:'
  116. self.num_errors += 1
  117. else:
  118. suffix = ' passed'
  119. lines = []
  120. if self.options.verbose:
  121. write = self.meter.writeln
  122. else:
  123. write = self.meter.write_throttled_update
  124. if self.options.timing:
  125. suffix += ' %.4fs' % test_time
  126. self.num_started += 1
  127. if test_name == self.running_tests[0]:
  128. self.completed_tests.insert(0, [test_name, suffix, lines])
  129. else:
  130. self.completed_tests.append([test_name, suffix, lines])
  131. self.running_tests.remove(test_name)
  132. for test_name, msg, lines in self.completed_tests:
  133. if lines:
  134. self.meter.writeln(self._test_line(test_name, msg))
  135. for line in lines:
  136. self.meter.writeln(' ' + line)
  137. else:
  138. write(self._test_line(test_name, msg))
  139. self.completed_tests = []
  140. def _test_line(self, test_name, suffix):
  141. format_string = '[%d/%d] %s%s'
  142. status_line = format_string % (self.num_started, self.num_tests, test_name, suffix)
  143. if len(status_line) > self.meter.number_of_columns():
  144. overflow_columns = len(status_line) - self.meter.number_of_columns()
  145. ellipsis = '...'
  146. if len(test_name) < overflow_columns + len(ellipsis) + 3:
  147. # We don't have enough space even if we elide, just show the test method name.
  148. test_name = test_name.split('.')[-1]
  149. else:
  150. new_length = len(test_name) - overflow_columns - len(ellipsis)
  151. prefix = int(new_length / 2)
  152. test_name = test_name[:prefix] + ellipsis + test_name[-(new_length - prefix):]
  153. return format_string % (self.num_started, self.num_tests, test_name, suffix)
  154. def print_result(self, run_time):
  155. write = self.meter.writeln
  156. write('Ran %d test%s in %.3fs' % (self.num_started, self.num_started != 1 and "s" or "", run_time))
  157. if self.num_failures or self.num_errors:
  158. write('FAILED (failures=%d, errors=%d)\n' % (self.num_failures, self.num_errors))
  159. else:
  160. write('\nOK\n')
  161. class _CaptureAndPassThroughStream(object):
  162. def __init__(self, stream):
  163. self._buffer = StringIO.StringIO()
  164. self._stream = stream
  165. def write(self, msg):
  166. self._stream.write(msg)
  167. # Note that we don't want to capture any output generated by the debugger
  168. # because that could cause the results of capture_output() to be invalid.
  169. if not self._message_is_from_pdb():
  170. self._buffer.write(msg)
  171. def _message_is_from_pdb(self):
  172. # We will assume that if the pdb module is in the stack then the output
  173. # is being generated by the python debugger (or the user calling something
  174. # from inside the debugger).
  175. import inspect
  176. import pdb
  177. stack = inspect.stack()
  178. return any(frame[1] == pdb.__file__.replace('.pyc', '.py') for frame in stack)
  179. def flush(self):
  180. self._stream.flush()
  181. def getvalue(self):
  182. return self._buffer.getvalue()