Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
michael@0 | 1 | #!/usr/bin/env python |
michael@0 | 2 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 5 | |
michael@0 | 6 | |
michael@0 | 7 | # jit_test.py -- Python harness for JavaScript trace tests. |
michael@0 | 8 | |
michael@0 | 9 | from __future__ import print_function |
michael@0 | 10 | import os, posixpath, sys, tempfile, traceback, time |
michael@0 | 11 | import subprocess |
michael@0 | 12 | from subprocess import Popen, PIPE |
michael@0 | 13 | from threading import Thread |
michael@0 | 14 | import signal |
michael@0 | 15 | import StringIO |
michael@0 | 16 | |
michael@0 | 17 | try: |
michael@0 | 18 | from multiprocessing import Process, Manager, cpu_count |
michael@0 | 19 | HAVE_MULTIPROCESSING = True |
michael@0 | 20 | except ImportError: |
michael@0 | 21 | HAVE_MULTIPROCESSING = False |
michael@0 | 22 | |
michael@0 | 23 | from progressbar import ProgressBar, NullProgressBar |
michael@0 | 24 | from results import TestOutput |
michael@0 | 25 | |
michael@0 | 26 | TESTS_LIB_DIR = os.path.dirname(os.path.abspath(__file__)) |
michael@0 | 27 | JS_DIR = os.path.dirname(os.path.dirname(TESTS_LIB_DIR)) |
michael@0 | 28 | TOP_SRC_DIR = os.path.dirname(os.path.dirname(JS_DIR)) |
michael@0 | 29 | TEST_DIR = os.path.join(JS_DIR, 'jit-test', 'tests') |
michael@0 | 30 | LIB_DIR = os.path.join(JS_DIR, 'jit-test', 'lib') + os.path.sep |
michael@0 | 31 | JS_CACHE_DIR = os.path.join(JS_DIR, 'jit-test', '.js-cache') |
michael@0 | 32 | JS_TESTS_DIR = posixpath.join(JS_DIR, 'tests') |
michael@0 | 33 | |
michael@0 | 34 | # Backported from Python 3.1 posixpath.py |
michael@0 | 35 | def _relpath(path, start=None): |
michael@0 | 36 | """Return a relative version of a path""" |
michael@0 | 37 | |
michael@0 | 38 | if not path: |
michael@0 | 39 | raise ValueError("no path specified") |
michael@0 | 40 | |
michael@0 | 41 | if start is None: |
michael@0 | 42 | start = os.curdir |
michael@0 | 43 | |
michael@0 | 44 | start_list = os.path.abspath(start).split(os.sep) |
michael@0 | 45 | path_list = os.path.abspath(path).split(os.sep) |
michael@0 | 46 | |
michael@0 | 47 | # Work out how much of the filepath is shared by start and path. |
michael@0 | 48 | i = len(os.path.commonprefix([start_list, path_list])) |
michael@0 | 49 | |
michael@0 | 50 | rel_list = [os.pardir] * (len(start_list)-i) + path_list[i:] |
michael@0 | 51 | if not rel_list: |
michael@0 | 52 | return os.curdir |
michael@0 | 53 | return os.path.join(*rel_list) |
michael@0 | 54 | |
michael@0 | 55 | os.path.relpath = _relpath |
michael@0 | 56 | |
michael@0 | 57 | class Test: |
michael@0 | 58 | |
michael@0 | 59 | VALGRIND_CMD = [] |
michael@0 | 60 | paths = (d for d in os.environ['PATH'].split(os.pathsep)) |
michael@0 | 61 | valgrinds = (os.path.join(d, 'valgrind') for d in paths) |
michael@0 | 62 | if any(os.path.exists(p) for p in valgrinds): |
michael@0 | 63 | VALGRIND_CMD = [ |
michael@0 | 64 | 'valgrind', '-q', '--smc-check=all-non-file', |
michael@0 | 65 | '--error-exitcode=1', '--gen-suppressions=all', |
michael@0 | 66 | '--show-possibly-lost=no', '--leak-check=full', |
michael@0 | 67 | ] |
michael@0 | 68 | if os.uname()[0] == 'Darwin': |
michael@0 | 69 | VALGRIND_CMD.append('--dsymutil=yes') |
michael@0 | 70 | |
michael@0 | 71 | del paths |
michael@0 | 72 | del valgrinds |
michael@0 | 73 | |
michael@0 | 74 | def __init__(self, path): |
michael@0 | 75 | # Absolute path of the test file. |
michael@0 | 76 | self.path = path |
michael@0 | 77 | |
michael@0 | 78 | # Path relative to the top mozilla/ directory. |
michael@0 | 79 | self.relpath_top = os.path.relpath(path, TOP_SRC_DIR) |
michael@0 | 80 | |
michael@0 | 81 | # Path relative to mozilla/js/src/jit-test/tests/. |
michael@0 | 82 | self.relpath_tests = os.path.relpath(path, TEST_DIR) |
michael@0 | 83 | |
michael@0 | 84 | self.jitflags = [] # jit flags to enable |
michael@0 | 85 | self.slow = False # True means the test is slow-running |
michael@0 | 86 | self.allow_oom = False # True means that OOM is not considered a failure |
michael@0 | 87 | self.valgrind = False # True means run under valgrind |
michael@0 | 88 | self.tz_pacific = False # True means force Pacific time for the test |
michael@0 | 89 | self.expect_error = '' # Errors to expect and consider passing |
michael@0 | 90 | self.expect_status = 0 # Exit status to expect from shell |
michael@0 | 91 | |
michael@0 | 92 | def copy(self): |
michael@0 | 93 | t = Test(self.path) |
michael@0 | 94 | t.jitflags = self.jitflags[:] |
michael@0 | 95 | t.slow = self.slow |
michael@0 | 96 | t.allow_oom = self.allow_oom |
michael@0 | 97 | t.valgrind = self.valgrind |
michael@0 | 98 | t.tz_pacific = self.tz_pacific |
michael@0 | 99 | t.expect_error = self.expect_error |
michael@0 | 100 | t.expect_status = self.expect_status |
michael@0 | 101 | return t |
michael@0 | 102 | |
michael@0 | 103 | COOKIE = '|jit-test|' |
michael@0 | 104 | CacheDir = JS_CACHE_DIR |
michael@0 | 105 | |
michael@0 | 106 | @classmethod |
michael@0 | 107 | def from_file(cls, path, options): |
michael@0 | 108 | test = cls(path) |
michael@0 | 109 | |
michael@0 | 110 | line = open(path).readline() |
michael@0 | 111 | i = line.find(cls.COOKIE) |
michael@0 | 112 | if i != -1: |
michael@0 | 113 | meta = line[i + len(cls.COOKIE):].strip('\n') |
michael@0 | 114 | parts = meta.split(';') |
michael@0 | 115 | for part in parts: |
michael@0 | 116 | part = part.strip() |
michael@0 | 117 | if not part: |
michael@0 | 118 | continue |
michael@0 | 119 | name, _, value = part.partition(':') |
michael@0 | 120 | if value: |
michael@0 | 121 | value = value.strip() |
michael@0 | 122 | if name == 'error': |
michael@0 | 123 | test.expect_error = value |
michael@0 | 124 | elif name == 'exitstatus': |
michael@0 | 125 | try: |
michael@0 | 126 | test.expect_status = int(value, 0); |
michael@0 | 127 | except ValueError: |
michael@0 | 128 | print("warning: couldn't parse exit status %s" % value) |
michael@0 | 129 | elif name == 'thread-count': |
michael@0 | 130 | try: |
michael@0 | 131 | test.jitflags.append('--thread-count=' + int(value, 0)); |
michael@0 | 132 | except ValueError: |
michael@0 | 133 | print("warning: couldn't parse thread-count %s" % value) |
michael@0 | 134 | else: |
michael@0 | 135 | print('warning: unrecognized |jit-test| attribute %s' % part) |
michael@0 | 136 | else: |
michael@0 | 137 | if name == 'slow': |
michael@0 | 138 | test.slow = True |
michael@0 | 139 | elif name == 'allow-oom': |
michael@0 | 140 | test.allow_oom = True |
michael@0 | 141 | elif name == 'valgrind': |
michael@0 | 142 | test.valgrind = options.valgrind |
michael@0 | 143 | elif name == 'tz-pacific': |
michael@0 | 144 | test.tz_pacific = True |
michael@0 | 145 | elif name == 'debug': |
michael@0 | 146 | test.jitflags.append('--debugjit') |
michael@0 | 147 | elif name == 'ion-eager': |
michael@0 | 148 | test.jitflags.append('--ion-eager') |
michael@0 | 149 | elif name == 'no-ion': |
michael@0 | 150 | test.jitflags.append('--no-ion') |
michael@0 | 151 | elif name == 'dump-bytecode': |
michael@0 | 152 | test.jitflags.append('--dump-bytecode') |
michael@0 | 153 | else: |
michael@0 | 154 | print('warning: unrecognized |jit-test| attribute %s' % part) |
michael@0 | 155 | |
michael@0 | 156 | if options.valgrind_all: |
michael@0 | 157 | test.valgrind = True |
michael@0 | 158 | |
michael@0 | 159 | return test |
michael@0 | 160 | |
michael@0 | 161 | def command(self, prefix, libdir, remote_prefix=None): |
michael@0 | 162 | path = self.path |
michael@0 | 163 | if remote_prefix: |
michael@0 | 164 | path = self.path.replace(TEST_DIR, remote_prefix) |
michael@0 | 165 | |
michael@0 | 166 | scriptdir_var = os.path.dirname(path); |
michael@0 | 167 | if not scriptdir_var.endswith('/'): |
michael@0 | 168 | scriptdir_var += '/' |
michael@0 | 169 | |
michael@0 | 170 | # Platforms where subprocess immediately invokes exec do not care |
michael@0 | 171 | # whether we use double or single quotes. On windows and when using |
michael@0 | 172 | # a remote device, however, we have to be careful to use the quote |
michael@0 | 173 | # style that is the opposite of what the exec wrapper uses. |
michael@0 | 174 | # This uses %r to get single quotes on windows and special cases |
michael@0 | 175 | # the remote device. |
michael@0 | 176 | fmt = 'const platform=%r; const libdir=%r; const scriptdir=%r' |
michael@0 | 177 | if remote_prefix: |
michael@0 | 178 | fmt = 'const platform="%s"; const libdir="%s"; const scriptdir="%s"' |
michael@0 | 179 | expr = fmt % (sys.platform, libdir, scriptdir_var) |
michael@0 | 180 | |
michael@0 | 181 | # We may have specified '-a' or '-d' twice: once via --jitflags, once |
michael@0 | 182 | # via the "|jit-test|" line. Remove dups because they are toggles. |
michael@0 | 183 | cmd = prefix + ['--js-cache', Test.CacheDir] |
michael@0 | 184 | cmd += list(set(self.jitflags)) + ['-e', expr, '-f', path] |
michael@0 | 185 | if self.valgrind: |
michael@0 | 186 | cmd = self.VALGRIND_CMD + cmd |
michael@0 | 187 | return cmd |
michael@0 | 188 | |
michael@0 | 189 | def find_tests(substring=None): |
michael@0 | 190 | ans = [] |
michael@0 | 191 | for dirpath, dirnames, filenames in os.walk(TEST_DIR): |
michael@0 | 192 | dirnames.sort() |
michael@0 | 193 | filenames.sort() |
michael@0 | 194 | if dirpath == '.': |
michael@0 | 195 | continue |
michael@0 | 196 | for filename in filenames: |
michael@0 | 197 | if not filename.endswith('.js'): |
michael@0 | 198 | continue |
michael@0 | 199 | if filename in ('shell.js', 'browser.js', 'jsref.js'): |
michael@0 | 200 | continue |
michael@0 | 201 | test = os.path.join(dirpath, filename) |
michael@0 | 202 | if substring is None or substring in os.path.relpath(test, TEST_DIR): |
michael@0 | 203 | ans.append(test) |
michael@0 | 204 | return ans |
michael@0 | 205 | |
michael@0 | 206 | def tmppath(token): |
michael@0 | 207 | fd, path = tempfile.mkstemp(prefix=token) |
michael@0 | 208 | os.close(fd) |
michael@0 | 209 | return path |
michael@0 | 210 | |
michael@0 | 211 | def read_and_unlink(path): |
michael@0 | 212 | f = open(path) |
michael@0 | 213 | d = f.read() |
michael@0 | 214 | f.close() |
michael@0 | 215 | os.unlink(path) |
michael@0 | 216 | return d |
michael@0 | 217 | |
michael@0 | 218 | def th_run_cmd(cmdline, options, l): |
michael@0 | 219 | # close_fds is not supported on Windows and will cause a ValueError. |
michael@0 | 220 | if sys.platform != 'win32': |
michael@0 | 221 | options["close_fds"] = True |
michael@0 | 222 | p = Popen(cmdline, stdin=PIPE, stdout=PIPE, stderr=PIPE, **options) |
michael@0 | 223 | |
michael@0 | 224 | l[0] = p |
michael@0 | 225 | out, err = p.communicate() |
michael@0 | 226 | l[1] = (out, err, p.returncode) |
michael@0 | 227 | |
michael@0 | 228 | def run_timeout_cmd(cmdline, options, timeout=60.0): |
michael@0 | 229 | l = [ None, None ] |
michael@0 | 230 | timed_out = False |
michael@0 | 231 | th = Thread(target=th_run_cmd, args=(cmdline, options, l)) |
michael@0 | 232 | |
michael@0 | 233 | # If our SIGINT handler is set to SIG_IGN (ignore) |
michael@0 | 234 | # then we are running as a child process for parallel |
michael@0 | 235 | # execution and we must ensure to kill our child |
michael@0 | 236 | # when we are signaled to exit. |
michael@0 | 237 | sigint_handler = signal.getsignal(signal.SIGINT) |
michael@0 | 238 | sigterm_handler = signal.getsignal(signal.SIGTERM) |
michael@0 | 239 | if (sigint_handler == signal.SIG_IGN): |
michael@0 | 240 | def handleChildSignal(sig, frame): |
michael@0 | 241 | try: |
michael@0 | 242 | if sys.platform != 'win32': |
michael@0 | 243 | os.kill(l[0].pid, signal.SIGKILL) |
michael@0 | 244 | else: |
michael@0 | 245 | import ctypes |
michael@0 | 246 | ctypes.windll.kernel32.TerminateProcess(int(l[0]._handle), -1) |
michael@0 | 247 | except OSError: |
michael@0 | 248 | pass |
michael@0 | 249 | if (sig == signal.SIGTERM): |
michael@0 | 250 | sys.exit(0) |
michael@0 | 251 | signal.signal(signal.SIGINT, handleChildSignal) |
michael@0 | 252 | signal.signal(signal.SIGTERM, handleChildSignal) |
michael@0 | 253 | |
michael@0 | 254 | th.start() |
michael@0 | 255 | th.join(timeout) |
michael@0 | 256 | while th.isAlive(): |
michael@0 | 257 | if l[0] is not None: |
michael@0 | 258 | try: |
michael@0 | 259 | # In Python 3, we could just do l[0].kill(). |
michael@0 | 260 | if sys.platform != 'win32': |
michael@0 | 261 | os.kill(l[0].pid, signal.SIGKILL) |
michael@0 | 262 | else: |
michael@0 | 263 | import ctypes |
michael@0 | 264 | ctypes.windll.kernel32.TerminateProcess(int(l[0]._handle), -1) |
michael@0 | 265 | time.sleep(.1) |
michael@0 | 266 | timed_out = True |
michael@0 | 267 | except OSError: |
michael@0 | 268 | # Expecting a "No such process" error |
michael@0 | 269 | pass |
michael@0 | 270 | th.join() |
michael@0 | 271 | |
michael@0 | 272 | # Restore old signal handlers |
michael@0 | 273 | if (sigint_handler == signal.SIG_IGN): |
michael@0 | 274 | signal.signal(signal.SIGINT, signal.SIG_IGN) |
michael@0 | 275 | signal.signal(signal.SIGTERM, sigterm_handler) |
michael@0 | 276 | |
michael@0 | 277 | (out, err, code) = l[1] |
michael@0 | 278 | |
michael@0 | 279 | return (out, err, code, timed_out) |
michael@0 | 280 | |
michael@0 | 281 | def run_cmd(cmdline, env, timeout): |
michael@0 | 282 | return run_timeout_cmd(cmdline, { 'env': env }, timeout) |
michael@0 | 283 | |
michael@0 | 284 | def run_cmd_avoid_stdio(cmdline, env, timeout): |
michael@0 | 285 | stdoutPath, stderrPath = tmppath('jsstdout'), tmppath('jsstderr') |
michael@0 | 286 | env['JS_STDOUT'] = stdoutPath |
michael@0 | 287 | env['JS_STDERR'] = stderrPath |
michael@0 | 288 | _, __, code = run_timeout_cmd(cmdline, { 'env': env }, timeout) |
michael@0 | 289 | return read_and_unlink(stdoutPath), read_and_unlink(stderrPath), code |
michael@0 | 290 | |
michael@0 | 291 | def run_test(test, prefix, options): |
michael@0 | 292 | cmd = test.command(prefix, LIB_DIR) |
michael@0 | 293 | if options.show_cmd: |
michael@0 | 294 | print(subprocess.list2cmdline(cmd)) |
michael@0 | 295 | |
michael@0 | 296 | if options.avoid_stdio: |
michael@0 | 297 | run = run_cmd_avoid_stdio |
michael@0 | 298 | else: |
michael@0 | 299 | run = run_cmd |
michael@0 | 300 | |
michael@0 | 301 | env = os.environ.copy() |
michael@0 | 302 | if test.tz_pacific: |
michael@0 | 303 | env['TZ'] = 'PST8PDT' |
michael@0 | 304 | |
michael@0 | 305 | # Ensure interpreter directory is in shared library path. |
michael@0 | 306 | pathvar = '' |
michael@0 | 307 | if sys.platform.startswith('linux'): |
michael@0 | 308 | pathvar = 'LD_LIBRARY_PATH' |
michael@0 | 309 | elif sys.platform.startswith('darwin'): |
michael@0 | 310 | pathvar = 'DYLD_LIBRARY_PATH' |
michael@0 | 311 | elif sys.platform.startswith('win'): |
michael@0 | 312 | pathvar = 'PATH' |
michael@0 | 313 | if pathvar: |
michael@0 | 314 | bin_dir = os.path.dirname(cmd[0]) |
michael@0 | 315 | if pathvar in env: |
michael@0 | 316 | env[pathvar] = '%s%s%s' % (bin_dir, os.pathsep, env[pathvar]) |
michael@0 | 317 | else: |
michael@0 | 318 | env[pathvar] = bin_dir |
michael@0 | 319 | |
michael@0 | 320 | out, err, code, timed_out = run(cmd, env, options.timeout) |
michael@0 | 321 | return TestOutput(test, cmd, out, err, code, None, timed_out) |
michael@0 | 322 | |
michael@0 | 323 | def run_test_remote(test, device, prefix, options): |
michael@0 | 324 | cmd = test.command(prefix, posixpath.join(options.remote_test_root, 'lib/'), posixpath.join(options.remote_test_root, 'tests')) |
michael@0 | 325 | if options.show_cmd: |
michael@0 | 326 | print(subprocess.list2cmdline(cmd)) |
michael@0 | 327 | |
michael@0 | 328 | env = {} |
michael@0 | 329 | if test.tz_pacific: |
michael@0 | 330 | env['TZ'] = 'PST8PDT' |
michael@0 | 331 | |
michael@0 | 332 | env['LD_LIBRARY_PATH'] = options.remote_test_root |
michael@0 | 333 | |
michael@0 | 334 | buf = StringIO.StringIO() |
michael@0 | 335 | returncode = device.shell(cmd, buf, env=env, cwd=options.remote_test_root, |
michael@0 | 336 | timeout=int(options.timeout)) |
michael@0 | 337 | |
michael@0 | 338 | out = buf.getvalue() |
michael@0 | 339 | # We can't distinguish between stdout and stderr so we pass |
michael@0 | 340 | # the same buffer to both. |
michael@0 | 341 | return TestOutput(test, cmd, out, out, returncode, None, False) |
michael@0 | 342 | |
michael@0 | 343 | def check_output(out, err, rc, timed_out, test): |
michael@0 | 344 | if timed_out: |
michael@0 | 345 | # The shell sometimes hangs on shutdown on Windows 7 and Windows |
michael@0 | 346 | # Server 2008. See bug 970063 comment 7 for a description of the |
michael@0 | 347 | # problem. Until bug 956899 is fixed, ignore timeouts on these |
michael@0 | 348 | # platforms (versions 6.0 and 6.1). |
michael@0 | 349 | if sys.platform == 'win32': |
michael@0 | 350 | ver = sys.getwindowsversion() |
michael@0 | 351 | if ver.major == 6 and ver.minor <= 1: |
michael@0 | 352 | return True |
michael@0 | 353 | return False |
michael@0 | 354 | |
michael@0 | 355 | if test.expect_error: |
michael@0 | 356 | # The shell exits with code 3 on uncaught exceptions. |
michael@0 | 357 | # Sometimes 0 is returned on Windows for unknown reasons. |
michael@0 | 358 | # See bug 899697. |
michael@0 | 359 | if sys.platform in ['win32', 'cygwin']: |
michael@0 | 360 | if rc != 3 and rc != 0: |
michael@0 | 361 | return False |
michael@0 | 362 | else: |
michael@0 | 363 | if rc != 3: |
michael@0 | 364 | return False |
michael@0 | 365 | |
michael@0 | 366 | return test.expect_error in err |
michael@0 | 367 | |
michael@0 | 368 | for line in out.split('\n'): |
michael@0 | 369 | if line.startswith('Trace stats check failed'): |
michael@0 | 370 | return False |
michael@0 | 371 | |
michael@0 | 372 | for line in err.split('\n'): |
michael@0 | 373 | if 'Assertion failed:' in line: |
michael@0 | 374 | return False |
michael@0 | 375 | |
michael@0 | 376 | if rc != test.expect_status: |
michael@0 | 377 | # Tests which expect a timeout check for exit code 6. |
michael@0 | 378 | # Sometimes 0 is returned on Windows for unknown reasons. |
michael@0 | 379 | # See bug 899697. |
michael@0 | 380 | if sys.platform in ['win32', 'cygwin'] and rc == 0: |
michael@0 | 381 | return True |
michael@0 | 382 | |
michael@0 | 383 | # Allow a non-zero exit code if we want to allow OOM, but only if we |
michael@0 | 384 | # actually got OOM. |
michael@0 | 385 | return test.allow_oom and 'out of memory' in err and 'Assertion failure' not in err |
michael@0 | 386 | |
michael@0 | 387 | return True |
michael@0 | 388 | |
michael@0 | 389 | def print_tinderbox(ok, res): |
michael@0 | 390 | # Output test failures in a TBPL parsable format, eg: |
michael@0 | 391 | # TEST-RESULT | filename.js | Failure description (code N, args "--foobar") |
michael@0 | 392 | # |
michael@0 | 393 | # Example: |
michael@0 | 394 | # TEST-PASS | foo/bar/baz.js | (code 0, args "--ion-eager") |
michael@0 | 395 | # TEST-UNEXPECTED-FAIL | foo/bar/baz.js | TypeError: or something (code -9, args "--no-ion") |
michael@0 | 396 | # INFO exit-status : 3 |
michael@0 | 397 | # INFO timed-out : False |
michael@0 | 398 | # INFO stdout > foo |
michael@0 | 399 | # INFO stdout > bar |
michael@0 | 400 | # INFO stdout > baz |
michael@0 | 401 | # INFO stderr 2> TypeError: or something |
michael@0 | 402 | # TEST-UNEXPECTED-FAIL | jit_test.py: Test execution interrupted by user |
michael@0 | 403 | result = "TEST-PASS" if ok else "TEST-UNEXPECTED-FAIL" |
michael@0 | 404 | message = "Success" if ok else res.describe_failure() |
michael@0 | 405 | jitflags = " ".join(res.test.jitflags) |
michael@0 | 406 | print("{} | {} | {} (code {}, args \"{}\")".format( |
michael@0 | 407 | result, res.test.relpath_top, message, res.rc, jitflags)) |
michael@0 | 408 | |
michael@0 | 409 | # For failed tests, print as much information as we have, to aid debugging. |
michael@0 | 410 | if ok: |
michael@0 | 411 | return |
michael@0 | 412 | print("INFO exit-status : {}".format(res.rc)) |
michael@0 | 413 | print("INFO timed-out : {}".format(res.timed_out)) |
michael@0 | 414 | for line in res.out.splitlines(): |
michael@0 | 415 | print("INFO stdout > " + line.strip()) |
michael@0 | 416 | for line in res.err.splitlines(): |
michael@0 | 417 | print("INFO stderr 2> " + line.strip()) |
michael@0 | 418 | |
michael@0 | 419 | def wrap_parallel_run_test(test, prefix, resultQueue, options): |
michael@0 | 420 | # Ignore SIGINT in the child |
michael@0 | 421 | signal.signal(signal.SIGINT, signal.SIG_IGN) |
michael@0 | 422 | result = run_test(test, prefix, options) |
michael@0 | 423 | resultQueue.put(result) |
michael@0 | 424 | return result |
michael@0 | 425 | |
michael@0 | 426 | def run_tests_parallel(tests, prefix, options): |
michael@0 | 427 | # This queue will contain the results of the various tests run. |
michael@0 | 428 | # We could make this queue a global variable instead of using |
michael@0 | 429 | # a manager to share, but this will not work on Windows. |
michael@0 | 430 | queue_manager = Manager() |
michael@0 | 431 | async_test_result_queue = queue_manager.Queue() |
michael@0 | 432 | |
michael@0 | 433 | # This queue will be used by the result process to indicate |
michael@0 | 434 | # that it has received a result and we can start a new process |
michael@0 | 435 | # on our end. The advantage is that we don't have to sleep and |
michael@0 | 436 | # check for worker completion ourselves regularly. |
michael@0 | 437 | notify_queue = queue_manager.Queue() |
michael@0 | 438 | |
michael@0 | 439 | # This queue will contain the return value of the function |
michael@0 | 440 | # processing the test results. |
michael@0 | 441 | total_tests = len(tests) * options.repeat |
michael@0 | 442 | result_process_return_queue = queue_manager.Queue() |
michael@0 | 443 | result_process = Process(target=process_test_results_parallel, |
michael@0 | 444 | args=(async_test_result_queue, result_process_return_queue, |
michael@0 | 445 | notify_queue, total_tests, options)) |
michael@0 | 446 | result_process.start() |
michael@0 | 447 | |
michael@0 | 448 | # Ensure that a SIGTERM is handled the same way as SIGINT |
michael@0 | 449 | # to terminate all child processes. |
michael@0 | 450 | sigint_handler = signal.getsignal(signal.SIGINT) |
michael@0 | 451 | signal.signal(signal.SIGTERM, sigint_handler) |
michael@0 | 452 | |
michael@0 | 453 | worker_processes = [] |
michael@0 | 454 | |
michael@0 | 455 | def remove_completed_workers(workers): |
michael@0 | 456 | new_workers = [] |
michael@0 | 457 | for worker in workers: |
michael@0 | 458 | if worker.is_alive(): |
michael@0 | 459 | new_workers.append(worker) |
michael@0 | 460 | else: |
michael@0 | 461 | worker.join() |
michael@0 | 462 | return new_workers |
michael@0 | 463 | |
michael@0 | 464 | try: |
michael@0 | 465 | testcnt = 0 |
michael@0 | 466 | # Initially start as many jobs as allowed to run parallel |
michael@0 | 467 | for i in range(min(options.max_jobs,total_tests)): |
michael@0 | 468 | notify_queue.put(True) |
michael@0 | 469 | |
michael@0 | 470 | # For every item in the notify queue, start one new worker. |
michael@0 | 471 | # Every completed worker adds a new item to this queue. |
michael@0 | 472 | while notify_queue.get(): |
michael@0 | 473 | if (testcnt < total_tests): |
michael@0 | 474 | # Start one new worker |
michael@0 | 475 | test = tests[testcnt % len(tests)] |
michael@0 | 476 | worker_process = Process(target=wrap_parallel_run_test, args=(test, prefix, async_test_result_queue, options)) |
michael@0 | 477 | worker_processes.append(worker_process) |
michael@0 | 478 | worker_process.start() |
michael@0 | 479 | testcnt += 1 |
michael@0 | 480 | |
michael@0 | 481 | # Collect completed workers |
michael@0 | 482 | worker_processes = remove_completed_workers(worker_processes) |
michael@0 | 483 | else: |
michael@0 | 484 | break |
michael@0 | 485 | |
michael@0 | 486 | # Wait for all processes to terminate |
michael@0 | 487 | while len(worker_processes) > 0: |
michael@0 | 488 | worker_processes = remove_completed_workers(worker_processes) |
michael@0 | 489 | |
michael@0 | 490 | # Signal completion to result processor, then wait for it to complete on its own |
michael@0 | 491 | async_test_result_queue.put(None) |
michael@0 | 492 | result_process.join() |
michael@0 | 493 | |
michael@0 | 494 | # Return what the result process has returned to us |
michael@0 | 495 | return result_process_return_queue.get() |
michael@0 | 496 | except (Exception, KeyboardInterrupt) as e: |
michael@0 | 497 | # Print the exception if it's not an interrupt, |
michael@0 | 498 | # might point to a bug or other faulty condition |
michael@0 | 499 | if not isinstance(e,KeyboardInterrupt): |
michael@0 | 500 | traceback.print_exc() |
michael@0 | 501 | |
michael@0 | 502 | for worker in worker_processes: |
michael@0 | 503 | try: |
michael@0 | 504 | worker.terminate() |
michael@0 | 505 | except: |
michael@0 | 506 | pass |
michael@0 | 507 | |
michael@0 | 508 | result_process.terminate() |
michael@0 | 509 | |
michael@0 | 510 | return False |
michael@0 | 511 | |
michael@0 | 512 | def get_parallel_results(async_test_result_queue, notify_queue): |
michael@0 | 513 | while True: |
michael@0 | 514 | async_test_result = async_test_result_queue.get() |
michael@0 | 515 | |
michael@0 | 516 | # Check if we are supposed to terminate |
michael@0 | 517 | if (async_test_result == None): |
michael@0 | 518 | return |
michael@0 | 519 | |
michael@0 | 520 | # Notify parent that we got a result |
michael@0 | 521 | notify_queue.put(True) |
michael@0 | 522 | |
michael@0 | 523 | yield async_test_result |
michael@0 | 524 | |
michael@0 | 525 | def process_test_results_parallel(async_test_result_queue, return_queue, notify_queue, num_tests, options): |
michael@0 | 526 | gen = get_parallel_results(async_test_result_queue, notify_queue) |
michael@0 | 527 | ok = process_test_results(gen, num_tests, options) |
michael@0 | 528 | return_queue.put(ok) |
michael@0 | 529 | |
michael@0 | 530 | def print_test_summary(num_tests, failures, complete, doing, options): |
michael@0 | 531 | if failures: |
michael@0 | 532 | if options.write_failures: |
michael@0 | 533 | try: |
michael@0 | 534 | out = open(options.write_failures, 'w') |
michael@0 | 535 | # Don't write duplicate entries when we are doing multiple failures per job. |
michael@0 | 536 | written = set() |
michael@0 | 537 | for res in failures: |
michael@0 | 538 | if res.test.path not in written: |
michael@0 | 539 | out.write(os.path.relpath(res.test.path, TEST_DIR) + '\n') |
michael@0 | 540 | if options.write_failure_output: |
michael@0 | 541 | out.write(res.out) |
michael@0 | 542 | out.write(res.err) |
michael@0 | 543 | out.write('Exit code: ' + str(res.rc) + "\n") |
michael@0 | 544 | written.add(res.test.path) |
michael@0 | 545 | out.close() |
michael@0 | 546 | except IOError: |
michael@0 | 547 | sys.stderr.write("Exception thrown trying to write failure file '%s'\n"% |
michael@0 | 548 | options.write_failures) |
michael@0 | 549 | traceback.print_exc() |
michael@0 | 550 | sys.stderr.write('---\n') |
michael@0 | 551 | |
michael@0 | 552 | def show_test(res): |
michael@0 | 553 | if options.show_failed: |
michael@0 | 554 | print(' ' + subprocess.list2cmdline(res.cmd)) |
michael@0 | 555 | else: |
michael@0 | 556 | print(' ' + ' '.join(res.test.jitflags + [res.test.path])) |
michael@0 | 557 | |
michael@0 | 558 | print('FAILURES:') |
michael@0 | 559 | for res in failures: |
michael@0 | 560 | if not res.timed_out: |
michael@0 | 561 | show_test(res) |
michael@0 | 562 | |
michael@0 | 563 | print('TIMEOUTS:') |
michael@0 | 564 | for res in failures: |
michael@0 | 565 | if res.timed_out: |
michael@0 | 566 | show_test(res) |
michael@0 | 567 | else: |
michael@0 | 568 | print('PASSED ALL' + ('' if complete else ' (partial run -- interrupted by user %s)' % doing)) |
michael@0 | 569 | |
michael@0 | 570 | if options.tinderbox: |
michael@0 | 571 | num_failures = len(failures) if failures else 0 |
michael@0 | 572 | print('Result summary:') |
michael@0 | 573 | print('Passed: %d' % (num_tests - num_failures)) |
michael@0 | 574 | print('Failed: %d' % num_failures) |
michael@0 | 575 | |
michael@0 | 576 | return not failures |
michael@0 | 577 | |
michael@0 | 578 | def process_test_results(results, num_tests, options): |
michael@0 | 579 | pb = NullProgressBar() |
michael@0 | 580 | if not options.hide_progress and not options.show_cmd and ProgressBar.conservative_isatty(): |
michael@0 | 581 | fmt = [ |
michael@0 | 582 | {'value': 'PASS', 'color': 'green'}, |
michael@0 | 583 | {'value': 'FAIL', 'color': 'red'}, |
michael@0 | 584 | {'value': 'TIMEOUT', 'color': 'blue'}, |
michael@0 | 585 | {'value': 'SKIP', 'color': 'brightgray'}, |
michael@0 | 586 | ] |
michael@0 | 587 | pb = ProgressBar(num_tests, fmt) |
michael@0 | 588 | |
michael@0 | 589 | failures = [] |
michael@0 | 590 | timeouts = 0 |
michael@0 | 591 | complete = False |
michael@0 | 592 | doing = 'before starting' |
michael@0 | 593 | try: |
michael@0 | 594 | for i, res in enumerate(results): |
michael@0 | 595 | if options.show_output: |
michael@0 | 596 | sys.stdout.write(res.out) |
michael@0 | 597 | sys.stdout.write(res.err) |
michael@0 | 598 | sys.stdout.write('Exit code: %s\n' % res.rc) |
michael@0 | 599 | if res.test.valgrind: |
michael@0 | 600 | sys.stdout.write(res.err) |
michael@0 | 601 | |
michael@0 | 602 | ok = check_output(res.out, res.err, res.rc, res.timed_out, res.test) |
michael@0 | 603 | doing = 'after %s' % res.test.relpath_tests |
michael@0 | 604 | if not ok: |
michael@0 | 605 | failures.append(res) |
michael@0 | 606 | if res.timed_out: |
michael@0 | 607 | pb.message("TIMEOUT - %s" % res.test.relpath_tests) |
michael@0 | 608 | timeouts += 1 |
michael@0 | 609 | else: |
michael@0 | 610 | pb.message("FAIL - %s" % res.test.relpath_tests) |
michael@0 | 611 | |
michael@0 | 612 | if options.tinderbox: |
michael@0 | 613 | print_tinderbox(ok, res) |
michael@0 | 614 | |
michael@0 | 615 | n = i + 1 |
michael@0 | 616 | pb.update(n, { |
michael@0 | 617 | 'PASS': n - len(failures), |
michael@0 | 618 | 'FAIL': len(failures), |
michael@0 | 619 | 'TIMEOUT': timeouts, |
michael@0 | 620 | 'SKIP': 0} |
michael@0 | 621 | ) |
michael@0 | 622 | complete = True |
michael@0 | 623 | except KeyboardInterrupt: |
michael@0 | 624 | print("TEST-UNEXPECTED-FAIL | jit_test.py" + |
michael@0 | 625 | " : Test execution interrupted by user") |
michael@0 | 626 | |
michael@0 | 627 | pb.finish(True) |
michael@0 | 628 | return print_test_summary(num_tests, failures, complete, doing, options) |
michael@0 | 629 | |
michael@0 | 630 | def get_serial_results(tests, prefix, options): |
michael@0 | 631 | for i in xrange(0, options.repeat): |
michael@0 | 632 | for test in tests: |
michael@0 | 633 | yield run_test(test, prefix, options) |
michael@0 | 634 | |
michael@0 | 635 | def run_tests(tests, prefix, options): |
michael@0 | 636 | gen = get_serial_results(tests, prefix, options) |
michael@0 | 637 | ok = process_test_results(gen, len(tests) * options.repeat, options) |
michael@0 | 638 | return ok |
michael@0 | 639 | |
michael@0 | 640 | def get_remote_results(tests, device, prefix, options): |
michael@0 | 641 | for i in xrange(0, options.repeat): |
michael@0 | 642 | for test in tests: |
michael@0 | 643 | yield run_test_remote(test, device, prefix, options) |
michael@0 | 644 | |
michael@0 | 645 | def push_libs(options, device): |
michael@0 | 646 | # This saves considerable time in pushing unnecessary libraries |
michael@0 | 647 | # to the device but needs to be updated if the dependencies change. |
michael@0 | 648 | required_libs = ['libnss3.so', 'libmozglue.so'] |
michael@0 | 649 | |
michael@0 | 650 | for file in os.listdir(options.local_lib): |
michael@0 | 651 | if file in required_libs: |
michael@0 | 652 | remote_file = posixpath.join(options.remote_test_root, file) |
michael@0 | 653 | device.pushFile(os.path.join(options.local_lib, file), remote_file) |
michael@0 | 654 | |
michael@0 | 655 | def push_progs(options, device, progs): |
michael@0 | 656 | for local_file in progs: |
michael@0 | 657 | remote_file = posixpath.join(options.remote_test_root, os.path.basename(local_file)) |
michael@0 | 658 | device.pushFile(local_file, remote_file) |
michael@0 | 659 | |
michael@0 | 660 | def run_tests_remote(tests, prefix, options): |
michael@0 | 661 | # Setup device with everything needed to run our tests. |
michael@0 | 662 | from mozdevice import devicemanager, devicemanagerADB, devicemanagerSUT |
michael@0 | 663 | |
michael@0 | 664 | if options.device_transport == 'adb': |
michael@0 | 665 | if options.device_ip: |
michael@0 | 666 | dm = devicemanagerADB.DeviceManagerADB(options.device_ip, options.device_port, deviceSerial=options.device_serial, packageName=None, deviceRoot=options.remote_test_root) |
michael@0 | 667 | else: |
michael@0 | 668 | dm = devicemanagerADB.DeviceManagerADB(deviceSerial=options.device_serial, packageName=None, deviceRoot=options.remote_test_root) |
michael@0 | 669 | else: |
michael@0 | 670 | dm = devicemanagerSUT.DeviceManagerSUT(options.device_ip, options.device_port, deviceRoot=options.remote_test_root) |
michael@0 | 671 | if options.device_ip == None: |
michael@0 | 672 | print('Error: you must provide a device IP to connect to via the --device option') |
michael@0 | 673 | sys.exit(1) |
michael@0 | 674 | |
michael@0 | 675 | # Update the test root to point to our test directory. |
michael@0 | 676 | jit_tests_dir = posixpath.join(options.remote_test_root, 'jit-tests') |
michael@0 | 677 | options.remote_test_root = posixpath.join(jit_tests_dir, 'jit-tests') |
michael@0 | 678 | |
michael@0 | 679 | # Push js shell and libraries. |
michael@0 | 680 | if dm.dirExists(jit_tests_dir): |
michael@0 | 681 | dm.removeDir(jit_tests_dir) |
michael@0 | 682 | dm.mkDirs(options.remote_test_root) |
michael@0 | 683 | push_libs(options, dm) |
michael@0 | 684 | push_progs(options, dm, [prefix[0]]) |
michael@0 | 685 | dm.chmodDir(options.remote_test_root) |
michael@0 | 686 | |
michael@0 | 687 | Test.CacheDir = posixpath.join(options.remote_test_root, '.js-cache') |
michael@0 | 688 | dm.mkDir(Test.CacheDir) |
michael@0 | 689 | |
michael@0 | 690 | dm.pushDir(JS_TESTS_DIR, posixpath.join(jit_tests_dir, 'tests'), timeout=600) |
michael@0 | 691 | |
michael@0 | 692 | dm.pushDir(os.path.dirname(TEST_DIR), options.remote_test_root, timeout=600) |
michael@0 | 693 | prefix[0] = os.path.join(options.remote_test_root, 'js') |
michael@0 | 694 | |
michael@0 | 695 | # Run all tests. |
michael@0 | 696 | gen = get_remote_results(tests, dm, prefix, options) |
michael@0 | 697 | ok = process_test_results(gen, len(tests) * options.repeat, options) |
michael@0 | 698 | return ok |
michael@0 | 699 | |
michael@0 | 700 | def parse_jitflags(options): |
michael@0 | 701 | jitflags = [ [ '-' + flag for flag in flags ] |
michael@0 | 702 | for flags in options.jitflags.split(',') ] |
michael@0 | 703 | for flags in jitflags: |
michael@0 | 704 | for flag in flags: |
michael@0 | 705 | if flag not in ('-m', '-a', '-p', '-d', '-n'): |
michael@0 | 706 | print('Invalid jit flag: "%s"' % flag) |
michael@0 | 707 | sys.exit(1) |
michael@0 | 708 | return jitflags |
michael@0 | 709 | |
michael@0 | 710 | def platform_might_be_android(): |
michael@0 | 711 | try: |
michael@0 | 712 | # The python package for SL4A provides an |android| module. |
michael@0 | 713 | # If that module is present, we're likely in SL4A-python on |
michael@0 | 714 | # device. False positives and negatives are possible, |
michael@0 | 715 | # however. |
michael@0 | 716 | import android |
michael@0 | 717 | return True |
michael@0 | 718 | except ImportError: |
michael@0 | 719 | return False |
michael@0 | 720 | |
michael@0 | 721 | def stdio_might_be_broken(): |
michael@0 | 722 | return platform_might_be_android() |
michael@0 | 723 | |
michael@0 | 724 | if __name__ == '__main__': |
michael@0 | 725 | print('Use ../jit-test/jit_test.py to run these tests.') |