Source code

Revision control

Copy as Markdown

Other Tools

# 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