michael@0: # Library for JSTest tests. michael@0: # michael@0: # This contains classes that represent an individual test, including michael@0: # metadata, and know how to run the tests and determine failures. michael@0: michael@0: import datetime, os, sys, time michael@0: from subprocess import Popen, PIPE michael@0: from threading import Thread michael@0: michael@0: from results import TestOutput michael@0: michael@0: # When run on tbpl, we run each test multiple times with the following arguments. michael@0: TBPL_FLAGS = [ michael@0: [], # no flags, normal baseline and ion michael@0: ['--ion-eager', '--ion-parallel-compile=off'], # implies --baseline-eager michael@0: ['--ion-eager', '--ion-parallel-compile=off', '--ion-check-range-analysis', '--no-sse3'], michael@0: ['--baseline-eager'], michael@0: ['--baseline-eager', '--no-fpu'], michael@0: ['--no-baseline', '--no-ion'], michael@0: ] michael@0: michael@0: def do_run_cmd(cmd): michael@0: l = [ None, None ] michael@0: th_run_cmd(cmd, l) michael@0: return l[1] michael@0: michael@0: def set_limits(): michael@0: # resource module not supported on all platforms michael@0: try: michael@0: import resource michael@0: GB = 2**30 michael@0: resource.setrlimit(resource.RLIMIT_AS, (2*GB, 2*GB)) michael@0: except: michael@0: return michael@0: michael@0: def th_run_cmd(cmd, l): michael@0: t0 = datetime.datetime.now() michael@0: michael@0: # close_fds and preexec_fn are not supported on Windows and will michael@0: # cause a ValueError. michael@0: options = {} michael@0: if sys.platform != 'win32': michael@0: options["close_fds"] = True michael@0: options["preexec_fn"] = set_limits michael@0: p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, **options) michael@0: michael@0: l[0] = p michael@0: out, err = p.communicate() michael@0: t1 = datetime.datetime.now() michael@0: dd = t1-t0 michael@0: dt = dd.seconds + 1e-6 * dd.microseconds michael@0: l[1] = (out, err, p.returncode, dt) michael@0: michael@0: def run_cmd(cmd, timeout=60.0): michael@0: if timeout is None: michael@0: return do_run_cmd(cmd) michael@0: michael@0: l = [ None, None ] michael@0: timed_out = False michael@0: th = Thread(target=th_run_cmd, args=(cmd, l)) michael@0: th.start() michael@0: th.join(timeout) michael@0: while th.isAlive(): michael@0: if l[0] is not None: michael@0: try: michael@0: # In Python 3, we could just do l[0].kill(). michael@0: import signal michael@0: if sys.platform != 'win32': michael@0: os.kill(l[0].pid, signal.SIGKILL) michael@0: time.sleep(.1) michael@0: timed_out = True michael@0: except OSError: michael@0: # Expecting a "No such process" error michael@0: pass michael@0: th.join() michael@0: return l[1] + (timed_out,) michael@0: michael@0: class Test(object): michael@0: """A runnable test.""" michael@0: def __init__(self, path): michael@0: self.path = path # str: path of JS file relative to tests root dir michael@0: michael@0: @staticmethod michael@0: def prefix_command(path): michael@0: """Return the '-f shell.js' options needed to run a test with the given path.""" michael@0: if path == '': michael@0: return [ '-f', 'shell.js' ] michael@0: head, base = os.path.split(path) michael@0: return Test.prefix_command(head) + [ '-f', os.path.join(path, 'shell.js') ] michael@0: michael@0: def get_command(self, js_cmd_prefix): michael@0: dirname, filename = os.path.split(self.path) michael@0: cmd = js_cmd_prefix + self.options + Test.prefix_command(dirname) + [ '-f', self.path ] michael@0: return cmd michael@0: michael@0: def run(self, js_cmd_prefix, timeout=30.0): michael@0: cmd = self.get_command(js_cmd_prefix) michael@0: out, err, rc, dt, timed_out = run_cmd(cmd, timeout) michael@0: return TestOutput(self, cmd, out, err, rc, dt, timed_out) michael@0: michael@0: class TestCase(Test): michael@0: """A test case consisting of a test and an expected result.""" michael@0: js_cmd_prefix = None michael@0: michael@0: def __init__(self, path): michael@0: Test.__init__(self, path) michael@0: self.enable = True # bool: True => run test, False => don't run michael@0: self.expect = True # bool: expected result, True => pass michael@0: self.random = False # bool: True => ignore output as 'random' michael@0: self.slow = False # bool: True => test may run slowly michael@0: self.options = [] # [str]: Extra options to pass to the shell michael@0: michael@0: # The terms parsed to produce the above properties. michael@0: self.terms = None michael@0: michael@0: # The tag between |...| in the test header. michael@0: self.tag = None michael@0: michael@0: # Anything occuring after -- in the test header. michael@0: self.comment = None michael@0: michael@0: def __str__(self): michael@0: ans = self.path michael@0: if not self.enable: michael@0: ans += ', skip' michael@0: if not self.expect: michael@0: ans += ', fails' michael@0: if self.random: michael@0: ans += ', random' michael@0: if self.slow: michael@0: ans += ', slow' michael@0: if '-d' in self.options: michael@0: ans += ', debugMode' michael@0: return ans michael@0: michael@0: @classmethod michael@0: def set_js_cmd_prefix(self, js_path, js_args, debugger_prefix): michael@0: parts = [] michael@0: if debugger_prefix: michael@0: parts += debugger_prefix michael@0: parts.append(js_path) michael@0: if js_args: michael@0: parts += js_args michael@0: self.js_cmd_prefix = parts michael@0: michael@0: def __cmp__(self, other): michael@0: if self.path == other.path: michael@0: return 0 michael@0: elif self.path < other.path: michael@0: return -1 michael@0: return 1 michael@0: michael@0: def __hash__(self): michael@0: return self.path.__hash__()