blob: 8caa58c44ccb4864eaccfd804151961a4aed646c [file] [log] [blame]
Ben Murdochb8a8cc12014-11-26 15:28:44 +00001# Copyright 2012 the V8 project authors. All rights reserved.
2# Redistribution and use in source and binary forms, with or without
3# modification, are permitted provided that the following conditions are
4# met:
5#
6# * Redistributions of source code must retain the above copyright
7# notice, this list of conditions and the following disclaimer.
8# * Redistributions in binary form must reproduce the above
9# copyright notice, this list of conditions and the following
10# disclaimer in the documentation and/or other materials provided
11# with the distribution.
12# * Neither the name of Google Inc. nor the names of its
13# contributors may be used to endorse or promote products derived
14# from this software without specific prior written permission.
15#
16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28
29import json
30import os
31import sys
32import time
33
34from . import junit_output
35
36
37ABS_PATH_PREFIX = os.getcwd() + os.sep
38
39
40def EscapeCommand(command):
41 parts = []
42 for part in command:
43 if ' ' in part:
44 # Escape spaces. We may need to escape more characters for this
45 # to work properly.
46 parts.append('"%s"' % part)
47 else:
48 parts.append(part)
49 return " ".join(parts)
50
51
52class ProgressIndicator(object):
53
54 def __init__(self):
55 self.runner = None
56
57 def Starting(self):
58 pass
59
60 def Done(self):
61 pass
62
63 def AboutToRun(self, test):
64 pass
65
66 def HasRun(self, test, has_unexpected_output):
67 pass
68
69 def PrintFailureHeader(self, test):
70 if test.suite.IsNegativeTest(test):
71 negative_marker = '[negative] '
72 else:
73 negative_marker = ''
74 print "=== %(label)s %(negative)s===" % {
75 'label': test.GetLabel(),
76 'negative': negative_marker
77 }
78
79
80class SimpleProgressIndicator(ProgressIndicator):
81 """Abstract base class for {Verbose,Dots}ProgressIndicator"""
82
83 def Starting(self):
84 print 'Running %i tests' % self.runner.total
85
86 def Done(self):
87 print
88 for failed in self.runner.failed:
89 self.PrintFailureHeader(failed)
90 if failed.output.stderr:
91 print "--- stderr ---"
92 print failed.output.stderr.strip()
93 if failed.output.stdout:
94 print "--- stdout ---"
95 print failed.output.stdout.strip()
96 print "Command: %s" % EscapeCommand(self.runner.GetCommand(failed))
97 if failed.output.HasCrashed():
98 print "exit code: %d" % failed.output.exit_code
99 print "--- CRASHED ---"
100 if failed.output.HasTimedOut():
101 print "--- TIMEOUT ---"
102 if len(self.runner.failed) == 0:
103 print "==="
104 print "=== All tests succeeded"
105 print "==="
106 else:
107 print
108 print "==="
109 print "=== %i tests failed" % len(self.runner.failed)
110 if self.runner.crashed > 0:
111 print "=== %i tests CRASHED" % self.runner.crashed
112 print "==="
113
114
115class VerboseProgressIndicator(SimpleProgressIndicator):
116
117 def AboutToRun(self, test):
118 print 'Starting %s...' % test.GetLabel()
119 sys.stdout.flush()
120
121 def HasRun(self, test, has_unexpected_output):
122 if has_unexpected_output:
123 if test.output.HasCrashed():
124 outcome = 'CRASH'
125 else:
126 outcome = 'FAIL'
127 else:
128 outcome = 'pass'
129 print 'Done running %s: %s' % (test.GetLabel(), outcome)
130
131
132class DotsProgressIndicator(SimpleProgressIndicator):
133
134 def HasRun(self, test, has_unexpected_output):
135 total = self.runner.succeeded + len(self.runner.failed)
136 if (total > 1) and (total % 50 == 1):
137 sys.stdout.write('\n')
138 if has_unexpected_output:
139 if test.output.HasCrashed():
140 sys.stdout.write('C')
141 sys.stdout.flush()
142 elif test.output.HasTimedOut():
143 sys.stdout.write('T')
144 sys.stdout.flush()
145 else:
146 sys.stdout.write('F')
147 sys.stdout.flush()
148 else:
149 sys.stdout.write('.')
150 sys.stdout.flush()
151
152
153class CompactProgressIndicator(ProgressIndicator):
154 """Abstract base class for {Color,Monochrome}ProgressIndicator"""
155
156 def __init__(self, templates):
157 super(CompactProgressIndicator, self).__init__()
158 self.templates = templates
159 self.last_status_length = 0
160 self.start_time = time.time()
161
162 def Done(self):
163 self.PrintProgress('Done')
164 print "" # Line break.
165
166 def AboutToRun(self, test):
167 self.PrintProgress(test.GetLabel())
168
169 def HasRun(self, test, has_unexpected_output):
170 if has_unexpected_output:
171 self.ClearLine(self.last_status_length)
172 self.PrintFailureHeader(test)
173 stdout = test.output.stdout.strip()
174 if len(stdout):
175 print self.templates['stdout'] % stdout
176 stderr = test.output.stderr.strip()
177 if len(stderr):
178 print self.templates['stderr'] % stderr
179 print "Command: %s" % EscapeCommand(self.runner.GetCommand(test))
180 if test.output.HasCrashed():
181 print "exit code: %d" % test.output.exit_code
182 print "--- CRASHED ---"
183 if test.output.HasTimedOut():
184 print "--- TIMEOUT ---"
185
186 def Truncate(self, string, length):
187 if length and (len(string) > (length - 3)):
188 return string[:(length - 3)] + "..."
189 else:
190 return string
191
192 def PrintProgress(self, name):
193 self.ClearLine(self.last_status_length)
194 elapsed = time.time() - self.start_time
195 status = self.templates['status_line'] % {
196 'passed': self.runner.succeeded,
197 'remaining': (((self.runner.total - self.runner.remaining) * 100) //
198 self.runner.total),
199 'failed': len(self.runner.failed),
200 'test': name,
201 'mins': int(elapsed) / 60,
202 'secs': int(elapsed) % 60
203 }
204 status = self.Truncate(status, 78)
205 self.last_status_length = len(status)
206 print status,
207 sys.stdout.flush()
208
209
210class ColorProgressIndicator(CompactProgressIndicator):
211
212 def __init__(self):
213 templates = {
214 'status_line': ("[%(mins)02i:%(secs)02i|"
215 "\033[34m%%%(remaining) 4d\033[0m|"
216 "\033[32m+%(passed) 4d\033[0m|"
217 "\033[31m-%(failed) 4d\033[0m]: %(test)s"),
218 'stdout': "\033[1m%s\033[0m",
219 'stderr': "\033[31m%s\033[0m",
220 }
221 super(ColorProgressIndicator, self).__init__(templates)
222
223 def ClearLine(self, last_line_length):
224 print "\033[1K\r",
225
226
227class MonochromeProgressIndicator(CompactProgressIndicator):
228
229 def __init__(self):
230 templates = {
231 'status_line': ("[%(mins)02i:%(secs)02i|%%%(remaining) 4d|"
232 "+%(passed) 4d|-%(failed) 4d]: %(test)s"),
233 'stdout': '%s',
234 'stderr': '%s',
235 }
236 super(MonochromeProgressIndicator, self).__init__(templates)
237
238 def ClearLine(self, last_line_length):
239 print ("\r" + (" " * last_line_length) + "\r"),
240
241
242class JUnitTestProgressIndicator(ProgressIndicator):
243
244 def __init__(self, progress_indicator, junitout, junittestsuite):
245 self.progress_indicator = progress_indicator
246 self.outputter = junit_output.JUnitTestOutput(junittestsuite)
247 if junitout:
248 self.outfile = open(junitout, "w")
249 else:
250 self.outfile = sys.stdout
251
252 def Starting(self):
253 self.progress_indicator.runner = self.runner
254 self.progress_indicator.Starting()
255
256 def Done(self):
257 self.progress_indicator.Done()
258 self.outputter.FinishAndWrite(self.outfile)
259 if self.outfile != sys.stdout:
260 self.outfile.close()
261
262 def AboutToRun(self, test):
263 self.progress_indicator.AboutToRun(test)
264
265 def HasRun(self, test, has_unexpected_output):
266 self.progress_indicator.HasRun(test, has_unexpected_output)
267 fail_text = ""
268 if has_unexpected_output:
269 stdout = test.output.stdout.strip()
270 if len(stdout):
271 fail_text += "stdout:\n%s\n" % stdout
272 stderr = test.output.stderr.strip()
273 if len(stderr):
274 fail_text += "stderr:\n%s\n" % stderr
275 fail_text += "Command: %s" % EscapeCommand(self.runner.GetCommand(test))
276 if test.output.HasCrashed():
277 fail_text += "exit code: %d\n--- CRASHED ---" % test.output.exit_code
278 if test.output.HasTimedOut():
279 fail_text += "--- TIMEOUT ---"
280 self.outputter.HasRunTest(
281 [test.GetLabel()] + self.runner.context.mode_flags + test.flags,
282 test.duration,
283 fail_text)
284
285
286class JsonTestProgressIndicator(ProgressIndicator):
287
288 def __init__(self, progress_indicator, json_test_results, arch, mode):
289 self.progress_indicator = progress_indicator
290 self.json_test_results = json_test_results
291 self.arch = arch
292 self.mode = mode
293 self.results = []
294
295 def Starting(self):
296 self.progress_indicator.runner = self.runner
297 self.progress_indicator.Starting()
298
299 def Done(self):
300 self.progress_indicator.Done()
301 complete_results = []
302 if os.path.exists(self.json_test_results):
303 with open(self.json_test_results, "r") as f:
304 # Buildbot might start out with an empty file.
305 complete_results = json.loads(f.read() or "[]")
306
307 complete_results.append({
308 "arch": self.arch,
309 "mode": self.mode,
310 "results": self.results,
311 })
312
313 with open(self.json_test_results, "w") as f:
314 f.write(json.dumps(complete_results))
315
316 def AboutToRun(self, test):
317 self.progress_indicator.AboutToRun(test)
318
319 def HasRun(self, test, has_unexpected_output):
320 self.progress_indicator.HasRun(test, has_unexpected_output)
321 if not has_unexpected_output:
322 # Omit tests that run as expected. Passing tests of reruns after failures
323 # will have unexpected_output to be reported here has well.
324 return
325
326 self.results.append({
327 "name": test.GetLabel(),
328 "flags": test.flags,
329 "command": EscapeCommand(self.runner.GetCommand(test)).replace(
330 ABS_PATH_PREFIX, ""),
331 "run": test.run,
332 "stdout": test.output.stdout,
333 "stderr": test.output.stderr,
334 "exit_code": test.output.exit_code,
335 "result": test.suite.GetOutcome(test),
336 })
337
338
339PROGRESS_INDICATORS = {
340 'verbose': VerboseProgressIndicator,
341 'dots': DotsProgressIndicator,
342 'color': ColorProgressIndicator,
343 'mono': MonochromeProgressIndicator
344}