1# Copyright 2018 the V8 project authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5""" 6Wrapper around the Android device abstraction from src/build/android. 7""" 8 9import logging 10import os 11import sys 12 13 14BASE_DIR = os.path.normpath( 15 os.path.join(os.path.dirname(__file__), '..', '..', '..')) 16ANDROID_DIR = os.path.join(BASE_DIR, 'build', 'android') 17DEVICE_DIR = '/data/local/tmp/v8/' 18 19 20class TimeoutException(Exception): 21 def __init__(self, timeout): 22 self.timeout = timeout 23 24 25class CommandFailedException(Exception): 26 def __init__(self, status, output): 27 self.status = status 28 self.output = output 29 30 31class _Driver(object): 32 """Helper class to execute shell commands on an Android device.""" 33 def __init__(self, device=None): 34 assert os.path.exists(ANDROID_DIR) 35 sys.path.insert(0, ANDROID_DIR) 36 37 # We import the dependencies only on demand, so that this file can be 38 # imported unconditionally. 39 import devil_chromium 40 from devil.android import device_errors # pylint: disable=import-error 41 from devil.android import device_utils # pylint: disable=import-error 42 from devil.android.perf import cache_control # pylint: disable=import-error 43 from devil.android.perf import perf_control # pylint: disable=import-error 44 from devil.android.sdk import adb_wrapper # pylint: disable=import-error 45 global cache_control 46 global device_errors 47 global perf_control 48 49 devil_chromium.Initialize() 50 51 if not device: 52 # Detect attached device if not specified. 53 devices = adb_wrapper.AdbWrapper.Devices() 54 assert devices, 'No devices detected' 55 assert len(devices) == 1, 'Multiple devices detected.' 56 device = str(devices[0]) 57 self.adb_wrapper = adb_wrapper.AdbWrapper(device) 58 self.device = device_utils.DeviceUtils(self.adb_wrapper) 59 60 # This remembers what we have already pushed to the device. 61 self.pushed = set() 62 63 def tear_down(self): 64 """Clean up files after running all tests.""" 65 self.device.RemovePath(DEVICE_DIR, force=True, recursive=True) 66 67 def push_file(self, host_dir, file_name, target_rel='.', 68 skip_if_missing=False): 69 """Push a single file to the device (cached). 70 71 Args: 72 host_dir: Absolute parent directory of the file to push. 73 file_name: Name of the file to push. 74 target_rel: Parent directory of the target location on the device 75 (relative to the device's base dir for testing). 76 skip_if_missing: Keeps silent about missing files when set. Otherwise logs 77 error. 78 """ 79 file_on_host = os.path.join(host_dir, file_name) 80 81 # Only push files not yet pushed in one execution. 82 if file_on_host in self.pushed: 83 return 84 85 file_on_device_tmp = os.path.join(DEVICE_DIR, '_tmp_', file_name) 86 file_on_device = os.path.join(DEVICE_DIR, target_rel, file_name) 87 folder_on_device = os.path.dirname(file_on_device) 88 89 # Only attempt to push files that exist. 90 if not os.path.exists(file_on_host): 91 if not skip_if_missing: 92 logging.critical('Missing file on host: %s' % file_on_host) 93 return 94 95 # Work-around for 'text file busy' errors. Push the files to a temporary 96 # location and then copy them with a shell command. 97 output = self.adb_wrapper.Push(file_on_host, file_on_device_tmp) 98 # Success looks like this: '3035 KB/s (12512056 bytes in 4.025s)'. 99 # Errors look like this: 'failed to copy ... '. 100 if output and not re.search('^[0-9]', output.splitlines()[-1]): 101 logging.critical('PUSH FAILED: ' + output) 102 self.adb_wrapper.Shell('mkdir -p %s' % folder_on_device) 103 self.adb_wrapper.Shell('cp %s %s' % (file_on_device_tmp, file_on_device)) 104 self.pushed.add(file_on_host) 105 106 def push_executable(self, shell_dir, target_dir, binary): 107 """Push files required to run a V8 executable. 108 109 Args: 110 shell_dir: Absolute parent directory of the executable on the host. 111 target_dir: Parent directory of the executable on the device (relative to 112 devices' base dir for testing). 113 binary: Name of the binary to push. 114 """ 115 self.push_file(shell_dir, binary, target_dir) 116 117 # Push external startup data. Backwards compatible for revisions where 118 # these files didn't exist. Or for bots that don't produce these files. 119 self.push_file( 120 shell_dir, 121 'natives_blob.bin', 122 target_dir, 123 skip_if_missing=True, 124 ) 125 self.push_file( 126 shell_dir, 127 'snapshot_blob.bin', 128 target_dir, 129 skip_if_missing=True, 130 ) 131 self.push_file( 132 shell_dir, 133 'snapshot_blob_trusted.bin', 134 target_dir, 135 skip_if_missing=True, 136 ) 137 self.push_file( 138 shell_dir, 139 'icudtl.dat', 140 target_dir, 141 skip_if_missing=True, 142 ) 143 144 def run(self, target_dir, binary, args, rel_path, timeout, env=None, 145 logcat_file=False): 146 """Execute a command on the device's shell. 147 148 Args: 149 target_dir: Parent directory of the executable on the device (relative to 150 devices' base dir for testing). 151 binary: Name of the binary. 152 args: List of arguments to pass to the binary. 153 rel_path: Relative path on device to use as CWD. 154 timeout: Timeout in seconds. 155 env: The environment variables with which the command should be run. 156 logcat_file: File into which to stream adb logcat log. 157 """ 158 binary_on_device = os.path.join(DEVICE_DIR, target_dir, binary) 159 cmd = [binary_on_device] + args 160 def run_inner(): 161 try: 162 output = self.device.RunShellCommand( 163 cmd, 164 cwd=os.path.join(DEVICE_DIR, rel_path), 165 check_return=True, 166 env=env, 167 timeout=timeout, 168 retries=0, 169 ) 170 return '\n'.join(output) 171 except device_errors.AdbCommandFailedError as e: 172 raise CommandFailedException(e.status, e.output) 173 except device_errors.CommandTimeoutError: 174 raise TimeoutException(timeout) 175 176 177 if logcat_file: 178 with self.device.GetLogcatMonitor(output_file=logcat_file) as logmon: 179 result = run_inner() 180 logmon.Close() 181 return result 182 else: 183 return run_inner() 184 185 def drop_ram_caches(self): 186 """Drop ran caches on device.""" 187 cache = cache_control.CacheControl(self.device) 188 cache.DropRamCaches() 189 190 def set_high_perf_mode(self): 191 """Set device into high performance mode.""" 192 perf = perf_control.PerfControl(self.device) 193 perf.SetHighPerfMode() 194 195 def set_default_perf_mode(self): 196 """Set device into default performance mode.""" 197 perf = perf_control.PerfControl(self.device) 198 perf.SetDefaultPerfMode() 199 200 201_ANDROID_DRIVER = None 202def android_driver(device=None): 203 """Singleton access method to the driver class.""" 204 global _ANDROID_DRIVER 205 if not _ANDROID_DRIVER: 206 _ANDROID_DRIVER = _Driver(device) 207 return _ANDROID_DRIVER 208