• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2#
3# Copyright 2008 the V8 project authors. All rights reserved.
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met:
7#
8#     * Redistributions of source code must retain the above copyright
9#       notice, this list of conditions and the following disclaimer.
10#     * Redistributions in binary form must reproduce the above
11#       copyright notice, this list of conditions and the following
12#       disclaimer in the documentation and/or other materials provided
13#       with the distribution.
14#     * Neither the name of Google Inc. nor the names of its
15#       contributors may be used to endorse or promote products derived
16#       from this software without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30
31from __future__ import print_function
32import logging
33import optparse
34import os
35import re
36import signal
37import subprocess
38import sys
39import tempfile
40import time
41import threading
42import utils
43import multiprocessing
44import errno
45import copy
46
47
48if sys.version_info >= (3, 5):
49  from importlib import machinery, util
50  def get_module(name, path):
51    loader_details = (machinery.SourceFileLoader, machinery.SOURCE_SUFFIXES)
52    spec = machinery.FileFinder(path, loader_details).find_spec(name)
53    module = util.module_from_spec(spec)
54    spec.loader.exec_module(module)
55    return module
56else:
57  import imp
58  def get_module(name, path):
59    file = None
60    try:
61      (file, pathname, description) = imp.find_module(name, [path])
62      return imp.load_module(name, file, pathname, description)
63    finally:
64      if file:
65        file.close()
66
67
68from io import open
69from os.path import join, dirname, abspath, basename, isdir, exists
70from datetime import datetime
71try:
72    from queue import Queue, Empty  # Python 3
73except ImportError:
74    from Queue import Queue, Empty  # Python 2
75
76from functools import reduce
77
78try:
79  from urllib.parse import unquote    # Python 3
80except ImportError:
81  from urllib import unquote          # Python 2
82
83
84logger = logging.getLogger('testrunner')
85skip_regex = re.compile(r'# SKIP\S*\s+(.*)', re.IGNORECASE)
86
87VERBOSE = False
88
89os.umask(0o022)
90os.environ['NODE_OPTIONS'] = ''
91
92# ---------------------------------------------
93# --- P r o g r e s s   I n d i c a t o r s ---
94# ---------------------------------------------
95
96
97class ProgressIndicator(object):
98
99  def __init__(self, cases, flaky_tests_mode):
100    self.cases = cases
101    self.flaky_tests_mode = flaky_tests_mode
102    self.parallel_queue = Queue(len(cases))
103    self.sequential_queue = Queue(len(cases))
104    for case in cases:
105      if case.parallel:
106        self.parallel_queue.put_nowait(case)
107      else:
108        self.sequential_queue.put_nowait(case)
109    self.succeeded = 0
110    self.remaining = len(cases)
111    self.total = len(cases)
112    self.failed = [ ]
113    self.flaky_failed = [ ]
114    self.crashed = 0
115    self.lock = threading.Lock()
116    self.shutdown_event = threading.Event()
117
118  def PrintFailureHeader(self, test):
119    if test.IsNegative():
120      negative_marker = '[negative] '
121    else:
122      negative_marker = ''
123    print("=== %(label)s %(negative)s===" % {
124      'label': test.GetLabel(),
125      'negative': negative_marker
126    })
127    print("Path: %s" % "/".join(test.path))
128
129  def Run(self, tasks):
130    self.Starting()
131    threads = []
132    # Spawn N-1 threads and then use this thread as the last one.
133    # That way -j1 avoids threading altogether which is a nice fallback
134    # in case of threading problems.
135    for i in range(tasks - 1):
136      thread = threading.Thread(target=self.RunSingle, args=[True, i + 1])
137      threads.append(thread)
138      thread.start()
139    try:
140      self.RunSingle(False, 0)
141      # Wait for the remaining threads
142      for thread in threads:
143        # Use a timeout so that signals (ctrl-c) will be processed.
144        thread.join(timeout=1000000)
145    except (KeyboardInterrupt, SystemExit):
146      self.shutdown_event.set()
147    except Exception:
148      # If there's an exception we schedule an interruption for any
149      # remaining threads.
150      self.shutdown_event.set()
151      # ...and then reraise the exception to bail out
152      raise
153    self.Done()
154    return not self.failed
155
156  def RunSingle(self, parallel, thread_id):
157    while not self.shutdown_event.is_set():
158      try:
159        test = self.parallel_queue.get_nowait()
160      except Empty:
161        if parallel:
162          return
163        try:
164          test = self.sequential_queue.get_nowait()
165        except Empty:
166          return
167      case = test
168      case.thread_id = thread_id
169      self.lock.acquire()
170      self.AboutToRun(case)
171      self.lock.release()
172      try:
173        start = datetime.now()
174        output = case.Run()
175        # SmartOS has a bug that causes unexpected ECONNREFUSED errors.
176        # See https://smartos.org/bugview/OS-2767
177        # If ECONNREFUSED on SmartOS, retry the test one time.
178        if (output.UnexpectedOutput() and
179          sys.platform == 'sunos5' and
180          'ECONNREFUSED' in output.output.stderr):
181            output = case.Run()
182            output.diagnostic.append('ECONNREFUSED received, test retried')
183        case.duration = (datetime.now() - start)
184      except IOError:
185        return
186      if self.shutdown_event.is_set():
187        return
188      self.lock.acquire()
189      if output.UnexpectedOutput():
190        if FLAKY in output.test.outcomes and self.flaky_tests_mode == DONTCARE:
191          self.flaky_failed.append(output)
192        else:
193          self.failed.append(output)
194          if output.HasCrashed():
195            self.crashed += 1
196      else:
197        self.succeeded += 1
198      self.remaining -= 1
199      self.HasRun(output)
200      self.lock.release()
201
202
203def EscapeCommand(command):
204  parts = []
205  for part in command:
206    if ' ' in part:
207      # Escape spaces.  We may need to escape more characters for this
208      # to work properly.
209      parts.append('"%s"' % part)
210    else:
211      parts.append(part)
212  return " ".join(parts)
213
214
215class SimpleProgressIndicator(ProgressIndicator):
216
217  def Starting(self):
218    print('Running %i tests' % len(self.cases))
219
220  def Done(self):
221    print()
222    for failed in self.failed:
223      self.PrintFailureHeader(failed.test)
224      if failed.output.stderr:
225        print("--- stderr ---")
226        print(failed.output.stderr.strip())
227      if failed.output.stdout:
228        print("--- stdout ---")
229        print(failed.output.stdout.strip())
230      print("Command: %s" % EscapeCommand(failed.command))
231      if failed.HasCrashed():
232        print("--- %s ---" % PrintCrashed(failed.output.exit_code))
233      if failed.HasTimedOut():
234        print("--- TIMEOUT ---")
235    if len(self.failed) == 0:
236      print("===")
237      print("=== All tests succeeded")
238      print("===")
239    else:
240      print()
241      print("===")
242      print("=== %i tests failed" % len(self.failed))
243      if self.crashed > 0:
244        print("=== %i tests CRASHED" % self.crashed)
245      print("===")
246
247
248class VerboseProgressIndicator(SimpleProgressIndicator):
249
250  def AboutToRun(self, case):
251    print('Starting %s...' % case.GetLabel())
252    sys.stdout.flush()
253
254  def HasRun(self, output):
255    if output.UnexpectedOutput():
256      if output.HasCrashed():
257        outcome = 'CRASH'
258      else:
259        outcome = 'FAIL'
260    else:
261      outcome = 'pass'
262    print('Done running %s: %s' % (output.test.GetLabel(), outcome))
263
264
265class DotsProgressIndicator(SimpleProgressIndicator):
266
267  def AboutToRun(self, case):
268    pass
269
270  def HasRun(self, output):
271    total = self.succeeded + len(self.failed)
272    if (total > 1) and (total % 50 == 1):
273      sys.stdout.write('\n')
274    if output.UnexpectedOutput():
275      if output.HasCrashed():
276        sys.stdout.write('C')
277        sys.stdout.flush()
278      elif output.HasTimedOut():
279        sys.stdout.write('T')
280        sys.stdout.flush()
281      else:
282        sys.stdout.write('F')
283        sys.stdout.flush()
284    else:
285      sys.stdout.write('.')
286      sys.stdout.flush()
287
288
289class TapProgressIndicator(SimpleProgressIndicator):
290
291  def _printDiagnostic(self):
292    logger.info('  severity: %s', self.severity)
293    self.exitcode and logger.info('  exitcode: %s', self.exitcode)
294    logger.info('  stack: |-')
295
296    for l in self.traceback.splitlines():
297      logger.info('    ' + l)
298
299  def Starting(self):
300    logger.info('TAP version 13')
301    logger.info('1..%i' % len(self.cases))
302    self._done = 0
303
304  def AboutToRun(self, case):
305    pass
306
307  def HasRun(self, output):
308    self._done += 1
309    self.traceback = ''
310    self.severity = 'ok'
311    self.exitcode = ''
312
313    # Print test name as (for example) "parallel/test-assert".  Tests that are
314    # scraped from the addons documentation are all named test.js, making it
315    # hard to decipher what test is running when only the filename is printed.
316    prefix = abspath(join(dirname(__file__), '../test')) + os.sep
317    command = output.command[-1]
318    command = NormalizePath(command, prefix)
319
320    if output.UnexpectedOutput():
321      status_line = 'not ok %i %s' % (self._done, command)
322      self.severity = 'fail'
323      self.exitcode = output.output.exit_code
324      self.traceback = output.output.stdout + output.output.stderr
325
326      if FLAKY in output.test.outcomes and self.flaky_tests_mode == DONTCARE:
327        status_line = status_line + ' # TODO : Fix flaky test'
328        self.severity = 'flaky'
329
330      logger.info(status_line)
331
332      if output.HasCrashed():
333        self.severity = 'crashed'
334
335      elif output.HasTimedOut():
336        self.severity = 'fail'
337
338    else:
339      skip = skip_regex.search(output.output.stdout)
340      if skip:
341        logger.info(
342          'ok %i %s # skip %s' % (self._done, command, skip.group(1)))
343      else:
344        status_line = 'ok %i %s' % (self._done, command)
345        if FLAKY in output.test.outcomes:
346          status_line = status_line + ' # TODO : Fix flaky test'
347        logger.info(status_line)
348
349      if output.diagnostic:
350        self.severity = 'ok'
351        self.traceback = output.diagnostic
352
353
354    duration = output.test.duration
355
356    # total_seconds() was added in 2.7
357    total_seconds = (duration.microseconds +
358      (duration.seconds + duration.days * 24 * 3600) * 10**6) / 10**6
359
360    # duration_ms is measured in seconds and is read as such by TAP parsers.
361    # It should read as "duration including ms" rather than "duration in ms"
362    logger.info('  ---')
363    logger.info('  duration_ms: %d.%d' %
364      (total_seconds, duration.microseconds / 1000))
365    if self.severity != 'ok' or self.traceback != '':
366      if output.HasTimedOut():
367        self.traceback = 'timeout\n' + output.output.stdout + output.output.stderr
368      self._printDiagnostic()
369    logger.info('  ...')
370
371  def Done(self):
372    pass
373
374class DeoptsCheckProgressIndicator(SimpleProgressIndicator):
375
376  def Starting(self):
377    pass
378
379  def AboutToRun(self, case):
380    pass
381
382  def HasRun(self, output):
383    # Print test name as (for example) "parallel/test-assert".  Tests that are
384    # scraped from the addons documentation are all named test.js, making it
385    # hard to decipher what test is running when only the filename is printed.
386    prefix = abspath(join(dirname(__file__), '../test')) + os.sep
387    command = output.command[-1]
388    command = NormalizePath(command, prefix)
389
390    stdout = output.output.stdout.strip()
391    printed_file = False
392    for line in stdout.splitlines():
393      if (
394        (line.startswith("[aborted optimiz") or line.startswith("[disabled optimiz")) and
395        ("because:" in line or "reason:" in line)
396      ):
397        if not printed_file:
398          printed_file = True
399          print('==== %s ====' % command)
400          self.failed.append(output)
401        print('  %s' % line)
402
403  def Done(self):
404    pass
405
406
407class CompactProgressIndicator(ProgressIndicator):
408
409  def __init__(self, cases, flaky_tests_mode, templates):
410    super(CompactProgressIndicator, self).__init__(cases, flaky_tests_mode)
411    self.templates = templates
412    self.last_status_length = 0
413    self.start_time = time.time()
414
415  def Starting(self):
416    pass
417
418  def Done(self):
419    self.PrintProgress('Done\n')
420
421  def AboutToRun(self, case):
422    self.PrintProgress(case.GetLabel())
423
424  def HasRun(self, output):
425    if output.UnexpectedOutput():
426      self.ClearLine(self.last_status_length)
427      self.PrintFailureHeader(output.test)
428      stdout = output.output.stdout.strip()
429      if len(stdout):
430        print(self.templates['stdout'] % stdout)
431      stderr = output.output.stderr.strip()
432      if len(stderr):
433        print(self.templates['stderr'] % stderr)
434      print("Command: %s" % EscapeCommand(output.command))
435      if output.HasCrashed():
436        print("--- %s ---" % PrintCrashed(output.output.exit_code))
437      if output.HasTimedOut():
438        print("--- TIMEOUT ---")
439
440  def Truncate(self, str, length):
441    if length and (len(str) > (length - 3)):
442      return str[:(length-3)] + "..."
443    else:
444      return str
445
446  def PrintProgress(self, name):
447    self.ClearLine(self.last_status_length)
448    elapsed = time.time() - self.start_time
449    status = self.templates['status_line'] % {
450      'passed': self.succeeded,
451      'remaining': (((self.total - self.remaining) * 100) // self.total),
452      'failed': len(self.failed),
453      'test': name,
454      'mins': int(elapsed) / 60,
455      'secs': int(elapsed) % 60
456    }
457    status = self.Truncate(status, 78)
458    self.last_status_length = len(status)
459    print(status, end='')
460    sys.stdout.flush()
461
462
463class ColorProgressIndicator(CompactProgressIndicator):
464
465  def __init__(self, cases, flaky_tests_mode):
466    templates = {
467      '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",
468      'stdout': "\033[1m%s\033[0m",
469      'stderr': "\033[31m%s\033[0m",
470    }
471    super(ColorProgressIndicator, self).__init__(cases, flaky_tests_mode, templates)
472
473  def ClearLine(self, last_line_length):
474    print("\033[1K\r", end='')
475
476
477class MonochromeProgressIndicator(CompactProgressIndicator):
478
479  def __init__(self, cases, flaky_tests_mode):
480    templates = {
481      'status_line': "[%(mins)02i:%(secs)02i|%%%(remaining) 4d|+%(passed) 4d|-%(failed) 4d]: %(test)s",
482      'stdout': '%s',
483      'stderr': '%s',
484      'clear': lambda last_line_length: ("\r" + (" " * last_line_length) + "\r"),
485      'max_length': 78
486    }
487    super(MonochromeProgressIndicator, self).__init__(cases, flaky_tests_mode, templates)
488
489  def ClearLine(self, last_line_length):
490    print(("\r" + (" " * last_line_length) + "\r"), end='')
491
492
493PROGRESS_INDICATORS = {
494  'verbose': VerboseProgressIndicator,
495  'dots': DotsProgressIndicator,
496  'color': ColorProgressIndicator,
497  'tap': TapProgressIndicator,
498  'mono': MonochromeProgressIndicator,
499  'deopts': DeoptsCheckProgressIndicator
500}
501
502
503# -------------------------
504# --- F r a m e w o r k ---
505# -------------------------
506
507
508class CommandOutput(object):
509
510  def __init__(self, exit_code, timed_out, stdout, stderr):
511    self.exit_code = exit_code
512    self.timed_out = timed_out
513    self.stdout = stdout
514    self.stderr = stderr
515    self.failed = None
516
517
518class TestCase(object):
519
520  def __init__(self, context, path, arch, mode):
521    self.path = path
522    self.context = context
523    self.duration = None
524    self.arch = arch
525    self.mode = mode
526    self.parallel = False
527    self.disable_core_files = False
528    self.thread_id = 0
529
530  def IsNegative(self):
531    return self.context.expect_fail
532
533  def DidFail(self, output):
534    if output.failed is None:
535      output.failed = self.IsFailureOutput(output)
536    return output.failed
537
538  def IsFailureOutput(self, output):
539    return output.exit_code != 0
540
541  def GetSource(self):
542    return "(no source available)"
543
544  def RunCommand(self, command, env):
545    full_command = self.context.processor(command)
546    output = Execute(full_command,
547                     self.context,
548                     self.context.GetTimeout(self.mode),
549                     env,
550                     disable_core_files = self.disable_core_files)
551    return TestOutput(self,
552                      full_command,
553                      output,
554                      self.context.store_unexpected_output)
555
556  def Run(self):
557    try:
558      result = self.RunCommand(self.GetCommand(), {
559        "TEST_THREAD_ID": "%d" % self.thread_id,
560        "TEST_PARALLEL" : "%d" % self.parallel
561      })
562    finally:
563      # Tests can leave the tty in non-blocking mode. If the test runner
564      # tries to print to stdout/stderr after that and the tty buffer is
565      # full, it'll die with a EAGAIN OSError. Ergo, put the tty back in
566      # blocking mode before proceeding.
567      if sys.platform != 'win32':
568        from fcntl import fcntl, F_GETFL, F_SETFL
569        from os import O_NONBLOCK
570        for fd in 0,1,2: fcntl(fd, F_SETFL, ~O_NONBLOCK & fcntl(fd, F_GETFL))
571
572    return result
573
574
575class TestOutput(object):
576
577  def __init__(self, test, command, output, store_unexpected_output):
578    self.test = test
579    self.command = command
580    self.output = output
581    self.store_unexpected_output = store_unexpected_output
582    self.diagnostic = []
583
584  def UnexpectedOutput(self):
585    if self.HasCrashed():
586      outcome = CRASH
587    elif self.HasTimedOut():
588      outcome = TIMEOUT
589    elif self.HasFailed():
590      outcome = FAIL
591    else:
592      outcome = PASS
593    return not outcome in self.test.outcomes
594
595  def HasCrashed(self):
596    if utils.IsWindows():
597      return 0x80000000 & self.output.exit_code and not (0x3FFFFF00 & self.output.exit_code)
598    else:
599      # Timed out tests will have exit_code -signal.SIGTERM.
600      if self.output.timed_out:
601        return False
602      return self.output.exit_code < 0
603
604  def HasTimedOut(self):
605    return self.output.timed_out
606
607  def HasFailed(self):
608    execution_failed = self.test.DidFail(self.output)
609    if self.test.IsNegative():
610      return not execution_failed
611    else:
612      return execution_failed
613
614
615def KillProcessWithID(pid, signal_to_send=signal.SIGTERM):
616  if utils.IsWindows():
617    os.popen('taskkill /T /F /PID %d' % pid)
618  else:
619    os.kill(pid, signal_to_send)
620
621
622MAX_SLEEP_TIME = 0.1
623INITIAL_SLEEP_TIME = 0.0001
624SLEEP_TIME_FACTOR = 1.25
625
626SEM_INVALID_VALUE = -1
627SEM_NOGPFAULTERRORBOX = 0x0002 # Microsoft Platform SDK WinBase.h
628
629def Win32SetErrorMode(mode):
630  prev_error_mode = SEM_INVALID_VALUE
631  try:
632    import ctypes
633    prev_error_mode = ctypes.windll.kernel32.SetErrorMode(mode)
634  except ImportError:
635    pass
636  return prev_error_mode
637
638
639def KillTimedOutProcess(context, pid):
640  signal_to_send = signal.SIGTERM
641  if context.abort_on_timeout:
642    # Using SIGABRT here allows the OS to generate a core dump that can be
643    # looked at post-mortem, which helps for investigating failures that are
644    # difficult to reproduce.
645    signal_to_send = signal.SIGABRT
646  KillProcessWithID(pid, signal_to_send)
647
648
649def RunProcess(context, timeout, args, **rest):
650  if context.verbose: print("#", " ".join(args))
651  popen_args = args
652  prev_error_mode = SEM_INVALID_VALUE
653  if utils.IsWindows():
654    if context.suppress_dialogs:
655      # Try to change the error mode to avoid dialogs on fatal errors. Don't
656      # touch any existing error mode flags by merging the existing error mode.
657      # See http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx.
658      error_mode = SEM_NOGPFAULTERRORBOX
659      prev_error_mode = Win32SetErrorMode(error_mode)
660      Win32SetErrorMode(error_mode | prev_error_mode)
661
662  process = subprocess.Popen(
663    args = popen_args,
664    **rest
665  )
666  if utils.IsWindows() and context.suppress_dialogs and prev_error_mode != SEM_INVALID_VALUE:
667    Win32SetErrorMode(prev_error_mode)
668  # Compute the end time - if the process crosses this limit we
669  # consider it timed out.
670  if timeout is None: end_time = None
671  else: end_time = time.time() + timeout
672  timed_out = False
673  # Repeatedly check the exit code from the process in a
674  # loop and keep track of whether or not it times out.
675  exit_code = None
676  sleep_time = INITIAL_SLEEP_TIME
677
678  while exit_code is None:
679    if (not end_time is None) and (time.time() >= end_time):
680      # Kill the process and wait for it to exit.
681      KillTimedOutProcess(context, process.pid)
682      exit_code = process.wait()
683      timed_out = True
684    else:
685      exit_code = process.poll()
686      time.sleep(sleep_time)
687      sleep_time = sleep_time * SLEEP_TIME_FACTOR
688      if sleep_time > MAX_SLEEP_TIME:
689        sleep_time = MAX_SLEEP_TIME
690  return (process, exit_code, timed_out)
691
692
693def PrintError(str):
694  sys.stderr.write(str)
695  sys.stderr.write('\n')
696
697
698def CheckedUnlink(name):
699  while True:
700    try:
701      os.unlink(name)
702    except OSError as e:
703      # On Windows unlink() fails if another process (typically a virus scanner
704      # or the indexing service) has the file open. Those processes keep a
705      # file open for a short time only, so yield and try again; it'll succeed.
706      if sys.platform == 'win32' and e.errno == errno.EACCES:
707        time.sleep(0)
708        continue
709      PrintError("os.unlink() " + str(e))
710    break
711
712def Execute(args, context, timeout=None, env=None, disable_core_files=False, stdin=None):
713  (fd_out, outname) = tempfile.mkstemp()
714  (fd_err, errname) = tempfile.mkstemp()
715
716  if env is None:
717    env = {}
718  env_copy = os.environ.copy()
719
720  # Remove NODE_PATH
721  if "NODE_PATH" in env_copy:
722    del env_copy["NODE_PATH"]
723
724  # Remove NODE_REPL_EXTERNAL_MODULE
725  if "NODE_REPL_EXTERNAL_MODULE" in env_copy:
726    del env_copy["NODE_REPL_EXTERNAL_MODULE"]
727
728  # Extend environment
729  for key, value in env.items():
730    env_copy[key] = value
731
732  preexec_fn = None
733
734  if disable_core_files and not utils.IsWindows():
735    def disableCoreFiles():
736      import resource
737      resource.setrlimit(resource.RLIMIT_CORE, (0,0))
738    preexec_fn = disableCoreFiles
739
740  (process, exit_code, timed_out) = RunProcess(
741    context,
742    timeout,
743    args = args,
744    stdin = stdin,
745    stdout = fd_out,
746    stderr = fd_err,
747    env = env_copy,
748    preexec_fn = preexec_fn
749  )
750  os.close(fd_out)
751  os.close(fd_err)
752  output = open(outname, encoding='utf8').read()
753  errors = open(errname, encoding='utf8').read()
754  CheckedUnlink(outname)
755  CheckedUnlink(errname)
756
757  return CommandOutput(exit_code, timed_out, output, errors)
758
759
760def CarCdr(path):
761  if len(path) == 0:
762    return (None, [ ])
763  else:
764    return (path[0], path[1:])
765
766
767class TestConfiguration(object):
768  def __init__(self, context, root, section):
769    self.context = context
770    self.root = root
771    self.section = section
772
773  def Contains(self, path, file):
774    if len(path) > len(file):
775      return False
776    for i in range(len(path)):
777      if not path[i].match(NormalizePath(file[i])):
778        return False
779    return True
780
781  def GetTestStatus(self, sections, defs):
782    status_file = join(self.root, '%s.status' % self.section)
783    if exists(status_file):
784      ReadConfigurationInto(status_file, sections, defs)
785
786
787class TestSuite(object):
788
789  def __init__(self, name):
790    self.name = name
791
792  def GetName(self):
793    return self.name
794
795
796class TestRepository(TestSuite):
797
798  def __init__(self, path):
799    normalized_path = abspath(path)
800    super(TestRepository, self).__init__(basename(normalized_path))
801    self.path = normalized_path
802    self.is_loaded = False
803    self.config = None
804
805  def GetConfiguration(self, context):
806    if self.is_loaded:
807      return self.config
808    self.is_loaded = True
809
810    module = get_module('testcfg', self.path)
811    self.config = module.GetConfiguration(context, self.path)
812    if hasattr(self.config, 'additional_flags'):
813      self.config.additional_flags += context.node_args
814    else:
815      self.config.additional_flags = context.node_args
816    return self.config
817
818  def GetBuildRequirements(self, path, context):
819    return self.GetConfiguration(context).GetBuildRequirements()
820
821  def AddTestsToList(self, result, current_path, path, context, arch, mode):
822    tests = self.GetConfiguration(context).ListTests(current_path, path,
823                                                     arch, mode)
824    result += tests
825    for i in range(1, context.repeat):
826      result += copy.deepcopy(tests)
827
828  def GetTestStatus(self, context, sections, defs):
829    self.GetConfiguration(context).GetTestStatus(sections, defs)
830
831
832class LiteralTestSuite(TestSuite):
833  def __init__(self, tests_repos, test_root):
834    super(LiteralTestSuite, self).__init__('root')
835    self.tests_repos = tests_repos
836    self.test_root = test_root
837
838  def GetBuildRequirements(self, path, context):
839    (name, rest) = CarCdr(path)
840    result = [ ]
841    for test in self.tests_repos:
842      if not name or name.match(test.GetName()):
843        result += test.GetBuildRequirements(rest, context)
844    return result
845
846  def ListTests(self, current_path, path, context, arch, mode):
847    (name, rest) = CarCdr(path)
848    result = [ ]
849    for test in self.tests_repos:
850      test_name = test.GetName()
851      if not name or name.match(test_name):
852        full_path = current_path + [test_name]
853        test.AddTestsToList(result, full_path, path, context, arch, mode)
854    result.sort(key=lambda x: x.GetName())
855    return result
856
857  def GetTestStatus(self, context, sections, defs):
858    # Just read the test configuration from root_path/root.status.
859    root = TestConfiguration(context, self.test_root, 'root')
860    root.GetTestStatus(sections, defs)
861    for tests_repos in self.tests_repos:
862      tests_repos.GetTestStatus(context, sections, defs)
863
864
865TIMEOUT_SCALEFACTOR = {
866    'armv6' : { 'debug' : 12, 'release' : 3 },  # The ARM buildbots are slow.
867    'arm'   : { 'debug' :  8, 'release' : 2 },
868    'ia32'  : { 'debug' :  4, 'release' : 1 },
869    'ppc'   : { 'debug' :  4, 'release' : 1 },
870    's390'  : { 'debug' :  4, 'release' : 1 } }
871
872
873class Context(object):
874
875  def __init__(self, workspace, verbose, vm, args, expect_fail,
876               timeout, processor, suppress_dialogs,
877               store_unexpected_output, repeat, abort_on_timeout):
878    self.workspace = workspace
879    self.verbose = verbose
880    self.vm = vm
881    self.node_args = args
882    self.expect_fail = expect_fail
883    self.timeout = timeout
884    self.processor = processor
885    self.suppress_dialogs = suppress_dialogs
886    self.store_unexpected_output = store_unexpected_output
887    self.repeat = repeat
888    self.abort_on_timeout = abort_on_timeout
889    self.v8_enable_inspector = True
890    self.node_has_crypto = True
891
892  def GetVm(self, arch, mode):
893    if self.vm is not None:
894      return self.vm
895    if arch == 'none':
896      name = 'out/Debug/node' if mode == 'debug' else 'out/Release/node'
897    else:
898      name = 'out/%s.%s/node' % (arch, mode)
899
900    # Currently GYP does not support output_dir for MSVS.
901    # http://code.google.com/p/gyp/issues/detail?id=40
902    # It will put the builds into Release/node.exe or Debug/node.exe
903    if utils.IsWindows():
904      if not exists(name + '.exe'):
905        name = name.replace('out/', '')
906      name = os.path.abspath(name + '.exe')
907
908    if not exists(name):
909      raise ValueError('Could not find executable. Should be ' + name)
910
911    return name
912
913  def GetTimeout(self, mode):
914    return self.timeout * TIMEOUT_SCALEFACTOR[ARCH_GUESS or 'ia32'][mode]
915
916def RunTestCases(cases_to_run, progress, tasks, flaky_tests_mode):
917  progress = PROGRESS_INDICATORS[progress](cases_to_run, flaky_tests_mode)
918  return progress.Run(tasks)
919
920# -------------------------------------------
921# --- T e s t   C o n f i g u r a t i o n ---
922# -------------------------------------------
923
924
925RUN = 'run'
926SKIP = 'skip'
927FAIL = 'fail'
928PASS = 'pass'
929OKAY = 'okay'
930TIMEOUT = 'timeout'
931CRASH = 'crash'
932SLOW = 'slow'
933FLAKY = 'flaky'
934DONTCARE = 'dontcare'
935
936class Expression(object):
937  pass
938
939
940class Constant(Expression):
941
942  def __init__(self, value):
943    self.value = value
944
945  def Evaluate(self, env, defs):
946    return self.value
947
948
949class Variable(Expression):
950
951  def __init__(self, name):
952    self.name = name
953
954  def GetOutcomes(self, env, defs):
955    if self.name in env: return set([env[self.name]])
956    else: return set()
957
958
959class Outcome(Expression):
960
961  def __init__(self, name):
962    self.name = name
963
964  def GetOutcomes(self, env, defs):
965    if self.name in defs:
966      return defs[self.name].GetOutcomes(env, defs)
967    else:
968      return set([self.name])
969
970
971class Operation(Expression):
972
973  def __init__(self, left, op, right):
974    self.left = left
975    self.op = op
976    self.right = right
977
978  def Evaluate(self, env, defs):
979    if self.op == '||' or self.op == ',':
980      return self.left.Evaluate(env, defs) or self.right.Evaluate(env, defs)
981    elif self.op == 'if':
982      return False
983    elif self.op == '==':
984      inter = self.left.GetOutcomes(env, defs) & self.right.GetOutcomes(env, defs)
985      return bool(inter)
986    else:
987      assert self.op == '&&'
988      return self.left.Evaluate(env, defs) and self.right.Evaluate(env, defs)
989
990  def GetOutcomes(self, env, defs):
991    if self.op == '||' or self.op == ',':
992      return self.left.GetOutcomes(env, defs) | self.right.GetOutcomes(env, defs)
993    elif self.op == 'if':
994      if self.right.Evaluate(env, defs):
995        return self.left.GetOutcomes(env, defs)
996      else:
997        return set()
998    else:
999      assert self.op == '&&'
1000      return self.left.GetOutcomes(env, defs) & self.right.GetOutcomes(env, defs)
1001
1002
1003def IsAlpha(str):
1004  for char in str:
1005    if not (char.isalpha() or char.isdigit() or char == '_'):
1006      return False
1007  return True
1008
1009
1010class Tokenizer(object):
1011  """A simple string tokenizer that chops expressions into variables,
1012  parens and operators"""
1013
1014  def __init__(self, expr):
1015    self.index = 0
1016    self.expr = expr
1017    self.length = len(expr)
1018    self.tokens = None
1019
1020  def Current(self, length = 1):
1021    if not self.HasMore(length): return ""
1022    return self.expr[self.index:self.index+length]
1023
1024  def HasMore(self, length = 1):
1025    return self.index < self.length + (length - 1)
1026
1027  def Advance(self, count = 1):
1028    self.index = self.index + count
1029
1030  def AddToken(self, token):
1031    self.tokens.append(token)
1032
1033  def SkipSpaces(self):
1034    while self.HasMore() and self.Current().isspace():
1035      self.Advance()
1036
1037  def Tokenize(self):
1038    self.tokens = [ ]
1039    while self.HasMore():
1040      self.SkipSpaces()
1041      if not self.HasMore():
1042        return None
1043      if self.Current() == '(':
1044        self.AddToken('(')
1045        self.Advance()
1046      elif self.Current() == ')':
1047        self.AddToken(')')
1048        self.Advance()
1049      elif self.Current() == '$':
1050        self.AddToken('$')
1051        self.Advance()
1052      elif self.Current() == ',':
1053        self.AddToken(',')
1054        self.Advance()
1055      elif IsAlpha(self.Current()):
1056        buf = ""
1057        while self.HasMore() and IsAlpha(self.Current()):
1058          buf += self.Current()
1059          self.Advance()
1060        self.AddToken(buf)
1061      elif self.Current(2) == '&&':
1062        self.AddToken('&&')
1063        self.Advance(2)
1064      elif self.Current(2) == '||':
1065        self.AddToken('||')
1066        self.Advance(2)
1067      elif self.Current(2) == '==':
1068        self.AddToken('==')
1069        self.Advance(2)
1070      else:
1071        return None
1072    return self.tokens
1073
1074
1075class Scanner(object):
1076  """A simple scanner that can serve out tokens from a given list"""
1077
1078  def __init__(self, tokens):
1079    self.tokens = tokens
1080    self.length = len(tokens)
1081    self.index = 0
1082
1083  def HasMore(self):
1084    return self.index < self.length
1085
1086  def Current(self):
1087    return self.tokens[self.index]
1088
1089  def Advance(self):
1090    self.index = self.index + 1
1091
1092
1093def ParseAtomicExpression(scan):
1094  if scan.Current() == "true":
1095    scan.Advance()
1096    return Constant(True)
1097  elif scan.Current() == "false":
1098    scan.Advance()
1099    return Constant(False)
1100  elif IsAlpha(scan.Current()):
1101    name = scan.Current()
1102    scan.Advance()
1103    return Outcome(name.lower())
1104  elif scan.Current() == '$':
1105    scan.Advance()
1106    if not IsAlpha(scan.Current()):
1107      return None
1108    name = scan.Current()
1109    scan.Advance()
1110    return Variable(name.lower())
1111  elif scan.Current() == '(':
1112    scan.Advance()
1113    result = ParseLogicalExpression(scan)
1114    if (not result) or (scan.Current() != ')'):
1115      return None
1116    scan.Advance()
1117    return result
1118  else:
1119    return None
1120
1121
1122BINARIES = ['==']
1123def ParseOperatorExpression(scan):
1124  left = ParseAtomicExpression(scan)
1125  if not left: return None
1126  while scan.HasMore() and (scan.Current() in BINARIES):
1127    op = scan.Current()
1128    scan.Advance()
1129    right = ParseOperatorExpression(scan)
1130    if not right:
1131      return None
1132    left = Operation(left, op, right)
1133  return left
1134
1135
1136def ParseConditionalExpression(scan):
1137  left = ParseOperatorExpression(scan)
1138  if not left: return None
1139  while scan.HasMore() and (scan.Current() == 'if'):
1140    scan.Advance()
1141    right = ParseOperatorExpression(scan)
1142    if not right:
1143      return None
1144    left=  Operation(left, 'if', right)
1145  return left
1146
1147
1148LOGICALS = ["&&", "||", ","]
1149def ParseLogicalExpression(scan):
1150  left = ParseConditionalExpression(scan)
1151  if not left: return None
1152  while scan.HasMore() and (scan.Current() in LOGICALS):
1153    op = scan.Current()
1154    scan.Advance()
1155    right = ParseConditionalExpression(scan)
1156    if not right:
1157      return None
1158    left = Operation(left, op, right)
1159  return left
1160
1161
1162def ParseCondition(expr):
1163  """Parses a logical expression into an Expression object"""
1164  tokens = Tokenizer(expr).Tokenize()
1165  if not tokens:
1166    print("Malformed expression: '%s'" % expr)
1167    return None
1168  scan = Scanner(tokens)
1169  ast = ParseLogicalExpression(scan)
1170  if not ast:
1171    print("Malformed expression: '%s'" % expr)
1172    return None
1173  if scan.HasMore():
1174    print("Malformed expression: '%s'" % expr)
1175    return None
1176  return ast
1177
1178
1179class Configuration(object):
1180  """The parsed contents of a configuration file"""
1181
1182  def __init__(self, sections, defs):
1183    self.sections = sections
1184    self.defs = defs
1185
1186  def ClassifyTests(self, cases, env):
1187    sections = [ s for s in self.sections if s.condition.Evaluate(env, self.defs) ]
1188    all_rules = reduce(list.__add__, [s.rules for s in sections], [])
1189    unused_rules = set(all_rules)
1190    result = []
1191    for case in cases:
1192      matches = [ r for r in all_rules if r.Contains(case.path) ]
1193      outcomes_list = [ r.GetOutcomes(env, self.defs) for r in matches ]
1194      outcomes = reduce(set.union, outcomes_list, set())
1195      unused_rules.difference_update(matches)
1196      case.outcomes = set(outcomes) or set([PASS])
1197      # slow tests may also just pass.
1198      if SLOW in case.outcomes:
1199        case.outcomes.add(PASS)
1200      result.append(case)
1201    return result, unused_rules
1202
1203
1204class Section(object):
1205  """A section of the configuration file.  Sections are enabled or
1206  disabled prior to running the tests, based on their conditions"""
1207
1208  def __init__(self, condition):
1209    self.condition = condition
1210    self.rules = [ ]
1211
1212  def AddRule(self, rule):
1213    self.rules.append(rule)
1214
1215
1216class Rule(object):
1217  """A single rule that specifies the expected outcome for a single
1218  test."""
1219
1220  def __init__(self, raw_path, path, value):
1221    self.raw_path = raw_path
1222    self.path = path
1223    self.value = value
1224
1225  def GetOutcomes(self, env, defs):
1226    return self.value.GetOutcomes(env, defs)
1227
1228  def Contains(self, path):
1229    if len(self.path) > len(path):
1230      return False
1231    for i in range(len(self.path)):
1232      if not self.path[i].match(path[i]):
1233        return False
1234    return True
1235
1236
1237HEADER_PATTERN = re.compile(r'\[([^]]+)\]')
1238RULE_PATTERN = re.compile(r'\s*([^: ]*)\s*:(.*)')
1239DEF_PATTERN = re.compile(r'^def\s*(\w+)\s*=(.*)$')
1240PREFIX_PATTERN = re.compile(r'^\s*prefix\s+([\w_.\-/]+)$')
1241
1242
1243def ReadConfigurationInto(path, sections, defs):
1244  current_section = Section(Constant(True))
1245  sections.append(current_section)
1246  prefix = []
1247  for line in utils.ReadLinesFrom(path):
1248    header_match = HEADER_PATTERN.match(line)
1249    if header_match:
1250      condition_str = header_match.group(1).strip()
1251      condition = ParseCondition(condition_str)
1252      new_section = Section(condition)
1253      sections.append(new_section)
1254      current_section = new_section
1255      continue
1256    rule_match = RULE_PATTERN.match(line)
1257    if rule_match:
1258      path = prefix + SplitPath(rule_match.group(1).strip())
1259      value_str = rule_match.group(2).strip()
1260      value = ParseCondition(value_str)
1261      if not value:
1262        return False
1263      current_section.AddRule(Rule(rule_match.group(1), path, value))
1264      continue
1265    def_match = DEF_PATTERN.match(line)
1266    if def_match:
1267      name = def_match.group(1).lower()
1268      value = ParseCondition(def_match.group(2).strip())
1269      if not value:
1270        return False
1271      defs[name] = value
1272      continue
1273    prefix_match = PREFIX_PATTERN.match(line)
1274    if prefix_match:
1275      prefix = SplitPath(prefix_match.group(1).strip())
1276      continue
1277    raise Exception("Malformed line: '%s'." % line)
1278
1279
1280# ---------------
1281# --- M a i n ---
1282# ---------------
1283
1284
1285ARCH_GUESS = utils.GuessArchitecture()
1286
1287
1288def BuildOptions():
1289  result = optparse.OptionParser()
1290  result.add_option("-m", "--mode", help="The test modes in which to run (comma-separated)",
1291      default='release')
1292  result.add_option("-v", "--verbose", help="Verbose output",
1293      default=False, action="store_true")
1294  result.add_option('--logfile', dest='logfile',
1295      help='write test output to file. NOTE: this only applies the tap progress indicator')
1296  result.add_option("-p", "--progress",
1297      help="The style of progress indicator (verbose, dots, color, mono, tap)",
1298      choices=list(PROGRESS_INDICATORS.keys()), default="mono")
1299  result.add_option("--report", help="Print a summary of the tests to be run",
1300      default=False, action="store_true")
1301  result.add_option("-s", "--suite", help="A test suite",
1302      default=[], action="append")
1303  result.add_option("-t", "--timeout", help="Timeout in seconds",
1304      default=120, type="int")
1305  result.add_option("--arch", help='The architecture to run tests for',
1306      default='none')
1307  result.add_option("--snapshot", help="Run the tests with snapshot turned on",
1308      default=False, action="store_true")
1309  result.add_option("--special-command", default=None)
1310  result.add_option("--node-args", dest="node_args", help="Args to pass through to Node",
1311      default=[], action="append")
1312  result.add_option("--expect-fail", dest="expect_fail",
1313      help="Expect test cases to fail", default=False, action="store_true")
1314  result.add_option("--valgrind", help="Run tests through valgrind",
1315      default=False, action="store_true")
1316  result.add_option("--worker", help="Run parallel tests inside a worker context",
1317      default=False, action="store_true")
1318  result.add_option("--check-deopts", help="Check tests for permanent deoptimizations",
1319      default=False, action="store_true")
1320  result.add_option("--cat", help="Print the source of the tests",
1321      default=False, action="store_true")
1322  result.add_option("--flaky-tests",
1323      help="Regard tests marked as flaky (run|skip|dontcare)",
1324      default="run")
1325  result.add_option("--skip-tests",
1326      help="Tests that should not be executed (comma-separated)",
1327      default="")
1328  result.add_option("--warn-unused", help="Report unused rules",
1329      default=False, action="store_true")
1330  result.add_option("-j", help="The number of parallel tasks to run",
1331      default=1, type="int")
1332  result.add_option("-J", help="Run tasks in parallel on all cores",
1333      default=False, action="store_true")
1334  result.add_option("--time", help="Print timing information after running",
1335      default=False, action="store_true")
1336  result.add_option("--suppress-dialogs", help="Suppress Windows dialogs for crashing tests",
1337        dest="suppress_dialogs", default=True, action="store_true")
1338  result.add_option("--no-suppress-dialogs", help="Display Windows dialogs for crashing tests",
1339        dest="suppress_dialogs", action="store_false")
1340  result.add_option("--shell", help="Path to node executable", default=None)
1341  result.add_option("--store-unexpected-output",
1342      help="Store the temporary JS files from tests that fails",
1343      dest="store_unexpected_output", default=True, action="store_true")
1344  result.add_option("--no-store-unexpected-output",
1345      help="Deletes the temporary JS files from tests that fails",
1346      dest="store_unexpected_output", action="store_false")
1347  result.add_option("-r", "--run",
1348      help="Divide the tests in m groups (interleaved) and run tests from group n (--run=n,m with n < m)",
1349      default="")
1350  result.add_option('--temp-dir',
1351      help='Optional path to change directory used for tests', default=False)
1352  result.add_option('--test-root',
1353      help='Optional path to change test directory', dest='test_root', default=None)
1354  result.add_option('--repeat',
1355      help='Number of times to repeat given tests',
1356      default=1, type="int")
1357  result.add_option('--abort-on-timeout',
1358      help='Send SIGABRT instead of SIGTERM to kill processes that time out',
1359      default=False, action="store_true", dest="abort_on_timeout")
1360  result.add_option("--type",
1361      help="Type of build (simple, fips, coverage)",
1362      default=None)
1363  return result
1364
1365
1366def ProcessOptions(options):
1367  global VERBOSE
1368  VERBOSE = options.verbose
1369  options.arch = options.arch.split(',')
1370  options.mode = options.mode.split(',')
1371  options.run = options.run.split(',')
1372  # Split at commas and filter out all the empty strings.
1373  options.skip_tests = [test for test in options.skip_tests.split(',') if test]
1374  if options.run == [""]:
1375    options.run = None
1376  elif len(options.run) != 2:
1377    print("The run argument must be two comma-separated integers.")
1378    return False
1379  else:
1380    try:
1381      options.run = [int(level) for level in options.run]
1382    except ValueError:
1383      print("Could not parse the integers from the run argument.")
1384      return False
1385    if options.run[0] < 0 or options.run[1] < 0:
1386      print("The run argument cannot have negative integers.")
1387      return False
1388    if options.run[0] >= options.run[1]:
1389      print("The test group to run (n) must be smaller than number of groups (m).")
1390      return False
1391  if options.J:
1392    # inherit JOBS from environment if provided. some virtualised systems
1393    # tends to exaggerate the number of available cpus/cores.
1394    cores = os.environ.get('JOBS')
1395    options.j = int(cores) if cores is not None else multiprocessing.cpu_count()
1396  if options.flaky_tests not in [RUN, SKIP, DONTCARE]:
1397    print("Unknown flaky-tests mode %s" % options.flaky_tests)
1398    return False
1399  return True
1400
1401
1402REPORT_TEMPLATE = """\
1403Total: %(total)i tests
1404 * %(skipped)4d tests will be skipped
1405 * %(pass)4d tests are expected to pass
1406 * %(fail_ok)4d tests are expected to fail that we won't fix
1407 * %(fail)4d tests are expected to fail that we should fix\
1408"""
1409
1410
1411class Pattern(object):
1412
1413  def __init__(self, pattern):
1414    self.pattern = pattern
1415    self.compiled = None
1416
1417  def match(self, str):
1418    if not self.compiled:
1419      pattern = "^" + self.pattern.replace('*', '.*') + "$"
1420      self.compiled = re.compile(pattern)
1421    return self.compiled.match(str)
1422
1423  def __str__(self):
1424    return self.pattern
1425
1426
1427def SplitPath(path_arg):
1428  stripped = [c.strip() for c in path_arg.split('/')]
1429  return [Pattern(s) for s in stripped if len(s) > 0]
1430
1431def NormalizePath(path, prefix='test/'):
1432  # strip the extra path information of the specified test
1433  prefix = prefix.replace('\\', '/')
1434  path = path.replace('\\', '/')
1435  if path.startswith(prefix):
1436    path = path[len(prefix):]
1437  if path.endswith('.js'):
1438    path = path[:-3]
1439  elif path.endswith('.mjs'):
1440    path = path[:-4]
1441  return path
1442
1443def GetSpecialCommandProcessor(value):
1444  if (not value) or (value.find('@') == -1):
1445    def ExpandCommand(args):
1446      return args
1447    return ExpandCommand
1448  else:
1449    prefix, _, suffix = value.partition('@')
1450    prefix = unquote(prefix).split()
1451    suffix = unquote(suffix).split()
1452    def ExpandCommand(args):
1453      return prefix + args + suffix
1454    return ExpandCommand
1455
1456def GetSuites(test_root):
1457  def IsSuite(path):
1458    return isdir(path) and exists(join(path, 'testcfg.py'))
1459  return [ f for f in os.listdir(test_root) if IsSuite(join(test_root, f)) ]
1460
1461
1462def FormatTime(d):
1463  millis = round(d * 1000) % 1000
1464  return time.strftime("%M:%S.", time.gmtime(d)) + ("%03i" % millis)
1465
1466
1467def FormatTimedelta(td):
1468  if hasattr(td, 'total_seconds'):
1469    d = td.total_seconds()
1470  else: # python2.6 compat
1471    d =  td.seconds + (td.microseconds / 10.0**6)
1472  return FormatTime(d)
1473
1474
1475def PrintCrashed(code):
1476  if utils.IsWindows():
1477    return "CRASHED"
1478  else:
1479    return "CRASHED (Signal: %d)" % -code
1480
1481
1482# these suites represent special cases that should not be run as part of the
1483# default JavaScript test-run, e.g., internet/ requires a network connection,
1484# addons/ requires compilation.
1485IGNORED_SUITES = [
1486  'addons',
1487  'benchmark',
1488  'doctool',
1489  'embedding',
1490  'internet',
1491  'js-native-api',
1492  'node-api',
1493  'pummel',
1494  'tick-processor',
1495  'v8-updates'
1496]
1497
1498
1499def ArgsToTestPaths(test_root, args, suites):
1500  if len(args) == 0 or 'default' in args:
1501    def_suites = [s for s in suites if s not in IGNORED_SUITES]
1502    args = [a for a in args if a != 'default'] + def_suites
1503  subsystem_regex = re.compile(r'^[a-zA-Z-]*$')
1504  check = lambda arg: subsystem_regex.match(arg) and (arg not in suites)
1505  mapped_args = ["*/test*-%s-*" % arg if check(arg) else arg for arg in args]
1506  paths = [SplitPath(NormalizePath(a)) for a in mapped_args]
1507  return paths
1508
1509
1510def get_env_type(vm, options_type, context):
1511  if options_type is not None:
1512    env_type = options_type
1513  else:
1514    # 'simple' is the default value for 'env_type'.
1515    env_type = 'simple'
1516    ssl_ver = Execute([vm, '-p', 'process.versions.openssl'], context).stdout
1517    if 'fips' in ssl_ver:
1518      env_type = 'fips'
1519  return env_type
1520
1521
1522def Main():
1523  parser = BuildOptions()
1524  (options, args) = parser.parse_args()
1525  if not ProcessOptions(options):
1526    parser.print_help()
1527    return 1
1528
1529  ch = logging.StreamHandler(sys.stdout)
1530  logger.addHandler(ch)
1531  logger.setLevel(logging.INFO)
1532  if options.logfile:
1533    fh = logging.FileHandler(options.logfile, encoding='utf-8', mode='w')
1534    logger.addHandler(fh)
1535
1536  workspace = abspath(join(dirname(sys.argv[0]), '..'))
1537  test_root = join(workspace, 'test')
1538  if options.test_root is not None:
1539    test_root = options.test_root
1540  suites = GetSuites(test_root)
1541  repositories = [TestRepository(join(test_root, name)) for name in suites]
1542  repositories += [TestRepository(a) for a in options.suite]
1543
1544  root = LiteralTestSuite(repositories, test_root)
1545  paths = ArgsToTestPaths(test_root, args, suites)
1546
1547  # Check for --valgrind option. If enabled, we overwrite the special
1548  # command flag with a command that uses the run-valgrind.py script.
1549  if options.valgrind:
1550    run_valgrind = join(workspace, "tools", "run-valgrind.py")
1551    options.special_command = "python -u " + run_valgrind + " @"
1552
1553  if options.check_deopts:
1554    options.node_args.append("--trace-opt")
1555    options.node_args.append("--trace-file-names")
1556    # --always-opt is needed because many tests do not run long enough for the
1557    # optimizer to kick in, so this flag will force it to run.
1558    options.node_args.append("--always-opt")
1559    options.progress = "deopts"
1560
1561  if options.worker:
1562    run_worker = join(workspace, "tools", "run-worker.js")
1563    options.node_args.append(run_worker)
1564
1565  processor = GetSpecialCommandProcessor(options.special_command)
1566
1567  context = Context(workspace,
1568                    VERBOSE,
1569                    options.shell,
1570                    options.node_args,
1571                    options.expect_fail,
1572                    options.timeout,
1573                    processor,
1574                    options.suppress_dialogs,
1575                    options.store_unexpected_output,
1576                    options.repeat,
1577                    options.abort_on_timeout)
1578
1579  # Get status for tests
1580  sections = [ ]
1581  defs = { }
1582  root.GetTestStatus(context, sections, defs)
1583  config = Configuration(sections, defs)
1584
1585  # List the tests
1586  all_cases = [ ]
1587  all_unused = [ ]
1588  unclassified_tests = [ ]
1589  globally_unused_rules = None
1590  for path in paths:
1591    for arch in options.arch:
1592      for mode in options.mode:
1593        vm = context.GetVm(arch, mode)
1594        if not exists(vm):
1595          print("Can't find shell executable: '%s'" % vm)
1596          continue
1597        archEngineContext = Execute([vm, "-p", "process.arch"], context)
1598        vmArch = archEngineContext.stdout.rstrip()
1599        if archEngineContext.exit_code != 0 or vmArch == "undefined":
1600          print("Can't determine the arch of: '%s'" % vm)
1601          print(archEngineContext.stderr.rstrip())
1602          continue
1603        env = {
1604          'mode': mode,
1605          'system': utils.GuessOS(),
1606          'arch': vmArch,
1607          'type': get_env_type(vm, options.type, context),
1608        }
1609        test_list = root.ListTests([], path, context, arch, mode)
1610        unclassified_tests += test_list
1611        cases, unused_rules = config.ClassifyTests(test_list, env)
1612        if globally_unused_rules is None:
1613          globally_unused_rules = set(unused_rules)
1614        else:
1615          globally_unused_rules = (
1616              globally_unused_rules.intersection(unused_rules))
1617        all_cases += cases
1618        all_unused.append(unused_rules)
1619
1620  # We want to skip the inspector tests if node was built without the inspector.
1621  has_inspector = Execute([vm,
1622      '-p', 'process.features.inspector'], context)
1623  if has_inspector.stdout.rstrip() == 'false':
1624    context.v8_enable_inspector = False
1625
1626  has_crypto = Execute([vm,
1627      '-p', 'process.versions.openssl'], context)
1628  if has_crypto.stdout.rstrip() == 'undefined':
1629    context.node_has_crypto = False
1630
1631  if options.cat:
1632    visited = set()
1633    for test in unclassified_tests:
1634      key = tuple(test.path)
1635      if key in visited:
1636        continue
1637      visited.add(key)
1638      print("--- begin source: %s ---" % test.GetLabel())
1639      source = test.GetSource().strip()
1640      print(source)
1641      print("--- end source: %s ---" % test.GetLabel())
1642    return 0
1643
1644  if options.warn_unused:
1645    for rule in globally_unused_rules:
1646      print("Rule for '%s' was not used." % '/'.join([str(s) for s in rule.path]))
1647
1648  tempdir = os.environ.get('NODE_TEST_DIR') or options.temp_dir
1649  if tempdir:
1650    os.environ['NODE_TEST_DIR'] = tempdir
1651    try:
1652      os.makedirs(tempdir)
1653    except OSError as exception:
1654      if exception.errno != errno.EEXIST:
1655        print("Could not create the temporary directory", options.temp_dir)
1656        sys.exit(1)
1657
1658  def should_keep(case):
1659    if any((s in case.file) for s in options.skip_tests):
1660      return False
1661    elif SKIP in case.outcomes:
1662      return False
1663    elif (options.flaky_tests == SKIP) and (set([SLOW, FLAKY]) & case.outcomes):
1664      return False
1665    else:
1666      return True
1667
1668  cases_to_run = [
1669    test_case for test_case in all_cases if should_keep(test_case)
1670  ]
1671
1672  if options.report:
1673    print(REPORT_TEMPLATE % {
1674      'total': len(all_cases),
1675      'skipped': len(all_cases) - len(cases_to_run),
1676      'pass': len([t for t in cases_to_run if PASS in t.outcomes]),
1677      'fail_ok': len([t for t in cases_to_run if t.outcomes == set([FAIL, OKAY])]),
1678      'fail': len([t for t in cases_to_run if t.outcomes == set([FAIL])])
1679    })
1680
1681  if options.run is not None:
1682    # Must ensure the list of tests is sorted before selecting, to avoid
1683    # silent errors if this file is changed to list the tests in a way that
1684    # can be different in different machines
1685    cases_to_run.sort(key=lambda c: (c.arch, c.mode, c.file))
1686    cases_to_run = [ cases_to_run[i] for i
1687                     in range(options.run[0],
1688                               len(cases_to_run),
1689                               options.run[1]) ]
1690  if len(cases_to_run) == 0:
1691    print("No tests to run.")
1692    return 1
1693  else:
1694    try:
1695      start = time.time()
1696      if RunTestCases(cases_to_run, options.progress, options.j, options.flaky_tests):
1697        result = 0
1698      else:
1699        result = 1
1700      duration = time.time() - start
1701    except KeyboardInterrupt:
1702      print("Interrupted")
1703      return 1
1704
1705  if options.time:
1706    # Write the times to stderr to make it easy to separate from the
1707    # test output.
1708    print()
1709    sys.stderr.write("--- Total time: %s ---\n" % FormatTime(duration))
1710    timed_tests = [ t for t in cases_to_run if not t.duration is None ]
1711    timed_tests.sort(key=lambda x: x.duration)
1712    for i, entry in enumerate(timed_tests[:20], start=1):
1713      t = FormatTimedelta(entry.duration)
1714      sys.stderr.write("%4i (%s) %s\n" % (i, t, entry.GetLabel()))
1715
1716  return result
1717
1718
1719if __name__ == '__main__':
1720  sys.exit(Main())
1721