OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # | |
3 # Copyright 2012 the V8 project authors. All rights reserved. | |
4 # Redistribution and use in source and binary forms, with or without | |
5 # modification, are permitted provided that the following conditions are | |
6 # met: | |
7 # | |
8 # * Redistributions of source code must retain the above copyright | |
9 # notice, this list of conditions and the following disclaimer. | |
10 # * Redistributions in binary form must reproduce the above | |
11 # copyright notice, this list of conditions and the following | |
12 # disclaimer in the documentation and/or other materials provided | |
13 # with the distribution. | |
14 # * Neither the name of Google Inc. nor the names of its | |
15 # contributors may be used to endorse or promote products derived | |
16 # from this software without specific prior written permission. | |
17 # | |
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
29 | |
30 | |
31 import imp | |
32 import optparse | |
33 import os | |
34 from os.path import join, dirname, abspath, basename, isdir, exists | |
35 import platform | |
36 import re | |
37 import signal | |
38 import subprocess | |
39 import sys | |
40 import tempfile | |
41 import time | |
42 import threading | |
43 import utils | |
44 from Queue import Queue, Empty | |
45 | |
46 | |
47 VERBOSE = False | |
48 | |
49 | |
50 # --------------------------------------------- | |
51 # --- P r o g r e s s I n d i c a t o r s --- | |
52 # --------------------------------------------- | |
53 | |
54 | |
55 class ProgressIndicator(object): | |
56 | |
57 def __init__(self, cases): | |
58 self.cases = cases | |
59 self.queue = Queue(len(cases)) | |
60 for case in cases: | |
61 self.queue.put_nowait(case) | |
62 self.succeeded = 0 | |
63 self.remaining = len(cases) | |
64 self.total = len(cases) | |
65 self.failed = [ ] | |
66 self.crashed = 0 | |
67 self.terminate = False | |
68 self.lock = threading.Lock() | |
69 | |
70 def PrintFailureHeader(self, test): | |
71 if test.IsNegative(): | |
72 negative_marker = '[negative] ' | |
73 else: | |
74 negative_marker = '' | |
75 print "=== %(label)s %(negative)s===" % { | |
76 'label': test.GetLabel(), | |
77 'negative': negative_marker | |
78 } | |
79 print "Path: %s" % "/".join(test.path) | |
80 | |
81 def Run(self, tasks): | |
82 self.Starting() | |
83 threads = [] | |
84 # Spawn N-1 threads and then use this thread as the last one. | |
85 # That way -j1 avoids threading altogether which is a nice fallback | |
86 # in case of threading problems. | |
87 for i in xrange(tasks - 1): | |
88 thread = threading.Thread(target=self.RunSingle, args=[]) | |
89 threads.append(thread) | |
90 thread.start() | |
91 try: | |
92 self.RunSingle() | |
93 # Wait for the remaining threads | |
94 for thread in threads: | |
95 # Use a timeout so that signals (ctrl-c) will be processed. | |
96 thread.join(timeout=10000000) | |
97 except Exception, e: | |
98 # If there's an exception we schedule an interruption for any | |
99 # remaining threads. | |
100 self.terminate = True | |
101 # ...and then reraise the exception to bail out | |
102 raise | |
103 self.Done() | |
104 return not self.failed | |
105 | |
106 def RunSingle(self): | |
107 while not self.terminate: | |
108 try: | |
109 test = self.queue.get_nowait() | |
110 except Empty: | |
111 return | |
112 case = test.case | |
113 self.lock.acquire() | |
114 self.AboutToRun(case) | |
115 self.lock.release() | |
116 try: | |
117 start = time.time() | |
118 output = case.Run() | |
119 case.duration = (time.time() - start) | |
120 except BreakNowException: | |
121 self.terminate = True | |
122 except IOError, e: | |
123 assert self.terminate | |
124 return | |
125 if self.terminate: | |
126 return | |
127 self.lock.acquire() | |
128 if output.UnexpectedOutput(): | |
129 self.failed.append(output) | |
130 if output.HasCrashed(): | |
131 self.crashed += 1 | |
132 else: | |
133 self.succeeded += 1 | |
134 self.remaining -= 1 | |
135 self.HasRun(output) | |
136 self.lock.release() | |
137 | |
138 | |
139 def EscapeCommand(command): | |
140 parts = [] | |
141 for part in command: | |
142 if ' ' in part: | |
143 # Escape spaces and double quotes. We may need to escape more characters | |
144 # for this to work properly. | |
145 parts.append('"%s"' % part.replace('"', '\\"')) | |
146 else: | |
147 parts.append(part) | |
148 return " ".join(parts) | |
149 | |
150 | |
151 class SimpleProgressIndicator(ProgressIndicator): | |
152 | |
153 def Starting(self): | |
154 print 'Running %i tests' % len(self.cases) | |
155 | |
156 def Done(self): | |
157 print | |
158 for failed in self.failed: | |
159 self.PrintFailureHeader(failed.test) | |
160 if failed.output.stderr: | |
161 print "--- stderr ---" | |
162 print failed.output.stderr.strip() | |
163 if failed.output.stdout: | |
164 print "--- stdout ---" | |
165 print failed.output.stdout.strip() | |
166 print "Command: %s" % EscapeCommand(failed.command) | |
167 if failed.HasCrashed(): | |
168 print "--- CRASHED ---" | |
169 if failed.HasTimedOut(): | |
170 print "--- TIMEOUT ---" | |
171 if len(self.failed) == 0: | |
172 print "===" | |
173 print "=== All tests succeeded" | |
174 print "===" | |
175 else: | |
176 print | |
177 print "===" | |
178 print "=== %i tests failed" % len(self.failed) | |
179 if self.crashed > 0: | |
180 print "=== %i tests CRASHED" % self.crashed | |
181 print "===" | |
182 | |
183 | |
184 class VerboseProgressIndicator(SimpleProgressIndicator): | |
185 | |
186 def AboutToRun(self, case): | |
187 print 'Starting %s...' % case.GetLabel() | |
188 sys.stdout.flush() | |
189 | |
190 def HasRun(self, output): | |
191 if output.UnexpectedOutput(): | |
192 if output.HasCrashed(): | |
193 outcome = 'CRASH' | |
194 else: | |
195 outcome = 'FAIL' | |
196 else: | |
197 outcome = 'pass' | |
198 print 'Done running %s: %s' % (output.test.GetLabel(), outcome) | |
199 | |
200 | |
201 class DotsProgressIndicator(SimpleProgressIndicator): | |
202 | |
203 def AboutToRun(self, case): | |
204 pass | |
205 | |
206 def HasRun(self, output): | |
207 total = self.succeeded + len(self.failed) | |
208 if (total > 1) and (total % 50 == 1): | |
209 sys.stdout.write('\n') | |
210 if output.UnexpectedOutput(): | |
211 if output.HasCrashed(): | |
212 sys.stdout.write('C') | |
213 sys.stdout.flush() | |
214 elif output.HasTimedOut(): | |
215 sys.stdout.write('T') | |
216 sys.stdout.flush() | |
217 else: | |
218 sys.stdout.write('F') | |
219 sys.stdout.flush() | |
220 else: | |
221 sys.stdout.write('.') | |
222 sys.stdout.flush() | |
223 | |
224 | |
225 class CompactProgressIndicator(ProgressIndicator): | |
226 | |
227 def __init__(self, cases, templates): | |
228 super(CompactProgressIndicator, self).__init__(cases) | |
229 self.templates = templates | |
230 self.last_status_length = 0 | |
231 self.start_time = time.time() | |
232 | |
233 def Starting(self): | |
234 pass | |
235 | |
236 def Done(self): | |
237 self.PrintProgress('Done') | |
238 | |
239 def AboutToRun(self, case): | |
240 self.PrintProgress(case.GetLabel()) | |
241 | |
242 def HasRun(self, output): | |
243 if output.UnexpectedOutput(): | |
244 self.ClearLine(self.last_status_length) | |
245 self.PrintFailureHeader(output.test) | |
246 stdout = output.output.stdout.strip() | |
247 if len(stdout): | |
248 print self.templates['stdout'] % stdout | |
249 stderr = output.output.stderr.strip() | |
250 if len(stderr): | |
251 print self.templates['stderr'] % stderr | |
252 print "Command: %s" % EscapeCommand(output.command) | |
253 if output.HasCrashed(): | |
254 print "--- CRASHED ---" | |
255 if output.HasTimedOut(): | |
256 print "--- TIMEOUT ---" | |
257 | |
258 def Truncate(self, str, length): | |
259 if length and (len(str) > (length - 3)): | |
260 return str[:(length-3)] + "..." | |
261 else: | |
262 return str | |
263 | |
264 def PrintProgress(self, name): | |
265 self.ClearLine(self.last_status_length) | |
266 elapsed = time.time() - self.start_time | |
267 status = self.templates['status_line'] % { | |
268 'passed': self.succeeded, | |
269 'remaining': (((self.total - self.remaining) * 100) // self.total), | |
270 'failed': len(self.failed), | |
271 'test': name, | |
272 'mins': int(elapsed) / 60, | |
273 'secs': int(elapsed) % 60 | |
274 } | |
275 status = self.Truncate(status, 78) | |
276 self.last_status_length = len(status) | |
277 print status, | |
278 sys.stdout.flush() | |
279 | |
280 | |
281 class ColorProgressIndicator(CompactProgressIndicator): | |
282 | |
283 def __init__(self, cases): | |
284 templates = { | |
285 'status_line': "[%(mins)02i:%(secs)02i|\033[34m%%%(remaining) 4d\033[0m|\0
33[32m+%(passed) 4d\033[0m|\033[31m-%(failed) 4d\033[0m]: %(test)s", | |
286 'stdout': "\033[1m%s\033[0m", | |
287 'stderr': "\033[31m%s\033[0m", | |
288 } | |
289 super(ColorProgressIndicator, self).__init__(cases, templates) | |
290 | |
291 def ClearLine(self, last_line_length): | |
292 print "\033[1K\r", | |
293 | |
294 | |
295 class MonochromeProgressIndicator(CompactProgressIndicator): | |
296 | |
297 def __init__(self, cases): | |
298 templates = { | |
299 'status_line': "[%(mins)02i:%(secs)02i|%%%(remaining) 4d|+%(passed) 4d|-%(
failed) 4d]: %(test)s", | |
300 'stdout': '%s', | |
301 'stderr': '%s', | |
302 } | |
303 super(MonochromeProgressIndicator, self).__init__(cases, templates) | |
304 | |
305 def ClearLine(self, last_line_length): | |
306 print ("\r" + (" " * last_line_length) + "\r"), | |
307 | |
308 | |
309 PROGRESS_INDICATORS = { | |
310 'verbose': VerboseProgressIndicator, | |
311 'dots': DotsProgressIndicator, | |
312 'color': ColorProgressIndicator, | |
313 'mono': MonochromeProgressIndicator | |
314 } | |
315 | |
316 | |
317 # ------------------------- | |
318 # --- F r a m e w o r k --- | |
319 # ------------------------- | |
320 | |
321 class BreakNowException(Exception): | |
322 def __init__(self, value): | |
323 self.value = value | |
324 def __str__(self): | |
325 return repr(self.value) | |
326 | |
327 | |
328 class CommandOutput(object): | |
329 | |
330 def __init__(self, exit_code, timed_out, stdout, stderr): | |
331 self.exit_code = exit_code | |
332 self.timed_out = timed_out | |
333 self.stdout = stdout | |
334 self.stderr = stderr | |
335 self.failed = None | |
336 | |
337 | |
338 class TestCase(object): | |
339 | |
340 def __init__(self, context, path, mode): | |
341 self.path = path | |
342 self.context = context | |
343 self.duration = None | |
344 self.mode = mode | |
345 | |
346 def IsNegative(self): | |
347 return False | |
348 | |
349 def TestsIsolates(self): | |
350 return False | |
351 | |
352 def CompareTime(self, other): | |
353 return cmp(other.duration, self.duration) | |
354 | |
355 def DidFail(self, output): | |
356 if output.failed is None: | |
357 output.failed = self.IsFailureOutput(output) | |
358 return output.failed | |
359 | |
360 def IsFailureOutput(self, output): | |
361 return output.exit_code != 0 | |
362 | |
363 def GetSource(self): | |
364 return "(no source available)" | |
365 | |
366 def RunCommand(self, command): | |
367 full_command = self.context.processor(command) | |
368 output = Execute(full_command, | |
369 self.context, | |
370 self.context.GetTimeout(self, self.mode)) | |
371 self.Cleanup() | |
372 return TestOutput(self, | |
373 full_command, | |
374 output, | |
375 self.context.store_unexpected_output) | |
376 | |
377 def BeforeRun(self): | |
378 pass | |
379 | |
380 def AfterRun(self, result): | |
381 pass | |
382 | |
383 def GetCustomFlags(self, mode): | |
384 return None | |
385 | |
386 def Run(self): | |
387 self.BeforeRun() | |
388 result = None | |
389 try: | |
390 result = self.RunCommand(self.GetCommand()) | |
391 except: | |
392 self.terminate = True | |
393 raise BreakNowException("User pressed CTRL+C or IO went wrong") | |
394 finally: | |
395 self.AfterRun(result) | |
396 return result | |
397 | |
398 def Cleanup(self): | |
399 return | |
400 | |
401 | |
402 class TestOutput(object): | |
403 | |
404 def __init__(self, test, command, output, store_unexpected_output): | |
405 self.test = test | |
406 self.command = command | |
407 self.output = output | |
408 self.store_unexpected_output = store_unexpected_output | |
409 | |
410 def UnexpectedOutput(self): | |
411 if self.HasCrashed(): | |
412 outcome = CRASH | |
413 elif self.HasTimedOut(): | |
414 outcome = TIMEOUT | |
415 elif self.HasFailed(): | |
416 outcome = FAIL | |
417 else: | |
418 outcome = PASS | |
419 return not outcome in self.test.outcomes | |
420 | |
421 def HasPreciousOutput(self): | |
422 return self.UnexpectedOutput() and self.store_unexpected_output | |
423 | |
424 def HasCrashed(self): | |
425 if utils.IsWindows(): | |
426 return 0x80000000 & self.output.exit_code and not (0x3FFFFF00 & self.outpu
t.exit_code) | |
427 else: | |
428 # Timed out tests will have exit_code -signal.SIGTERM. | |
429 if self.output.timed_out: | |
430 return False | |
431 return self.output.exit_code < 0 and \ | |
432 self.output.exit_code != -signal.SIGABRT | |
433 | |
434 def HasTimedOut(self): | |
435 return self.output.timed_out | |
436 | |
437 def HasFailed(self): | |
438 execution_failed = self.test.DidFail(self.output) | |
439 if self.test.IsNegative(): | |
440 return not execution_failed | |
441 else: | |
442 return execution_failed | |
443 | |
444 | |
445 def KillProcessWithID(pid): | |
446 if utils.IsWindows(): | |
447 os.popen('taskkill /T /F /PID %d' % pid) | |
448 else: | |
449 os.kill(pid, signal.SIGTERM) | |
450 | |
451 | |
452 MAX_SLEEP_TIME = 0.1 | |
453 INITIAL_SLEEP_TIME = 0.0001 | |
454 SLEEP_TIME_FACTOR = 1.25 | |
455 | |
456 SEM_INVALID_VALUE = -1 | |
457 SEM_NOGPFAULTERRORBOX = 0x0002 # Microsoft Platform SDK WinBase.h | |
458 | |
459 def Win32SetErrorMode(mode): | |
460 prev_error_mode = SEM_INVALID_VALUE | |
461 try: | |
462 import ctypes | |
463 prev_error_mode = ctypes.windll.kernel32.SetErrorMode(mode) | |
464 except ImportError: | |
465 pass | |
466 return prev_error_mode | |
467 | |
468 def RunProcess(context, timeout, args, **rest): | |
469 if context.verbose: print "#", " ".join(args) | |
470 popen_args = args | |
471 prev_error_mode = SEM_INVALID_VALUE | |
472 if utils.IsWindows(): | |
473 popen_args = subprocess.list2cmdline(args) | |
474 if context.suppress_dialogs: | |
475 # Try to change the error mode to avoid dialogs on fatal errors. Don't | |
476 # touch any existing error mode flags by merging the existing error mode. | |
477 # See http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx. | |
478 error_mode = SEM_NOGPFAULTERRORBOX | |
479 prev_error_mode = Win32SetErrorMode(error_mode) | |
480 Win32SetErrorMode(error_mode | prev_error_mode) | |
481 process = subprocess.Popen( | |
482 shell = utils.IsWindows(), | |
483 args = popen_args, | |
484 **rest | |
485 ) | |
486 if utils.IsWindows() and context.suppress_dialogs and prev_error_mode != SEM_I
NVALID_VALUE: | |
487 Win32SetErrorMode(prev_error_mode) | |
488 # Compute the end time - if the process crosses this limit we | |
489 # consider it timed out. | |
490 if timeout is None: end_time = None | |
491 else: end_time = time.time() + timeout | |
492 timed_out = False | |
493 # Repeatedly check the exit code from the process in a | |
494 # loop and keep track of whether or not it times out. | |
495 exit_code = None | |
496 sleep_time = INITIAL_SLEEP_TIME | |
497 while exit_code is None: | |
498 if (not end_time is None) and (time.time() >= end_time): | |
499 # Kill the process and wait for it to exit. | |
500 KillProcessWithID(process.pid) | |
501 exit_code = process.wait() | |
502 timed_out = True | |
503 else: | |
504 exit_code = process.poll() | |
505 time.sleep(sleep_time) | |
506 sleep_time = sleep_time * SLEEP_TIME_FACTOR | |
507 if sleep_time > MAX_SLEEP_TIME: | |
508 sleep_time = MAX_SLEEP_TIME | |
509 return (process, exit_code, timed_out) | |
510 | |
511 | |
512 def PrintError(str): | |
513 sys.stderr.write(str) | |
514 sys.stderr.write('\n') | |
515 | |
516 | |
517 def CheckedUnlink(name): | |
518 # On Windows, when run with -jN in parallel processes, | |
519 # OS often fails to unlink the temp file. Not sure why. | |
520 # Need to retry. | |
521 # Idea from https://bugs.webkit.org/attachment.cgi?id=75982&action=prettypatch | |
522 retry_count = 0 | |
523 while retry_count < 30: | |
524 try: | |
525 os.unlink(name) | |
526 return | |
527 except OSError, e: | |
528 retry_count += 1 | |
529 time.sleep(retry_count * 0.1) | |
530 PrintError("os.unlink() " + str(e)) | |
531 | |
532 def Execute(args, context, timeout=None): | |
533 (fd_out, outname) = tempfile.mkstemp() | |
534 (fd_err, errname) = tempfile.mkstemp() | |
535 (process, exit_code, timed_out) = RunProcess( | |
536 context, | |
537 timeout, | |
538 args = args, | |
539 stdout = fd_out, | |
540 stderr = fd_err, | |
541 ) | |
542 os.close(fd_out) | |
543 os.close(fd_err) | |
544 output = file(outname).read() | |
545 errors = file(errname).read() | |
546 CheckedUnlink(outname) | |
547 CheckedUnlink(errname) | |
548 return CommandOutput(exit_code, timed_out, output, errors) | |
549 | |
550 | |
551 def ExecuteNoCapture(args, context, timeout=None): | |
552 (process, exit_code, timed_out) = RunProcess( | |
553 context, | |
554 timeout, | |
555 args = args, | |
556 ) | |
557 return CommandOutput(exit_code, False, "", "") | |
558 | |
559 | |
560 def CarCdr(path): | |
561 if len(path) == 0: | |
562 return (None, [ ]) | |
563 else: | |
564 return (path[0], path[1:]) | |
565 | |
566 | |
567 # Use this to run several variants of the tests, e.g.: | |
568 # VARIANT_FLAGS = [[], ['--always_compact', '--noflush_code']] | |
569 VARIANT_FLAGS = [[], | |
570 ['--stress-opt', '--always-opt'], | |
571 ['--nocrankshaft']] | |
572 | |
573 | |
574 class TestConfiguration(object): | |
575 | |
576 def __init__(self, context, root): | |
577 self.context = context | |
578 self.root = root | |
579 | |
580 def Contains(self, path, file): | |
581 if len(path) > len(file): | |
582 return False | |
583 for i in xrange(len(path)): | |
584 if not path[i].match(file[i]): | |
585 return False | |
586 return True | |
587 | |
588 def GetTestStatus(self, sections, defs): | |
589 pass | |
590 | |
591 def VariantFlags(self): | |
592 return VARIANT_FLAGS | |
593 | |
594 | |
595 | |
596 | |
597 class TestSuite(object): | |
598 | |
599 def __init__(self, name): | |
600 self.name = name | |
601 | |
602 def GetName(self): | |
603 return self.name | |
604 | |
605 | |
606 class TestRepository(TestSuite): | |
607 | |
608 def __init__(self, path): | |
609 normalized_path = abspath(path) | |
610 super(TestRepository, self).__init__(basename(normalized_path)) | |
611 self.path = normalized_path | |
612 self.is_loaded = False | |
613 self.config = None | |
614 | |
615 def GetConfiguration(self, context): | |
616 if self.is_loaded: | |
617 return self.config | |
618 self.is_loaded = True | |
619 file = None | |
620 try: | |
621 (file, pathname, description) = imp.find_module('testcfg', [ self.path ]) | |
622 module = imp.load_module('testcfg', file, pathname, description) | |
623 self.config = module.GetConfiguration(context, self.path) | |
624 finally: | |
625 if file: | |
626 file.close() | |
627 return self.config | |
628 | |
629 def GetBuildRequirements(self, path, context): | |
630 return self.GetConfiguration(context).GetBuildRequirements() | |
631 | |
632 def DownloadData(self, context): | |
633 config = self.GetConfiguration(context) | |
634 if 'DownloadData' in dir(config): | |
635 config.DownloadData() | |
636 | |
637 def AddTestsToList(self, result, current_path, path, context, mode): | |
638 config = self.GetConfiguration(context) | |
639 for v in config.VariantFlags(): | |
640 tests = config.ListTests(current_path, path, mode, v) | |
641 for t in tests: t.variant_flags = v | |
642 result += tests | |
643 | |
644 def GetTestStatus(self, context, sections, defs): | |
645 self.GetConfiguration(context).GetTestStatus(sections, defs) | |
646 | |
647 | |
648 class LiteralTestSuite(TestSuite): | |
649 | |
650 def __init__(self, tests): | |
651 super(LiteralTestSuite, self).__init__('root') | |
652 self.tests = tests | |
653 | |
654 def GetBuildRequirements(self, path, context): | |
655 (name, rest) = CarCdr(path) | |
656 result = [ ] | |
657 for test in self.tests: | |
658 if not name or name.match(test.GetName()): | |
659 result += test.GetBuildRequirements(rest, context) | |
660 return result | |
661 | |
662 def DownloadData(self, path, context): | |
663 (name, rest) = CarCdr(path) | |
664 for test in self.tests: | |
665 if not name or name.match(test.GetName()): | |
666 test.DownloadData(context) | |
667 | |
668 def ListTests(self, current_path, path, context, mode, variant_flags): | |
669 (name, rest) = CarCdr(path) | |
670 result = [ ] | |
671 for test in self.tests: | |
672 test_name = test.GetName() | |
673 if not name or name.match(test_name): | |
674 full_path = current_path + [test_name] | |
675 test.AddTestsToList(result, full_path, path, context, mode) | |
676 return result | |
677 | |
678 def GetTestStatus(self, context, sections, defs): | |
679 for test in self.tests: | |
680 test.GetTestStatus(context, sections, defs) | |
681 | |
682 | |
683 SUFFIX = { | |
684 'debug' : '_g', | |
685 'release' : '' } | |
686 FLAGS = { | |
687 'debug' : ['--nobreak-on-abort', '--nodead-code-elimination', | |
688 '--nofold-constants', '--enable-slow-asserts', | |
689 '--debug-code', '--verify-heap'], | |
690 'release' : ['--nobreak-on-abort', '--nodead-code-elimination', | |
691 '--nofold-constants']} | |
692 TIMEOUT_SCALEFACTOR = { | |
693 'debug' : 4, | |
694 'release' : 1 } | |
695 | |
696 | |
697 class Context(object): | |
698 | |
699 def __init__(self, workspace, buildspace, verbose, vm, timeout, processor, sup
press_dialogs, store_unexpected_output): | |
700 self.workspace = workspace | |
701 self.buildspace = buildspace | |
702 self.verbose = verbose | |
703 self.vm_root = vm | |
704 self.timeout = timeout | |
705 self.processor = processor | |
706 self.suppress_dialogs = suppress_dialogs | |
707 self.store_unexpected_output = store_unexpected_output | |
708 | |
709 def GetVm(self, mode): | |
710 name = self.vm_root + SUFFIX[mode] | |
711 if utils.IsWindows() and not name.endswith('.exe'): | |
712 name = name + '.exe' | |
713 return name | |
714 | |
715 def GetVmCommand(self, testcase, mode): | |
716 return [self.GetVm(mode)] + self.GetVmFlags(testcase, mode) | |
717 | |
718 def GetVmFlags(self, testcase, mode): | |
719 flags = testcase.GetCustomFlags(mode) | |
720 if flags is None: | |
721 flags = FLAGS[mode] | |
722 return testcase.variant_flags + flags | |
723 | |
724 def GetTimeout(self, testcase, mode): | |
725 result = self.timeout * TIMEOUT_SCALEFACTOR[mode] | |
726 if '--stress-opt' in self.GetVmFlags(testcase, mode): | |
727 return result * 4 | |
728 else: | |
729 return result | |
730 | |
731 def RunTestCases(cases_to_run, progress, tasks): | |
732 progress = PROGRESS_INDICATORS[progress](cases_to_run) | |
733 result = 0 | |
734 try: | |
735 result = progress.Run(tasks) | |
736 except Exception, e: | |
737 print "\n", e | |
738 return result | |
739 | |
740 | |
741 def BuildRequirements(context, requirements, mode, scons_flags): | |
742 command_line = (['scons', '-Y', context.workspace, 'mode=' + ",".join(mode)] | |
743 + requirements | |
744 + scons_flags) | |
745 output = ExecuteNoCapture(command_line, context) | |
746 return output.exit_code == 0 | |
747 | |
748 | |
749 # ------------------------------------------- | |
750 # --- T e s t C o n f i g u r a t i o n --- | |
751 # ------------------------------------------- | |
752 | |
753 | |
754 SKIP = 'skip' | |
755 FAIL = 'fail' | |
756 PASS = 'pass' | |
757 OKAY = 'okay' | |
758 TIMEOUT = 'timeout' | |
759 CRASH = 'crash' | |
760 SLOW = 'slow' | |
761 | |
762 | |
763 class Expression(object): | |
764 pass | |
765 | |
766 | |
767 class Constant(Expression): | |
768 | |
769 def __init__(self, value): | |
770 self.value = value | |
771 | |
772 def Evaluate(self, env, defs): | |
773 return self.value | |
774 | |
775 | |
776 class Variable(Expression): | |
777 | |
778 def __init__(self, name): | |
779 self.name = name | |
780 | |
781 def GetOutcomes(self, env, defs): | |
782 if self.name in env: return ListSet([env[self.name]]) | |
783 else: return Nothing() | |
784 | |
785 def Evaluate(self, env, defs): | |
786 return env[self.name] | |
787 | |
788 | |
789 class Outcome(Expression): | |
790 | |
791 def __init__(self, name): | |
792 self.name = name | |
793 | |
794 def GetOutcomes(self, env, defs): | |
795 if self.name in defs: | |
796 return defs[self.name].GetOutcomes(env, defs) | |
797 else: | |
798 return ListSet([self.name]) | |
799 | |
800 | |
801 class Set(object): | |
802 pass | |
803 | |
804 | |
805 class ListSet(Set): | |
806 | |
807 def __init__(self, elms): | |
808 self.elms = elms | |
809 | |
810 def __str__(self): | |
811 return "ListSet%s" % str(self.elms) | |
812 | |
813 def Intersect(self, that): | |
814 if not isinstance(that, ListSet): | |
815 return that.Intersect(self) | |
816 return ListSet([ x for x in self.elms if x in that.elms ]) | |
817 | |
818 def Union(self, that): | |
819 if not isinstance(that, ListSet): | |
820 return that.Union(self) | |
821 return ListSet(self.elms + [ x for x in that.elms if x not in self.elms ]) | |
822 | |
823 def IsEmpty(self): | |
824 return len(self.elms) == 0 | |
825 | |
826 | |
827 class Everything(Set): | |
828 | |
829 def Intersect(self, that): | |
830 return that | |
831 | |
832 def Union(self, that): | |
833 return self | |
834 | |
835 def IsEmpty(self): | |
836 return False | |
837 | |
838 | |
839 class Nothing(Set): | |
840 | |
841 def Intersect(self, that): | |
842 return self | |
843 | |
844 def Union(self, that): | |
845 return that | |
846 | |
847 def IsEmpty(self): | |
848 return True | |
849 | |
850 | |
851 class Operation(Expression): | |
852 | |
853 def __init__(self, left, op, right): | |
854 self.left = left | |
855 self.op = op | |
856 self.right = right | |
857 | |
858 def Evaluate(self, env, defs): | |
859 if self.op == '||' or self.op == ',': | |
860 return self.left.Evaluate(env, defs) or self.right.Evaluate(env, defs) | |
861 elif self.op == 'if': | |
862 return False | |
863 elif self.op == '==': | |
864 inter = self.left.GetOutcomes(env, defs).Intersect(self.right.GetOutcomes(
env, defs)) | |
865 return not inter.IsEmpty() | |
866 elif self.op == '!=': | |
867 inter = self.left.GetOutcomes(env, defs).Intersect(self.right.GetOutcomes(
env, defs)) | |
868 return inter.IsEmpty() | |
869 else: | |
870 assert self.op == '&&' | |
871 return self.left.Evaluate(env, defs) and self.right.Evaluate(env, defs) | |
872 | |
873 def GetOutcomes(self, env, defs): | |
874 if self.op == '||' or self.op == ',': | |
875 return self.left.GetOutcomes(env, defs).Union(self.right.GetOutcomes(env,
defs)) | |
876 elif self.op == 'if': | |
877 if self.right.Evaluate(env, defs): return self.left.GetOutcomes(env, defs) | |
878 else: return Nothing() | |
879 else: | |
880 assert self.op == '&&' | |
881 return self.left.GetOutcomes(env, defs).Intersect(self.right.GetOutcomes(e
nv, defs)) | |
882 | |
883 | |
884 def IsAlpha(str): | |
885 for char in str: | |
886 if not (char.isalpha() or char.isdigit() or char == '_'): | |
887 return False | |
888 return True | |
889 | |
890 | |
891 class Tokenizer(object): | |
892 """A simple string tokenizer that chops expressions into variables, | |
893 parens and operators""" | |
894 | |
895 def __init__(self, expr): | |
896 self.index = 0 | |
897 self.expr = expr | |
898 self.length = len(expr) | |
899 self.tokens = None | |
900 | |
901 def Current(self, length = 1): | |
902 if not self.HasMore(length): return "" | |
903 return self.expr[self.index:self.index+length] | |
904 | |
905 def HasMore(self, length = 1): | |
906 return self.index < self.length + (length - 1) | |
907 | |
908 def Advance(self, count = 1): | |
909 self.index = self.index + count | |
910 | |
911 def AddToken(self, token): | |
912 self.tokens.append(token) | |
913 | |
914 def SkipSpaces(self): | |
915 while self.HasMore() and self.Current().isspace(): | |
916 self.Advance() | |
917 | |
918 def Tokenize(self): | |
919 self.tokens = [ ] | |
920 while self.HasMore(): | |
921 self.SkipSpaces() | |
922 if not self.HasMore(): | |
923 return None | |
924 if self.Current() == '(': | |
925 self.AddToken('(') | |
926 self.Advance() | |
927 elif self.Current() == ')': | |
928 self.AddToken(')') | |
929 self.Advance() | |
930 elif self.Current() == '$': | |
931 self.AddToken('$') | |
932 self.Advance() | |
933 elif self.Current() == ',': | |
934 self.AddToken(',') | |
935 self.Advance() | |
936 elif IsAlpha(self.Current()): | |
937 buf = "" | |
938 while self.HasMore() and IsAlpha(self.Current()): | |
939 buf += self.Current() | |
940 self.Advance() | |
941 self.AddToken(buf) | |
942 elif self.Current(2) == '&&': | |
943 self.AddToken('&&') | |
944 self.Advance(2) | |
945 elif self.Current(2) == '||': | |
946 self.AddToken('||') | |
947 self.Advance(2) | |
948 elif self.Current(2) == '==': | |
949 self.AddToken('==') | |
950 self.Advance(2) | |
951 elif self.Current(2) == '!=': | |
952 self.AddToken('!=') | |
953 self.Advance(2) | |
954 else: | |
955 return None | |
956 return self.tokens | |
957 | |
958 | |
959 class Scanner(object): | |
960 """A simple scanner that can serve out tokens from a given list""" | |
961 | |
962 def __init__(self, tokens): | |
963 self.tokens = tokens | |
964 self.length = len(tokens) | |
965 self.index = 0 | |
966 | |
967 def HasMore(self): | |
968 return self.index < self.length | |
969 | |
970 def Current(self): | |
971 return self.tokens[self.index] | |
972 | |
973 def Advance(self): | |
974 self.index = self.index + 1 | |
975 | |
976 | |
977 def ParseAtomicExpression(scan): | |
978 if scan.Current() == "true": | |
979 scan.Advance() | |
980 return Constant(True) | |
981 elif scan.Current() == "false": | |
982 scan.Advance() | |
983 return Constant(False) | |
984 elif IsAlpha(scan.Current()): | |
985 name = scan.Current() | |
986 scan.Advance() | |
987 return Outcome(name.lower()) | |
988 elif scan.Current() == '$': | |
989 scan.Advance() | |
990 if not IsAlpha(scan.Current()): | |
991 return None | |
992 name = scan.Current() | |
993 scan.Advance() | |
994 return Variable(name.lower()) | |
995 elif scan.Current() == '(': | |
996 scan.Advance() | |
997 result = ParseLogicalExpression(scan) | |
998 if (not result) or (scan.Current() != ')'): | |
999 return None | |
1000 scan.Advance() | |
1001 return result | |
1002 else: | |
1003 return None | |
1004 | |
1005 | |
1006 BINARIES = ['==', '!='] | |
1007 def ParseOperatorExpression(scan): | |
1008 left = ParseAtomicExpression(scan) | |
1009 if not left: return None | |
1010 while scan.HasMore() and (scan.Current() in BINARIES): | |
1011 op = scan.Current() | |
1012 scan.Advance() | |
1013 right = ParseOperatorExpression(scan) | |
1014 if not right: | |
1015 return None | |
1016 left = Operation(left, op, right) | |
1017 return left | |
1018 | |
1019 | |
1020 def ParseConditionalExpression(scan): | |
1021 left = ParseOperatorExpression(scan) | |
1022 if not left: return None | |
1023 while scan.HasMore() and (scan.Current() == 'if'): | |
1024 scan.Advance() | |
1025 right = ParseOperatorExpression(scan) | |
1026 if not right: | |
1027 return None | |
1028 left = Operation(left, 'if', right) | |
1029 return left | |
1030 | |
1031 | |
1032 LOGICALS = ["&&", "||", ","] | |
1033 def ParseLogicalExpression(scan): | |
1034 left = ParseConditionalExpression(scan) | |
1035 if not left: return None | |
1036 while scan.HasMore() and (scan.Current() in LOGICALS): | |
1037 op = scan.Current() | |
1038 scan.Advance() | |
1039 right = ParseConditionalExpression(scan) | |
1040 if not right: | |
1041 return None | |
1042 left = Operation(left, op, right) | |
1043 return left | |
1044 | |
1045 | |
1046 def ParseCondition(expr): | |
1047 """Parses a logical expression into an Expression object""" | |
1048 tokens = Tokenizer(expr).Tokenize() | |
1049 if not tokens: | |
1050 print "Malformed expression: '%s'" % expr | |
1051 return None | |
1052 scan = Scanner(tokens) | |
1053 ast = ParseLogicalExpression(scan) | |
1054 if not ast: | |
1055 print "Malformed expression: '%s'" % expr | |
1056 return None | |
1057 if scan.HasMore(): | |
1058 print "Malformed expression: '%s'" % expr | |
1059 return None | |
1060 return ast | |
1061 | |
1062 | |
1063 class ClassifiedTest(object): | |
1064 | |
1065 def __init__(self, case, outcomes): | |
1066 self.case = case | |
1067 self.outcomes = outcomes | |
1068 | |
1069 def TestsIsolates(self): | |
1070 return self.case.TestsIsolates() | |
1071 | |
1072 | |
1073 class Configuration(object): | |
1074 """The parsed contents of a configuration file""" | |
1075 | |
1076 def __init__(self, sections, defs): | |
1077 self.sections = sections | |
1078 self.defs = defs | |
1079 | |
1080 def ClassifyTests(self, cases, env): | |
1081 sections = [s for s in self.sections if s.condition.Evaluate(env, self.defs)
] | |
1082 all_rules = reduce(list.__add__, [s.rules for s in sections], []) | |
1083 unused_rules = set(all_rules) | |
1084 result = [ ] | |
1085 all_outcomes = set([]) | |
1086 for case in cases: | |
1087 matches = [ r for r in all_rules if r.Contains(case.path) ] | |
1088 outcomes = set([]) | |
1089 for rule in matches: | |
1090 outcomes = outcomes.union(rule.GetOutcomes(env, self.defs)) | |
1091 unused_rules.discard(rule) | |
1092 if not outcomes: | |
1093 outcomes = [PASS] | |
1094 case.outcomes = outcomes | |
1095 all_outcomes = all_outcomes.union(outcomes) | |
1096 result.append(ClassifiedTest(case, outcomes)) | |
1097 return (result, list(unused_rules), all_outcomes) | |
1098 | |
1099 | |
1100 class Section(object): | |
1101 """A section of the configuration file. Sections are enabled or | |
1102 disabled prior to running the tests, based on their conditions""" | |
1103 | |
1104 def __init__(self, condition): | |
1105 self.condition = condition | |
1106 self.rules = [ ] | |
1107 | |
1108 def AddRule(self, rule): | |
1109 self.rules.append(rule) | |
1110 | |
1111 | |
1112 class Rule(object): | |
1113 """A single rule that specifies the expected outcome for a single | |
1114 test.""" | |
1115 | |
1116 def __init__(self, raw_path, path, value): | |
1117 self.raw_path = raw_path | |
1118 self.path = path | |
1119 self.value = value | |
1120 | |
1121 def GetOutcomes(self, env, defs): | |
1122 set = self.value.GetOutcomes(env, defs) | |
1123 assert isinstance(set, ListSet) | |
1124 return set.elms | |
1125 | |
1126 def Contains(self, path): | |
1127 if len(self.path) > len(path): | |
1128 return False | |
1129 for i in xrange(len(self.path)): | |
1130 if not self.path[i].match(path[i]): | |
1131 return False | |
1132 return True | |
1133 | |
1134 | |
1135 HEADER_PATTERN = re.compile(r'\[([^]]+)\]') | |
1136 RULE_PATTERN = re.compile(r'\s*([^: ]*)\s*:(.*)') | |
1137 DEF_PATTERN = re.compile(r'^def\s*(\w+)\s*=(.*)$') | |
1138 PREFIX_PATTERN = re.compile(r'^\s*prefix\s+([\w\_\.\-\/]+)$') | |
1139 | |
1140 | |
1141 def ReadConfigurationInto(path, sections, defs): | |
1142 current_section = Section(Constant(True)) | |
1143 sections.append(current_section) | |
1144 prefix = [] | |
1145 for line in utils.ReadLinesFrom(path): | |
1146 header_match = HEADER_PATTERN.match(line) | |
1147 if header_match: | |
1148 condition_str = header_match.group(1).strip() | |
1149 condition = ParseCondition(condition_str) | |
1150 new_section = Section(condition) | |
1151 sections.append(new_section) | |
1152 current_section = new_section | |
1153 continue | |
1154 rule_match = RULE_PATTERN.match(line) | |
1155 if rule_match: | |
1156 path = prefix + SplitPath(rule_match.group(1).strip()) | |
1157 value_str = rule_match.group(2).strip() | |
1158 value = ParseCondition(value_str) | |
1159 if not value: | |
1160 return False | |
1161 current_section.AddRule(Rule(rule_match.group(1), path, value)) | |
1162 continue | |
1163 def_match = DEF_PATTERN.match(line) | |
1164 if def_match: | |
1165 name = def_match.group(1).lower() | |
1166 value = ParseCondition(def_match.group(2).strip()) | |
1167 if not value: | |
1168 return False | |
1169 defs[name] = value | |
1170 continue | |
1171 prefix_match = PREFIX_PATTERN.match(line) | |
1172 if prefix_match: | |
1173 prefix = SplitPath(prefix_match.group(1).strip()) | |
1174 continue | |
1175 print "Malformed line: '%s'." % line | |
1176 return False | |
1177 return True | |
1178 | |
1179 | |
1180 # --------------- | |
1181 # --- M a i n --- | |
1182 # --------------- | |
1183 | |
1184 | |
1185 ARCH_GUESS = utils.GuessArchitecture() | |
1186 TIMEOUT_DEFAULT = 60; | |
1187 | |
1188 | |
1189 def BuildOptions(): | |
1190 result = optparse.OptionParser() | |
1191 result.add_option("-m", "--mode", help="The test modes in which to run (comma-
separated)", | |
1192 default='release') | |
1193 result.add_option("-v", "--verbose", help="Verbose output", | |
1194 default=False, action="store_true") | |
1195 result.add_option("-S", dest="scons_flags", help="Flag to pass through to scon
s", | |
1196 default=[], action="append") | |
1197 result.add_option("-p", "--progress", | |
1198 help="The style of progress indicator (verbose, dots, color, mono)", | |
1199 choices=PROGRESS_INDICATORS.keys(), default="mono") | |
1200 result.add_option("--no-build", help="Don't build requirements", | |
1201 default=False, action="store_true") | |
1202 result.add_option("--build-only", help="Only build requirements, don't run the
tests", | |
1203 default=False, action="store_true") | |
1204 result.add_option("--build-system", help="Build system in use (scons or gyp)", | |
1205 default='scons') | |
1206 result.add_option("--report", help="Print a summary of the tests to be run", | |
1207 default=False, action="store_true") | |
1208 result.add_option("--download-data", help="Download missing test suite data", | |
1209 default=False, action="store_true") | |
1210 result.add_option("-s", "--suite", help="A test suite", | |
1211 default=[], action="append") | |
1212 result.add_option("-t", "--timeout", help="Timeout in seconds", | |
1213 default=-1, type="int") | |
1214 result.add_option("--arch", help='The architecture to run tests for', | |
1215 default='none') | |
1216 result.add_option("--snapshot", help="Run the tests with snapshot turned on", | |
1217 default=False, action="store_true") | |
1218 result.add_option("--simulator", help="Run tests with architecture simulator", | |
1219 default='none') | |
1220 result.add_option("--special-command", default=None) | |
1221 result.add_option("--valgrind", help="Run tests through valgrind", | |
1222 default=False, action="store_true") | |
1223 result.add_option("--cat", help="Print the source of the tests", | |
1224 default=False, action="store_true") | |
1225 result.add_option("--warn-unused", help="Report unused rules", | |
1226 default=False, action="store_true") | |
1227 result.add_option("-j", help="The number of parallel tasks to run", | |
1228 default=1, type="int") | |
1229 result.add_option("--time", help="Print timing information after running", | |
1230 default=False, action="store_true") | |
1231 result.add_option("--suppress-dialogs", help="Suppress Windows dialogs for cra
shing tests", | |
1232 dest="suppress_dialogs", default=True, action="store_true") | |
1233 result.add_option("--no-suppress-dialogs", help="Display Windows dialogs for c
rashing tests", | |
1234 dest="suppress_dialogs", action="store_false") | |
1235 result.add_option("--mips-arch-variant", help="mips architecture variant: mips
32r1/mips32r2", default="mips32r2"); | |
1236 result.add_option("--shell", help="Path to V8 shell", default="d8") | |
1237 result.add_option("--isolates", help="Whether to test isolates", default=False
, action="store_true") | |
1238 result.add_option("--store-unexpected-output", | |
1239 help="Store the temporary JS files from tests that fails", | |
1240 dest="store_unexpected_output", default=True, action="store_true") | |
1241 result.add_option("--no-store-unexpected-output", | |
1242 help="Deletes the temporary JS files from tests that fails", | |
1243 dest="store_unexpected_output", action="store_false") | |
1244 result.add_option("--stress-only", | |
1245 help="Only run tests with --always-opt --stress-opt", | |
1246 default=False, action="store_true") | |
1247 result.add_option("--nostress", | |
1248 help="Don't run crankshaft --always-opt --stress-op test", | |
1249 default=False, action="store_true") | |
1250 result.add_option("--shard-count", | |
1251 help="Split testsuites into this number of shards", | |
1252 default=1, type="int") | |
1253 result.add_option("--shard-run", | |
1254 help="Run this shard from the split up tests.", | |
1255 default=1, type="int") | |
1256 result.add_option("--noprof", help="Disable profiling support", | |
1257 default=False) | |
1258 return result | |
1259 | |
1260 | |
1261 def ProcessOptions(options): | |
1262 global VERBOSE | |
1263 VERBOSE = options.verbose | |
1264 options.mode = options.mode.split(',') | |
1265 for mode in options.mode: | |
1266 if not mode in ['debug', 'release']: | |
1267 print "Unknown mode %s" % mode | |
1268 return False | |
1269 if options.simulator != 'none': | |
1270 # Simulator argument was set. Make sure arch and simulator agree. | |
1271 if options.simulator != options.arch: | |
1272 if options.arch == 'none': | |
1273 options.arch = options.simulator | |
1274 else: | |
1275 print "Architecture %s does not match sim %s" %(options.arch, options.si
mulator) | |
1276 return False | |
1277 # Ensure that the simulator argument is handed down to scons. | |
1278 options.scons_flags.append("simulator=" + options.simulator) | |
1279 else: | |
1280 # If options.arch is not set by the command line and no simulator setting | |
1281 # was found, set the arch to the guess. | |
1282 if options.arch == 'none': | |
1283 options.arch = ARCH_GUESS | |
1284 options.scons_flags.append("arch=" + options.arch) | |
1285 # Simulators are slow, therefore allow a longer default timeout. | |
1286 if options.timeout == -1: | |
1287 if options.arch in ['android', 'arm', 'mipsel']: | |
1288 options.timeout = 2 * TIMEOUT_DEFAULT; | |
1289 else: | |
1290 options.timeout = TIMEOUT_DEFAULT; | |
1291 if options.snapshot: | |
1292 options.scons_flags.append("snapshot=on") | |
1293 global VARIANT_FLAGS | |
1294 if options.mips_arch_variant: | |
1295 options.scons_flags.append("mips_arch_variant=" + options.mips_arch_variant) | |
1296 | |
1297 if options.stress_only: | |
1298 VARIANT_FLAGS = [['--stress-opt', '--always-opt']] | |
1299 if options.nostress: | |
1300 VARIANT_FLAGS = [[],['--nocrankshaft']] | |
1301 if options.shell.endswith("d8"): | |
1302 if options.special_command: | |
1303 options.special_command += " --test" | |
1304 else: | |
1305 options.special_command = "@ --test" | |
1306 if options.noprof: | |
1307 options.scons_flags.append("prof=off") | |
1308 options.scons_flags.append("profilingsupport=off") | |
1309 if options.build_system == 'gyp': | |
1310 if options.build_only: | |
1311 print "--build-only not supported for gyp, please build manually." | |
1312 options.build_only = False | |
1313 return True | |
1314 | |
1315 | |
1316 def DoSkip(case): | |
1317 return (SKIP in case.outcomes) or (SLOW in case.outcomes) | |
1318 | |
1319 | |
1320 REPORT_TEMPLATE = """\ | |
1321 Total: %(total)i tests | |
1322 * %(skipped)4d tests will be skipped | |
1323 * %(timeout)4d tests are expected to timeout sometimes | |
1324 * %(nocrash)4d tests are expected to be flaky but not crash | |
1325 * %(pass)4d tests are expected to pass | |
1326 * %(fail_ok)4d tests are expected to fail that we won't fix | |
1327 * %(fail)4d tests are expected to fail that we should fix\ | |
1328 """ | |
1329 | |
1330 def PrintReport(cases): | |
1331 def IsFlaky(o): | |
1332 return (PASS in o) and (FAIL in o) and (not CRASH in o) and (not OKAY in o) | |
1333 def IsFailOk(o): | |
1334 return (len(o) == 2) and (FAIL in o) and (OKAY in o) | |
1335 unskipped = [c for c in cases if not DoSkip(c)] | |
1336 print REPORT_TEMPLATE % { | |
1337 'total': len(cases), | |
1338 'skipped': len(cases) - len(unskipped), | |
1339 'timeout': len([t for t in unskipped if TIMEOUT in t.outcomes]), | |
1340 'nocrash': len([t for t in unskipped if IsFlaky(t.outcomes)]), | |
1341 'pass': len([t for t in unskipped if list(t.outcomes) == [PASS]]), | |
1342 'fail_ok': len([t for t in unskipped if IsFailOk(t.outcomes)]), | |
1343 'fail': len([t for t in unskipped if list(t.outcomes) == [FAIL]]) | |
1344 } | |
1345 | |
1346 | |
1347 class Pattern(object): | |
1348 | |
1349 def __init__(self, pattern): | |
1350 self.pattern = pattern | |
1351 self.compiled = None | |
1352 | |
1353 def match(self, str): | |
1354 if not self.compiled: | |
1355 pattern = "^" + self.pattern.replace('*', '.*') + "$" | |
1356 self.compiled = re.compile(pattern) | |
1357 return self.compiled.match(str) | |
1358 | |
1359 def __str__(self): | |
1360 return self.pattern | |
1361 | |
1362 | |
1363 def SplitPath(s): | |
1364 stripped = [ c.strip() for c in s.split('/') ] | |
1365 return [ Pattern(s) for s in stripped if len(s) > 0 ] | |
1366 | |
1367 | |
1368 def GetSpecialCommandProcessor(value): | |
1369 if (not value) or (value.find('@') == -1): | |
1370 def ExpandCommand(args): | |
1371 return args | |
1372 return ExpandCommand | |
1373 else: | |
1374 pos = value.find('@') | |
1375 import urllib | |
1376 import shlex | |
1377 prefix = shlex.split(urllib.unquote(value[:pos])) | |
1378 suffix = shlex.split(urllib.unquote(value[pos+1:])) | |
1379 def ExpandCommand(args): | |
1380 return prefix + args + suffix | |
1381 return ExpandCommand | |
1382 | |
1383 | |
1384 BUILT_IN_TESTS = ['mjsunit', 'cctest', 'message', 'preparser'] | |
1385 | |
1386 | |
1387 def GetSuites(test_root): | |
1388 def IsSuite(path): | |
1389 return isdir(path) and exists(join(path, 'testcfg.py')) | |
1390 return [ f for f in os.listdir(test_root) if IsSuite(join(test_root, f)) ] | |
1391 | |
1392 | |
1393 def FormatTime(d): | |
1394 millis = round(d * 1000) % 1000 | |
1395 return time.strftime("%M:%S.", time.gmtime(d)) + ("%03i" % millis) | |
1396 | |
1397 def ShardTests(tests, options): | |
1398 if options.shard_count < 2: | |
1399 return tests | |
1400 if options.shard_run < 1 or options.shard_run > options.shard_count: | |
1401 print "shard-run not a valid number, should be in [1:shard-count]" | |
1402 print "defaulting back to running all tests" | |
1403 return tests | |
1404 count = 0 | |
1405 shard = [] | |
1406 for test in tests: | |
1407 if count % options.shard_count == options.shard_run - 1: | |
1408 shard.append(test) | |
1409 count += 1 | |
1410 return shard | |
1411 | |
1412 def Main(): | |
1413 parser = BuildOptions() | |
1414 (options, args) = parser.parse_args() | |
1415 if not ProcessOptions(options): | |
1416 parser.print_help() | |
1417 return 1 | |
1418 | |
1419 workspace = abspath(join(dirname(sys.argv[0]), '..')) | |
1420 suites = GetSuites(join(workspace, 'test')) | |
1421 repositories = [TestRepository(join(workspace, 'test', name)) for name in suit
es] | |
1422 repositories += [TestRepository(a) for a in options.suite] | |
1423 | |
1424 root = LiteralTestSuite(repositories) | |
1425 if len(args) == 0: | |
1426 paths = [SplitPath(t) for t in BUILT_IN_TESTS] | |
1427 else: | |
1428 paths = [ ] | |
1429 for arg in args: | |
1430 path = SplitPath(arg) | |
1431 paths.append(path) | |
1432 | |
1433 # Check for --valgrind option. If enabled, we overwrite the special | |
1434 # command flag with a command that uses the run-valgrind.py script. | |
1435 if options.valgrind: | |
1436 run_valgrind = join(workspace, "tools", "run-valgrind.py") | |
1437 options.special_command = "python -u " + run_valgrind + " @" | |
1438 | |
1439 if options.build_system == 'gyp': | |
1440 SUFFIX['debug'] = '' | |
1441 | |
1442 shell = abspath(options.shell) | |
1443 buildspace = dirname(shell) | |
1444 | |
1445 context = Context(workspace, buildspace, VERBOSE, | |
1446 shell, | |
1447 options.timeout, | |
1448 GetSpecialCommandProcessor(options.special_command), | |
1449 options.suppress_dialogs, | |
1450 options.store_unexpected_output) | |
1451 # First build the required targets | |
1452 if not options.no_build: | |
1453 reqs = [ ] | |
1454 for path in paths: | |
1455 reqs += root.GetBuildRequirements(path, context) | |
1456 reqs = list(set(reqs)) | |
1457 if len(reqs) > 0: | |
1458 if options.j != 1: | |
1459 options.scons_flags += ['-j', str(options.j)] | |
1460 if not BuildRequirements(context, reqs, options.mode, options.scons_flags)
: | |
1461 return 1 | |
1462 | |
1463 # Just return if we are only building the targets for running the tests. | |
1464 if options.build_only: | |
1465 return 0 | |
1466 | |
1467 # Get status for tests | |
1468 sections = [ ] | |
1469 defs = { } | |
1470 root.GetTestStatus(context, sections, defs) | |
1471 config = Configuration(sections, defs) | |
1472 | |
1473 # Download missing test suite data if requested. | |
1474 if options.download_data: | |
1475 for path in paths: | |
1476 root.DownloadData(path, context) | |
1477 | |
1478 # List the tests | |
1479 all_cases = [ ] | |
1480 all_unused = [ ] | |
1481 unclassified_tests = [ ] | |
1482 globally_unused_rules = None | |
1483 for path in paths: | |
1484 for mode in options.mode: | |
1485 env = { | |
1486 'mode': mode, | |
1487 'system': utils.GuessOS(), | |
1488 'arch': options.arch, | |
1489 'simulator': options.simulator, | |
1490 'isolates': options.isolates | |
1491 } | |
1492 test_list = root.ListTests([], path, context, mode, []) | |
1493 unclassified_tests += test_list | |
1494 (cases, unused_rules, all_outcomes) = config.ClassifyTests(test_list, env) | |
1495 if globally_unused_rules is None: | |
1496 globally_unused_rules = set(unused_rules) | |
1497 else: | |
1498 globally_unused_rules = globally_unused_rules.intersection(unused_rules) | |
1499 all_cases += ShardTests(cases, options) | |
1500 all_unused.append(unused_rules) | |
1501 | |
1502 if options.cat: | |
1503 visited = set() | |
1504 for test in unclassified_tests: | |
1505 key = tuple(test.path) | |
1506 if key in visited: | |
1507 continue | |
1508 visited.add(key) | |
1509 print "--- begin source: %s ---" % test.GetLabel() | |
1510 source = test.GetSource().strip() | |
1511 print source | |
1512 print "--- end source: %s ---" % test.GetLabel() | |
1513 return 0 | |
1514 | |
1515 if options.warn_unused: | |
1516 for rule in globally_unused_rules: | |
1517 print "Rule for '%s' was not used." % '/'.join([str(s) for s in rule.path]
) | |
1518 | |
1519 if not options.isolates: | |
1520 all_cases = [c for c in all_cases if not c.TestsIsolates()] | |
1521 | |
1522 if options.report: | |
1523 PrintReport(all_cases) | |
1524 | |
1525 result = None | |
1526 cases_to_run = [ c for c in all_cases if not DoSkip(c) ] | |
1527 if len(cases_to_run) == 0: | |
1528 print "No tests to run." | |
1529 return 0 | |
1530 else: | |
1531 try: | |
1532 start = time.time() | |
1533 if RunTestCases(cases_to_run, options.progress, options.j): | |
1534 result = 0 | |
1535 else: | |
1536 result = 1 | |
1537 duration = time.time() - start | |
1538 except KeyboardInterrupt: | |
1539 print "Interrupted" | |
1540 return 1 | |
1541 | |
1542 if options.time: | |
1543 # Write the times to stderr to make it easy to separate from the | |
1544 # test output. | |
1545 print | |
1546 sys.stderr.write("--- Total time: %s ---\n" % FormatTime(duration)) | |
1547 timed_tests = [ t.case for t in cases_to_run if not t.case.duration is None
] | |
1548 timed_tests.sort(lambda a, b: a.CompareTime(b)) | |
1549 index = 1 | |
1550 for entry in timed_tests[:20]: | |
1551 t = FormatTime(entry.duration) | |
1552 sys.stderr.write("%4i (%s) %s\n" % (index, t, entry.GetLabel())) | |
1553 index += 1 | |
1554 | |
1555 return result | |
1556 | |
1557 | |
1558 if __name__ == '__main__': | |
1559 sys.exit(Main()) | |
OLD | NEW |