Imported Upstream version 1.0.0
[platform/upstream/js.git] / js / src / tests / tests.py
1 # Library for JSTest tests.
2 #
3 # This contains classes that represent an individual test, including
4 # metadata, and know how to run the tests and determine failures.
5
6 import datetime, os, re, sys, time
7 from subprocess import *
8 from threading import *
9
10 def do_run_cmd(cmd):
11     l = [ None, None ]
12     th_run_cmd(cmd, l)
13     return l[1]
14
15 def set_limits():
16     # resource module not supported on all platforms
17     try:
18         import resource
19         GB = 2**30
20         resource.setrlimit(resource.RLIMIT_AS, (1*GB, 1*GB))
21     except:
22         return
23
24 def th_run_cmd(cmd, l):
25     t0 = datetime.datetime.now()
26
27     # close_fds and preexec_fn are not supported on Windows and will
28     # cause a ValueError.
29     options = {}
30     if sys.platform != 'win32':
31         options["close_fds"] = True
32         options["preexec_fn"] = set_limits
33     p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, **options)
34
35     l[0] = p
36     out, err = p.communicate()
37     t1 = datetime.datetime.now()
38     dd = t1-t0
39     dt = dd.seconds + 1e-6 * dd.microseconds
40     l[1] = (out, err, p.returncode, dt)
41
42 def run_cmd(cmd, timeout=60.0):
43     if timeout is None:
44         return do_run_cmd(cmd)
45
46     l = [ None, None ]
47     th = Thread(target=th_run_cmd, args=(cmd, l))
48     th.start()
49     th.join(timeout)
50     while th.isAlive():
51         if l[0] is not None:
52             try:
53                 # In Python 3, we could just do l[0].kill().
54                 import signal
55                 if sys.platform != 'win32':
56                     os.kill(l[0].pid, signal.SIGKILL)
57                 time.sleep(.1)
58             except OSError:
59                 # Expecting a "No such process" error
60                 pass
61     th.join()
62     return l[1]
63
64 class Test(object):
65     """A runnable test."""
66     def __init__(self, path):
67         self.path = path         # str:  path of JS file relative to tests root dir
68
69     @staticmethod
70     def prefix_command(path):
71         """Return the '-f shell.js' options needed to run a test with the given path."""
72         if path == '':
73             return [ '-f', 'shell.js' ]
74         head, base = os.path.split(path)
75         return Test.prefix_command(head) + [ '-f', os.path.join(path, 'shell.js') ]
76
77     def get_command(self, js_cmd_prefix):
78         dir, filename = os.path.split(self.path)
79         # There is a test that requires the path to start with './'.
80         return js_cmd_prefix + Test.prefix_command(dir) + [ '-f', './' + self.path ]
81
82     def run(self, js_cmd_prefix, timeout=30.0):
83         cmd = self.get_command(js_cmd_prefix)
84         out, err, rc, dt = run_cmd(cmd, timeout)
85         return TestOutput(self, cmd, out, err, rc, dt);
86
87 class TestCase(Test):
88     """A test case consisting of a test and an expected result."""
89
90     def __init__(self, path, enable, expect, random, slow):
91         Test.__init__(self, path)
92         self.enable = enable     # bool: True => run test, False => don't run
93         self.expect = expect     # bool: expected result, True => pass
94         self.random = random     # bool: True => ignore output as 'random'
95         self.slow = slow         # bool: True => test may run slowly
96
97     def __str__(self):
98         ans = self.path
99         if not self.enable:
100             ans += ', skip'
101         if not self.expect:
102             ans += ', fails'
103         if self.random:
104             ans += ', random'
105         if self.slow:
106             ans += ', slow'
107         return ans
108
109 class TestOutput:
110     """Output from a test run."""
111     def __init__(self, test, cmd, out, err, rc, dt):
112         self.test = test   # Test
113         self.cmd = cmd     # str:   command line of test
114         self.out = out     # str:   stdout
115         self.err = err     # str:   stderr
116         self.rc = rc       # int:   return code
117         self.dt = dt       # float: run time
118
119 class NullTestOutput:
120     """Variant of TestOutput that indicates a test was not run."""
121     def __init__(self, test):
122         self.test = test
123         self.cmd = ''
124         self.out = ''
125         self.err = ''
126         self.rc = 0
127         self.dt = 0.0
128
129 class TestResult:
130     PASS = 'PASS'
131     FAIL = 'FAIL'
132     CRASH = 'CRASH'
133
134     """Classified result from a test run."""
135     def __init__(self, test, result, results):
136         self.test = test
137         self.result = result
138         self.results = results
139
140     @classmethod
141     def from_output(cls, output):
142         test = output.test
143         result = None          # str:      overall result, see class-level variables
144         results = []           # (str,str) list: subtest results (pass/fail, message)
145
146         out, rc = output.out, output.rc
147
148         failures = 0
149         passes = 0
150
151         expected_rcs = []
152         if test.path.endswith('-n.js'):
153             expected_rcs.append(3)
154
155         for line in out.split('\n'):
156             if line.startswith(' FAILED!'):
157                 failures += 1
158                 msg = line[len(' FAILED! '):]
159                 results.append((cls.FAIL, msg))
160             elif line.startswith(' PASSED!'):
161                 passes += 1
162                 msg = line[len(' PASSED! '):]
163                 results.append((cls.PASS, msg))
164             else:
165                 m = re.match('--- NOTE: IN THIS TESTCASE, WE EXPECT EXIT CODE ((?:-|\\d)+) ---', line)
166                 if m:
167                     expected_rcs.append(int(m.group(1)))
168
169         if rc and not rc in expected_rcs:
170             if rc == 3:
171                 result = cls.FAIL
172             else:
173                 result = cls.CRASH
174         else:
175             if (rc or passes > 0) and failures == 0:
176                 result = cls.PASS
177             else:
178                 result = cls.FAIL
179
180         return cls(test, result, results)