2018-08-06 12:37:39 +00:00
|
|
|
# Copyright 2018 the V8 project authors. All rights reserved.
|
|
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
|
|
# found in the LICENSE file.
|
|
|
|
|
|
|
|
"""
|
|
|
|
Wrapper around the Android device abstraction from src/build/android.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import sys
|
2018-11-16 06:59:19 +00:00
|
|
|
import time
|
2018-08-06 12:37:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
BASE_DIR = os.path.normpath(
|
|
|
|
os.path.join(os.path.dirname(__file__), '..', '..', '..'))
|
|
|
|
ANDROID_DIR = os.path.join(BASE_DIR, 'build', 'android')
|
|
|
|
DEVICE_DIR = '/data/local/tmp/v8/'
|
|
|
|
|
|
|
|
|
|
|
|
class TimeoutException(Exception):
|
2018-09-12 14:57:46 +00:00
|
|
|
def __init__(self, timeout, output=None):
|
2018-08-06 12:37:39 +00:00
|
|
|
self.timeout = timeout
|
2018-09-12 14:57:46 +00:00
|
|
|
self.output = output
|
2018-08-06 12:37:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
class CommandFailedException(Exception):
|
|
|
|
def __init__(self, status, output):
|
|
|
|
self.status = status
|
|
|
|
self.output = output
|
|
|
|
|
|
|
|
|
|
|
|
class _Driver(object):
|
|
|
|
"""Helper class to execute shell commands on an Android device."""
|
|
|
|
def __init__(self, device=None):
|
|
|
|
assert os.path.exists(ANDROID_DIR)
|
|
|
|
sys.path.insert(0, ANDROID_DIR)
|
|
|
|
|
|
|
|
# We import the dependencies only on demand, so that this file can be
|
|
|
|
# imported unconditionally.
|
|
|
|
import devil_chromium
|
|
|
|
from devil.android import device_errors # pylint: disable=import-error
|
|
|
|
from devil.android import device_utils # pylint: disable=import-error
|
|
|
|
from devil.android.perf import cache_control # pylint: disable=import-error
|
|
|
|
from devil.android.perf import perf_control # pylint: disable=import-error
|
|
|
|
from devil.android.sdk import adb_wrapper # pylint: disable=import-error
|
|
|
|
global cache_control
|
|
|
|
global device_errors
|
|
|
|
global perf_control
|
|
|
|
|
|
|
|
devil_chromium.Initialize()
|
|
|
|
|
|
|
|
if not device:
|
2018-11-16 06:59:19 +00:00
|
|
|
# Detect attached device if not specified. Retry 3 times with 60 second
|
|
|
|
# sleep in-between.
|
|
|
|
for _ in range(3):
|
|
|
|
devices = adb_wrapper.AdbWrapper.Devices()
|
|
|
|
if devices:
|
|
|
|
break
|
|
|
|
logging.warning('Failed to detect device. Will retry in 60 secs...')
|
|
|
|
time.sleep(60)
|
2018-08-06 12:37:39 +00:00
|
|
|
assert devices, 'No devices detected'
|
|
|
|
assert len(devices) == 1, 'Multiple devices detected.'
|
|
|
|
device = str(devices[0])
|
|
|
|
self.adb_wrapper = adb_wrapper.AdbWrapper(device)
|
|
|
|
self.device = device_utils.DeviceUtils(self.adb_wrapper)
|
|
|
|
|
|
|
|
# This remembers what we have already pushed to the device.
|
|
|
|
self.pushed = set()
|
|
|
|
|
|
|
|
def tear_down(self):
|
|
|
|
"""Clean up files after running all tests."""
|
|
|
|
self.device.RemovePath(DEVICE_DIR, force=True, recursive=True)
|
|
|
|
|
|
|
|
def push_file(self, host_dir, file_name, target_rel='.',
|
|
|
|
skip_if_missing=False):
|
|
|
|
"""Push a single file to the device (cached).
|
|
|
|
|
|
|
|
Args:
|
|
|
|
host_dir: Absolute parent directory of the file to push.
|
|
|
|
file_name: Name of the file to push.
|
|
|
|
target_rel: Parent directory of the target location on the device
|
|
|
|
(relative to the device's base dir for testing).
|
|
|
|
skip_if_missing: Keeps silent about missing files when set. Otherwise logs
|
|
|
|
error.
|
|
|
|
"""
|
|
|
|
file_on_host = os.path.join(host_dir, file_name)
|
|
|
|
|
|
|
|
# Only push files not yet pushed in one execution.
|
|
|
|
if file_on_host in self.pushed:
|
|
|
|
return
|
|
|
|
|
|
|
|
file_on_device_tmp = os.path.join(DEVICE_DIR, '_tmp_', file_name)
|
|
|
|
file_on_device = os.path.join(DEVICE_DIR, target_rel, file_name)
|
|
|
|
folder_on_device = os.path.dirname(file_on_device)
|
|
|
|
|
|
|
|
# Only attempt to push files that exist.
|
|
|
|
if not os.path.exists(file_on_host):
|
|
|
|
if not skip_if_missing:
|
|
|
|
logging.critical('Missing file on host: %s' % file_on_host)
|
|
|
|
return
|
|
|
|
|
|
|
|
# Work-around for 'text file busy' errors. Push the files to a temporary
|
|
|
|
# location and then copy them with a shell command.
|
|
|
|
output = self.adb_wrapper.Push(file_on_host, file_on_device_tmp)
|
|
|
|
# Success looks like this: '3035 KB/s (12512056 bytes in 4.025s)'.
|
|
|
|
# Errors look like this: 'failed to copy ... '.
|
|
|
|
if output and not re.search('^[0-9]', output.splitlines()[-1]):
|
|
|
|
logging.critical('PUSH FAILED: ' + output)
|
|
|
|
self.adb_wrapper.Shell('mkdir -p %s' % folder_on_device)
|
|
|
|
self.adb_wrapper.Shell('cp %s %s' % (file_on_device_tmp, file_on_device))
|
|
|
|
self.pushed.add(file_on_host)
|
|
|
|
|
|
|
|
def push_executable(self, shell_dir, target_dir, binary):
|
|
|
|
"""Push files required to run a V8 executable.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
shell_dir: Absolute parent directory of the executable on the host.
|
|
|
|
target_dir: Parent directory of the executable on the device (relative to
|
|
|
|
devices' base dir for testing).
|
|
|
|
binary: Name of the binary to push.
|
|
|
|
"""
|
|
|
|
self.push_file(shell_dir, binary, target_dir)
|
|
|
|
|
|
|
|
# Push external startup data. Backwards compatible for revisions where
|
|
|
|
# these files didn't exist. Or for bots that don't produce these files.
|
|
|
|
self.push_file(
|
|
|
|
shell_dir,
|
|
|
|
'natives_blob.bin',
|
|
|
|
target_dir,
|
|
|
|
skip_if_missing=True,
|
|
|
|
)
|
|
|
|
self.push_file(
|
|
|
|
shell_dir,
|
|
|
|
'snapshot_blob.bin',
|
|
|
|
target_dir,
|
|
|
|
skip_if_missing=True,
|
|
|
|
)
|
|
|
|
self.push_file(
|
|
|
|
shell_dir,
|
|
|
|
'snapshot_blob_trusted.bin',
|
|
|
|
target_dir,
|
|
|
|
skip_if_missing=True,
|
|
|
|
)
|
|
|
|
self.push_file(
|
|
|
|
shell_dir,
|
|
|
|
'icudtl.dat',
|
|
|
|
target_dir,
|
|
|
|
skip_if_missing=True,
|
|
|
|
)
|
|
|
|
|
2018-08-11 09:02:00 +00:00
|
|
|
def run(self, target_dir, binary, args, rel_path, timeout, env=None,
|
|
|
|
logcat_file=False):
|
2018-08-06 12:37:39 +00:00
|
|
|
"""Execute a command on the device's shell.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
target_dir: Parent directory of the executable on the device (relative to
|
|
|
|
devices' base dir for testing).
|
|
|
|
binary: Name of the binary.
|
|
|
|
args: List of arguments to pass to the binary.
|
|
|
|
rel_path: Relative path on device to use as CWD.
|
|
|
|
timeout: Timeout in seconds.
|
2018-08-10 18:58:07 +00:00
|
|
|
env: The environment variables with which the command should be run.
|
2018-08-11 09:02:00 +00:00
|
|
|
logcat_file: File into which to stream adb logcat log.
|
2018-08-06 12:37:39 +00:00
|
|
|
"""
|
|
|
|
binary_on_device = os.path.join(DEVICE_DIR, target_dir, binary)
|
|
|
|
cmd = [binary_on_device] + args
|
2018-08-11 09:02:00 +00:00
|
|
|
def run_inner():
|
|
|
|
try:
|
|
|
|
output = self.device.RunShellCommand(
|
|
|
|
cmd,
|
|
|
|
cwd=os.path.join(DEVICE_DIR, rel_path),
|
|
|
|
check_return=True,
|
|
|
|
env=env,
|
|
|
|
timeout=timeout,
|
|
|
|
retries=0,
|
|
|
|
)
|
|
|
|
return '\n'.join(output)
|
|
|
|
except device_errors.AdbCommandFailedError as e:
|
|
|
|
raise CommandFailedException(e.status, e.output)
|
2018-09-12 14:57:46 +00:00
|
|
|
except device_errors.CommandTimeoutError as e:
|
|
|
|
raise TimeoutException(timeout, e.output)
|
2018-08-11 09:02:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
if logcat_file:
|
|
|
|
with self.device.GetLogcatMonitor(output_file=logcat_file) as logmon:
|
|
|
|
result = run_inner()
|
|
|
|
logmon.Close()
|
|
|
|
return result
|
|
|
|
else:
|
|
|
|
return run_inner()
|
2018-08-06 12:37:39 +00:00
|
|
|
|
|
|
|
def drop_ram_caches(self):
|
|
|
|
"""Drop ran caches on device."""
|
|
|
|
cache = cache_control.CacheControl(self.device)
|
|
|
|
cache.DropRamCaches()
|
|
|
|
|
|
|
|
def set_high_perf_mode(self):
|
|
|
|
"""Set device into high performance mode."""
|
|
|
|
perf = perf_control.PerfControl(self.device)
|
|
|
|
perf.SetHighPerfMode()
|
|
|
|
|
|
|
|
def set_default_perf_mode(self):
|
|
|
|
"""Set device into default performance mode."""
|
|
|
|
perf = perf_control.PerfControl(self.device)
|
|
|
|
perf.SetDefaultPerfMode()
|
|
|
|
|
|
|
|
|
|
|
|
_ANDROID_DRIVER = None
|
|
|
|
def android_driver(device=None):
|
|
|
|
"""Singleton access method to the driver class."""
|
|
|
|
global _ANDROID_DRIVER
|
|
|
|
if not _ANDROID_DRIVER:
|
|
|
|
_ANDROID_DRIVER = _Driver(device)
|
|
|
|
return _ANDROID_DRIVER
|