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