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.
43 import multiprocessing
46 from os.path import join, dirname, abspath, basename, isdir, exists
47 from datetime import datetime
48 from Queue import Queue, Empty
53 # ---------------------------------------------
54 # --- P r o g r e s s I n d i c a t o r s ---
55 # ---------------------------------------------
58 class ProgressIndicator(object):
60 def __init__(self, cases, flaky_tests_mode):
62 self.flaky_tests_mode = flaky_tests_mode
63 self.parallel_queue = Queue(len(cases))
64 self.sequential_queue = Queue(len(cases))
67 self.parallel_queue.put_nowait(case)
69 self.sequential_queue.put_nowait(case)
71 self.remaining = len(cases)
72 self.total = len(cases)
75 self.lock = threading.Lock()
76 self.shutdown_event = threading.Event()
78 def PrintFailureHeader(self, test):
80 negative_marker = '[negative] '
83 print "=== %(label)s %(negative)s===" % {
84 'label': test.GetLabel(),
85 'negative': negative_marker
87 print "Path: %s" % "/".join(test.path)
92 # Spawn N-1 threads and then use this thread as the last one.
93 # That way -j1 avoids threading altogether which is a nice fallback
94 # in case of threading problems.
95 for i in xrange(tasks - 1):
96 thread = threading.Thread(target=self.RunSingle, args=[True, i + 1])
97 threads.append(thread)
100 self.RunSingle(False, 0)
101 # Wait for the remaining threads
102 for thread in threads:
103 # Use a timeout so that signals (ctrl-c) will be processed.
104 thread.join(timeout=10000000)
105 except (KeyboardInterrupt, SystemExit), e:
106 self.shutdown_event.set()
108 # If there's an exception we schedule an interruption for any
110 self.shutdown_event.set()
111 # ...and then reraise the exception to bail out
114 return not self.failed
116 def RunSingle(self, parallel, thread_id):
117 while not self.shutdown_event.is_set():
119 test = self.parallel_queue.get_nowait()
124 test = self.sequential_queue.get_nowait()
128 case.thread_id = thread_id
130 self.AboutToRun(case)
133 start = datetime.now()
135 case.duration = (datetime.now() - start)
138 if self.shutdown_event.is_set():
141 if output.UnexpectedOutput():
142 self.failed.append(output)
143 if output.HasCrashed():
152 def EscapeCommand(command):
156 # Escape spaces. We may need to escape more characters for this
158 parts.append('"%s"' % part)
161 return " ".join(parts)
164 class SimpleProgressIndicator(ProgressIndicator):
167 print 'Running %i tests' % len(self.cases)
171 for failed in self.failed:
172 self.PrintFailureHeader(failed.test)
173 if failed.output.stderr:
174 print "--- stderr ---"
175 print failed.output.stderr.strip()
176 if failed.output.stdout:
177 print "--- stdout ---"
178 print failed.output.stdout.strip()
179 print "Command: %s" % EscapeCommand(failed.command)
180 if failed.HasCrashed():
181 print "--- CRASHED ---"
182 if failed.HasTimedOut():
183 print "--- TIMEOUT ---"
184 if len(self.failed) == 0:
186 print "=== All tests succeeded"
191 print "=== %i tests failed" % len(self.failed)
193 print "=== %i tests CRASHED" % self.crashed
197 class VerboseProgressIndicator(SimpleProgressIndicator):
199 def AboutToRun(self, case):
200 print 'Starting %s...' % case.GetLabel()
203 def HasRun(self, output):
204 if output.UnexpectedOutput():
205 if output.HasCrashed():
211 print 'Done running %s: %s' % (output.test.GetLabel(), outcome)
214 class DotsProgressIndicator(SimpleProgressIndicator):
216 def AboutToRun(self, case):
219 def HasRun(self, output):
220 total = self.succeeded + len(self.failed)
221 if (total > 1) and (total % 50 == 1):
222 sys.stdout.write('\n')
223 if output.UnexpectedOutput():
224 if output.HasCrashed():
225 sys.stdout.write('C')
227 elif output.HasTimedOut():
228 sys.stdout.write('T')
231 sys.stdout.write('F')
234 sys.stdout.write('.')
238 class TapProgressIndicator(SimpleProgressIndicator):
241 print '1..%i' % len(self.cases)
244 def AboutToRun(self, case):
247 def HasRun(self, output):
249 command = basename(output.command[-1])
250 if output.UnexpectedOutput():
251 status_line = 'not ok %i - %s' % (self._done, command)
252 if FLAKY in output.test.outcomes and self.flaky_tests_mode == "dontcare":
253 status_line = status_line + " # TODO : Fix flaky test"
255 for l in output.output.stderr.splitlines():
257 for l in output.output.stdout.splitlines():
260 status_line = 'ok %i - %s' % (self._done, command)
261 if FLAKY in output.test.outcomes:
262 status_line = status_line + " # TODO : Fix flaky test"
265 duration = output.test.duration
267 # total_seconds() was added in 2.7
268 total_seconds = (duration.microseconds +
269 (duration.seconds + duration.days * 24 * 3600) * 10**6) / 10**6
272 print ' duration_ms: %d.%d' % (total_seconds, duration.microseconds / 1000)
279 class CompactProgressIndicator(ProgressIndicator):
281 def __init__(self, cases, flaky_tests_mode, templates):
282 super(CompactProgressIndicator, self).__init__(cases, flaky_tests_mode)
283 self.templates = templates
284 self.last_status_length = 0
285 self.start_time = time.time()
291 self.PrintProgress('Done')
293 def AboutToRun(self, case):
294 self.PrintProgress(case.GetLabel())
296 def HasRun(self, output):
297 if output.UnexpectedOutput():
298 self.ClearLine(self.last_status_length)
299 self.PrintFailureHeader(output.test)
300 stdout = output.output.stdout.strip()
302 print self.templates['stdout'] % stdout
303 stderr = output.output.stderr.strip()
305 print self.templates['stderr'] % stderr
306 print "Command: %s" % EscapeCommand(output.command)
307 if output.HasCrashed():
308 print "--- CRASHED ---"
309 if output.HasTimedOut():
310 print "--- TIMEOUT ---"
312 def Truncate(self, str, length):
313 if length and (len(str) > (length - 3)):
314 return str[:(length-3)] + "..."
318 def PrintProgress(self, name):
319 self.ClearLine(self.last_status_length)
320 elapsed = time.time() - self.start_time
321 status = self.templates['status_line'] % {
322 'passed': self.succeeded,
323 'remaining': (((self.total - self.remaining) * 100) // self.total),
324 'failed': len(self.failed),
326 'mins': int(elapsed) / 60,
327 'secs': int(elapsed) % 60
329 status = self.Truncate(status, 78)
330 self.last_status_length = len(status)
335 class ColorProgressIndicator(CompactProgressIndicator):
337 def __init__(self, cases, flaky_tests_mode):
339 '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",
340 'stdout': "\033[1m%s\033[0m",
341 'stderr': "\033[31m%s\033[0m",
343 super(ColorProgressIndicator, self).__init__(cases, flaky_tests_mode, templates)
345 def ClearLine(self, last_line_length):
349 class MonochromeProgressIndicator(CompactProgressIndicator):
351 def __init__(self, cases, flaky_tests_mode):
353 'status_line': "[%(mins)02i:%(secs)02i|%%%(remaining) 4d|+%(passed) 4d|-%(failed) 4d]: %(test)s",
356 'clear': lambda last_line_length: ("\r" + (" " * last_line_length) + "\r"),
359 super(MonochromeProgressIndicator, self).__init__(cases, flaky_tests_mode, templates)
361 def ClearLine(self, last_line_length):
362 print ("\r" + (" " * last_line_length) + "\r"),
365 PROGRESS_INDICATORS = {
366 'verbose': VerboseProgressIndicator,
367 'dots': DotsProgressIndicator,
368 'color': ColorProgressIndicator,
369 'tap': TapProgressIndicator,
370 'mono': MonochromeProgressIndicator
374 # -------------------------
375 # --- F r a m e w o r k ---
376 # -------------------------
379 class CommandOutput(object):
381 def __init__(self, exit_code, timed_out, stdout, stderr):
382 self.exit_code = exit_code
383 self.timed_out = timed_out
389 class TestCase(object):
391 def __init__(self, context, path, arch, mode):
393 self.context = context
397 self.parallel = False
400 def IsNegative(self):
403 def CompareTime(self, other):
404 return cmp(other.duration, self.duration)
406 def DidFail(self, output):
407 if output.failed is None:
408 output.failed = self.IsFailureOutput(output)
411 def IsFailureOutput(self, output):
412 return output.exit_code != 0
415 return "(no source available)"
417 def RunCommand(self, command, env):
418 full_command = self.context.processor(command)
419 output = Execute(full_command,
421 self.context.GetTimeout(self.mode),
424 return TestOutput(self,
427 self.context.store_unexpected_output)
432 def AfterRun(self, result):
439 result = self.RunCommand(self.GetCommand(), {
440 "TEST_THREAD_ID": "%d" % self.thread_id
443 # Tests can leave the tty in non-blocking mode. If the test runner
444 # tries to print to stdout/stderr after that and the tty buffer is
445 # full, it'll die with a EAGAIN OSError. Ergo, put the tty back in
446 # blocking mode before proceeding.
447 if sys.platform != 'win32':
448 from fcntl import fcntl, F_GETFL, F_SETFL
449 from os import O_NONBLOCK
450 for fd in 0,1,2: fcntl(fd, F_SETFL, ~O_NONBLOCK & fcntl(fd, F_GETFL))
452 self.AfterRun(result)
459 class TestOutput(object):
461 def __init__(self, test, command, output, store_unexpected_output):
463 self.command = command
465 self.store_unexpected_output = store_unexpected_output
467 def UnexpectedOutput(self):
468 if self.HasCrashed():
470 elif self.HasTimedOut():
472 elif self.HasFailed():
476 return not outcome in self.test.outcomes
478 def HasPreciousOutput(self):
479 return self.UnexpectedOutput() and self.store_unexpected_output
481 def HasCrashed(self):
482 if utils.IsWindows():
483 return 0x80000000 & self.output.exit_code and not (0x3FFFFF00 & self.output.exit_code)
485 # Timed out tests will have exit_code -signal.SIGTERM.
486 if self.output.timed_out:
488 return self.output.exit_code < 0 and \
489 self.output.exit_code != -signal.SIGABRT
491 def HasTimedOut(self):
492 return self.output.timed_out;
495 execution_failed = self.test.DidFail(self.output)
496 if self.test.IsNegative():
497 return not execution_failed
499 return execution_failed
502 def KillProcessWithID(pid):
503 if utils.IsWindows():
504 os.popen('taskkill /T /F /PID %d' % pid)
506 os.kill(pid, signal.SIGTERM)
510 INITIAL_SLEEP_TIME = 0.0001
511 SLEEP_TIME_FACTOR = 1.25
513 SEM_INVALID_VALUE = -1
514 SEM_NOGPFAULTERRORBOX = 0x0002 # Microsoft Platform SDK WinBase.h
516 def Win32SetErrorMode(mode):
517 prev_error_mode = SEM_INVALID_VALUE
520 prev_error_mode = ctypes.windll.kernel32.SetErrorMode(mode);
523 return prev_error_mode
525 def RunProcess(context, timeout, args, **rest):
526 if context.verbose: print "#", " ".join(args)
528 prev_error_mode = SEM_INVALID_VALUE;
529 if utils.IsWindows():
530 if context.suppress_dialogs:
531 # Try to change the error mode to avoid dialogs on fatal errors. Don't
532 # touch any existing error mode flags by merging the existing error mode.
533 # See http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx.
534 error_mode = SEM_NOGPFAULTERRORBOX;
535 prev_error_mode = Win32SetErrorMode(error_mode);
536 Win32SetErrorMode(error_mode | prev_error_mode);
537 process = subprocess.Popen(
538 shell = utils.IsWindows(),
542 if utils.IsWindows() and context.suppress_dialogs and prev_error_mode != SEM_INVALID_VALUE:
543 Win32SetErrorMode(prev_error_mode)
544 # Compute the end time - if the process crosses this limit we
545 # consider it timed out.
546 if timeout is None: end_time = None
547 else: end_time = time.time() + timeout
549 # Repeatedly check the exit code from the process in a
550 # loop and keep track of whether or not it times out.
552 sleep_time = INITIAL_SLEEP_TIME
553 while exit_code is None:
554 if (not end_time is None) and (time.time() >= end_time):
555 # Kill the process and wait for it to exit.
556 KillProcessWithID(process.pid)
557 exit_code = process.wait()
560 exit_code = process.poll()
561 time.sleep(sleep_time)
562 sleep_time = sleep_time * SLEEP_TIME_FACTOR
563 if sleep_time > MAX_SLEEP_TIME:
564 sleep_time = MAX_SLEEP_TIME
565 return (process, exit_code, timed_out)
569 sys.stderr.write(str)
570 sys.stderr.write('\n')
573 def CheckedUnlink(name):
578 # On Windows unlink() fails if another process (typically a virus scanner
579 # or the indexing service) has the file open. Those processes keep a
580 # file open for a short time only, so yield and try again; it'll succeed.
581 if sys.platform == 'win32' and e.errno == errno.EACCES:
584 PrintError("os.unlink() " + str(e))
587 def Execute(args, context, timeout=None, env={}):
588 (fd_out, outname) = tempfile.mkstemp()
589 (fd_err, errname) = tempfile.mkstemp()
592 env_copy = os.environ.copy()
593 for key, value in env.iteritems():
594 env_copy[key] = value
596 (process, exit_code, timed_out) = RunProcess(
606 output = file(outname).read()
607 errors = file(errname).read()
608 CheckedUnlink(outname)
609 CheckedUnlink(errname)
610 return CommandOutput(exit_code, timed_out, output, errors)
613 def ExecuteNoCapture(args, context, timeout=None):
614 (process, exit_code, timed_out) = RunProcess(
619 return CommandOutput(exit_code, False, "", "")
626 return (path[0], path[1:])
629 class TestConfiguration(object):
631 def __init__(self, context, root):
632 self.context = context
635 def Contains(self, path, file):
636 if len(path) > len(file):
638 for i in xrange(len(path)):
639 if not path[i].match(file[i]):
643 def GetTestStatus(self, sections, defs):
647 class TestSuite(object):
649 def __init__(self, name):
656 # Use this to run several variants of the tests, e.g.:
657 # VARIANT_FLAGS = [[], ['--always_compact', '--noflush_code']]
661 class TestRepository(TestSuite):
663 def __init__(self, path):
664 normalized_path = abspath(path)
665 super(TestRepository, self).__init__(basename(normalized_path))
666 self.path = normalized_path
667 self.is_loaded = False
670 def GetConfiguration(self, context):
673 self.is_loaded = True
676 (file, pathname, description) = imp.find_module('testcfg', [ self.path ])
677 module = imp.load_module('testcfg', file, pathname, description)
678 self.config = module.GetConfiguration(context, self.path)
684 def GetBuildRequirements(self, path, context):
685 return self.GetConfiguration(context).GetBuildRequirements()
687 def AddTestsToList(self, result, current_path, path, context, arch, mode):
688 for v in VARIANT_FLAGS:
689 tests = self.GetConfiguration(context).ListTests(current_path, path,
691 for t in tests: t.variant_flags = v
695 def GetTestStatus(self, context, sections, defs):
696 self.GetConfiguration(context).GetTestStatus(sections, defs)
699 class LiteralTestSuite(TestSuite):
701 def __init__(self, tests):
702 super(LiteralTestSuite, self).__init__('root')
705 def GetBuildRequirements(self, path, context):
706 (name, rest) = CarCdr(path)
708 for test in self.tests:
709 if not name or name.match(test.GetName()):
710 result += test.GetBuildRequirements(rest, context)
713 def ListTests(self, current_path, path, context, arch, mode):
714 (name, rest) = CarCdr(path)
716 for test in self.tests:
717 test_name = test.GetName()
718 if not name or name.match(test_name):
719 full_path = current_path + [test_name]
720 test.AddTestsToList(result, full_path, path, context, arch, mode)
721 result.sort(cmp=lambda a, b: cmp(a.GetName(), b.GetName()))
724 def GetTestStatus(self, context, sections, defs):
725 for test in self.tests:
726 test.GetTestStatus(context, sections, defs)
733 'debug' : ['--enable-slow-asserts', '--debug-code', '--verify-heap'],
735 TIMEOUT_SCALEFACTOR = {
740 class Context(object):
742 def __init__(self, workspace, buildspace, verbose, vm, timeout, processor, suppress_dialogs, store_unexpected_output):
743 self.workspace = workspace
744 self.buildspace = buildspace
745 self.verbose = verbose
747 self.timeout = timeout
748 self.processor = processor
749 self.suppress_dialogs = suppress_dialogs
750 self.store_unexpected_output = store_unexpected_output
752 def GetVm(self, arch, mode):
754 name = 'out/Debug/iojs' if mode == 'debug' else 'out/Release/iojs'
756 name = 'out/%s.%s/iojs' % (arch, mode)
758 # Currently GYP does not support output_dir for MSVS.
759 # http://code.google.com/p/gyp/issues/detail?id=40
760 # It will put the builds into Release/iojs.exe or Debug/iojs.exe
761 if utils.IsWindows():
762 out_dir = os.path.join(dirname(__file__), "..", "out")
763 if not exists(out_dir):
765 name = os.path.abspath('Debug/iojs.exe')
767 name = os.path.abspath('Release/iojs.exe')
769 name = os.path.abspath(name + '.exe')
773 def GetVmFlags(self, testcase, mode):
774 return testcase.variant_flags + FLAGS[mode]
776 def GetTimeout(self, mode):
777 return self.timeout * TIMEOUT_SCALEFACTOR[mode]
779 def RunTestCases(cases_to_run, progress, tasks, flaky_tests_mode):
780 progress = PROGRESS_INDICATORS[progress](cases_to_run, flaky_tests_mode)
781 return progress.Run(tasks)
784 def BuildRequirements(context, requirements, mode, scons_flags):
785 command_line = (['scons', '-Y', context.workspace, 'mode=' + ",".join(mode)]
788 output = ExecuteNoCapture(command_line, context)
789 return output.exit_code == 0
792 # -------------------------------------------
793 # --- T e s t C o n f i g u r a t i o n ---
794 # -------------------------------------------
807 class Expression(object):
811 class Constant(Expression):
813 def __init__(self, value):
816 def Evaluate(self, env, defs):
820 class Variable(Expression):
822 def __init__(self, name):
825 def GetOutcomes(self, env, defs):
826 if self.name in env: return ListSet([env[self.name]])
827 else: return Nothing()
830 class Outcome(Expression):
832 def __init__(self, name):
835 def GetOutcomes(self, env, defs):
836 if self.name in defs:
837 return defs[self.name].GetOutcomes(env, defs)
839 return ListSet([self.name])
848 def __init__(self, elms):
852 return "ListSet%s" % str(self.elms)
854 def Intersect(self, that):
855 if not isinstance(that, ListSet):
856 return that.Intersect(self)
857 return ListSet([ x for x in self.elms if x in that.elms ])
859 def Union(self, that):
860 if not isinstance(that, ListSet):
861 return that.Union(self)
862 return ListSet(self.elms + [ x for x in that.elms if x not in self.elms ])
865 return len(self.elms) == 0
868 class Everything(Set):
870 def Intersect(self, that):
873 def Union(self, that):
882 def Intersect(self, that):
885 def Union(self, that):
892 class Operation(Expression):
894 def __init__(self, left, op, right):
899 def Evaluate(self, env, defs):
900 if self.op == '||' or self.op == ',':
901 return self.left.Evaluate(env, defs) or self.right.Evaluate(env, defs)
902 elif self.op == 'if':
904 elif self.op == '==':
905 inter = self.left.GetOutcomes(env, defs).Intersect(self.right.GetOutcomes(env, defs))
906 return not inter.IsEmpty()
908 assert self.op == '&&'
909 return self.left.Evaluate(env, defs) and self.right.Evaluate(env, defs)
911 def GetOutcomes(self, env, defs):
912 if self.op == '||' or self.op == ',':
913 return self.left.GetOutcomes(env, defs).Union(self.right.GetOutcomes(env, defs))
914 elif self.op == 'if':
915 if self.right.Evaluate(env, defs): return self.left.GetOutcomes(env, defs)
916 else: return Nothing()
918 assert self.op == '&&'
919 return self.left.GetOutcomes(env, defs).Intersect(self.right.GetOutcomes(env, defs))
924 if not (char.isalpha() or char.isdigit() or char == '_'):
929 class Tokenizer(object):
930 """A simple string tokenizer that chops expressions into variables,
931 parens and operators"""
933 def __init__(self, expr):
936 self.length = len(expr)
939 def Current(self, length = 1):
940 if not self.HasMore(length): return ""
941 return self.expr[self.index:self.index+length]
943 def HasMore(self, length = 1):
944 return self.index < self.length + (length - 1)
946 def Advance(self, count = 1):
947 self.index = self.index + count
949 def AddToken(self, token):
950 self.tokens.append(token)
952 def SkipSpaces(self):
953 while self.HasMore() and self.Current().isspace():
958 while self.HasMore():
960 if not self.HasMore():
962 if self.Current() == '(':
965 elif self.Current() == ')':
968 elif self.Current() == '$':
971 elif self.Current() == ',':
974 elif IsAlpha(self.Current()):
976 while self.HasMore() and IsAlpha(self.Current()):
977 buf += self.Current()
980 elif self.Current(2) == '&&':
983 elif self.Current(2) == '||':
986 elif self.Current(2) == '==':
994 class Scanner(object):
995 """A simple scanner that can serve out tokens from a given list"""
997 def __init__(self, tokens):
999 self.length = len(tokens)
1003 return self.index < self.length
1006 return self.tokens[self.index]
1009 self.index = self.index + 1
1012 def ParseAtomicExpression(scan):
1013 if scan.Current() == "true":
1015 return Constant(True)
1016 elif scan.Current() == "false":
1018 return Constant(False)
1019 elif IsAlpha(scan.Current()):
1020 name = scan.Current()
1022 return Outcome(name.lower())
1023 elif scan.Current() == '$':
1025 if not IsAlpha(scan.Current()):
1027 name = scan.Current()
1029 return Variable(name.lower())
1030 elif scan.Current() == '(':
1032 result = ParseLogicalExpression(scan)
1033 if (not result) or (scan.Current() != ')'):
1042 def ParseOperatorExpression(scan):
1043 left = ParseAtomicExpression(scan)
1044 if not left: return None
1045 while scan.HasMore() and (scan.Current() in BINARIES):
1048 right = ParseOperatorExpression(scan)
1051 left = Operation(left, op, right)
1055 def ParseConditionalExpression(scan):
1056 left = ParseOperatorExpression(scan)
1057 if not left: return None
1058 while scan.HasMore() and (scan.Current() == 'if'):
1060 right = ParseOperatorExpression(scan)
1063 left= Operation(left, 'if', right)
1067 LOGICALS = ["&&", "||", ","]
1068 def ParseLogicalExpression(scan):
1069 left = ParseConditionalExpression(scan)
1070 if not left: return None
1071 while scan.HasMore() and (scan.Current() in LOGICALS):
1074 right = ParseConditionalExpression(scan)
1077 left = Operation(left, op, right)
1081 def ParseCondition(expr):
1082 """Parses a logical expression into an Expression object"""
1083 tokens = Tokenizer(expr).Tokenize()
1085 print "Malformed expression: '%s'" % expr
1087 scan = Scanner(tokens)
1088 ast = ParseLogicalExpression(scan)
1090 print "Malformed expression: '%s'" % expr
1093 print "Malformed expression: '%s'" % expr
1098 class ClassifiedTest(object):
1100 def __init__(self, case, outcomes):
1102 self.outcomes = outcomes
1103 self.parallel = self.case.parallel
1106 class Configuration(object):
1107 """The parsed contents of a configuration file"""
1109 def __init__(self, sections, defs):
1110 self.sections = sections
1113 def ClassifyTests(self, cases, env):
1114 sections = [s for s in self.sections if s.condition.Evaluate(env, self.defs)]
1115 all_rules = reduce(list.__add__, [s.rules for s in sections], [])
1116 unused_rules = set(all_rules)
1118 all_outcomes = set([])
1120 matches = [ r for r in all_rules if r.Contains(case.path) ]
1122 for rule in matches:
1123 outcomes = outcomes.union(rule.GetOutcomes(env, self.defs))
1124 unused_rules.discard(rule)
1127 case.outcomes = outcomes
1128 all_outcomes = all_outcomes.union(outcomes)
1129 result.append(ClassifiedTest(case, outcomes))
1130 return (result, list(unused_rules), all_outcomes)
1133 class Section(object):
1134 """A section of the configuration file. Sections are enabled or
1135 disabled prior to running the tests, based on their conditions"""
1137 def __init__(self, condition):
1138 self.condition = condition
1141 def AddRule(self, rule):
1142 self.rules.append(rule)
1146 """A single rule that specifies the expected outcome for a single
1149 def __init__(self, raw_path, path, value):
1150 self.raw_path = raw_path
1154 def GetOutcomes(self, env, defs):
1155 set = self.value.GetOutcomes(env, defs)
1156 assert isinstance(set, ListSet)
1159 def Contains(self, path):
1160 if len(self.path) > len(path):
1162 for i in xrange(len(self.path)):
1163 if not self.path[i].match(path[i]):
1168 HEADER_PATTERN = re.compile(r'\[([^]]+)\]')
1169 RULE_PATTERN = re.compile(r'\s*([^: ]*)\s*:(.*)')
1170 DEF_PATTERN = re.compile(r'^def\s*(\w+)\s*=(.*)$')
1171 PREFIX_PATTERN = re.compile(r'^\s*prefix\s+([\w\_\.\-\/]+)$')
1174 def ReadConfigurationInto(path, sections, defs):
1175 current_section = Section(Constant(True))
1176 sections.append(current_section)
1178 for line in utils.ReadLinesFrom(path):
1179 header_match = HEADER_PATTERN.match(line)
1181 condition_str = header_match.group(1).strip()
1182 condition = ParseCondition(condition_str)
1183 new_section = Section(condition)
1184 sections.append(new_section)
1185 current_section = new_section
1187 rule_match = RULE_PATTERN.match(line)
1189 path = prefix + SplitPath(rule_match.group(1).strip())
1190 value_str = rule_match.group(2).strip()
1191 value = ParseCondition(value_str)
1194 current_section.AddRule(Rule(rule_match.group(1), path, value))
1196 def_match = DEF_PATTERN.match(line)
1198 name = def_match.group(1).lower()
1199 value = ParseCondition(def_match.group(2).strip())
1204 prefix_match = PREFIX_PATTERN.match(line)
1206 prefix = SplitPath(prefix_match.group(1).strip())
1208 print "Malformed line: '%s'." % line
1218 ARCH_GUESS = utils.GuessArchitecture()
1222 result = optparse.OptionParser()
1223 result.add_option("-m", "--mode", help="The test modes in which to run (comma-separated)",
1225 result.add_option("-v", "--verbose", help="Verbose output",
1226 default=False, action="store_true")
1227 result.add_option("-S", dest="scons_flags", help="Flag to pass through to scons",
1228 default=[], action="append")
1229 result.add_option("-p", "--progress",
1230 help="The style of progress indicator (verbose, dots, color, mono, tap)",
1231 choices=PROGRESS_INDICATORS.keys(), default="mono")
1232 result.add_option("--no-build", help="Don't build requirements",
1233 default=True, action="store_true")
1234 result.add_option("--build-only", help="Only build requirements, don't run the tests",
1235 default=False, action="store_true")
1236 result.add_option("--report", help="Print a summary of the tests to be run",
1237 default=False, action="store_true")
1238 result.add_option("-s", "--suite", help="A test suite",
1239 default=[], action="append")
1240 result.add_option("-t", "--timeout", help="Timeout in seconds",
1241 default=60, type="int")
1242 result.add_option("--arch", help='The architecture to run tests for',
1244 result.add_option("--snapshot", help="Run the tests with snapshot turned on",
1245 default=False, action="store_true")
1246 result.add_option("--special-command", default=None)
1247 result.add_option("--valgrind", help="Run tests through valgrind",
1248 default=False, action="store_true")
1249 result.add_option("--cat", help="Print the source of the tests",
1250 default=False, action="store_true")
1251 result.add_option("--flaky-tests",
1252 help="Regard tests marked as flaky (run|skip|dontcare)",
1254 result.add_option("--warn-unused", help="Report unused rules",
1255 default=False, action="store_true")
1256 result.add_option("-j", help="The number of parallel tasks to run",
1257 default=1, type="int")
1258 result.add_option("-J", help="Run tasks in parallel on all cores",
1259 default=False, action="store_true")
1260 result.add_option("--time", help="Print timing information after running",
1261 default=False, action="store_true")
1262 result.add_option("--suppress-dialogs", help="Suppress Windows dialogs for crashing tests",
1263 dest="suppress_dialogs", default=True, action="store_true")
1264 result.add_option("--no-suppress-dialogs", help="Display Windows dialogs for crashing tests",
1265 dest="suppress_dialogs", action="store_false")
1266 result.add_option("--shell", help="Path to V8 shell", default="shell")
1267 result.add_option("--store-unexpected-output",
1268 help="Store the temporary JS files from tests that fails",
1269 dest="store_unexpected_output", default=True, action="store_true")
1270 result.add_option("--no-store-unexpected-output",
1271 help="Deletes the temporary JS files from tests that fails",
1272 dest="store_unexpected_output", action="store_false")
1276 def ProcessOptions(options):
1278 VERBOSE = options.verbose
1279 options.arch = options.arch.split(',')
1280 options.mode = options.mode.split(',')
1282 options.j = multiprocessing.cpu_count()
1283 def CheckTestMode(name, option):
1284 if not option in ["run", "skip", "dontcare"]:
1285 print "Unknown %s mode %s" % (name, option)
1288 if not CheckTestMode("--flaky-tests", options.flaky_tests):
1293 REPORT_TEMPLATE = """\
1294 Total: %(total)i tests
1295 * %(skipped)4d tests will be skipped
1296 * %(nocrash)4d tests are expected to be flaky but not crash
1297 * %(pass)4d tests are expected to pass
1298 * %(fail_ok)4d tests are expected to fail that we won't fix
1299 * %(fail)4d tests are expected to fail that we should fix\
1302 def PrintReport(cases):
1304 return (PASS in o) and (FAIL in o) and (not CRASH in o) and (not OKAY in o)
1306 return (len(o) == 2) and (FAIL in o) and (OKAY in o)
1307 unskipped = [c for c in cases if not SKIP in c.outcomes]
1308 print REPORT_TEMPLATE % {
1309 'total': len(cases),
1310 'skipped': len(cases) - len(unskipped),
1311 'nocrash': len([t for t in unskipped if IsFlaky(t.outcomes)]),
1312 'pass': len([t for t in unskipped if list(t.outcomes) == [PASS]]),
1313 'fail_ok': len([t for t in unskipped if IsFailOk(t.outcomes)]),
1314 'fail': len([t for t in unskipped if list(t.outcomes) == [FAIL]])
1318 class Pattern(object):
1320 def __init__(self, pattern):
1321 self.pattern = pattern
1322 self.compiled = None
1324 def match(self, str):
1325 if not self.compiled:
1326 pattern = "^" + self.pattern.replace('*', '.*') + "$"
1327 self.compiled = re.compile(pattern)
1328 return self.compiled.match(str)
1335 stripped = [ c.strip() for c in s.split('/') ]
1336 return [ Pattern(s) for s in stripped if len(s) > 0 ]
1339 def GetSpecialCommandProcessor(value):
1340 if (not value) or (value.find('@') == -1):
1341 def ExpandCommand(args):
1343 return ExpandCommand
1345 pos = value.find('@')
1347 prefix = urllib.unquote(value[:pos]).split()
1348 suffix = urllib.unquote(value[pos+1:]).split()
1349 def ExpandCommand(args):
1350 return prefix + args + suffix
1351 return ExpandCommand
1366 def GetSuites(test_root):
1368 return isdir(path) and exists(join(path, 'testcfg.py'))
1369 return [ f for f in os.listdir(test_root) if IsSuite(join(test_root, f)) ]
1373 millis = round(d * 1000) % 1000
1374 return time.strftime("%M:%S.", time.gmtime(d)) + ("%03i" % millis)
1378 parser = BuildOptions()
1379 (options, args) = parser.parse_args()
1380 if not ProcessOptions(options):
1384 workspace = abspath(join(dirname(sys.argv[0]), '..'))
1385 suites = GetSuites(join(workspace, 'test'))
1386 repositories = [TestRepository(join(workspace, 'test', name)) for name in suites]
1387 repositories += [TestRepository(a) for a in options.suite]
1389 root = LiteralTestSuite(repositories)
1391 paths = [SplitPath(t) for t in BUILT_IN_TESTS]
1395 path = SplitPath(arg)
1398 # Check for --valgrind option. If enabled, we overwrite the special
1399 # command flag with a command that uses the run-valgrind.py script.
1400 if options.valgrind:
1401 run_valgrind = join(workspace, "tools", "run-valgrind.py")
1402 options.special_command = "python -u " + run_valgrind + " @"
1404 shell = abspath(options.shell)
1405 buildspace = dirname(shell)
1407 processor = GetSpecialCommandProcessor(options.special_command)
1408 context = Context(workspace,
1414 options.suppress_dialogs,
1415 options.store_unexpected_output)
1416 # First build the required targets
1417 if not options.no_build:
1420 reqs += root.GetBuildRequirements(path, context)
1421 reqs = list(set(reqs))
1424 options.scons_flags += ['-j', str(options.j)]
1425 if not BuildRequirements(context, reqs, options.mode, options.scons_flags):
1428 # Just return if we are only building the targets for running the tests.
1429 if options.build_only:
1432 # Get status for tests
1435 root.GetTestStatus(context, sections, defs)
1436 config = Configuration(sections, defs)
1441 unclassified_tests = [ ]
1442 globally_unused_rules = None
1444 for arch in options.arch:
1445 for mode in options.mode:
1446 vm = context.GetVm(arch, mode)
1448 print "Can't find shell executable: '%s'" % vm
1452 'system': utils.GuessOS(),
1455 test_list = root.ListTests([], path, context, arch, mode)
1456 unclassified_tests += test_list
1457 (cases, unused_rules, all_outcomes) = (
1458 config.ClassifyTests(test_list, env))
1459 if globally_unused_rules is None:
1460 globally_unused_rules = set(unused_rules)
1462 globally_unused_rules = (
1463 globally_unused_rules.intersection(unused_rules))
1465 all_unused.append(unused_rules)
1469 for test in unclassified_tests:
1470 key = tuple(test.path)
1474 print "--- begin source: %s ---" % test.GetLabel()
1475 source = test.GetSource().strip()
1477 print "--- end source: %s ---" % test.GetLabel()
1480 if options.warn_unused:
1481 for rule in globally_unused_rules:
1482 print "Rule for '%s' was not used." % '/'.join([str(s) for s in rule.path])
1485 PrintReport(all_cases)
1489 return SKIP in case.outcomes or SLOW in case.outcomes or (FLAKY in case.outcomes and options.flaky_tests == "skip")
1490 cases_to_run = [ c for c in all_cases if not DoSkip(c) ]
1491 if len(cases_to_run) == 0:
1492 print "No tests to run."
1497 if RunTestCases(cases_to_run, options.progress, options.j, options.flaky_tests):
1501 duration = time.time() - start
1502 except KeyboardInterrupt:
1507 # Write the times to stderr to make it easy to separate from the
1510 sys.stderr.write("--- Total time: %s ---\n" % FormatTime(duration))
1511 timed_tests = [ t.case for t in cases_to_run if not t.case.duration is None ]
1512 timed_tests.sort(lambda a, b: a.CompareTime(b))
1514 for entry in timed_tests[:20]:
1515 t = FormatTime(entry.duration)
1516 sys.stderr.write("%4i (%s) %s\n" % (index, t, entry.GetLabel()))
1522 if __name__ == '__main__':