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.layout_tests.views.metered_stream import MeteredStream
30
_log = logging.getLogger(__name__)
33
class Printer(object):
34
def __init__(self, stream, options=None):
37
self.options = options
39
self.num_completed = 0
42
self.running_tests = []
43
self.completed_tests = []
45
self.configure(options)
47
def configure(self, options):
48
self.options = options
51
# --timing implies --verbose
52
options.verbose = max(options.verbose, 1)
54
log_level = logging.INFO
56
log_level = logging.WARNING
57
elif options.verbose == 2:
58
log_level = logging.DEBUG
60
self.meter = MeteredStream(self.stream, (options.verbose == 2))
62
handler = logging.StreamHandler(self.stream)
63
# We constrain the level on the handler rather than on the root
64
# logger itself. This is probably better because the handler is
65
# configured and known only to this module, whereas the root logger
66
# is an object shared (and potentially modified) by many modules.
67
# Modifying the handler, then, is less intrusive and less likely to
68
# interfere with modifications made by other modules (e.g. in unit
70
handler.name = __name__
71
handler.setLevel(log_level)
72
formatter = logging.Formatter("%(message)s")
73
handler.setFormatter(formatter)
75
logger = logging.getLogger()
76
logger.addHandler(handler)
77
logger.setLevel(logging.NOTSET)
79
# Filter out most webkitpy messages.
81
# Messages can be selectively re-enabled for this script by updating
82
# this method accordingly.
83
def filter_records(record):
84
"""Filter out autoinstall and non-third-party webkitpy messages."""
85
# FIXME: Figure out a way not to use strings here, for example by
86
# using syntax like webkitpy.test.__name__. We want to be
87
# sure not to import any non-Python 2.4 code, though, until
88
# after the version-checking code has executed.
89
if (record.name.startswith("webkitpy.common.system.autoinstall") or
90
record.name.startswith("webkitpy.test")):
92
if record.name.startswith("webkitpy"):
96
testing_filter = logging.Filter()
97
testing_filter.filter = filter_records
99
# Display a message so developers are not mystified as to why
100
# logging does not work in the unit tests.
101
_log.info("Suppressing most webkitpy logging while running unit tests.")
102
handler.addFilter(testing_filter)
104
if self.options.pass_through:
105
outputcapture.OutputCapture.stream_wrapper = _CaptureAndPassThroughStream
107
def write_update(self, msg):
108
self.meter.write_update(msg)
110
def print_started_test(self, source, test_name):
111
self.running_tests.append(test_name)
112
if len(self.running_tests) > 1:
113
suffix = ' (+%d)' % (len(self.running_tests) - 1)
117
if self.options.verbose:
118
write = self.meter.write_update
120
write = self.meter.write_throttled_update
122
write(self._test_line(self.running_tests[0], suffix))
124
def print_finished_test(self, source, test_name, test_time, failures, errors):
125
write = self.meter.writeln
127
lines = failures[0].splitlines() + ['']
129
self.num_failures += 1
131
lines = errors[0].splitlines() + ['']
137
if self.options.verbose:
138
write = self.meter.writeln
140
write = self.meter.write_throttled_update
141
if self.options.timing:
142
suffix += ' %.4fs' % test_time
144
self.num_completed += 1
146
if test_name == self.running_tests[0]:
147
self.completed_tests.insert(0, [test_name, suffix, lines])
149
self.completed_tests.append([test_name, suffix, lines])
150
self.running_tests.remove(test_name)
152
for test_name, msg, lines in self.completed_tests:
154
self.meter.writeln(self._test_line(test_name, msg))
156
self.meter.writeln(' ' + line)
158
write(self._test_line(test_name, msg))
159
self.completed_tests = []
161
def _test_line(self, test_name, suffix):
162
return '[%d/%d] %s%s' % (self.num_completed, self.num_tests, test_name, suffix)
164
def print_result(self, run_time):
165
write = self.meter.writeln
166
write('Ran %d test%s in %.3fs' % (self.num_completed, self.num_completed != 1 and "s" or "", run_time))
167
if self.num_failures or self.num_errors:
168
write('FAILED (failures=%d, errors=%d)\n' % (self.num_failures, self.num_errors))
173
class _CaptureAndPassThroughStream(object):
174
def __init__(self, stream):
175
self._buffer = StringIO.StringIO()
176
self._stream = stream
178
def write(self, msg):
179
self._stream.write(msg)
181
# Note that we don't want to capture any output generated by the debugger
182
# because that could cause the results of capture_output() to be invalid.
183
if not self._message_is_from_pdb():
184
self._buffer.write(msg)
186
def _message_is_from_pdb(self):
187
# We will assume that if the pdb module is in the stack then the output
188
# is being generated by the python debugger (or the user calling something
189
# from inside the debugger).
192
stack = inspect.stack()
193
return any(frame[1] == pdb.__file__.replace('.pyc', '.py') for frame in stack)
199
return self._buffer.getvalue()