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, flaky_tests_mode):
66 self.flaky_tests_mode = flaky_tests_mode
67 self.parallel_queue = Queue(len(cases))
68 self.sequential_queue = Queue(len(cases))
71 self.parallel_queue.put_nowait(case)
73 self.sequential_queue.put_nowait(case)
75 self.remaining = len(cases)
76 self.total = len(cases)
78 self.flaky_failed = [ ]
80 self.flaky_crashed = 0
81 self.lock = threading.Lock()
82 self.shutdown_event = threading.Event()
84 def PrintFailureHeader(self, test):
86 negative_marker = '[negative] '
89 print "=== %(label)s %(negative)s===" % {
90 'label': test.GetLabel(),
91 'negative': negative_marker
93 print "Path: %s" % "/".join(test.path)
98 # Spawn N-1 threads and then use this thread as the last one.
99 # That way -j1 avoids threading altogether which is a nice fallback
100 # in case of threading problems.
101 for i in xrange(tasks - 1):
102 thread = threading.Thread(target=self.RunSingle, args=[True, i + 1])
103 threads.append(thread)
106 self.RunSingle(False, 0)
107 # Wait for the remaining threads
108 for thread in threads:
109 # Use a timeout so that signals (ctrl-c) will be processed.
110 thread.join(timeout=10000000)
111 except (KeyboardInterrupt, SystemExit), e:
112 self.shutdown_event.set()
114 # If there's an exception we schedule an interruption for any
116 self.shutdown_event.set()
117 # ...and then reraise the exception to bail out
120 return not self.failed
122 def RunSingle(self, parallel, thread_id):
123 while not self.shutdown_event.is_set():
125 test = self.parallel_queue.get_nowait()
130 test = self.sequential_queue.get_nowait()
134 case.thread_id = thread_id
136 self.AboutToRun(case)
139 start = datetime.now()
141 case.duration = (datetime.now() - start)
144 if self.shutdown_event.is_set():
147 if output.UnexpectedOutput():
148 if FLAKY in output.test.outcomes and self.flaky_tests_mode == DONTCARE:
149 self.flaky_failed.append(output)
150 if output.HasCrashed():
151 self.flaky_crashed += 1
153 self.failed.append(output)
154 if output.HasCrashed():
163 def EscapeCommand(command):
167 # Escape spaces. We may need to escape more characters for this
169 parts.append('"%s"' % part)
172 return " ".join(parts)
175 class SimpleProgressIndicator(ProgressIndicator):
178 print 'Running %i tests' % len(self.cases)
182 for failed in self.failed:
183 self.PrintFailureHeader(failed.test)
184 if failed.output.stderr:
185 print "--- stderr ---"
186 print failed.output.stderr.strip()
187 if failed.output.stdout:
188 print "--- stdout ---"
189 print failed.output.stdout.strip()
190 print "Command: %s" % EscapeCommand(failed.command)
191 if failed.HasCrashed():
192 print "--- CRASHED ---"
193 if failed.HasTimedOut():
194 print "--- TIMEOUT ---"
195 if len(self.failed) == 0:
197 print "=== All tests succeeded"
202 print "=== %i tests failed" % len(self.failed)
204 print "=== %i tests CRASHED" % self.crashed
208 class VerboseProgressIndicator(SimpleProgressIndicator):
210 def AboutToRun(self, case):
211 print 'Starting %s...' % case.GetLabel()
214 def HasRun(self, output):
215 if output.UnexpectedOutput():
216 if output.HasCrashed():
222 print 'Done running %s: %s' % (output.test.GetLabel(), outcome)
225 class DotsProgressIndicator(SimpleProgressIndicator):
227 def AboutToRun(self, case):
230 def HasRun(self, output):
231 total = self.succeeded + len(self.failed)
232 if (total > 1) and (total % 50 == 1):
233 sys.stdout.write('\n')
234 if output.UnexpectedOutput():
235 if output.HasCrashed():
236 sys.stdout.write('C')
238 elif output.HasTimedOut():
239 sys.stdout.write('T')
242 sys.stdout.write('F')
245 sys.stdout.write('.')
249 class TapProgressIndicator(SimpleProgressIndicator):
252 logger.info('1..%i' % len(self.cases))
255 def AboutToRun(self, case):
258 def HasRun(self, output):
260 command = basename(output.command[-1])
261 if output.UnexpectedOutput():
262 status_line = 'not ok %i - %s' % (self._done, command)
263 if FLAKY in output.test.outcomes and self.flaky_tests_mode == DONTCARE:
264 status_line = status_line + ' # TODO : Fix flaky test'
265 logger.info(status_line)
266 for l in output.output.stderr.splitlines():
268 for l in output.output.stdout.splitlines():
271 skip = skip_regex.search(output.output.stdout)
274 'ok %i - %s # skip %s' % (self._done, command, skip.group(1)))
276 status_line = 'ok %i - %s' % (self._done, command)
277 if FLAKY in output.test.outcomes:
278 status_line = status_line + ' # TODO : Fix flaky test'
279 logger.info(status_line)
281 duration = output.test.duration
283 # total_seconds() was added in 2.7
284 total_seconds = (duration.microseconds +
285 (duration.seconds + duration.days * 24 * 3600) * 10**6) / 10**6
288 logger.info(' duration_ms: %d.%d' % (total_seconds, duration.microseconds / 1000))
295 class CompactProgressIndicator(ProgressIndicator):
297 def __init__(self, cases, flaky_tests_mode, templates):
298 super(CompactProgressIndicator, self).__init__(cases, flaky_tests_mode)
299 self.templates = templates
300 self.last_status_length = 0
301 self.start_time = time.time()
307 self.PrintProgress('Done')
309 def AboutToRun(self, case):
310 self.PrintProgress(case.GetLabel())
312 def HasRun(self, output):
313 if output.UnexpectedOutput():
314 self.ClearLine(self.last_status_length)
315 self.PrintFailureHeader(output.test)
316 stdout = output.output.stdout.strip()
318 print self.templates['stdout'] % stdout
319 stderr = output.output.stderr.strip()
321 print self.templates['stderr'] % stderr
322 print "Command: %s" % EscapeCommand(output.command)
323 if output.HasCrashed():
324 print "--- CRASHED ---"
325 if output.HasTimedOut():
326 print "--- TIMEOUT ---"
328 def Truncate(self, str, length):
329 if length and (len(str) > (length - 3)):
330 return str[:(length-3)] + "..."
334 def PrintProgress(self, name):
335 self.ClearLine(self.last_status_length)
336 elapsed = time.time() - self.start_time
337 status = self.templates['status_line'] % {
338 'passed': self.succeeded,
339 'remaining': (((self.total - self.remaining) * 100) // self.total),
340 'failed': len(self.failed),
342 'mins': int(elapsed) / 60,
343 'secs': int(elapsed) % 60
345 status = self.Truncate(status, 78)
346 self.last_status_length = len(status)
351 class ColorProgressIndicator(CompactProgressIndicator):
353 def __init__(self, cases, flaky_tests_mode):
355 'status_line': "[%(mins)02i:%(secs)02i|\033[34m%%%(remaining) 4d\033[0m|\033[32m+%(passed) 4d\033[0m|\033[31m-%(failed) 4d\033[0m]: %(test)s",
356 'stdout': "\033[1m%s\033[0m",
357 'stderr': "\033[31m%s\033[0m",
359 super(ColorProgressIndicator, self).__init__(cases, flaky_tests_mode, templates)
361 def ClearLine(self, last_line_length):
365 class MonochromeProgressIndicator(CompactProgressIndicator):
367 def __init__(self, cases, flaky_tests_mode):
369 'status_line': "[%(mins)02i:%(secs)02i|%%%(remaining) 4d|+%(passed) 4d|-%(failed) 4d]: %(test)s",
372 'clear': lambda last_line_length: ("\r" + (" " * last_line_length) + "\r"),
375 super(MonochromeProgressIndicator, self).__init__(cases, flaky_tests_mode, templates)
377 def ClearLine(self, last_line_length):
378 print ("\r" + (" " * last_line_length) + "\r"),
381 PROGRESS_INDICATORS = {
382 'verbose': VerboseProgressIndicator,
383 'dots': DotsProgressIndicator,
384 'color': ColorProgressIndicator,
385 'tap': TapProgressIndicator,
386 'mono': MonochromeProgressIndicator
390 # -------------------------
391 # --- F r a m e w o r k ---
392 # -------------------------
395 class CommandOutput(object):
397 def __init__(self, exit_code, timed_out, stdout, stderr):
398 self.exit_code = exit_code
399 self.timed_out = timed_out
405 class TestCase(object):
407 def __init__(self, context, path, arch, mode):
409 self.context = context
413 self.parallel = False
416 def IsNegative(self):
419 def CompareTime(self, other):
420 return cmp(other.duration, self.duration)
422 def DidFail(self, output):
423 if output.failed is None:
424 output.failed = self.IsFailureOutput(output)
427 def IsFailureOutput(self, output):
428 return output.exit_code != 0
431 return "(no source available)"
433 def RunCommand(self, command, env):
434 full_command = self.context.processor(command)
435 output = Execute(full_command,
437 self.context.GetTimeout(self.mode),
440 return TestOutput(self,
443 self.context.store_unexpected_output)
448 def AfterRun(self, result):
455 result = self.RunCommand(self.GetCommand(), {
456 "TEST_THREAD_ID": "%d" % self.thread_id
459 # Tests can leave the tty in non-blocking mode. If the test runner
460 # tries to print to stdout/stderr after that and the tty buffer is
461 # full, it'll die with a EAGAIN OSError. Ergo, put the tty back in
462 # blocking mode before proceeding.
463 if sys.platform != 'win32':
464 from fcntl import fcntl, F_GETFL, F_SETFL
465 from os import O_NONBLOCK
466 for fd in 0,1,2: fcntl(fd, F_SETFL, ~O_NONBLOCK & fcntl(fd, F_GETFL))
468 self.AfterRun(result)
475 class TestOutput(object):
477 def __init__(self, test, command, output, store_unexpected_output):
479 self.command = command
481 self.store_unexpected_output = store_unexpected_output
483 def UnexpectedOutput(self):
484 if self.HasCrashed():
486 elif self.HasTimedOut():
488 elif self.HasFailed():
492 return not outcome in self.test.outcomes
494 def HasPreciousOutput(self):
495 return self.UnexpectedOutput() and self.store_unexpected_output
497 def HasCrashed(self):
498 if utils.IsWindows():
499 return 0x80000000 & self.output.exit_code and not (0x3FFFFF00 & self.output.exit_code)
501 # Timed out tests will have exit_code -signal.SIGTERM.
502 if self.output.timed_out:
504 return self.output.exit_code < 0 and \
505 self.output.exit_code != -signal.SIGABRT
507 def HasTimedOut(self):
508 return self.output.timed_out;
511 execution_failed = self.test.DidFail(self.output)
512 if self.test.IsNegative():
513 return not execution_failed
515 return execution_failed
518 def KillProcessWithID(pid):
519 if utils.IsWindows():
520 os.popen('taskkill /T /F /PID %d' % pid)
522 os.kill(pid, signal.SIGTERM)
526 INITIAL_SLEEP_TIME = 0.0001
527 SLEEP_TIME_FACTOR = 1.25
529 SEM_INVALID_VALUE = -1
530 SEM_NOGPFAULTERRORBOX = 0x0002 # Microsoft Platform SDK WinBase.h
532 def Win32SetErrorMode(mode):
533 prev_error_mode = SEM_INVALID_VALUE
536 prev_error_mode = ctypes.windll.kernel32.SetErrorMode(mode);
539 return prev_error_mode
541 def RunProcess(context, timeout, args, **rest):
542 if context.verbose: print "#", " ".join(args)
544 prev_error_mode = SEM_INVALID_VALUE;
545 if utils.IsWindows():
546 if context.suppress_dialogs:
547 # Try to change the error mode to avoid dialogs on fatal errors. Don't
548 # touch any existing error mode flags by merging the existing error mode.
549 # See http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx.
550 error_mode = SEM_NOGPFAULTERRORBOX;
551 prev_error_mode = Win32SetErrorMode(error_mode);
552 Win32SetErrorMode(error_mode | prev_error_mode);
553 process = subprocess.Popen(
554 shell = utils.IsWindows(),
558 if utils.IsWindows() and context.suppress_dialogs and prev_error_mode != SEM_INVALID_VALUE:
559 Win32SetErrorMode(prev_error_mode)
560 # Compute the end time - if the process crosses this limit we
561 # consider it timed out.
562 if timeout is None: end_time = None
563 else: end_time = time.time() + timeout
565 # Repeatedly check the exit code from the process in a
566 # loop and keep track of whether or not it times out.
568 sleep_time = INITIAL_SLEEP_TIME
569 while exit_code is None:
570 if (not end_time is None) and (time.time() >= end_time):
571 # Kill the process and wait for it to exit.
572 KillProcessWithID(process.pid)
573 exit_code = process.wait()
576 exit_code = process.poll()
577 time.sleep(sleep_time)
578 sleep_time = sleep_time * SLEEP_TIME_FACTOR
579 if sleep_time > MAX_SLEEP_TIME:
580 sleep_time = MAX_SLEEP_TIME
581 return (process, exit_code, timed_out)
585 sys.stderr.write(str)
586 sys.stderr.write('\n')
589 def CheckedUnlink(name):
594 # On Windows unlink() fails if another process (typically a virus scanner
595 # or the indexing service) has the file open. Those processes keep a
596 # file open for a short time only, so yield and try again; it'll succeed.
597 if sys.platform == 'win32' and e.errno == errno.EACCES:
600 PrintError("os.unlink() " + str(e))
603 def Execute(args, context, timeout=None, env={}):
604 (fd_out, outname) = tempfile.mkstemp()
605 (fd_err, errname) = tempfile.mkstemp()
608 env_copy = os.environ.copy()
609 for key, value in env.iteritems():
610 env_copy[key] = value
612 (process, exit_code, timed_out) = RunProcess(
622 output = file(outname).read()
623 errors = file(errname).read()
624 CheckedUnlink(outname)
625 CheckedUnlink(errname)
626 return CommandOutput(exit_code, timed_out, output, errors)
629 def ExecuteNoCapture(args, context, timeout=None):
630 (process, exit_code, timed_out) = RunProcess(
635 return CommandOutput(exit_code, False, "", "")
642 return (path[0], path[1:])
645 class TestConfiguration(object):
647 def __init__(self, context, root):
648 self.context = context
651 def Contains(self, path, file):
652 if len(path) > len(file):
654 for i in xrange(len(path)):
655 if not path[i].match(file[i]):
659 def GetTestStatus(self, sections, defs):
663 class TestSuite(object):
665 def __init__(self, name):
672 # Use this to run several variants of the tests, e.g.:
673 # VARIANT_FLAGS = [[], ['--always_compact', '--noflush_code']]
677 class TestRepository(TestSuite):
679 def __init__(self, path):
680 normalized_path = abspath(path)
681 super(TestRepository, self).__init__(basename(normalized_path))
682 self.path = normalized_path
683 self.is_loaded = False
686 def GetConfiguration(self, context):
689 self.is_loaded = True
692 (file, pathname, description) = imp.find_module('testcfg', [ self.path ])
693 module = imp.load_module('testcfg', file, pathname, description)
694 self.config = module.GetConfiguration(context, self.path)
700 def GetBuildRequirements(self, path, context):
701 return self.GetConfiguration(context).GetBuildRequirements()
703 def AddTestsToList(self, result, current_path, path, context, arch, mode):
704 for v in VARIANT_FLAGS:
705 tests = self.GetConfiguration(context).ListTests(current_path, path,
707 for t in tests: t.variant_flags = v
711 def GetTestStatus(self, context, sections, defs):
712 self.GetConfiguration(context).GetTestStatus(sections, defs)
715 class LiteralTestSuite(TestSuite):
717 def __init__(self, tests):
718 super(LiteralTestSuite, self).__init__('root')
721 def GetBuildRequirements(self, path, context):
722 (name, rest) = CarCdr(path)
724 for test in self.tests:
725 if not name or name.match(test.GetName()):
726 result += test.GetBuildRequirements(rest, context)
729 def ListTests(self, current_path, path, context, arch, mode):
730 (name, rest) = CarCdr(path)
732 for test in self.tests:
733 test_name = test.GetName()
734 if not name or name.match(test_name):
735 full_path = current_path + [test_name]
736 test.AddTestsToList(result, full_path, path, context, arch, mode)
737 result.sort(cmp=lambda a, b: cmp(a.GetName(), b.GetName()))
740 def GetTestStatus(self, context, sections, defs):
741 for test in self.tests:
742 test.GetTestStatus(context, sections, defs)
749 'debug' : ['--enable-slow-asserts', '--debug-code', '--verify-heap'],
751 TIMEOUT_SCALEFACTOR = {
752 'armv6' : { 'debug' : 12, 'release' : 3 }, # The ARM buildbots are slow.
753 'arm' : { 'debug' : 8, 'release' : 2 },
754 'ia32' : { 'debug' : 4, 'release' : 1 },
755 'ppc' : { 'debug' : 4, 'release' : 1 } }
758 class Context(object):
760 def __init__(self, workspace, buildspace, verbose, vm, timeout, processor, suppress_dialogs, store_unexpected_output):
761 self.workspace = workspace
762 self.buildspace = buildspace
763 self.verbose = verbose
765 self.timeout = timeout
766 self.processor = processor
767 self.suppress_dialogs = suppress_dialogs
768 self.store_unexpected_output = store_unexpected_output
770 def GetVm(self, arch, mode):
772 name = 'out/Debug/node' if mode == 'debug' else 'out/Release/node'
774 name = 'out/%s.%s/node' % (arch, mode)
776 # Currently GYP does not support output_dir for MSVS.
777 # http://code.google.com/p/gyp/issues/detail?id=40
778 # It will put the builds into Release/node.exe or Debug/node.exe
779 if utils.IsWindows():
780 out_dir = os.path.join(dirname(__file__), "..", "out")
781 if not exists(out_dir):
783 name = os.path.abspath('Debug/node.exe')
785 name = os.path.abspath('Release/node.exe')
787 name = os.path.abspath(name + '.exe')
791 def GetVmFlags(self, testcase, mode):
792 return testcase.variant_flags + FLAGS[mode]
794 def GetTimeout(self, mode):
795 return self.timeout * TIMEOUT_SCALEFACTOR[ARCH_GUESS or 'ia32'][mode]
797 def RunTestCases(cases_to_run, progress, tasks, flaky_tests_mode):
798 progress = PROGRESS_INDICATORS[progress](cases_to_run, flaky_tests_mode)
799 return progress.Run(tasks)
802 def BuildRequirements(context, requirements, mode, scons_flags):
803 command_line = (['scons', '-Y', context.workspace, 'mode=' + ",".join(mode)]
806 output = ExecuteNoCapture(command_line, context)
807 return output.exit_code == 0
810 # -------------------------------------------
811 # --- T e s t C o n f i g u r a t i o n ---
812 # -------------------------------------------
823 DONTCARE = 'dontcare'
825 class Expression(object):
829 class Constant(Expression):
831 def __init__(self, value):
834 def Evaluate(self, env, defs):
838 class Variable(Expression):
840 def __init__(self, name):
843 def GetOutcomes(self, env, defs):
844 if self.name in env: return ListSet([env[self.name]])
845 else: return Nothing()
848 class Outcome(Expression):
850 def __init__(self, name):
853 def GetOutcomes(self, env, defs):
854 if self.name in defs:
855 return defs[self.name].GetOutcomes(env, defs)
857 return ListSet([self.name])
866 def __init__(self, elms):
870 return "ListSet%s" % str(self.elms)
872 def Intersect(self, that):
873 if not isinstance(that, ListSet):
874 return that.Intersect(self)
875 return ListSet([ x for x in self.elms if x in that.elms ])
877 def Union(self, that):
878 if not isinstance(that, ListSet):
879 return that.Union(self)
880 return ListSet(self.elms + [ x for x in that.elms if x not in self.elms ])
883 return len(self.elms) == 0
886 class Everything(Set):
888 def Intersect(self, that):
891 def Union(self, that):
900 def Intersect(self, that):
903 def Union(self, that):
910 class Operation(Expression):
912 def __init__(self, left, op, right):
917 def Evaluate(self, env, defs):
918 if self.op == '||' or self.op == ',':
919 return self.left.Evaluate(env, defs) or self.right.Evaluate(env, defs)
920 elif self.op == 'if':
922 elif self.op == '==':
923 inter = self.left.GetOutcomes(env, defs).Intersect(self.right.GetOutcomes(env, defs))
924 return not inter.IsEmpty()
926 assert self.op == '&&'
927 return self.left.Evaluate(env, defs) and self.right.Evaluate(env, defs)
929 def GetOutcomes(self, env, defs):
930 if self.op == '||' or self.op == ',':
931 return self.left.GetOutcomes(env, defs).Union(self.right.GetOutcomes(env, defs))
932 elif self.op == 'if':
933 if self.right.Evaluate(env, defs): return self.left.GetOutcomes(env, defs)
934 else: return Nothing()
936 assert self.op == '&&'
937 return self.left.GetOutcomes(env, defs).Intersect(self.right.GetOutcomes(env, defs))
942 if not (char.isalpha() or char.isdigit() or char == '_'):
947 class Tokenizer(object):
948 """A simple string tokenizer that chops expressions into variables,
949 parens and operators"""
951 def __init__(self, expr):
954 self.length = len(expr)
957 def Current(self, length = 1):
958 if not self.HasMore(length): return ""
959 return self.expr[self.index:self.index+length]
961 def HasMore(self, length = 1):
962 return self.index < self.length + (length - 1)
964 def Advance(self, count = 1):
965 self.index = self.index + count
967 def AddToken(self, token):
968 self.tokens.append(token)
970 def SkipSpaces(self):
971 while self.HasMore() and self.Current().isspace():
976 while self.HasMore():
978 if not self.HasMore():
980 if self.Current() == '(':
983 elif self.Current() == ')':
986 elif self.Current() == '$':
989 elif self.Current() == ',':
992 elif IsAlpha(self.Current()):
994 while self.HasMore() and IsAlpha(self.Current()):
995 buf += self.Current()
998 elif self.Current(2) == '&&':
1001 elif self.Current(2) == '||':
1004 elif self.Current(2) == '==':
1012 class Scanner(object):
1013 """A simple scanner that can serve out tokens from a given list"""
1015 def __init__(self, tokens):
1016 self.tokens = tokens
1017 self.length = len(tokens)
1021 return self.index < self.length
1024 return self.tokens[self.index]
1027 self.index = self.index + 1
1030 def ParseAtomicExpression(scan):
1031 if scan.Current() == "true":
1033 return Constant(True)
1034 elif scan.Current() == "false":
1036 return Constant(False)
1037 elif IsAlpha(scan.Current()):
1038 name = scan.Current()
1040 return Outcome(name.lower())
1041 elif scan.Current() == '$':
1043 if not IsAlpha(scan.Current()):
1045 name = scan.Current()
1047 return Variable(name.lower())
1048 elif scan.Current() == '(':
1050 result = ParseLogicalExpression(scan)
1051 if (not result) or (scan.Current() != ')'):
1060 def ParseOperatorExpression(scan):
1061 left = ParseAtomicExpression(scan)
1062 if not left: return None
1063 while scan.HasMore() and (scan.Current() in BINARIES):
1066 right = ParseOperatorExpression(scan)
1069 left = Operation(left, op, right)
1073 def ParseConditionalExpression(scan):
1074 left = ParseOperatorExpression(scan)
1075 if not left: return None
1076 while scan.HasMore() and (scan.Current() == 'if'):
1078 right = ParseOperatorExpression(scan)
1081 left= Operation(left, 'if', right)
1085 LOGICALS = ["&&", "||", ","]
1086 def ParseLogicalExpression(scan):
1087 left = ParseConditionalExpression(scan)
1088 if not left: return None
1089 while scan.HasMore() and (scan.Current() in LOGICALS):
1092 right = ParseConditionalExpression(scan)
1095 left = Operation(left, op, right)
1099 def ParseCondition(expr):
1100 """Parses a logical expression into an Expression object"""
1101 tokens = Tokenizer(expr).Tokenize()
1103 print "Malformed expression: '%s'" % expr
1105 scan = Scanner(tokens)
1106 ast = ParseLogicalExpression(scan)
1108 print "Malformed expression: '%s'" % expr
1111 print "Malformed expression: '%s'" % expr
1116 class ClassifiedTest(object):
1118 def __init__(self, case, outcomes):
1120 self.outcomes = outcomes
1121 self.parallel = self.case.parallel
1124 class Configuration(object):
1125 """The parsed contents of a configuration file"""
1127 def __init__(self, sections, defs):
1128 self.sections = sections
1131 def ClassifyTests(self, cases, env):
1132 sections = [s for s in self.sections if s.condition.Evaluate(env, self.defs)]
1133 all_rules = reduce(list.__add__, [s.rules for s in sections], [])
1134 unused_rules = set(all_rules)
1136 all_outcomes = set([])
1138 matches = [ r for r in all_rules if r.Contains(case.path) ]
1140 for rule in matches:
1141 outcomes = outcomes.union(rule.GetOutcomes(env, self.defs))
1142 unused_rules.discard(rule)
1145 case.outcomes = outcomes
1146 all_outcomes = all_outcomes.union(outcomes)
1147 result.append(ClassifiedTest(case, outcomes))
1148 return (result, list(unused_rules), all_outcomes)
1151 class Section(object):
1152 """A section of the configuration file. Sections are enabled or
1153 disabled prior to running the tests, based on their conditions"""
1155 def __init__(self, condition):
1156 self.condition = condition
1159 def AddRule(self, rule):
1160 self.rules.append(rule)
1164 """A single rule that specifies the expected outcome for a single
1167 def __init__(self, raw_path, path, value):
1168 self.raw_path = raw_path
1172 def GetOutcomes(self, env, defs):
1173 set = self.value.GetOutcomes(env, defs)
1174 assert isinstance(set, ListSet)
1177 def Contains(self, path):
1178 if len(self.path) > len(path):
1180 for i in xrange(len(self.path)):
1181 if not self.path[i].match(path[i]):
1186 HEADER_PATTERN = re.compile(r'\[([^]]+)\]')
1187 RULE_PATTERN = re.compile(r'\s*([^: ]*)\s*:(.*)')
1188 DEF_PATTERN = re.compile(r'^def\s*(\w+)\s*=(.*)$')
1189 PREFIX_PATTERN = re.compile(r'^\s*prefix\s+([\w\_\.\-\/]+)$')
1192 def ReadConfigurationInto(path, sections, defs):
1193 current_section = Section(Constant(True))
1194 sections.append(current_section)
1196 for line in utils.ReadLinesFrom(path):
1197 header_match = HEADER_PATTERN.match(line)
1199 condition_str = header_match.group(1).strip()
1200 condition = ParseCondition(condition_str)
1201 new_section = Section(condition)
1202 sections.append(new_section)
1203 current_section = new_section
1205 rule_match = RULE_PATTERN.match(line)
1207 path = prefix + SplitPath(rule_match.group(1).strip())
1208 value_str = rule_match.group(2).strip()
1209 value = ParseCondition(value_str)
1212 current_section.AddRule(Rule(rule_match.group(1), path, value))
1214 def_match = DEF_PATTERN.match(line)
1216 name = def_match.group(1).lower()
1217 value = ParseCondition(def_match.group(2).strip())
1222 prefix_match = PREFIX_PATTERN.match(line)
1224 prefix = SplitPath(prefix_match.group(1).strip())
1226 print "Malformed line: '%s'." % line
1236 ARCH_GUESS = utils.GuessArchitecture()
1240 result = optparse.OptionParser()
1241 result.add_option("-m", "--mode", help="The test modes in which to run (comma-separated)",
1243 result.add_option("-v", "--verbose", help="Verbose output",
1244 default=False, action="store_true")
1245 result.add_option('--logfile', dest='logfile',
1246 help='write test output to file. NOTE: this only applies the tap progress indicator')
1247 result.add_option("-S", dest="scons_flags", help="Flag to pass through to scons",
1248 default=[], action="append")
1249 result.add_option("-p", "--progress",
1250 help="The style of progress indicator (verbose, dots, color, mono, tap)",
1251 choices=PROGRESS_INDICATORS.keys(), default="mono")
1252 result.add_option("--no-build", help="Don't build requirements",
1253 default=True, action="store_true")
1254 result.add_option("--build-only", help="Only build requirements, don't run the tests",
1255 default=False, action="store_true")
1256 result.add_option("--report", help="Print a summary of the tests to be run",
1257 default=False, action="store_true")
1258 result.add_option("-s", "--suite", help="A test suite",
1259 default=[], action="append")
1260 result.add_option("-t", "--timeout", help="Timeout in seconds",
1261 default=60, type="int")
1262 result.add_option("--arch", help='The architecture to run tests for',
1264 result.add_option("--snapshot", help="Run the tests with snapshot turned on",
1265 default=False, action="store_true")
1266 result.add_option("--special-command", default=None)
1267 result.add_option("--valgrind", help="Run tests through valgrind",
1268 default=False, action="store_true")
1269 result.add_option("--cat", help="Print the source of the tests",
1270 default=False, action="store_true")
1271 result.add_option("--flaky-tests",
1272 help="Regard tests marked as flaky (run|skip|dontcare)",
1274 result.add_option("--warn-unused", help="Report unused rules",
1275 default=False, action="store_true")
1276 result.add_option("-j", help="The number of parallel tasks to run",
1277 default=1, type="int")
1278 result.add_option("-J", help="Run tasks in parallel on all cores",
1279 default=False, action="store_true")
1280 result.add_option("--time", help="Print timing information after running",
1281 default=False, action="store_true")
1282 result.add_option("--suppress-dialogs", help="Suppress Windows dialogs for crashing tests",
1283 dest="suppress_dialogs", default=True, action="store_true")
1284 result.add_option("--no-suppress-dialogs", help="Display Windows dialogs for crashing tests",
1285 dest="suppress_dialogs", action="store_false")
1286 result.add_option("--shell", help="Path to V8 shell", default="shell")
1287 result.add_option("--store-unexpected-output",
1288 help="Store the temporary JS files from tests that fails",
1289 dest="store_unexpected_output", default=True, action="store_true")
1290 result.add_option("--no-store-unexpected-output",
1291 help="Deletes the temporary JS files from tests that fails",
1292 dest="store_unexpected_output", action="store_false")
1293 result.add_option("-r", "--run",
1294 help="Divide the tests in m groups (interleaved) and run tests from group n (--run=n,m with n < m)",
1299 def ProcessOptions(options):
1301 VERBOSE = options.verbose
1302 options.arch = options.arch.split(',')
1303 options.mode = options.mode.split(',')
1304 options.run = options.run.split(',')
1305 if options.run == [""]:
1307 elif len(options.run) != 2:
1308 print "The run argument must be two comma-separated integers."
1312 options.run = map(int, options.run)
1314 print "Could not parse the integers from the run argument."
1316 if options.run[0] < 0 or options.run[1] < 0:
1317 print "The run argument cannot have negative integers."
1319 if options.run[0] >= options.run[1]:
1320 print "The test group to run (n) must be smaller than number of groups (m)."
1323 options.j = multiprocessing.cpu_count()
1324 if options.flaky_tests not in ["run", "skip", "dontcare"]:
1325 print "Unknown flaky-tests mode %s" % options.flaky_tests
1330 REPORT_TEMPLATE = """\
1331 Total: %(total)i tests
1332 * %(skipped)4d tests will be skipped
1333 * %(pass)4d tests are expected to pass
1334 * %(fail_ok)4d tests are expected to fail that we won't fix
1335 * %(fail)4d tests are expected to fail that we should fix\
1338 def PrintReport(cases):
1340 return (len(o) == 2) and (FAIL in o) and (OKAY in o)
1341 unskipped = [c for c in cases if not SKIP in c.outcomes]
1342 print REPORT_TEMPLATE % {
1343 'total': len(cases),
1344 'skipped': len(cases) - len(unskipped),
1345 'pass': len([t for t in unskipped if list(t.outcomes) == [PASS]]),
1346 'fail_ok': len([t for t in unskipped if IsFailOk(t.outcomes)]),
1347 'fail': len([t for t in unskipped if list(t.outcomes) == [FAIL]])
1351 class Pattern(object):
1353 def __init__(self, pattern):
1354 self.pattern = pattern
1355 self.compiled = None
1357 def match(self, str):
1358 if not self.compiled:
1359 pattern = "^" + self.pattern.replace('*', '.*') + "$"
1360 self.compiled = re.compile(pattern)
1361 return self.compiled.match(str)
1368 stripped = [ c.strip() for c in s.split('/') ]
1369 return [ Pattern(s) for s in stripped if len(s) > 0 ]
1372 def GetSpecialCommandProcessor(value):
1373 if (not value) or (value.find('@') == -1):
1374 def ExpandCommand(args):
1376 return ExpandCommand
1378 pos = value.find('@')
1380 prefix = urllib.unquote(value[:pos]).split()
1381 suffix = urllib.unquote(value[pos+1:]).split()
1382 def ExpandCommand(args):
1383 return prefix + args + suffix
1384 return ExpandCommand
1399 def GetSuites(test_root):
1401 return isdir(path) and exists(join(path, 'testcfg.py'))
1402 return [ f for f in os.listdir(test_root) if IsSuite(join(test_root, f)) ]
1406 millis = round(d * 1000) % 1000
1407 return time.strftime("%M:%S.", time.gmtime(d)) + ("%03i" % millis)
1411 parser = BuildOptions()
1412 (options, args) = parser.parse_args()
1413 if not ProcessOptions(options):
1417 ch = logging.StreamHandler(sys.stdout)
1418 logger.addHandler(ch)
1419 logger.setLevel(logging.INFO)
1421 fh = logging.FileHandler(options.logfile)
1422 logger.addHandler(fh)
1424 workspace = abspath(join(dirname(sys.argv[0]), '..'))
1425 suites = GetSuites(join(workspace, 'test'))
1426 repositories = [TestRepository(join(workspace, 'test', name)) for name in suites]
1427 repositories += [TestRepository(a) for a in options.suite]
1429 root = LiteralTestSuite(repositories)
1431 paths = [SplitPath(t) for t in BUILT_IN_TESTS]
1435 path = SplitPath(arg)
1438 # Check for --valgrind option. If enabled, we overwrite the special
1439 # command flag with a command that uses the run-valgrind.py script.
1440 if options.valgrind:
1441 run_valgrind = join(workspace, "tools", "run-valgrind.py")
1442 options.special_command = "python -u " + run_valgrind + " @"
1444 shell = abspath(options.shell)
1445 buildspace = dirname(shell)
1447 processor = GetSpecialCommandProcessor(options.special_command)
1448 context = Context(workspace,
1454 options.suppress_dialogs,
1455 options.store_unexpected_output)
1456 # First build the required targets
1457 if not options.no_build:
1460 reqs += root.GetBuildRequirements(path, context)
1461 reqs = list(set(reqs))
1464 options.scons_flags += ['-j', str(options.j)]
1465 if not BuildRequirements(context, reqs, options.mode, options.scons_flags):
1468 # Just return if we are only building the targets for running the tests.
1469 if options.build_only:
1472 # Get status for tests
1475 root.GetTestStatus(context, sections, defs)
1476 config = Configuration(sections, defs)
1481 unclassified_tests = [ ]
1482 globally_unused_rules = None
1484 for arch in options.arch:
1485 for mode in options.mode:
1486 vm = context.GetVm(arch, mode)
1488 print "Can't find shell executable: '%s'" % vm
1492 'system': utils.GuessOS(),
1495 test_list = root.ListTests([], path, context, arch, mode)
1496 unclassified_tests += test_list
1497 (cases, unused_rules, all_outcomes) = (
1498 config.ClassifyTests(test_list, env))
1499 if globally_unused_rules is None:
1500 globally_unused_rules = set(unused_rules)
1502 globally_unused_rules = (
1503 globally_unused_rules.intersection(unused_rules))
1505 all_unused.append(unused_rules)
1509 for test in unclassified_tests:
1510 key = tuple(test.path)
1514 print "--- begin source: %s ---" % test.GetLabel()
1515 source = test.GetSource().strip()
1517 print "--- end source: %s ---" % test.GetLabel()
1520 if options.warn_unused:
1521 for rule in globally_unused_rules:
1522 print "Rule for '%s' was not used." % '/'.join([str(s) for s in rule.path])
1525 PrintReport(all_cases)
1529 if SKIP in case.outcomes or SLOW in case.outcomes:
1531 return FLAKY in case.outcomes and options.flaky_tests == SKIP
1532 cases_to_run = [ c for c in all_cases if not DoSkip(c) ]
1533 if options.run is not None:
1534 # Must ensure the list of tests is sorted before selecting, to avoid
1535 # silent errors if this file is changed to list the tests in a way that
1536 # can be different in different machines
1537 cases_to_run.sort(key=lambda c: (c.case.arch, c.case.mode, c.case.file))
1538 cases_to_run = [ cases_to_run[i] for i
1539 in xrange(options.run[0],
1542 if len(cases_to_run) == 0:
1543 print "No tests to run."
1548 if RunTestCases(cases_to_run, options.progress, options.j, options.flaky_tests):
1552 duration = time.time() - start
1553 except KeyboardInterrupt:
1558 # Write the times to stderr to make it easy to separate from the
1561 sys.stderr.write("--- Total time: %s ---\n" % FormatTime(duration))
1562 timed_tests = [ t.case for t in cases_to_run if not t.case.duration is None ]
1563 timed_tests.sort(lambda a, b: a.CompareTime(b))
1565 for entry in timed_tests[:20]:
1566 t = FormatTime(entry.duration)
1567 sys.stderr.write("%4i (%s) %s\n" % (index, t, entry.GetLabel()))
1573 if __name__ == '__main__':