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 'icudtl.dat', 132 target_dir, 133 skip_if_missing=True, 134 ) 135 136 def run(self, target_dir, binary, args, rel_path, timeout, env=None, 137 logcat_file=False): 138 """Execute a command on the device's shell. 139 140 Args: 141 target_dir: Parent directory of the executable on the device (relative to 142 devices' base dir for testing). 143 binary: Name of the binary. 144 args: List of arguments to pass to the binary. 145 rel_path: Relative path on device to use as CWD. 146 timeout: Timeout in seconds. 147 env: The environment variables with which the command should be run. 148 logcat_file: File into which to stream adb logcat log. 149 """ 150 binary_on_device = os.path.join(DEVICE_DIR, target_dir, binary) 151 cmd = [binary_on_device] + args 152 def run_inner(): 153 try: 154 output = self.device.RunShellCommand( 155 cmd, 156 cwd=os.path.join(DEVICE_DIR, rel_path), 157 check_return=True, 158 env=env, 159 timeout=timeout, 160 retries=0, 161 ) 162 return '\n'.join(output) 163 except device_errors.AdbCommandFailedError as e: 164 raise CommandFailedException(e.status, e.output) 165 except device_errors.CommandTimeoutError as e: 166 raise TimeoutException(timeout, e.output) 167 168 169 if logcat_file: 170 with self.device.GetLogcatMonitor(output_file=logcat_file) as logmon: 171 result = run_inner() 172 logmon.Close() 173 return result 174 else: 175 return run_inner() 176 177 def drop_ram_caches(self): 178 """Drop ran caches on device.""" 179 cache = cache_control.CacheControl(self.device) 180 cache.DropRamCaches() 181 182 def set_high_perf_mode(self): 183 """Set device into high performance mode.""" 184 perf = perf_control.PerfControl(self.device) 185 perf.SetHighPerfMode() 186 187 def set_default_perf_mode(self): 188 """Set device into default performance mode.""" 189 perf = perf_control.PerfControl(self.device) 190 perf.SetDefaultPerfMode() 191 192 193_ANDROID_DRIVER = None 194def android_driver(device=None): 195 """Singleton access method to the driver class.""" 196 global _ANDROID_DRIVER 197 if not _ANDROID_DRIVER: 198 _ANDROID_DRIVER = _Driver(device) 199 return _ANDROID_DRIVER 200