summaryrefslogtreecommitdiffstats
path: root/js/src/tests/lib/tasks_adb_remote.py
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/tests/lib/tasks_adb_remote.py')
-rw-r--r--js/src/tests/lib/tasks_adb_remote.py284
1 files changed, 284 insertions, 0 deletions
diff --git a/js/src/tests/lib/tasks_adb_remote.py b/js/src/tests/lib/tasks_adb_remote.py
new file mode 100644
index 0000000000..2d2739a281
--- /dev/null
+++ b/js/src/tests/lib/tasks_adb_remote.py
@@ -0,0 +1,284 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+import os
+import posixpath
+import sys
+import tempfile
+from datetime import timedelta
+
+from mozdevice import ADBDevice, ADBError, ADBProcessError, ADBTimeoutError
+
+from .adaptor import xdr_annotate
+from .remote import init_device
+from .results import TestOutput, escape_cmdline
+
+TESTS_LIB_DIR = os.path.dirname(os.path.abspath(__file__))
+JS_DIR = os.path.dirname(os.path.dirname(TESTS_LIB_DIR))
+JS_TESTS_DIR = posixpath.join(JS_DIR, "tests")
+TEST_DIR = os.path.join(JS_DIR, "jit-test", "tests")
+
+
+def aggregate_script_stdout(stdout_lines, prefix, tempdir, uniq_tag, tests, options):
+ test = None
+ tStart = None
+ cmd = ""
+ stdout = ""
+
+ # Use to debug this script in case of assertion failure.
+ meta_history = []
+ last_line = ""
+
+ # Assert that the streamed content is not interrupted.
+ ended = False
+
+ # Check if the tag is present, if so, this is controlled output
+ # produced by the test runner, otherwise this is stdout content.
+ try:
+ for line in stdout_lines:
+ last_line = line
+ if line.startswith(uniq_tag):
+ meta = line[len(uniq_tag) :].strip()
+ meta_history.append(meta)
+ if meta.startswith("START="):
+ assert test is None
+ params = meta[len("START=") :].split(",")
+ test_idx = int(params[0])
+ test = tests[test_idx]
+ tStart = timedelta(seconds=float(params[1]))
+ cmd = test.command(
+ prefix,
+ posixpath.join(options.remote_test_root, "lib/"),
+ posixpath.join(options.remote_test_root, "modules/"),
+ tempdir,
+ posixpath.join(options.remote_test_root, "tests"),
+ )
+ stdout = ""
+ if options.show_cmd:
+ print(escape_cmdline(cmd))
+ elif meta.startswith("STOP="):
+ assert test is not None
+ params = meta[len("STOP=") :].split(",")
+ exitcode = int(params[0])
+ dt = timedelta(seconds=float(params[1])) - tStart
+ yield TestOutput(
+ test,
+ cmd,
+ stdout,
+ # NOTE: mozdevice fuse stdout and stderr. Thus, we are
+ # using stdout for both stdout and stderr. So far,
+ # doing so did not cause any issues.
+ stdout,
+ exitcode,
+ dt.total_seconds(),
+ dt > timedelta(seconds=int(options.timeout)),
+ )
+ stdout = ""
+ cmd = ""
+ test = None
+ elif meta.startswith("RETRY="):
+ # On timeout, we discard the first timeout to avoid a
+ # random hang on pthread_join.
+ assert test is not None
+ stdout = ""
+ cmd = ""
+ test = None
+ else:
+ assert meta.startswith("THE_END")
+ ended = True
+ else:
+ assert uniq_tag not in line
+ stdout += line
+
+ # This assertion fails if the streamed content is interrupted, either
+ # by unplugging the phone or some adb failures.
+ assert ended
+ except AssertionError as e:
+ sys.stderr.write("Metadata history:\n{}\n".format("\n".join(meta_history)))
+ sys.stderr.write("Last line: {}\n".format(last_line))
+ raise e
+
+
+def setup_device(prefix, options):
+ try:
+ device = init_device(options)
+
+ def replace_lib_file(path, name):
+ localfile = os.path.join(JS_TESTS_DIR, *path)
+ remotefile = posixpath.join(options.remote_test_root, "lib", name)
+ device.push(localfile, remotefile, timeout=10)
+
+ prefix[0] = posixpath.join(options.remote_test_root, "bin", "js")
+ tempdir = posixpath.join(options.remote_test_root, "tmp")
+
+ print("tasks_adb_remote.py : Transfering test files")
+
+ # Push tests & lib directories.
+ device.push(os.path.dirname(TEST_DIR), options.remote_test_root, timeout=600)
+
+ # Substitute lib files which are aliasing non262 files.
+ replace_lib_file(["non262", "shell.js"], "non262.js")
+ replace_lib_file(["non262", "reflect-parse", "Match.js"], "match.js")
+ replace_lib_file(["non262", "Math", "shell.js"], "math.js")
+ device.chmod(options.remote_test_root, recursive=True)
+
+ print("tasks_adb_remote.py : Device initialization completed")
+ return device, tempdir
+ except (ADBError, ADBTimeoutError):
+ print(
+ "TEST-UNEXPECTED-FAIL | tasks_adb_remote.py : "
+ + "Device initialization failed"
+ )
+ raise
+
+
+def script_preamble(tag, prefix, options):
+ timeout = int(options.timeout)
+ retry = int(options.timeout_retry)
+ lib_path = os.path.dirname(prefix[0])
+ return """
+export LD_LIBRARY_PATH={lib_path}
+
+do_test()
+{{
+ local idx=$1; shift;
+ local attempt=$1; shift;
+
+ # Read 10ms timestamp in seconds using shell builtins and /proc/uptime.
+ local time;
+ local unused;
+
+ # When printing the tag, we prefix by a new line, in case the
+ # previous command output did not contain any new line.
+ read time unused < /proc/uptime
+ echo '\\n{tag}START='$idx,$time
+ timeout {timeout}s "$@"
+ local rc=$?
+ read time unused < /proc/uptime
+
+ # Retry on timeout, to mute unlikely pthread_join hang issue.
+ #
+ # The timeout command send a SIGTERM signal, which should return 143
+ # (=128+15). However, due to a bug in tinybox, it returns 142.
+ if test \( $rc -eq 143 -o $rc -eq 142 \) -a $attempt -lt {retry}; then
+ echo '\\n{tag}RETRY='$rc,$time
+ attempt=$((attempt + 1))
+ do_test $idx $attempt "$@"
+ else
+ echo '\\n{tag}STOP='$rc,$time
+ fi
+}}
+
+do_end()
+{{
+ echo '\\n{tag}THE_END'
+}}
+""".format(
+ tag=tag, lib_path=lib_path, timeout=timeout, retry=retry
+ )
+
+
+def setup_script(device, prefix, tempdir, options, uniq_tag, tests):
+ timeout = int(options.timeout)
+ script_timeout = 0
+ try:
+ tmpf = tempfile.NamedTemporaryFile(mode="w", delete=False)
+ tmpf.write(script_preamble(uniq_tag, prefix, options))
+ for i, test in enumerate(tests):
+ # This test is common to all tasks_*.py files, however, jit-test do
+ # not provide the `run_skipped` option, and all tests are always
+ # enabled.
+ assert test.enable # and not options.run_skipped
+ if options.test_reflect_stringify:
+ raise ValueError("can't run Reflect.stringify tests remotely")
+
+ cmd = test.command(
+ prefix,
+ posixpath.join(options.remote_test_root, "lib/"),
+ posixpath.join(options.remote_test_root, "modules/"),
+ tempdir,
+ posixpath.join(options.remote_test_root, "tests"),
+ )
+
+ # replace with shlex.join when move to Python 3.8+
+ cmd = ADBDevice._escape_command_line(cmd)
+
+ env = {}
+ if test.tz_pacific:
+ env["TZ"] = "PST8PDT"
+ envStr = "".join(key + "='" + val + "' " for key, val in env.items())
+
+ tmpf.write("{}do_test {} 0 {};\n".format(envStr, i, cmd))
+ script_timeout += timeout
+ tmpf.write("do_end;\n")
+ tmpf.close()
+ script = posixpath.join(options.remote_test_root, "test_manifest.sh")
+ device.push(tmpf.name, script)
+ device.chmod(script)
+ print("tasks_adb_remote.py : Batch script created")
+ except Exception as e:
+ print("tasks_adb_remote.py : Batch script failed")
+ raise e
+ finally:
+ if tmpf:
+ os.unlink(tmpf.name)
+ return script, script_timeout
+
+
+def start_script(
+ device, prefix, tempdir, script, uniq_tag, script_timeout, tests, options
+):
+ env = {}
+
+ # Allow ADBError or ADBTimeoutError to terminate the test run, but handle
+ # ADBProcessError in order to support the use of non-zero exit codes in the
+ # JavaScript shell tests.
+ #
+ # The stdout_callback will aggregate each output line, and reconstruct the
+ # output produced by each test, and queue TestOutput in the qResult queue.
+ try:
+ adb_process = device.shell(
+ "sh {}".format(script),
+ env=env,
+ cwd=options.remote_test_root,
+ timeout=script_timeout,
+ yield_stdout=True,
+ )
+ for test_output in aggregate_script_stdout(
+ adb_process, prefix, tempdir, uniq_tag, tests, options
+ ):
+ yield test_output
+ except ADBProcessError as e:
+ # After a device error, the device is typically in a
+ # state where all further tests will fail so there is no point in
+ # continuing here.
+ sys.stderr.write("Error running remote tests: {}".format(repr(e)))
+
+
+def get_remote_results(tests, prefix, pb, options):
+ """Create a script which batches the run of all tests, and spawn a thread to
+ reconstruct the TestOutput for each test. This is made to avoid multiple
+ `adb.shell` commands which has a high latency.
+ """
+ device, tempdir = setup_device(prefix, options)
+
+ # Tests are sequentially executed in a batch. The first test executed is in
+ # charge of creating the xdr file for the self-hosted code.
+ if options.use_xdr:
+ tests = xdr_annotate(tests, options)
+
+ # We need tests to be subscriptable to find the test structure matching the
+ # index within the generated script.
+ tests = list(tests)
+
+ # Create a script which spawn each test one after the other, and upload the
+ # script
+ uniq_tag = "@@@TASKS_ADB_REMOTE@@@"
+ script, script_timeout = setup_script(
+ device, prefix, tempdir, options, uniq_tag, tests
+ )
+
+ for test_output in start_script(
+ device, prefix, tempdir, script, uniq_tag, script_timeout, tests, options
+ ):
+ yield test_output