diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /js/src/tests/lib/tasks_adb_remote.py | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/tests/lib/tasks_adb_remote.py')
-rw-r--r-- | js/src/tests/lib/tasks_adb_remote.py | 284 |
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 |