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