# 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