1 # Copyright (C) 2012 Google, Inc.
2 # Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org)
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions
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.
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.
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
31 _log = logging.getLogger(__name__)
34 class Printer(object):
35 def __init__(self, stream, options=None):
38 self.options = options
40 self.num_completed = 0
43 self.running_tests = []
44 self.completed_tests = []
46 self.configure(options)
48 def configure(self, options):
49 self.options = options
52 # --timing implies --verbose
53 options.verbose = max(options.verbose, 1)
55 log_level = logging.INFO
57 log_level = logging.WARNING
58 elif options.verbose >= 2:
59 log_level = logging.DEBUG
61 self.meter = MeteredStream(self.stream, (options.verbose >= 2),
62 number_of_columns=SystemHost().platform.terminal_width())
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
72 handler.name = __name__
73 handler.setLevel(log_level)
74 formatter = logging.Formatter("%(message)s")
75 handler.setFormatter(formatter)
77 logger = logging.getLogger()
78 logger.addHandler(handler)
79 logger.setLevel(logging.NOTSET)
81 # Filter out most webkitpy messages.
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")):
93 if record.name.startswith("webkitpy"):
97 testing_filter = logging.Filter()
98 testing_filter.filter = filter_records
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)
105 if self.options.pass_through:
106 outputcapture.OutputCapture.stream_wrapper = _CaptureAndPassThroughStream
108 def write_update(self, msg):
109 self.meter.write_update(msg)
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)
118 if self.options.verbose:
119 write = self.meter.write_update
121 write = self.meter.write_throttled_update
123 write(self._test_line(self.running_tests[0], suffix))
125 def print_finished_test(self, source, test_name, test_time, failures, errors):
126 write = self.meter.writeln
128 lines = failures[0].splitlines() + ['']
130 self.num_failures += 1
132 lines = errors[0].splitlines() + ['']
138 if self.options.verbose:
139 write = self.meter.writeln
141 write = self.meter.write_throttled_update
142 if self.options.timing:
143 suffix += ' %.4fs' % test_time
145 self.num_completed += 1
147 if test_name == self.running_tests[0]:
148 self.completed_tests.insert(0, [test_name, suffix, lines])
150 self.completed_tests.append([test_name, suffix, lines])
151 self.running_tests.remove(test_name)
153 for test_name, msg, lines in self.completed_tests:
155 self.meter.writeln(self._test_line(test_name, msg))
157 self.meter.writeln(' ' + line)
159 write(self._test_line(test_name, msg))
160 self.completed_tests = []
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()
168 if len(test_name) < overflow_columns + len(ellipsis) + 3:
169 # We don't have enough space even if we elide, just show the test method name.
170 test_name = test_name.split('.')[-1]
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_length - prefix):]
175 return format_string % (self.num_completed, self.num_tests, test_name, suffix)
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))
186 class _CaptureAndPassThroughStream(object):
187 def __init__(self, stream):
188 self._buffer = StringIO.StringIO()
189 self._stream = stream
191 def write(self, msg):
192 self._stream.write(msg)
194 # Note that we don't want to capture any output generated by the debugger
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)
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 something
202 # from inside the debugger).
205 stack = inspect.stack()
206 return any(frame[1] == pdb.__file__.replace('.pyc', '.py') for frame in stack)
212 return self._buffer.getvalue()