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
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.
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.
44 import multiprocessing
47 from os.path import join, dirname, abspath, basename, isdir, exists
48 from datetime import datetime
49 from Queue import Queue, Empty
51 logger = logging.getLogger('testrunner')
52 skip_regex = re.compile(r'# SKIP\S*\s+(.*)', re.IGNORECASE)
57 # ---------------------------------------------
58 # --- P r o g r e s s I n d i c a t o r s ---
59 # ---------------------------------------------
62 class ProgressIndicator(object):
64 def __init__(self, cases):
66 self.parallel_queue = Queue(len(cases))
67 self.sequential_queue = Queue(len(cases))
70 self.parallel_queue.put_nowait(case)
72 self.sequential_queue.put_nowait(case)
74 self.remaining = len(cases)
75 self.total = len(cases)
78 self.lock = threading.Lock()
79 self.shutdown_event = threading.Event()
81 def PrintFailureHeader(self, test):
83 negative_marker = '[negative] '
86 print "=== %(label)s %(negative)s===" % {
87 'label': test.GetLabel(),
88 'negative': negative_marker
90 print "Path: %s" % "/".join(test.path)
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)
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()
111 # If there's an exception we schedule an interruption for any
113 self.shutdown_event.set()
114 # ...and then reraise the exception to bail out
117 return not self.failed
119 def RunSingle(self, parallel, thread_id):
120 while not self.shutdown_event.is_set():
122 test = self.parallel_queue.get_nowait()
127 test = self.sequential_queue.get_nowait()
131 case.thread_id = thread_id
133 self.AboutToRun(case)
136 start = datetime.now()
138 case.duration = (datetime.now() - start)
141 if self.shutdown_event.is_set():
144 if output.UnexpectedOutput():
145 self.failed.append(output)
146 if output.HasCrashed():
155 def EscapeCommand(command):
159 # Escape spaces. We may need to escape more characters for this
161 parts.append('"%s"' % part)
164 return " ".join(parts)
167 class SimpleProgressIndicator(ProgressIndicator):
170 print 'Running %i tests' % len(self.cases)
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:
189 print "=== All tests succeeded"
194 print "=== %i tests failed" % len(self.failed)
196 print "=== %i tests CRASHED" % self.crashed
200 class VerboseProgressIndicator(SimpleProgressIndicator):
202 def AboutToRun(self, case):
203 print 'Starting %s...' % case.GetLabel()
206 def HasRun(self, output):
207 if output.UnexpectedOutput():
208 if output.HasCrashed():
214 print 'Done running %s: %s' % (output.test.GetLabel(), outcome)
217 class DotsProgressIndicator(SimpleProgressIndicator):
219 def AboutToRun(self, case):
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')
230 elif output.HasTimedOut():
231 sys.stdout.write('T')
234 sys.stdout.write('F')
237 sys.stdout.write('.')
241 class TapProgressIndicator(SimpleProgressIndicator):
244 logger.info('1..%i' % len(self.cases))
247 def AboutToRun(self, case):
250 def HasRun(self, output):
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():
257 for l in output.output.stdout.splitlines():
260 skip = skip_regex.search(output.output.stdout)
263 'ok %i - %s # skip %s' % (self._done, command, skip.group(1)))
265 logger.info('ok %i - %s' % (self._done, command))
267 duration = output.test.duration
269 # total_seconds() was added in 2.7
270 total_seconds = (duration.microseconds +
271 (duration.seconds + duration.days * 24 * 3600) * 10**6) / 10**6
274 logger.info(' duration_ms: %d.%d' % (total_seconds, duration.microseconds / 1000))
281 class CompactProgressIndicator(ProgressIndicator):
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()
293 self.PrintProgress('Done')
295 def AboutToRun(self, case):
296 self.PrintProgress(case.GetLabel())
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()
304 print self.templates['stdout'] % stdout
305 stderr = output.output.stderr.strip()
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 ---"
314 def Truncate(self, str, length):
315 if length and (len(str) > (length - 3)):
316 return str[:(length-3)] + "..."
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),
328 'mins': int(elapsed) / 60,
329 'secs': int(elapsed) % 60
331 status = self.Truncate(status, 78)
332 self.last_status_length = len(status)
337 class ColorProgressIndicator(CompactProgressIndicator):
339 def __init__(self, cases):
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",
345 super(ColorProgressIndicator, self).__init__(cases, templates)
347 def ClearLine(self, last_line_length):
351 class MonochromeProgressIndicator(CompactProgressIndicator):
353 def __init__(self, cases):
355 'status_line': "[%(mins)02i:%(secs)02i|%%%(remaining) 4d|+%(passed) 4d|-%(failed) 4d]: %(test)s",
358 'clear': lambda last_line_length: ("\r" + (" " * last_line_length) + "\r"),
361 super(MonochromeProgressIndicator, self).__init__(cases, templates)
363 def ClearLine(self, last_line_length):
364 print ("\r" + (" " * last_line_length) + "\r"),
367 PROGRESS_INDICATORS = {
368 'verbose': VerboseProgressIndicator,
369 'dots': DotsProgressIndicator,
370 'color': ColorProgressIndicator,
371 'tap': TapProgressIndicator,
372 'mono': MonochromeProgressIndicator
376 # -------------------------
377 # --- F r a m e w o r k ---
378 # -------------------------
381 class CommandOutput(object):
383 def __init__(self, exit_code, timed_out, stdout, stderr):
384 self.exit_code = exit_code
385 self.timed_out = timed_out
391 class TestCase(object):
393 def __init__(self, context, path, arch, mode):
395 self.context = context
399 self.parallel = False
402 def IsNegative(self):
405 def CompareTime(self, other):
406 return cmp(other.duration, self.duration)
408 def DidFail(self, output):
409 if output.failed is None:
410 output.failed = self.IsFailureOutput(output)
413 def IsFailureOutput(self, output):
414 return output.exit_code != 0
417 return "(no source available)"
419 def RunCommand(self, command, env):
420 full_command = self.context.processor(command)
421 output = Execute(full_command,
423 self.context.GetTimeout(self.mode),
426 return TestOutput(self,
429 self.context.store_unexpected_output)
434 def AfterRun(self, result):
441 result = self.RunCommand(self.GetCommand(), {
442 "TEST_THREAD_ID": "%d" % self.thread_id
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))
454 self.AfterRun(result)
461 class TestOutput(object):
463 def __init__(self, test, command, output, store_unexpected_output):
465 self.command = command
467 self.store_unexpected_output = store_unexpected_output
469 def UnexpectedOutput(self):
470 if self.HasCrashed():
472 elif self.HasTimedOut():
474 elif self.HasFailed():
478 return not outcome in self.test.outcomes
480 def HasPreciousOutput(self):
481 return self.UnexpectedOutput() and self.store_unexpected_output
483 def HasCrashed(self):
484 if utils.IsWindows():
485 return 0x80000000 & self.output.exit_code and not (0x3FFFFF00 & self.output.exit_code)
487 # Timed out tests will have exit_code -signal.SIGTERM.
488 if self.output.timed_out:
490 return self.output.exit_code < 0 and \
491 self.output.exit_code != -signal.SIGABRT
493 def HasTimedOut(self):
494 return self.output.timed_out;
497 execution_failed = self.test.DidFail(self.output)
498 if self.test.IsNegative():
499 return not execution_failed
501 return execution_failed
504 def KillProcessWithID(pid):
505 if utils.IsWindows():
506 os.popen('taskkill /T /F /PID %d' % pid)
508 os.kill(pid, signal.SIGTERM)
512 INITIAL_SLEEP_TIME = 0.0001
513 SLEEP_TIME_FACTOR = 1.25
515 SEM_INVALID_VALUE = -1
516 SEM_NOGPFAULTERRORBOX = 0x0002 # Microsoft Platform SDK WinBase.h
518 def Win32SetErrorMode(mode):
519 prev_error_mode = SEM_INVALID_VALUE
522 prev_error_mode = ctypes.windll.kernel32.SetErrorMode(mode);
525 return prev_error_mode
527 def RunProcess(context, timeout, args, **rest):
528 if context.verbose: print "#", " ".join(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(),
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
551 # Repeatedly check the exit code from the process in a
552 # loop and keep track of whether or not it times out.
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()
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)
571 sys.stderr.write(str)
572 sys.stderr.write('\n')
575 def CheckedUnlink(name):
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:
586 PrintError("os.unlink() " + str(e))
589 def Execute(args, context, timeout=None, env={}):
590 (fd_out, outname) = tempfile.mkstemp()
591 (fd_err, errname) = tempfile.mkstemp()
594 env_copy = os.environ.copy()
595 for key, value in env.iteritems():
596 env_copy[key] = value
598 (process, exit_code, timed_out) = RunProcess(
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)
615 def ExecuteNoCapture(args, context, timeout=None):
616 (process, exit_code, timed_out) = RunProcess(
621 return CommandOutput(exit_code, False, "", "")
628 return (path[0], path[1:])
631 class TestConfiguration(object):
633 def __init__(self, context, root):
634 self.context = context
637 def Contains(self, path, file):
638 if len(path) > len(file):
640 for i in xrange(len(path)):
641 if not path[i].match(file[i]):
645 def GetTestStatus(self, sections, defs):
649 class TestSuite(object):
651 def __init__(self, name):
658 # Use this to run several variants of the tests, e.g.:
659 # VARIANT_FLAGS = [[], ['--always_compact', '--noflush_code']]
663 class TestRepository(TestSuite):
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
672 def GetConfiguration(self, context):
675 self.is_loaded = True
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)
686 def GetBuildRequirements(self, path, context):
687 return self.GetConfiguration(context).GetBuildRequirements()
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,
693 for t in tests: t.variant_flags = v
697 def GetTestStatus(self, context, sections, defs):
698 self.GetConfiguration(context).GetTestStatus(sections, defs)
701 class LiteralTestSuite(TestSuite):
703 def __init__(self, tests):
704 super(LiteralTestSuite, self).__init__('root')
707 def GetBuildRequirements(self, path, context):
708 (name, rest) = CarCdr(path)
710 for test in self.tests:
711 if not name or name.match(test.GetName()):
712 result += test.GetBuildRequirements(rest, context)
715 def ListTests(self, current_path, path, context, arch, mode):
716 (name, rest) = CarCdr(path)
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()))
726 def GetTestStatus(self, context, sections, defs):
727 for test in self.tests:
728 test.GetTestStatus(context, sections, defs)
735 'debug' : ['--enable-slow-asserts', '--debug-code', '--verify-heap'],
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 } }
744 class Context(object):
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
751 self.timeout = timeout
752 self.processor = processor
753 self.suppress_dialogs = suppress_dialogs
754 self.store_unexpected_output = store_unexpected_output
756 def GetVm(self, arch, mode):
758 name = 'out/Debug/iojs' if mode == 'debug' else 'out/Release/iojs'
760 name = 'out/%s.%s/iojs' % (arch, mode)
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):
769 name = os.path.abspath('Debug/iojs.exe')
771 name = os.path.abspath('Release/iojs.exe')
773 name = os.path.abspath(name + '.exe')
777 def GetVmFlags(self, testcase, mode):
778 return testcase.variant_flags + FLAGS[mode]
780 def GetTimeout(self, mode):
781 return self.timeout * TIMEOUT_SCALEFACTOR[ARCH_GUESS or 'ia32'][mode]
783 def RunTestCases(cases_to_run, progress, tasks):
784 progress = PROGRESS_INDICATORS[progress](cases_to_run)
785 return progress.Run(tasks)
788 def BuildRequirements(context, requirements, mode, scons_flags):
789 command_line = (['scons', '-Y', context.workspace, 'mode=' + ",".join(mode)]
792 output = ExecuteNoCapture(command_line, context)
793 return output.exit_code == 0
796 # -------------------------------------------
797 # --- T e s t C o n f i g u r a t i o n ---
798 # -------------------------------------------
810 class Expression(object):
814 class Constant(Expression):
816 def __init__(self, value):
819 def Evaluate(self, env, defs):
823 class Variable(Expression):
825 def __init__(self, name):
828 def GetOutcomes(self, env, defs):
829 if self.name in env: return ListSet([env[self.name]])
830 else: return Nothing()
833 class Outcome(Expression):
835 def __init__(self, name):
838 def GetOutcomes(self, env, defs):
839 if self.name in defs:
840 return defs[self.name].GetOutcomes(env, defs)
842 return ListSet([self.name])
851 def __init__(self, elms):
855 return "ListSet%s" % str(self.elms)
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 ])
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 ])
868 return len(self.elms) == 0
871 class Everything(Set):
873 def Intersect(self, that):
876 def Union(self, that):
885 def Intersect(self, that):
888 def Union(self, that):
895 class Operation(Expression):
897 def __init__(self, left, op, right):
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':
907 elif self.op == '==':
908 inter = self.left.GetOutcomes(env, defs).Intersect(self.right.GetOutcomes(env, defs))
909 return not inter.IsEmpty()
911 assert self.op == '&&'
912 return self.left.Evaluate(env, defs) and self.right.Evaluate(env, defs)
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()
921 assert self.op == '&&'
922 return self.left.GetOutcomes(env, defs).Intersect(self.right.GetOutcomes(env, defs))
927 if not (char.isalpha() or char.isdigit() or char == '_'):
932 class Tokenizer(object):
933 """A simple string tokenizer that chops expressions into variables,
934 parens and operators"""
936 def __init__(self, expr):
939 self.length = len(expr)
942 def Current(self, length = 1):
943 if not self.HasMore(length): return ""
944 return self.expr[self.index:self.index+length]
946 def HasMore(self, length = 1):
947 return self.index < self.length + (length - 1)
949 def Advance(self, count = 1):
950 self.index = self.index + count
952 def AddToken(self, token):
953 self.tokens.append(token)
955 def SkipSpaces(self):
956 while self.HasMore() and self.Current().isspace():
961 while self.HasMore():
963 if not self.HasMore():
965 if self.Current() == '(':
968 elif self.Current() == ')':
971 elif self.Current() == '$':
974 elif self.Current() == ',':
977 elif IsAlpha(self.Current()):
979 while self.HasMore() and IsAlpha(self.Current()):
980 buf += self.Current()
983 elif self.Current(2) == '&&':
986 elif self.Current(2) == '||':
989 elif self.Current(2) == '==':
997 class Scanner(object):
998 """A simple scanner that can serve out tokens from a given list"""
1000 def __init__(self, tokens):
1001 self.tokens = tokens
1002 self.length = len(tokens)
1006 return self.index < self.length
1009 return self.tokens[self.index]
1012 self.index = self.index + 1
1015 def ParseAtomicExpression(scan):
1016 if scan.Current() == "true":
1018 return Constant(True)
1019 elif scan.Current() == "false":
1021 return Constant(False)
1022 elif IsAlpha(scan.Current()):
1023 name = scan.Current()
1025 return Outcome(name.lower())
1026 elif scan.Current() == '$':
1028 if not IsAlpha(scan.Current()):
1030 name = scan.Current()
1032 return Variable(name.lower())
1033 elif scan.Current() == '(':
1035 result = ParseLogicalExpression(scan)
1036 if (not result) or (scan.Current() != ')'):
1045 def ParseOperatorExpression(scan):
1046 left = ParseAtomicExpression(scan)
1047 if not left: return None
1048 while scan.HasMore() and (scan.Current() in BINARIES):
1051 right = ParseOperatorExpression(scan)
1054 left = Operation(left, op, right)
1058 def ParseConditionalExpression(scan):
1059 left = ParseOperatorExpression(scan)
1060 if not left: return None
1061 while scan.HasMore() and (scan.Current() == 'if'):
1063 right = ParseOperatorExpression(scan)
1066 left= Operation(left, 'if', right)
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):
1077 right = ParseConditionalExpression(scan)
1080 left = Operation(left, op, right)
1084 def ParseCondition(expr):
1085 """Parses a logical expression into an Expression object"""
1086 tokens = Tokenizer(expr).Tokenize()
1088 print "Malformed expression: '%s'" % expr
1090 scan = Scanner(tokens)
1091 ast = ParseLogicalExpression(scan)
1093 print "Malformed expression: '%s'" % expr
1096 print "Malformed expression: '%s'" % expr
1101 class ClassifiedTest(object):
1103 def __init__(self, case, outcomes):
1105 self.outcomes = outcomes
1106 self.parallel = self.case.parallel
1109 class Configuration(object):
1110 """The parsed contents of a configuration file"""
1112 def __init__(self, sections, defs):
1113 self.sections = sections
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)
1121 all_outcomes = set([])
1123 matches = [ r for r in all_rules if r.Contains(case.path) ]
1125 for rule in matches:
1126 outcomes = outcomes.union(rule.GetOutcomes(env, self.defs))
1127 unused_rules.discard(rule)
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)
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"""
1140 def __init__(self, condition):
1141 self.condition = condition
1144 def AddRule(self, rule):
1145 self.rules.append(rule)
1149 """A single rule that specifies the expected outcome for a single
1152 def __init__(self, raw_path, path, value):
1153 self.raw_path = raw_path
1157 def GetOutcomes(self, env, defs):
1158 set = self.value.GetOutcomes(env, defs)
1159 assert isinstance(set, ListSet)
1162 def Contains(self, path):
1163 if len(self.path) > len(path):
1165 for i in xrange(len(self.path)):
1166 if not self.path[i].match(path[i]):
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\_\.\-\/]+)$')
1177 def ReadConfigurationInto(path, sections, defs):
1178 current_section = Section(Constant(True))
1179 sections.append(current_section)
1181 for line in utils.ReadLinesFrom(path):
1182 header_match = HEADER_PATTERN.match(line)
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
1190 rule_match = RULE_PATTERN.match(line)
1192 path = prefix + SplitPath(rule_match.group(1).strip())
1193 value_str = rule_match.group(2).strip()
1194 value = ParseCondition(value_str)
1197 current_section.AddRule(Rule(rule_match.group(1), path, value))
1199 def_match = DEF_PATTERN.match(line)
1201 name = def_match.group(1).lower()
1202 value = ParseCondition(def_match.group(2).strip())
1207 prefix_match = PREFIX_PATTERN.match(line)
1209 prefix = SplitPath(prefix_match.group(1).strip())
1211 print "Malformed line: '%s'." % line
1221 ARCH_GUESS = utils.GuessArchitecture()
1225 result = optparse.OptionParser()
1226 result.add_option("-m", "--mode", help="The test modes in which to run (comma-separated)",
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',
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")
1278 def ProcessOptions(options):
1280 VERBOSE = options.verbose
1281 options.arch = options.arch.split(',')
1282 options.mode = options.mode.split(',')
1284 options.j = multiprocessing.cpu_count()
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\
1296 def PrintReport(cases):
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]])
1309 class Pattern(object):
1311 def __init__(self, pattern):
1312 self.pattern = pattern
1313 self.compiled = None
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)
1326 stripped = [ c.strip() for c in s.split('/') ]
1327 return [ Pattern(s) for s in stripped if len(s) > 0 ]
1330 def GetSpecialCommandProcessor(value):
1331 if (not value) or (value.find('@') == -1):
1332 def ExpandCommand(args):
1334 return ExpandCommand
1336 pos = value.find('@')
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
1357 def GetSuites(test_root):
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)) ]
1364 millis = round(d * 1000) % 1000
1365 return time.strftime("%M:%S.", time.gmtime(d)) + ("%03i" % millis)
1369 parser = BuildOptions()
1370 (options, args) = parser.parse_args()
1371 if not ProcessOptions(options):
1375 ch = logging.StreamHandler(sys.stdout)
1376 logger.addHandler(ch)
1377 logger.setLevel(logging.INFO)
1379 fh = logging.FileHandler(options.logfile)
1380 logger.addHandler(fh)
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]
1387 root = LiteralTestSuite(repositories)
1389 paths = [SplitPath(t) for t in BUILT_IN_TESTS]
1393 path = SplitPath(arg)
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 + " @"
1402 shell = abspath(options.shell)
1403 buildspace = dirname(shell)
1405 processor = GetSpecialCommandProcessor(options.special_command)
1406 context = Context(workspace,
1412 options.suppress_dialogs,
1413 options.store_unexpected_output)
1414 # First build the required targets
1415 if not options.no_build:
1418 reqs += root.GetBuildRequirements(path, context)
1419 reqs = list(set(reqs))
1422 options.scons_flags += ['-j', str(options.j)]
1423 if not BuildRequirements(context, reqs, options.mode, options.scons_flags):
1426 # Just return if we are only building the targets for running the tests.
1427 if options.build_only:
1430 # Get status for tests
1433 root.GetTestStatus(context, sections, defs)
1434 config = Configuration(sections, defs)
1439 unclassified_tests = [ ]
1440 globally_unused_rules = None
1442 for arch in options.arch:
1443 for mode in options.mode:
1444 vm = context.GetVm(arch, mode)
1446 print "Can't find shell executable: '%s'" % vm
1450 'system': utils.GuessOS(),
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)
1460 globally_unused_rules = (
1461 globally_unused_rules.intersection(unused_rules))
1463 all_unused.append(unused_rules)
1467 for test in unclassified_tests:
1468 key = tuple(test.path)
1472 print "--- begin source: %s ---" % test.GetLabel()
1473 source = test.GetSource().strip()
1475 print "--- end source: %s ---" % test.GetLabel()
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])
1483 PrintReport(all_cases)
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."
1495 if RunTestCases(cases_to_run, options.progress, options.j):
1499 duration = time.time() - start
1500 except KeyboardInterrupt:
1505 # Write the times to stderr to make it easy to separate from the
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))
1512 for entry in timed_tests[:20]:
1513 t = FormatTime(entry.duration)
1514 sys.stderr.write("%4i (%s) %s\n" % (index, t, entry.GetLabel()))
1520 if __name__ == '__main__':