| OLD | NEW |
| (Empty) |
| 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 | |
| 24 import StringIO | |
| 25 import logging | |
| 26 | |
| 27 from webkitpy.common.system import outputcapture | |
| 28 from webkitpy.common.system.systemhost import SystemHost | |
| 29 from webkitpy.layout_tests.views.metered_stream import MeteredStream | |
| 30 | |
| 31 _log = logging.getLogger(__name__) | |
| 32 | |
| 33 | |
| 34 class Printer(object): | |
| 35 def __init__(self, stream, options=None): | |
| 36 self.stream = stream | |
| 37 self.meter = None | |
| 38 self.options = options | |
| 39 self.num_tests = 0 | |
| 40 self.num_completed = 0 | |
| 41 self.num_errors = 0 | |
| 42 self.num_failures = 0 | |
| 43 self.running_tests = [] | |
| 44 self.completed_tests = [] | |
| 45 if options: | |
| 46 self.configure(options) | |
| 47 | |
| 48 def configure(self, options): | |
| 49 self.options = options | |
| 50 | |
| 51 if options.timing: | |
| 52 # --timing implies --verbose | |
| 53 options.verbose = max(options.verbose, 1) | |
| 54 | |
| 55 log_level = logging.INFO | |
| 56 if options.quiet: | |
| 57 log_level = logging.WARNING | |
| 58 elif options.verbose >= 2: | |
| 59 log_level = logging.DEBUG | |
| 60 | |
| 61 self.meter = MeteredStream(self.stream, (options.verbose >= 2), | |
| 62 number_of_columns=SystemHost().platform.terminal_width()) | |
| 63 | |
| 64 handler = logging.StreamHandler(self.stream) | |
| 65 # We constrain the level on the handler rather than on the root | |
| 66 # logger itself. This is probably better because the handler is | |
| 67 # configured and known only to this module, whereas the root logger | |
| 68 # is an object shared (and potentially modified) by many modules. | |
| 69 # Modifying the handler, then, is less intrusive and less likely to | |
| 70 # interfere with modifications made by other modules (e.g. in unit | |
| 71 # tests). | |
| 72 handler.name = __name__ | |
| 73 handler.setLevel(log_level) | |
| 74 formatter = logging.Formatter("%(message)s") | |
| 75 handler.setFormatter(formatter) | |
| 76 | |
| 77 logger = logging.getLogger() | |
| 78 logger.addHandler(handler) | |
| 79 logger.setLevel(logging.NOTSET) | |
| 80 | |
| 81 # Filter out most webkitpy messages. | |
| 82 # | |
| 83 # Messages can be selectively re-enabled for this script by updating | |
| 84 # this method accordingly. | |
| 85 def filter_records(record): | |
| 86 """Filter out non-third-party webkitpy messages.""" | |
| 87 # FIXME: Figure out a way not to use strings here, for example by | |
| 88 # using syntax like webkitpy.test.__name__. We want to be | |
| 89 # sure not to import any non-Python 2.4 code, though, until | |
| 90 # after the version-checking code has executed. | |
| 91 if (record.name.startswith("webkitpy.test")): | |
| 92 return True | |
| 93 if record.name.startswith("webkitpy"): | |
| 94 return False | |
| 95 return True | |
| 96 | |
| 97 testing_filter = logging.Filter() | |
| 98 testing_filter.filter = filter_records | |
| 99 | |
| 100 # Display a message so developers are not mystified as to why | |
| 101 # logging does not work in the unit tests. | |
| 102 _log.info("Suppressing most webkitpy logging while running unit tests.") | |
| 103 handler.addFilter(testing_filter) | |
| 104 | |
| 105 if self.options.pass_through: | |
| 106 outputcapture.OutputCapture.stream_wrapper = _CaptureAndPassThroughS
tream | |
| 107 | |
| 108 def write_update(self, msg): | |
| 109 self.meter.write_update(msg) | |
| 110 | |
| 111 def print_started_test(self, source, test_name): | |
| 112 self.running_tests.append(test_name) | |
| 113 if len(self.running_tests) > 1: | |
| 114 suffix = ' (+%d)' % (len(self.running_tests) - 1) | |
| 115 else: | |
| 116 suffix = '' | |
| 117 | |
| 118 if self.options.verbose: | |
| 119 write = self.meter.write_update | |
| 120 else: | |
| 121 write = self.meter.write_throttled_update | |
| 122 | |
| 123 write(self._test_line(self.running_tests[0], suffix)) | |
| 124 | |
| 125 def print_finished_test(self, source, test_name, test_time, failures, errors
): | |
| 126 write = self.meter.writeln | |
| 127 if failures: | |
| 128 lines = failures[0].splitlines() + [''] | |
| 129 suffix = ' failed:' | |
| 130 self.num_failures += 1 | |
| 131 elif errors: | |
| 132 lines = errors[0].splitlines() + [''] | |
| 133 suffix = ' erred:' | |
| 134 self.num_errors += 1 | |
| 135 else: | |
| 136 suffix = ' passed' | |
| 137 lines = [] | |
| 138 if self.options.verbose: | |
| 139 write = self.meter.writeln | |
| 140 else: | |
| 141 write = self.meter.write_throttled_update | |
| 142 if self.options.timing: | |
| 143 suffix += ' %.4fs' % test_time | |
| 144 | |
| 145 self.num_completed += 1 | |
| 146 | |
| 147 if test_name == self.running_tests[0]: | |
| 148 self.completed_tests.insert(0, [test_name, suffix, lines]) | |
| 149 else: | |
| 150 self.completed_tests.append([test_name, suffix, lines]) | |
| 151 self.running_tests.remove(test_name) | |
| 152 | |
| 153 for test_name, msg, lines in self.completed_tests: | |
| 154 if lines: | |
| 155 self.meter.writeln(self._test_line(test_name, msg)) | |
| 156 for line in lines: | |
| 157 self.meter.writeln(' ' + line) | |
| 158 else: | |
| 159 write(self._test_line(test_name, msg)) | |
| 160 self.completed_tests = [] | |
| 161 | |
| 162 def _test_line(self, test_name, suffix): | |
| 163 format_string = '[%d/%d] %s%s' | |
| 164 status_line = format_string % (self.num_completed, self.num_tests, test_
name, suffix) | |
| 165 if len(status_line) > self.meter.number_of_columns(): | |
| 166 overflow_columns = len(status_line) - self.meter.number_of_columns() | |
| 167 ellipsis = '...' | |
| 168 if len(test_name) < overflow_columns + len(ellipsis) + 3: | |
| 169 # We don't have enough space even if we elide, just show the tes
t method name. | |
| 170 test_name = test_name.split('.')[-1] | |
| 171 else: | |
| 172 new_length = len(test_name) - overflow_columns - len(ellipsis) | |
| 173 prefix = int(new_length / 2) | |
| 174 test_name = test_name[:prefix] + ellipsis + test_name[-(new_leng
th - prefix):] | |
| 175 return format_string % (self.num_completed, self.num_tests, test_name, s
uffix) | |
| 176 | |
| 177 def print_result(self, run_time): | |
| 178 write = self.meter.writeln | |
| 179 write('Ran %d test%s in %.3fs' % (self.num_completed, self.num_completed
!= 1 and "s" or "", run_time)) | |
| 180 if self.num_failures or self.num_errors: | |
| 181 write('FAILED (failures=%d, errors=%d)\n' % (self.num_failures, self
.num_errors)) | |
| 182 else: | |
| 183 write('\nOK\n') | |
| 184 | |
| 185 | |
| 186 class _CaptureAndPassThroughStream(object): | |
| 187 def __init__(self, stream): | |
| 188 self._buffer = StringIO.StringIO() | |
| 189 self._stream = stream | |
| 190 | |
| 191 def write(self, msg): | |
| 192 self._stream.write(msg) | |
| 193 | |
| 194 # Note that we don't want to capture any output generated by the debugge
r | |
| 195 # because that could cause the results of capture_output() to be invalid
. | |
| 196 if not self._message_is_from_pdb(): | |
| 197 self._buffer.write(msg) | |
| 198 | |
| 199 def _message_is_from_pdb(self): | |
| 200 # We will assume that if the pdb module is in the stack then the output | |
| 201 # is being generated by the python debugger (or the user calling somethi
ng | |
| 202 # from inside the debugger). | |
| 203 import inspect | |
| 204 import pdb | |
| 205 stack = inspect.stack() | |
| 206 return any(frame[1] == pdb.__file__.replace('.pyc', '.py') for frame in
stack) | |
| 207 | |
| 208 def flush(self): | |
| 209 self._stream.flush() | |
| 210 | |
| 211 def getvalue(self): | |
| 212 return self._buffer.getvalue() | |
| OLD | NEW |