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