js/src/tests/lib/jittests.py

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rwxr-xr-x

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.')

mercurial