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 from os.path import join, dirname, abspath, basename, isdir, exists
45 from datetime import datetime
46 from Queue import Queue, Empty
51 # ---------------------------------------------
52 # --- P r o g r e s s I n d i c a t o r s ---
53 # ---------------------------------------------
56 class ProgressIndicator(object):
58 def __init__(self, cases):
60 self.queue = Queue(len(cases))
62 self.queue.put_nowait(case)
64 self.remaining = len(cases)
65 self.total = len(cases)
68 self.terminate = False
69 self.lock = threading.Lock()
71 def PrintFailureHeader(self, test):
73 negative_marker = '[negative] '
76 print "=== %(label)s %(negative)s===" % {
77 'label': test.GetLabel(),
78 'negative': negative_marker
80 print "Path: %s" % "/".join(test.path)
85 # Spawn N-1 threads and then use this thread as the last one.
86 # That way -j1 avoids threading altogether which is a nice fallback
87 # in case of threading problems.
88 for i in xrange(tasks - 1):
89 thread = threading.Thread(target=self.RunSingle, args=[])
90 threads.append(thread)
94 # Wait for the remaining threads
95 for thread in threads:
96 # Use a timeout so that signals (ctrl-c) will be processed.
97 thread.join(timeout=10000000)
99 # If there's an exception we schedule an interruption for any
101 self.terminate = True
102 # ...and then reraise the exception to bail out
105 return not self.failed
108 while not self.terminate:
110 test = self.queue.get_nowait()
115 self.AboutToRun(case)
118 start = datetime.now()
120 case.duration = (datetime.now() - start)
122 assert self.terminate
127 if output.UnexpectedOutput():
128 self.failed.append(output)
129 if output.HasCrashed():
138 def EscapeCommand(command):
142 # Escape spaces. We may need to escape more characters for this
144 parts.append('"%s"' % part)
147 return " ".join(parts)
150 class SimpleProgressIndicator(ProgressIndicator):
153 print 'Running %i tests' % len(self.cases)
157 for failed in self.failed:
158 self.PrintFailureHeader(failed.test)
159 if failed.output.stderr:
160 print "--- stderr ---"
161 print failed.output.stderr.strip()
162 if failed.output.stdout:
163 print "--- stdout ---"
164 print failed.output.stdout.strip()
165 print "Command: %s" % EscapeCommand(failed.command)
166 if failed.HasCrashed():
167 print "--- CRASHED ---"
168 if failed.HasTimedOut():
169 print "--- TIMEOUT ---"
170 if len(self.failed) == 0:
172 print "=== All tests succeeded"
177 print "=== %i tests failed" % len(self.failed)
179 print "=== %i tests CRASHED" % self.crashed
183 class VerboseProgressIndicator(SimpleProgressIndicator):
185 def AboutToRun(self, case):
186 print 'Starting %s...' % case.GetLabel()
189 def HasRun(self, output):
190 if output.UnexpectedOutput():
191 if output.HasCrashed():
197 print 'Done running %s: %s' % (output.test.GetLabel(), outcome)
200 class DotsProgressIndicator(SimpleProgressIndicator):
202 def AboutToRun(self, case):
205 def HasRun(self, output):
206 total = self.succeeded + len(self.failed)
207 if (total > 1) and (total % 50 == 1):
208 sys.stdout.write('\n')
209 if output.UnexpectedOutput():
210 if output.HasCrashed():
211 sys.stdout.write('C')
213 elif output.HasTimedOut():
214 sys.stdout.write('T')
217 sys.stdout.write('F')
220 sys.stdout.write('.')
224 class TapProgressIndicator(SimpleProgressIndicator):
227 print '1..%i' % len(self.cases)
230 def AboutToRun(self, case):
233 def HasRun(self, output):
235 command = basename(output.command[-1])
236 if output.UnexpectedOutput():
237 print 'not ok %i - %s' % (self._done, command)
238 for l in output.output.stderr.splitlines():
240 for l in output.output.stdout.splitlines():
243 print 'ok %i - %s' % (self._done, command)
245 duration = output.test.duration
247 # total_seconds() was added in 2.7
248 total_seconds = (duration.microseconds +
249 (duration.seconds + duration.days * 24 * 3600) * 10**6) / 10**6
252 print ' duration_ms: %d.%d' % (total_seconds, duration.microseconds / 1000)
259 class CompactProgressIndicator(ProgressIndicator):
261 def __init__(self, cases, templates):
262 super(CompactProgressIndicator, self).__init__(cases)
263 self.templates = templates
264 self.last_status_length = 0
265 self.start_time = time.time()
271 self.PrintProgress('Done')
273 def AboutToRun(self, case):
274 self.PrintProgress(case.GetLabel())
276 def HasRun(self, output):
277 if output.UnexpectedOutput():
278 self.ClearLine(self.last_status_length)
279 self.PrintFailureHeader(output.test)
280 stdout = output.output.stdout.strip()
282 print self.templates['stdout'] % stdout
283 stderr = output.output.stderr.strip()
285 print self.templates['stderr'] % stderr
286 print "Command: %s" % EscapeCommand(output.command)
287 if output.HasCrashed():
288 print "--- CRASHED ---"
289 if output.HasTimedOut():
290 print "--- TIMEOUT ---"
292 def Truncate(self, str, length):
293 if length and (len(str) > (length - 3)):
294 return str[:(length-3)] + "..."
298 def PrintProgress(self, name):
299 self.ClearLine(self.last_status_length)
300 elapsed = time.time() - self.start_time
301 status = self.templates['status_line'] % {
302 'passed': self.succeeded,
303 'remaining': (((self.total - self.remaining) * 100) // self.total),
304 'failed': len(self.failed),
306 'mins': int(elapsed) / 60,
307 'secs': int(elapsed) % 60
309 status = self.Truncate(status, 78)
310 self.last_status_length = len(status)
315 class ColorProgressIndicator(CompactProgressIndicator):
317 def __init__(self, cases):
319 '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",
320 'stdout': "\033[1m%s\033[0m",
321 'stderr': "\033[31m%s\033[0m",
323 super(ColorProgressIndicator, self).__init__(cases, templates)
325 def ClearLine(self, last_line_length):
329 class MonochromeProgressIndicator(CompactProgressIndicator):
331 def __init__(self, cases):
333 'status_line': "[%(mins)02i:%(secs)02i|%%%(remaining) 4d|+%(passed) 4d|-%(failed) 4d]: %(test)s",
336 'clear': lambda last_line_length: ("\r" + (" " * last_line_length) + "\r"),
339 super(MonochromeProgressIndicator, self).__init__(cases, templates)
341 def ClearLine(self, last_line_length):
342 print ("\r" + (" " * last_line_length) + "\r"),
345 PROGRESS_INDICATORS = {
346 'verbose': VerboseProgressIndicator,
347 'dots': DotsProgressIndicator,
348 'color': ColorProgressIndicator,
349 'tap': TapProgressIndicator,
350 'mono': MonochromeProgressIndicator
354 # -------------------------
355 # --- F r a m e w o r k ---
356 # -------------------------
359 class CommandOutput(object):
361 def __init__(self, exit_code, timed_out, stdout, stderr):
362 self.exit_code = exit_code
363 self.timed_out = timed_out
369 class TestCase(object):
371 def __init__(self, context, path, mode):
373 self.context = context
377 def IsNegative(self):
380 def CompareTime(self, other):
381 return cmp(other.duration, self.duration)
383 def DidFail(self, output):
384 if output.failed is None:
385 output.failed = self.IsFailureOutput(output)
388 def IsFailureOutput(self, output):
389 return output.exit_code != 0
392 return "(no source available)"
394 def RunCommand(self, command):
395 full_command = self.context.processor(command)
396 output = Execute(full_command,
398 self.context.GetTimeout(self.mode))
400 return TestOutput(self,
403 self.context.store_unexpected_output)
408 def AfterRun(self, result):
415 result = self.RunCommand(self.GetCommand())
417 # Tests can leave the tty in non-blocking mode. If the test runner
418 # tries to print to stdout/stderr after that and the tty buffer is
419 # full, it'll die with a EAGAIN OSError. Ergo, put the tty back in
420 # blocking mode before proceeding.
421 if sys.platform != 'win32':
422 from fcntl import fcntl, F_GETFL, F_SETFL
423 from os import O_NONBLOCK
424 for fd in 0,1,2: fcntl(fd, F_SETFL, ~O_NONBLOCK & fcntl(fd, F_GETFL))
426 self.AfterRun(result)
433 class TestOutput(object):
435 def __init__(self, test, command, output, store_unexpected_output):
437 self.command = command
439 self.store_unexpected_output = store_unexpected_output
441 def UnexpectedOutput(self):
442 if self.HasCrashed():
444 elif self.HasTimedOut():
446 elif self.HasFailed():
450 return not outcome in self.test.outcomes
452 def HasPreciousOutput(self):
453 return self.UnexpectedOutput() and self.store_unexpected_output
455 def HasCrashed(self):
456 if utils.IsWindows():
457 return 0x80000000 & self.output.exit_code and not (0x3FFFFF00 & self.output.exit_code)
459 # Timed out tests will have exit_code -signal.SIGTERM.
460 if self.output.timed_out:
462 return self.output.exit_code < 0 and \
463 self.output.exit_code != -signal.SIGABRT
465 def HasTimedOut(self):
466 return self.output.timed_out;
469 execution_failed = self.test.DidFail(self.output)
470 if self.test.IsNegative():
471 return not execution_failed
473 return execution_failed
476 def KillProcessWithID(pid):
477 if utils.IsWindows():
478 os.popen('taskkill /T /F /PID %d' % pid)
480 os.kill(pid, signal.SIGTERM)
484 INITIAL_SLEEP_TIME = 0.0001
485 SLEEP_TIME_FACTOR = 1.25
487 SEM_INVALID_VALUE = -1
488 SEM_NOGPFAULTERRORBOX = 0x0002 # Microsoft Platform SDK WinBase.h
490 def Win32SetErrorMode(mode):
491 prev_error_mode = SEM_INVALID_VALUE
494 prev_error_mode = ctypes.windll.kernel32.SetErrorMode(mode);
497 return prev_error_mode
499 def RunProcess(context, timeout, args, **rest):
500 if context.verbose: print "#", " ".join(args)
502 prev_error_mode = SEM_INVALID_VALUE;
503 if utils.IsWindows():
504 if context.suppress_dialogs:
505 # Try to change the error mode to avoid dialogs on fatal errors. Don't
506 # touch any existing error mode flags by merging the existing error mode.
507 # See http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx.
508 error_mode = SEM_NOGPFAULTERRORBOX;
509 prev_error_mode = Win32SetErrorMode(error_mode);
510 Win32SetErrorMode(error_mode | prev_error_mode);
511 process = subprocess.Popen(
512 shell = utils.IsWindows(),
516 if utils.IsWindows() and context.suppress_dialogs and prev_error_mode != SEM_INVALID_VALUE:
517 Win32SetErrorMode(prev_error_mode)
518 # Compute the end time - if the process crosses this limit we
519 # consider it timed out.
520 if timeout is None: end_time = None
521 else: end_time = time.time() + timeout
523 # Repeatedly check the exit code from the process in a
524 # loop and keep track of whether or not it times out.
526 sleep_time = INITIAL_SLEEP_TIME
527 while exit_code is None:
528 if (not end_time is None) and (time.time() >= end_time):
529 # Kill the process and wait for it to exit.
530 KillProcessWithID(process.pid)
531 exit_code = process.wait()
534 exit_code = process.poll()
535 time.sleep(sleep_time)
536 sleep_time = sleep_time * SLEEP_TIME_FACTOR
537 if sleep_time > MAX_SLEEP_TIME:
538 sleep_time = MAX_SLEEP_TIME
539 return (process, exit_code, timed_out)
543 sys.stderr.write(str)
544 sys.stderr.write('\n')
547 def CheckedUnlink(name):
551 PrintError("os.unlink() " + str(e))
554 def Execute(args, context, timeout=None):
555 (fd_out, outname) = tempfile.mkstemp()
556 (fd_err, errname) = tempfile.mkstemp()
557 (process, exit_code, timed_out) = RunProcess(
566 output = file(outname).read()
567 errors = file(errname).read()
568 CheckedUnlink(outname)
569 CheckedUnlink(errname)
570 return CommandOutput(exit_code, timed_out, output, errors)
573 def ExecuteNoCapture(args, context, timeout=None):
574 (process, exit_code, timed_out) = RunProcess(
579 return CommandOutput(exit_code, False, "", "")
586 return (path[0], path[1:])
589 class TestConfiguration(object):
591 def __init__(self, context, root):
592 self.context = context
595 def Contains(self, path, file):
596 if len(path) > len(file):
598 for i in xrange(len(path)):
599 if not path[i].match(file[i]):
603 def GetTestStatus(self, sections, defs):
607 class TestSuite(object):
609 def __init__(self, name):
616 # Use this to run several variants of the tests, e.g.:
617 # VARIANT_FLAGS = [[], ['--always_compact', '--noflush_code']]
621 class TestRepository(TestSuite):
623 def __init__(self, path):
624 normalized_path = abspath(path)
625 super(TestRepository, self).__init__(basename(normalized_path))
626 self.path = normalized_path
627 self.is_loaded = False
630 def GetConfiguration(self, context):
633 self.is_loaded = True
636 (file, pathname, description) = imp.find_module('testcfg', [ self.path ])
637 module = imp.load_module('testcfg', file, pathname, description)
638 self.config = module.GetConfiguration(context, self.path)
644 def GetBuildRequirements(self, path, context):
645 return self.GetConfiguration(context).GetBuildRequirements()
647 def AddTestsToList(self, result, current_path, path, context, mode):
648 for v in VARIANT_FLAGS:
649 tests = self.GetConfiguration(context).ListTests(current_path, path, mode)
650 for t in tests: t.variant_flags = v
654 def GetTestStatus(self, context, sections, defs):
655 self.GetConfiguration(context).GetTestStatus(sections, defs)
658 class LiteralTestSuite(TestSuite):
660 def __init__(self, tests):
661 super(LiteralTestSuite, self).__init__('root')
664 def GetBuildRequirements(self, path, context):
665 (name, rest) = CarCdr(path)
667 for test in self.tests:
668 if not name or name.match(test.GetName()):
669 result += test.GetBuildRequirements(rest, context)
672 def ListTests(self, current_path, path, context, mode):
673 (name, rest) = CarCdr(path)
675 for test in self.tests:
676 test_name = test.GetName()
677 if not name or name.match(test_name):
678 full_path = current_path + [test_name]
679 test.AddTestsToList(result, full_path, path, context, mode)
680 result.sort(cmp=lambda a, b: cmp(a.GetName(), b.GetName()))
683 def GetTestStatus(self, context, sections, defs):
684 for test in self.tests:
685 test.GetTestStatus(context, sections, defs)
692 'debug' : ['--enable-slow-asserts', '--debug-code', '--verify-heap'],
694 TIMEOUT_SCALEFACTOR = {
699 class Context(object):
701 def __init__(self, workspace, buildspace, verbose, vm, timeout, processor, suppress_dialogs, store_unexpected_output):
702 self.workspace = workspace
703 self.buildspace = buildspace
704 self.verbose = verbose
706 self.timeout = timeout
707 self.processor = processor
708 self.suppress_dialogs = suppress_dialogs
709 self.store_unexpected_output = store_unexpected_output
711 def GetVm(self, mode):
713 name = 'out/Debug/node'
715 name = 'out/Release/node'
717 # Currently GYP does not support output_dir for MSVS.
718 # http://code.google.com/p/gyp/issues/detail?id=40
719 # It will put the builds into Release/node.exe or Debug/node.exe
720 if utils.IsWindows():
721 out_dir = os.path.join(dirname(__file__), "..", "out")
722 if not exists(out_dir):
724 name = os.path.abspath('Debug/node.exe')
726 name = os.path.abspath('Release/node.exe')
728 name = os.path.abspath(name + '.exe')
732 def GetVmCommand(self, testcase, mode):
733 return [self.GetVm(mode)] + self.GetVmFlags(testcase, mode)
735 def GetVmFlags(self, testcase, mode):
736 return testcase.variant_flags + FLAGS[mode]
738 def GetTimeout(self, mode):
739 return self.timeout * TIMEOUT_SCALEFACTOR[mode]
741 def RunTestCases(cases_to_run, progress, tasks):
742 progress = PROGRESS_INDICATORS[progress](cases_to_run)
743 return progress.Run(tasks)
746 def BuildRequirements(context, requirements, mode, scons_flags):
747 command_line = (['scons', '-Y', context.workspace, 'mode=' + ",".join(mode)]
750 output = ExecuteNoCapture(command_line, context)
751 return output.exit_code == 0
754 # -------------------------------------------
755 # --- T e s t C o n f i g u r a t i o n ---
756 # -------------------------------------------
768 class Expression(object):
772 class Constant(Expression):
774 def __init__(self, value):
777 def Evaluate(self, env, defs):
781 class Variable(Expression):
783 def __init__(self, name):
786 def GetOutcomes(self, env, defs):
787 if self.name in env: return ListSet([env[self.name]])
788 else: return Nothing()
791 class Outcome(Expression):
793 def __init__(self, name):
796 def GetOutcomes(self, env, defs):
797 if self.name in defs:
798 return defs[self.name].GetOutcomes(env, defs)
800 return ListSet([self.name])
809 def __init__(self, elms):
813 return "ListSet%s" % str(self.elms)
815 def Intersect(self, that):
816 if not isinstance(that, ListSet):
817 return that.Intersect(self)
818 return ListSet([ x for x in self.elms if x in that.elms ])
820 def Union(self, that):
821 if not isinstance(that, ListSet):
822 return that.Union(self)
823 return ListSet(self.elms + [ x for x in that.elms if x not in self.elms ])
826 return len(self.elms) == 0
829 class Everything(Set):
831 def Intersect(self, that):
834 def Union(self, that):
843 def Intersect(self, that):
846 def Union(self, that):
853 class Operation(Expression):
855 def __init__(self, left, op, right):
860 def Evaluate(self, env, defs):
861 if self.op == '||' or self.op == ',':
862 return self.left.Evaluate(env, defs) or self.right.Evaluate(env, defs)
863 elif self.op == 'if':
865 elif self.op == '==':
866 inter = self.left.GetOutcomes(env, defs).Intersect(self.right.GetOutcomes(env, defs))
867 return not inter.IsEmpty()
869 assert self.op == '&&'
870 return self.left.Evaluate(env, defs) and self.right.Evaluate(env, defs)
872 def GetOutcomes(self, env, defs):
873 if self.op == '||' or self.op == ',':
874 return self.left.GetOutcomes(env, defs).Union(self.right.GetOutcomes(env, defs))
875 elif self.op == 'if':
876 if self.right.Evaluate(env, defs): return self.left.GetOutcomes(env, defs)
877 else: return Nothing()
879 assert self.op == '&&'
880 return self.left.GetOutcomes(env, defs).Intersect(self.right.GetOutcomes(env, defs))
885 if not (char.isalpha() or char.isdigit() or char == '_'):
890 class Tokenizer(object):
891 """A simple string tokenizer that chops expressions into variables,
892 parens and operators"""
894 def __init__(self, expr):
897 self.length = len(expr)
900 def Current(self, length = 1):
901 if not self.HasMore(length): return ""
902 return self.expr[self.index:self.index+length]
904 def HasMore(self, length = 1):
905 return self.index < self.length + (length - 1)
907 def Advance(self, count = 1):
908 self.index = self.index + count
910 def AddToken(self, token):
911 self.tokens.append(token)
913 def SkipSpaces(self):
914 while self.HasMore() and self.Current().isspace():
919 while self.HasMore():
921 if not self.HasMore():
923 if self.Current() == '(':
926 elif self.Current() == ')':
929 elif self.Current() == '$':
932 elif self.Current() == ',':
935 elif IsAlpha(self.Current()):
937 while self.HasMore() and IsAlpha(self.Current()):
938 buf += self.Current()
941 elif self.Current(2) == '&&':
944 elif self.Current(2) == '||':
947 elif self.Current(2) == '==':
955 class Scanner(object):
956 """A simple scanner that can serve out tokens from a given list"""
958 def __init__(self, tokens):
960 self.length = len(tokens)
964 return self.index < self.length
967 return self.tokens[self.index]
970 self.index = self.index + 1
973 def ParseAtomicExpression(scan):
974 if scan.Current() == "true":
976 return Constant(True)
977 elif scan.Current() == "false":
979 return Constant(False)
980 elif IsAlpha(scan.Current()):
981 name = scan.Current()
983 return Outcome(name.lower())
984 elif scan.Current() == '$':
986 if not IsAlpha(scan.Current()):
988 name = scan.Current()
990 return Variable(name.lower())
991 elif scan.Current() == '(':
993 result = ParseLogicalExpression(scan)
994 if (not result) or (scan.Current() != ')'):
1003 def ParseOperatorExpression(scan):
1004 left = ParseAtomicExpression(scan)
1005 if not left: return None
1006 while scan.HasMore() and (scan.Current() in BINARIES):
1009 right = ParseOperatorExpression(scan)
1012 left = Operation(left, op, right)
1016 def ParseConditionalExpression(scan):
1017 left = ParseOperatorExpression(scan)
1018 if not left: return None
1019 while scan.HasMore() and (scan.Current() == 'if'):
1021 right = ParseOperatorExpression(scan)
1024 left= Operation(left, 'if', right)
1028 LOGICALS = ["&&", "||", ","]
1029 def ParseLogicalExpression(scan):
1030 left = ParseConditionalExpression(scan)
1031 if not left: return None
1032 while scan.HasMore() and (scan.Current() in LOGICALS):
1035 right = ParseConditionalExpression(scan)
1038 left = Operation(left, op, right)
1042 def ParseCondition(expr):
1043 """Parses a logical expression into an Expression object"""
1044 tokens = Tokenizer(expr).Tokenize()
1046 print "Malformed expression: '%s'" % expr
1048 scan = Scanner(tokens)
1049 ast = ParseLogicalExpression(scan)
1051 print "Malformed expression: '%s'" % expr
1054 print "Malformed expression: '%s'" % expr
1059 class ClassifiedTest(object):
1061 def __init__(self, case, outcomes):
1063 self.outcomes = outcomes
1066 class Configuration(object):
1067 """The parsed contents of a configuration file"""
1069 def __init__(self, sections, defs):
1070 self.sections = sections
1073 def ClassifyTests(self, cases, env):
1074 sections = [s for s in self.sections if s.condition.Evaluate(env, self.defs)]
1075 all_rules = reduce(list.__add__, [s.rules for s in sections], [])
1076 unused_rules = set(all_rules)
1078 all_outcomes = set([])
1080 matches = [ r for r in all_rules if r.Contains(case.path) ]
1082 for rule in matches:
1083 outcomes = outcomes.union(rule.GetOutcomes(env, self.defs))
1084 unused_rules.discard(rule)
1087 case.outcomes = outcomes
1088 all_outcomes = all_outcomes.union(outcomes)
1089 result.append(ClassifiedTest(case, outcomes))
1090 return (result, list(unused_rules), all_outcomes)
1093 class Section(object):
1094 """A section of the configuration file. Sections are enabled or
1095 disabled prior to running the tests, based on their conditions"""
1097 def __init__(self, condition):
1098 self.condition = condition
1101 def AddRule(self, rule):
1102 self.rules.append(rule)
1106 """A single rule that specifies the expected outcome for a single
1109 def __init__(self, raw_path, path, value):
1110 self.raw_path = raw_path
1114 def GetOutcomes(self, env, defs):
1115 set = self.value.GetOutcomes(env, defs)
1116 assert isinstance(set, ListSet)
1119 def Contains(self, path):
1120 if len(self.path) > len(path):
1122 for i in xrange(len(self.path)):
1123 if not self.path[i].match(path[i]):
1128 HEADER_PATTERN = re.compile(r'\[([^]]+)\]')
1129 RULE_PATTERN = re.compile(r'\s*([^: ]*)\s*:(.*)')
1130 DEF_PATTERN = re.compile(r'^def\s*(\w+)\s*=(.*)$')
1131 PREFIX_PATTERN = re.compile(r'^\s*prefix\s+([\w\_\.\-\/]+)$')
1134 def ReadConfigurationInto(path, sections, defs):
1135 current_section = Section(Constant(True))
1136 sections.append(current_section)
1138 for line in utils.ReadLinesFrom(path):
1139 header_match = HEADER_PATTERN.match(line)
1141 condition_str = header_match.group(1).strip()
1142 condition = ParseCondition(condition_str)
1143 new_section = Section(condition)
1144 sections.append(new_section)
1145 current_section = new_section
1147 rule_match = RULE_PATTERN.match(line)
1149 path = prefix + SplitPath(rule_match.group(1).strip())
1150 value_str = rule_match.group(2).strip()
1151 value = ParseCondition(value_str)
1154 current_section.AddRule(Rule(rule_match.group(1), path, value))
1156 def_match = DEF_PATTERN.match(line)
1158 name = def_match.group(1).lower()
1159 value = ParseCondition(def_match.group(2).strip())
1164 prefix_match = PREFIX_PATTERN.match(line)
1166 prefix = SplitPath(prefix_match.group(1).strip())
1168 print "Malformed line: '%s'." % line
1178 ARCH_GUESS = utils.GuessArchitecture()
1182 result = optparse.OptionParser()
1183 result.add_option("-m", "--mode", help="The test modes in which to run (comma-separated)",
1185 result.add_option("-v", "--verbose", help="Verbose output",
1186 default=False, action="store_true")
1187 result.add_option("-S", dest="scons_flags", help="Flag to pass through to scons",
1188 default=[], action="append")
1189 result.add_option("-p", "--progress",
1190 help="The style of progress indicator (verbose, dots, color, mono, tap)",
1191 choices=PROGRESS_INDICATORS.keys(), default="mono")
1192 result.add_option("--no-build", help="Don't build requirements",
1193 default=True, action="store_true")
1194 result.add_option("--build-only", help="Only build requirements, don't run the tests",
1195 default=False, action="store_true")
1196 result.add_option("--report", help="Print a summary of the tests to be run",
1197 default=False, action="store_true")
1198 result.add_option("-s", "--suite", help="A test suite",
1199 default=[], action="append")
1200 result.add_option("-t", "--timeout", help="Timeout in seconds",
1201 default=60, type="int")
1202 result.add_option("--arch", help='The architecture to run tests for',
1204 result.add_option("--snapshot", help="Run the tests with snapshot turned on",
1205 default=False, action="store_true")
1206 result.add_option("--simulator", help="Run tests with architecture simulator",
1208 result.add_option("--special-command", default=None)
1209 result.add_option("--use-http1", help="Pass --use-http1 switch to node",
1210 default=False, action="store_true")
1211 result.add_option("--valgrind", help="Run tests through valgrind",
1212 default=False, action="store_true")
1213 result.add_option("--cat", help="Print the source of the tests",
1214 default=False, action="store_true")
1215 result.add_option("--warn-unused", help="Report unused rules",
1216 default=False, action="store_true")
1217 result.add_option("-j", help="The number of parallel tasks to run",
1218 default=1, type="int")
1219 result.add_option("--time", help="Print timing information after running",
1220 default=False, action="store_true")
1221 result.add_option("--suppress-dialogs", help="Suppress Windows dialogs for crashing tests",
1222 dest="suppress_dialogs", default=True, action="store_true")
1223 result.add_option("--no-suppress-dialogs", help="Display Windows dialogs for crashing tests",
1224 dest="suppress_dialogs", action="store_false")
1225 result.add_option("--shell", help="Path to V8 shell", default="shell")
1226 result.add_option("--store-unexpected-output",
1227 help="Store the temporary JS files from tests that fails",
1228 dest="store_unexpected_output", default=True, action="store_true")
1229 result.add_option("--no-store-unexpected-output",
1230 help="Deletes the temporary JS files from tests that fails",
1231 dest="store_unexpected_output", action="store_false")
1235 def ProcessOptions(options):
1237 VERBOSE = options.verbose
1238 options.mode = options.mode.split(',')
1239 for mode in options.mode:
1240 if not mode in ['debug', 'release']:
1241 print "Unknown mode %s" % mode
1243 if options.simulator != 'none':
1244 # Simulator argument was set. Make sure arch and simulator agree.
1245 if options.simulator != options.arch:
1246 if options.arch == 'none':
1247 options.arch = options.simulator
1249 print "Architecture %s does not match sim %s" %(options.arch, options.simulator)
1251 # Ensure that the simulator argument is handed down to scons.
1252 options.scons_flags.append("simulator=" + options.simulator)
1254 # If options.arch is not set by the command line and no simulator setting
1255 # was found, set the arch to the guess.
1256 if options.arch == 'none':
1257 options.arch = ARCH_GUESS
1258 options.scons_flags.append("arch=" + options.arch)
1259 if options.snapshot:
1260 options.scons_flags.append("snapshot=on")
1264 REPORT_TEMPLATE = """\
1265 Total: %(total)i tests
1266 * %(skipped)4d tests will be skipped
1267 * %(nocrash)4d tests are expected to be flaky but not crash
1268 * %(pass)4d tests are expected to pass
1269 * %(fail_ok)4d tests are expected to fail that we won't fix
1270 * %(fail)4d tests are expected to fail that we should fix\
1273 def PrintReport(cases):
1275 return (PASS in o) and (FAIL in o) and (not CRASH in o) and (not OKAY in o)
1277 return (len(o) == 2) and (FAIL in o) and (OKAY in o)
1278 unskipped = [c for c in cases if not SKIP in c.outcomes]
1279 print REPORT_TEMPLATE % {
1280 'total': len(cases),
1281 'skipped': len(cases) - len(unskipped),
1282 'nocrash': len([t for t in unskipped if IsFlaky(t.outcomes)]),
1283 'pass': len([t for t in unskipped if list(t.outcomes) == [PASS]]),
1284 'fail_ok': len([t for t in unskipped if IsFailOk(t.outcomes)]),
1285 'fail': len([t for t in unskipped if list(t.outcomes) == [FAIL]])
1289 class Pattern(object):
1291 def __init__(self, pattern):
1292 self.pattern = pattern
1293 self.compiled = None
1295 def match(self, str):
1296 if not self.compiled:
1297 pattern = "^" + self.pattern.replace('*', '.*') + "$"
1298 self.compiled = re.compile(pattern)
1299 return self.compiled.match(str)
1306 stripped = [ c.strip() for c in s.split('/') ]
1307 return [ Pattern(s) for s in stripped if len(s) > 0 ]
1310 def GetSpecialCommandProcessor(value):
1311 if (not value) or (value.find('@') == -1):
1312 def ExpandCommand(args):
1314 return ExpandCommand
1316 pos = value.find('@')
1318 prefix = urllib.unquote(value[:pos]).split()
1319 suffix = urllib.unquote(value[pos+1:]).split()
1320 def ExpandCommand(args):
1321 return prefix + args + suffix
1322 return ExpandCommand
1325 BUILT_IN_TESTS = ['simple', 'pummel', 'message', 'internet', 'gc']
1328 def GetSuites(test_root):
1330 return isdir(path) and exists(join(path, 'testcfg.py'))
1331 return [ f for f in os.listdir(test_root) if IsSuite(join(test_root, f)) ]
1335 millis = round(d * 1000) % 1000
1336 return time.strftime("%M:%S.", time.gmtime(d)) + ("%03i" % millis)
1340 parser = BuildOptions()
1341 (options, args) = parser.parse_args()
1342 if not ProcessOptions(options):
1346 workspace = abspath(join(dirname(sys.argv[0]), '..'))
1347 suites = GetSuites(join(workspace, 'test'))
1348 repositories = [TestRepository(join(workspace, 'test', name)) for name in suites]
1349 repositories += [TestRepository(a) for a in options.suite]
1351 root = LiteralTestSuite(repositories)
1353 paths = [SplitPath(t) for t in BUILT_IN_TESTS]
1357 path = SplitPath(arg)
1360 # Check for --valgrind option. If enabled, we overwrite the special
1361 # command flag with a command that uses the run-valgrind.py script.
1362 if options.valgrind:
1363 run_valgrind = join(workspace, "tools", "run-valgrind.py")
1364 options.special_command = "python -u " + run_valgrind + " @"
1366 shell = abspath(options.shell)
1367 buildspace = dirname(shell)
1369 processor = GetSpecialCommandProcessor(options.special_command)
1370 if options.use_http1:
1371 def wrap(processor):
1372 return lambda args: processor(args[:1] + ['--use-http1'] + args[1:])
1373 processor = wrap(processor)
1375 context = Context(workspace,
1381 options.suppress_dialogs,
1382 options.store_unexpected_output)
1383 # First build the required targets
1384 if not options.no_build:
1387 reqs += root.GetBuildRequirements(path, context)
1388 reqs = list(set(reqs))
1391 options.scons_flags += ['-j', str(options.j)]
1392 if not BuildRequirements(context, reqs, options.mode, options.scons_flags):
1395 # Just return if we are only building the targets for running the tests.
1396 if options.build_only:
1399 # Get status for tests
1402 root.GetTestStatus(context, sections, defs)
1403 config = Configuration(sections, defs)
1408 unclassified_tests = [ ]
1409 globally_unused_rules = None
1411 for mode in options.mode:
1412 if not exists(context.GetVm(mode)):
1413 print "Can't find shell executable: '%s'" % context.GetVm(mode)
1417 'system': utils.GuessOS(),
1418 'arch': options.arch,
1419 'simulator': options.simulator
1421 test_list = root.ListTests([], path, context, mode)
1422 unclassified_tests += test_list
1423 (cases, unused_rules, all_outcomes) = config.ClassifyTests(test_list, env)
1424 if globally_unused_rules is None:
1425 globally_unused_rules = set(unused_rules)
1427 globally_unused_rules = globally_unused_rules.intersection(unused_rules)
1429 all_unused.append(unused_rules)
1433 for test in unclassified_tests:
1434 key = tuple(test.path)
1438 print "--- begin source: %s ---" % test.GetLabel()
1439 source = test.GetSource().strip()
1441 print "--- end source: %s ---" % test.GetLabel()
1444 if options.warn_unused:
1445 for rule in globally_unused_rules:
1446 print "Rule for '%s' was not used." % '/'.join([str(s) for s in rule.path])
1449 PrintReport(all_cases)
1453 return SKIP in case.outcomes or SLOW in case.outcomes
1454 cases_to_run = [ c for c in all_cases if not DoSkip(c) ]
1455 if len(cases_to_run) == 0:
1456 print "No tests to run."
1461 if RunTestCases(cases_to_run, options.progress, options.j):
1465 duration = time.time() - start
1466 except KeyboardInterrupt:
1471 # Write the times to stderr to make it easy to separate from the
1474 sys.stderr.write("--- Total time: %s ---\n" % FormatTime(duration))
1475 timed_tests = [ t.case for t in cases_to_run if not t.case.duration is None ]
1476 timed_tests.sort(lambda a, b: a.CompareTime(b))
1478 for entry in timed_tests[:20]:
1479 t = FormatTime(entry.duration)
1480 sys.stderr.write("%4i (%s) %s\n" % (index, t, entry.GetLabel()))
1486 if __name__ == '__main__':