Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 1 | # Copyright 2015 The Chromium OS 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 | |
Victor Hsieh | 2593d86 | 2018-05-16 15:45:48 -0700 | [diff] [blame] | 5 | import collections |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 6 | import glob |
| 7 | import logging |
| 8 | import os |
| 9 | import pipes |
| 10 | import re |
| 11 | import shutil |
Luis Hector Chavez | 4443ced | 2018-06-22 10:58:12 -0700 | [diff] [blame] | 12 | import socket |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 13 | import sys |
| 14 | import tempfile |
Ben Lin | 474f1af | 2019-03-20 14:51:21 -0700 | [diff] [blame] | 15 | import time |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 16 | |
| 17 | from autotest_lib.client.bin import test, utils |
| 18 | from autotest_lib.client.common_lib import error |
| 19 | from autotest_lib.client.common_lib.cros import chrome, arc_common |
| 20 | |
| 21 | _ADB_KEYS_PATH = '/tmp/adb_keys' |
| 22 | _ADB_VENDOR_KEYS = 'ADB_VENDOR_KEYS' |
yusukes | eee955a | 2018-01-08 11:18:28 -0800 | [diff] [blame] | 23 | _ANDROID_CONTAINER_PID_PATH = '/run/containers/android*/container.pid' |
Dominik Laskowski | 16656d9 | 2018-01-04 19:01:35 -0800 | [diff] [blame] | 24 | _ANDROID_DATA_ROOT_PATH = '/opt/google/containers/android/rootfs/android-data' |
yusukes | ccf3be7 | 2018-01-10 09:00:51 -0800 | [diff] [blame] | 25 | _ANDROID_CONTAINER_ROOT_PATH = '/opt/google/containers/android/rootfs' |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 26 | _SCREENSHOT_DIR_PATH = '/var/log/arc-screenshots' |
| 27 | _SCREENSHOT_BASENAME = 'arc-screenshot' |
| 28 | _MAX_SCREENSHOT_NUM = 10 |
Luis Hector Chavez | 4443ced | 2018-06-22 10:58:12 -0700 | [diff] [blame] | 29 | # This address should match the one present in |
| 30 | # https://chromium.googlesource.com/chromiumos/overlays/chromiumos-overlay/+/master/chromeos-base/arc-sslh-init/files/sslh.conf |
| 31 | _ADBD_ADDRESS = ('100.115.92.2', 5555) |
Luis Hector Chavez | 51c6355 | 2018-03-08 09:03:23 -0800 | [diff] [blame] | 32 | _ADBD_PID_PATH = '/run/arc/adbd.pid' |
Mike Frysinger | 898bd55 | 2017-04-10 23:56:36 -0400 | [diff] [blame] | 33 | _SDCARD_PID_PATH = '/run/arc/sdcard.pid' |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 34 | _ANDROID_ADB_KEYS_PATH = '/data/misc/adb/adb_keys' |
| 35 | _PROCESS_CHECK_INTERVAL_SECONDS = 1 |
| 36 | _WAIT_FOR_ADB_READY = 60 |
| 37 | _WAIT_FOR_ANDROID_PROCESS_SECONDS = 60 |
Ted Lai | de93c93 | 2017-09-05 12:36:20 +0800 | [diff] [blame] | 38 | _PLAY_STORE_PKG = 'com.android.vending' |
| 39 | _SETTINGS_PKG = 'com.android.settings' |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 40 | |
| 41 | |
| 42 | def setup_adb_host(): |
| 43 | """Setup ADB host keys. |
| 44 | |
Ilja H. Friedel | ce8ebd6 | 2017-07-11 13:55:49 -0700 | [diff] [blame] | 45 | This sets up the files and environment variables that wait_for_adb_ready() |
| 46 | needs""" |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 47 | if _ADB_VENDOR_KEYS in os.environ: |
| 48 | return |
| 49 | if not os.path.exists(_ADB_KEYS_PATH): |
| 50 | os.mkdir(_ADB_KEYS_PATH) |
| 51 | # adb expects $HOME to be writable. |
| 52 | os.environ['HOME'] = _ADB_KEYS_PATH |
| 53 | |
| 54 | # Generate and save keys for adb if needed |
| 55 | key_path = os.path.join(_ADB_KEYS_PATH, 'test_key') |
| 56 | if not os.path.exists(key_path): |
| 57 | utils.system('adb keygen ' + pipes.quote(key_path)) |
| 58 | os.environ[_ADB_VENDOR_KEYS] = key_path |
| 59 | |
| 60 | |
Luis Hector Chavez | fdf1cfa | 2018-04-09 09:44:24 -0700 | [diff] [blame] | 61 | def adb_connect(attempts=1): |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 62 | """Attempt to connect ADB to the Android container. |
| 63 | |
| 64 | Returns true if successful. Do not call this function directly. Call |
Luis Hector Chavez | 3e6a935 | 2018-05-17 14:09:11 -0700 | [diff] [blame] | 65 | wait_for_adb_ready() instead. |
| 66 | """ |
Luis Hector Chavez | fdf1cfa | 2018-04-09 09:44:24 -0700 | [diff] [blame] | 67 | # Kill existing adb server every other invocation to ensure that a full |
| 68 | # reconnect is performed. |
| 69 | if attempts % 2 == 1: |
| 70 | utils.system('adb kill-server', ignore_status=True) |
| 71 | |
Ricky Zhou | 5c2745d | 2016-09-19 20:44:39 -0700 | [diff] [blame] | 72 | if utils.system('adb connect localhost:22', ignore_status=True) != 0: |
| 73 | return False |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 74 | return is_adb_connected() |
| 75 | |
| 76 | |
| 77 | def is_adb_connected(): |
| 78 | """Return true if adb is connected to the container.""" |
Ricky Zhou | 5c2745d | 2016-09-19 20:44:39 -0700 | [diff] [blame] | 79 | output = utils.system_output('adb get-state', ignore_status=True) |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 80 | logging.debug('adb get-state: %s', output) |
| 81 | return output.strip() == 'device' |
| 82 | |
| 83 | |
Victor Hsieh | 4ed69e0 | 2017-06-20 13:09:40 -0700 | [diff] [blame] | 84 | def _is_android_data_mounted(): |
| 85 | """Return true if Android's /data is mounted with partial boot enabled.""" |
| 86 | return _android_shell('getprop ro.data_mounted') == '1' |
| 87 | |
| 88 | |
Victor Hsieh | ea58552 | 2017-09-20 11:12:15 -0700 | [diff] [blame] | 89 | def get_zygote_type(): |
| 90 | """Return zygote service type.""" |
| 91 | return _android_shell('getprop ro.zygote') |
| 92 | |
| 93 | |
Lloyd Pique | 647599f | 2017-10-19 17:12:10 -0700 | [diff] [blame] | 94 | def get_sdk_version(): |
| 95 | """Return the SDK level version for Android.""" |
| 96 | return _android_shell('getprop ro.build.version.sdk') |
| 97 | |
| 98 | |
| 99 | def get_product(): |
| 100 | """Return the product string used for the Android build.""" |
| 101 | return _android_shell('getprop ro.build.product') |
| 102 | |
| 103 | |
Luis Hector Chavez | 4443ced | 2018-06-22 10:58:12 -0700 | [diff] [blame] | 104 | def _is_tcp_port_reachable(address): |
| 105 | """Return whether a TCP port described by |address| is reachable.""" |
| 106 | try: |
| 107 | s = socket.create_connection(address) |
| 108 | s.close() |
| 109 | return True |
| 110 | except socket.error: |
| 111 | return False |
| 112 | |
| 113 | |
Ben Lin | 474f1af | 2019-03-20 14:51:21 -0700 | [diff] [blame] | 114 | def _wait_for_data_mounted(timeout): |
Victor Hsieh | 4ed69e0 | 2017-06-20 13:09:40 -0700 | [diff] [blame] | 115 | utils.poll_for_condition( |
| 116 | condition=_is_android_data_mounted, |
| 117 | desc='Wait for /data mounted', |
| 118 | timeout=timeout, |
| 119 | sleep_interval=_PROCESS_CHECK_INTERVAL_SECONDS) |
| 120 | |
| 121 | |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 122 | def wait_for_adb_ready(timeout=_WAIT_FOR_ADB_READY): |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 123 | """Wait for the ADB client to connect to the ARC container. |
| 124 | |
| 125 | @param timeout: Timeout in seconds. |
| 126 | """ |
yusukes | 1c5fdc9 | 2018-05-03 13:42:11 -0700 | [diff] [blame] | 127 | # Although adbd is started at login screen, we still need /data to be |
| 128 | # mounted to set up key-based authentication. /data should be mounted |
| 129 | # once the user has logged in. |
Ben Lin | 474f1af | 2019-03-20 14:51:21 -0700 | [diff] [blame] | 130 | start_time = time.time() |
| 131 | _wait_for_data_mounted(timeout) |
| 132 | timeout -= (time.time() - start_time) |
| 133 | start_time = time.time() |
| 134 | arc_common.wait_for_android_boot(timeout) |
| 135 | timeout -= (time.time() - start_time) |
Victor Hsieh | 4ed69e0 | 2017-06-20 13:09:40 -0700 | [diff] [blame] | 136 | |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 137 | setup_adb_host() |
| 138 | if is_adb_connected(): |
Ricardo Quesada | a55d47e | 2018-02-15 12:53:42 -0800 | [diff] [blame] | 139 | return |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 140 | |
| 141 | # Push keys for adb. |
| 142 | pubkey_path = os.environ[_ADB_VENDOR_KEYS] + '.pub' |
| 143 | with open(pubkey_path, 'r') as f: |
| 144 | _write_android_file(_ANDROID_ADB_KEYS_PATH, f.read()) |
Luis Hector Chavez | b2e7cd7 | 2018-04-24 15:06:35 -0700 | [diff] [blame] | 145 | _android_shell('chown shell ' + pipes.quote(_ANDROID_ADB_KEYS_PATH)) |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 146 | _android_shell('restorecon ' + pipes.quote(_ANDROID_ADB_KEYS_PATH)) |
| 147 | |
Luis Hector Chavez | b2e7cd7 | 2018-04-24 15:06:35 -0700 | [diff] [blame] | 148 | # This starts adbd, restarting it if needed so it can read the updated key. |
| 149 | _android_shell('setprop sys.usb.config mtp') |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 150 | _android_shell('setprop sys.usb.config mtp,adb') |
| 151 | |
Luis Hector Chavez | 4443ced | 2018-06-22 10:58:12 -0700 | [diff] [blame] | 152 | exception = error.TestFail('Failed to connect to adb in %d seconds.' % timeout) |
Luis Hector Chavez | fdf1cfa | 2018-04-09 09:44:24 -0700 | [diff] [blame] | 153 | |
Ricardo Quesada | 0224279 | 2018-05-08 12:57:57 -0700 | [diff] [blame] | 154 | # Keeps track of how many times adb has attempted to establish a |
| 155 | # connection. |
Luis Hector Chavez | fdf1cfa | 2018-04-09 09:44:24 -0700 | [diff] [blame] | 156 | def _adb_connect_wrapper(): |
| 157 | _adb_connect_wrapper.attempts += 1 |
| 158 | return adb_connect(_adb_connect_wrapper.attempts) |
| 159 | _adb_connect_wrapper.attempts = 0 |
Luis Hector Chavez | 3e6a935 | 2018-05-17 14:09:11 -0700 | [diff] [blame] | 160 | try: |
| 161 | utils.poll_for_condition(_adb_connect_wrapper, |
| 162 | exception, |
| 163 | timeout) |
Luis Hector Chavez | 4443ced | 2018-06-22 10:58:12 -0700 | [diff] [blame] | 164 | except (utils.TimeoutError, error.TestFail): |
| 165 | # The operation has failed, but let's try to clarify the failure to |
| 166 | # avoid shifting blame to adb. |
| 167 | |
| 168 | # First, collect some information and log it. |
| 169 | arc_alive = is_android_container_alive() |
| 170 | arc_booted = _android_shell('getprop sys.boot_completed', |
| 171 | ignore_status=True) |
| 172 | arc_system_events = _android_shell( |
| 173 | 'logcat -d -b events *:S arc_system_event', ignore_status=True) |
| 174 | adbd_pid = _android_shell('pidof -s adbd', ignore_status=True) |
| 175 | adbd_port_reachable = _is_tcp_port_reachable(_ADBD_ADDRESS) |
| 176 | adb_state = utils.system_output('adb get-state', ignore_status=True) |
| 177 | logging.debug('ARC alive: %s', arc_alive) |
| 178 | logging.debug('ARC booted: %s', arc_booted) |
| 179 | logging.debug('ARC system events: %s', arc_system_events) |
| 180 | logging.debug('adbd process: %s', adbd_pid) |
| 181 | logging.debug('adbd port reachable: %s', adbd_port_reachable) |
| 182 | logging.debug('adb state: %s', adb_state) |
| 183 | |
| 184 | # Now go through the usual suspects and raise nicer errors to make the |
| 185 | # actual failure clearer. |
| 186 | if not arc_alive: |
| 187 | raise error.TestFail('ARC is not alive.') |
| 188 | if not adbd_pid: |
| 189 | raise error.TestFail('adbd is not running.') |
| 190 | if arc_booted != '1': |
| 191 | raise error.TestFail('ARC did not finish booting.') |
| 192 | if not adbd_port_reachable: |
| 193 | raise error.TestFail('adbd TCP port is not reachable.') |
| 194 | |
| 195 | # We exhausted all possibilities. Fall back to printing the generic |
| 196 | # error. |
Luis Hector Chavez | 3e6a935 | 2018-05-17 14:09:11 -0700 | [diff] [blame] | 197 | raise |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 198 | |
| 199 | |
Cheng-Yi Chiang | 21cf23a | 2016-09-10 01:31:31 +0800 | [diff] [blame] | 200 | def grant_permissions(package, permissions): |
| 201 | """Grants permissions to a package. |
| 202 | |
| 203 | @param package: Package name. |
| 204 | @param permissions: A list of permissions. |
| 205 | |
| 206 | """ |
| 207 | for permission in permissions: |
| 208 | adb_shell('pm grant %s android.permission.%s' % ( |
| 209 | pipes.quote(package), pipes.quote(permission))) |
| 210 | |
| 211 | |
Chung-yih Wang | 57761dd | 2017-07-03 05:22:14 +0000 | [diff] [blame] | 212 | def adb_cmd(cmd, **kwargs): |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 213 | """Executed cmd using adb. Must wait for adb ready. |
| 214 | |
| 215 | @param cmd: Command to run. |
| 216 | """ |
Lloyd Pique | 9368a8a | 2018-04-27 13:35:40 -0700 | [diff] [blame] | 217 | # TODO(b/79122489) - Assert if cmd == 'root' |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 218 | wait_for_adb_ready() |
Chung-yih Wang | 57761dd | 2017-07-03 05:22:14 +0000 | [diff] [blame] | 219 | return utils.system_output('adb %s' % cmd, **kwargs) |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 220 | |
| 221 | |
| 222 | def adb_shell(cmd, **kwargs): |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 223 | """Executed shell command with adb. |
| 224 | |
| 225 | @param cmd: Command to run. |
| 226 | """ |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 227 | output = adb_cmd('shell %s' % pipes.quote(cmd), **kwargs) |
| 228 | # Some android commands include a trailing CRLF in their output. |
| 229 | if kwargs.pop('strip_trailing_whitespace', True): |
Ricardo Quesada | a55d47e | 2018-02-15 12:53:42 -0800 | [diff] [blame] | 230 | output = output.rstrip() |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 231 | return output |
| 232 | |
| 233 | |
Lloyd Pique | 9faedd0 | 2018-05-01 12:21:49 -0700 | [diff] [blame] | 234 | def adb_install(apk, auto_grant_permissions=True): |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 235 | """Install an apk into container. You must connect first. |
| 236 | |
| 237 | @param apk: Package to install. |
Lloyd Pique | 9faedd0 | 2018-05-01 12:21:49 -0700 | [diff] [blame] | 238 | @param auto_grant_permissions: Set to false to not automatically grant all |
| 239 | permissions. Most tests should not care. |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 240 | """ |
Lloyd Pique | 9faedd0 | 2018-05-01 12:21:49 -0700 | [diff] [blame] | 241 | flags = '-g' if auto_grant_permissions else '' |
Yuichiro Hanada | 88036ac | 2018-11-20 14:58:52 +0900 | [diff] [blame] | 242 | return adb_cmd('install -r -t %s %s' % (flags, apk), timeout=60*5) |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 243 | |
| 244 | |
| 245 | def adb_uninstall(apk): |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 246 | """Remove an apk from container. You must connect first. |
| 247 | |
| 248 | @param apk: Package to uninstall. |
| 249 | """ |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 250 | return adb_cmd('uninstall %s' % apk) |
| 251 | |
| 252 | |
| 253 | def adb_reboot(): |
Victor Hsieh | 8bae778 | 2018-05-07 14:27:03 -0700 | [diff] [blame] | 254 | """Reboots the container and block until container pid is gone. |
| 255 | |
| 256 | You must connect first. |
| 257 | """ |
| 258 | old_pid = get_container_pid() |
Risan | 5417a38 | 2018-11-14 09:54:24 +0900 | [diff] [blame] | 259 | logging.info('Trying to reboot PID:%s', old_pid) |
Victor Hsieh | 8bae778 | 2018-05-07 14:27:03 -0700 | [diff] [blame] | 260 | adb_cmd('reboot', ignore_status=True) |
| 261 | # Ensure that the old container is no longer booted |
| 262 | utils.poll_for_condition( |
| 263 | lambda: not utils.pid_is_alive(int(old_pid)), timeout=10) |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 264 | |
| 265 | |
Lloyd Pique | 9368a8a | 2018-04-27 13:35:40 -0700 | [diff] [blame] | 266 | # This adb_root() function is deceiving in that it works just fine on debug |
| 267 | # builds of ARC (user-debug, eng). However "adb root" does not work on user |
| 268 | # builds as run by the autotest machines when testing prerelease images. In fact |
| 269 | # it will silently fail. You will need to find another way to do do what you |
| 270 | # need to do as root. |
| 271 | # |
| 272 | # TODO(b/79122489) - Remove this function. |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 273 | def adb_root(): |
| 274 | """Restart adbd with root permission.""" |
Lloyd Pique | 9368a8a | 2018-04-27 13:35:40 -0700 | [diff] [blame] | 275 | |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 276 | adb_cmd('root') |
| 277 | |
| 278 | |
| 279 | def get_container_root(): |
yusukes | e114031 | 2018-01-09 13:40:59 -0800 | [diff] [blame] | 280 | """Returns path to Android container root directory.""" |
yusukes | 19510e7 | 2018-02-09 19:26:13 -0800 | [diff] [blame] | 281 | return _ANDROID_CONTAINER_ROOT_PATH |
yusukes | e114031 | 2018-01-09 13:40:59 -0800 | [diff] [blame] | 282 | |
| 283 | |
| 284 | def get_container_pid_path(): |
| 285 | """Returns the container's PID file path. |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 286 | |
| 287 | Raises: |
yusukes | e114031 | 2018-01-09 13:40:59 -0800 | [diff] [blame] | 288 | TestError if no PID file is found, or more than one files are found. |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 289 | """ |
yusukes | 19510e7 | 2018-02-09 19:26:13 -0800 | [diff] [blame] | 290 | # Find the PID file rather than the android-XXXXXX/ directory to ignore |
| 291 | # stale and empty android-XXXXXX/ directories when they exist. |
Yusuke Sato | 55a8100 | 2017-07-07 15:43:31 +0900 | [diff] [blame] | 292 | arc_container_pid_files = glob.glob(_ANDROID_CONTAINER_PID_PATH) |
| 293 | |
| 294 | if len(arc_container_pid_files) == 0: |
yusukes | e114031 | 2018-01-09 13:40:59 -0800 | [diff] [blame] | 295 | raise error.TestError('Android container.pid not available') |
Yusuke Sato | 55a8100 | 2017-07-07 15:43:31 +0900 | [diff] [blame] | 296 | |
| 297 | if len(arc_container_pid_files) > 1: |
Ricardo Quesada | 0224279 | 2018-05-08 12:57:57 -0700 | [diff] [blame] | 298 | raise error.TestError( |
| 299 | 'Multiple Android container.pid files found: %r. ' |
| 300 | 'Reboot your DUT to recover.' % (arc_container_pid_files)) |
Yusuke Sato | 55a8100 | 2017-07-07 15:43:31 +0900 | [diff] [blame] | 301 | |
yusukes | e114031 | 2018-01-09 13:40:59 -0800 | [diff] [blame] | 302 | return arc_container_pid_files[0] |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 303 | |
| 304 | |
Dominik Laskowski | 16656d9 | 2018-01-04 19:01:35 -0800 | [diff] [blame] | 305 | def get_android_data_root(): |
| 306 | """Returns path to Chrome OS directory that bind-mounts Android's /data.""" |
| 307 | return _ANDROID_DATA_ROOT_PATH |
| 308 | |
| 309 | |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 310 | def get_job_pid(job_name): |
| 311 | """Returns the PID of an upstart job.""" |
| 312 | status = utils.system_output('status %s' % job_name) |
| 313 | match = re.match(r'^%s start/running, process (\d+)$' % job_name, |
| 314 | status) |
| 315 | if not match: |
| 316 | raise error.TestError('Unexpected status: "%s"' % status) |
| 317 | return match.group(1) |
| 318 | |
| 319 | |
| 320 | def get_container_pid(): |
| 321 | """Returns the PID of the container.""" |
yusukes | e114031 | 2018-01-09 13:40:59 -0800 | [diff] [blame] | 322 | return utils.read_one_line(get_container_pid_path()) |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 323 | |
| 324 | |
Luis Hector Chavez | 51c6355 | 2018-03-08 09:03:23 -0800 | [diff] [blame] | 325 | def get_adbd_pid(): |
| 326 | """Returns the PID of the adbd proxy container.""" |
| 327 | if not os.path.exists(_ADBD_PID_PATH): |
| 328 | # The adbd proxy does not run on all boards. |
| 329 | return None |
| 330 | return utils.read_one_line(_ADBD_PID_PATH) |
| 331 | |
| 332 | |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 333 | def get_sdcard_pid(): |
| 334 | """Returns the PID of the sdcard container.""" |
| 335 | return utils.read_one_line(_SDCARD_PID_PATH) |
| 336 | |
| 337 | |
Satoshi Niwa | 36d99c2 | 2019-02-15 13:18:21 +0900 | [diff] [blame] | 338 | def get_mount_passthrough_pid_list(): |
| 339 | """Returns PIDs of ARC mount-passthrough daemon jobs.""" |
| 340 | JOB_NAMES = [ 'arc-myfiles', 'arc-myfiles-default', |
| 341 | 'arc-myfiles-read', 'arc-myfiles-write', |
| 342 | 'arc-removable-media', 'arc-removable-media-default', |
| 343 | 'arc-removable-media-read', 'arc-removable-media-write' ] |
| 344 | pid_list = [] |
| 345 | for job_name in JOB_NAMES: |
| 346 | try: |
Ereth McKnight-MacNeil | d16ff73 | 2019-04-01 16:37:31 -0700 | [diff] [blame] | 347 | pid = get_job_pid(job_name) |
Satoshi Niwa | 36d99c2 | 2019-02-15 13:18:21 +0900 | [diff] [blame] | 348 | pid_list.append(pid) |
| 349 | except Exception, e: |
| 350 | logging.warning('Failed to find PID for %s : %s', job_name, e) |
| 351 | continue |
Risan | 5417a38 | 2018-11-14 09:54:24 +0900 | [diff] [blame] | 352 | |
Satoshi Niwa | 36d99c2 | 2019-02-15 13:18:21 +0900 | [diff] [blame] | 353 | return pid_list |
Risan | 5417a38 | 2018-11-14 09:54:24 +0900 | [diff] [blame] | 354 | |
| 355 | |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 356 | def get_obb_mounter_pid(): |
| 357 | """Returns the PID of the OBB mounter.""" |
| 358 | return utils.system_output('pgrep -f -u root ^/usr/bin/arc-obb-mounter') |
| 359 | |
| 360 | |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 361 | def is_android_process_running(process_name): |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 362 | """Return whether Android has completed booting. |
| 363 | |
| 364 | @param process_name: Process name. |
| 365 | """ |
Shao-Chuan Lee | 918c295 | 2019-04-18 15:17:26 +0900 | [diff] [blame^] | 366 | output = adb_shell('pgrep -c -f %s' % pipes.quote(process_name), |
| 367 | ignore_status=True) |
Luis Hector Chavez | 066c89e | 2018-02-02 14:24:35 -0800 | [diff] [blame] | 368 | return int(output) > 0 |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 369 | |
| 370 | |
Chen-Hao Chang@google.com | 1d6f3b4 | 2016-09-26 15:17:57 +0800 | [diff] [blame] | 371 | def check_android_file_exists(filename): |
| 372 | """Checks whether the given file exists in the Android filesystem |
| 373 | |
| 374 | @param filename: File to check. |
| 375 | """ |
Shao-Chuan Lee | 918c295 | 2019-04-18 15:17:26 +0900 | [diff] [blame^] | 376 | return adb_shell( |
| 377 | 'test -e {} && echo FileExists'.format(pipes.quote(filename)), |
| 378 | ignore_status=True).find("FileExists") >= 0 |
Chen-Hao Chang@google.com | 1d6f3b4 | 2016-09-26 15:17:57 +0800 | [diff] [blame] | 379 | |
| 380 | |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 381 | def read_android_file(filename): |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 382 | """Reads a file in Android filesystem. |
| 383 | |
| 384 | @param filename: File to read. |
| 385 | """ |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 386 | with tempfile.NamedTemporaryFile() as tmpfile: |
Ilja H. Friedel | ce8ebd6 | 2017-07-11 13:55:49 -0700 | [diff] [blame] | 387 | adb_cmd('pull %s %s' % (pipes.quote(filename), |
| 388 | pipes.quote(tmpfile.name))) |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 389 | with open(tmpfile.name) as f: |
| 390 | return f.read() |
| 391 | |
| 392 | return None |
| 393 | |
| 394 | |
| 395 | def write_android_file(filename, data): |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 396 | """Writes to a file in Android filesystem. |
| 397 | |
| 398 | @param filename: File to write. |
| 399 | @param data: Data to write. |
| 400 | """ |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 401 | with tempfile.NamedTemporaryFile() as tmpfile: |
| 402 | tmpfile.write(data) |
| 403 | tmpfile.flush() |
| 404 | |
Ilja H. Friedel | ce8ebd6 | 2017-07-11 13:55:49 -0700 | [diff] [blame] | 405 | adb_cmd('push %s %s' % (pipes.quote(tmpfile.name), |
| 406 | pipes.quote(filename))) |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 407 | |
| 408 | |
| 409 | def _write_android_file(filename, data): |
| 410 | """Writes to a file in Android filesystem. |
| 411 | |
| 412 | This is an internal function used to bootstrap adb. |
| 413 | Tests should use write_android_file instead. |
| 414 | """ |
| 415 | android_cmd = 'cat > %s' % pipes.quote(filename) |
| 416 | cros_cmd = 'android-sh -c %s' % pipes.quote(android_cmd) |
| 417 | utils.run(cros_cmd, stdin=data) |
| 418 | |
| 419 | |
Victor Hsieh | 2593d86 | 2018-05-16 15:45:48 -0700 | [diff] [blame] | 420 | def get_android_file_stats(filename): |
| 421 | """Returns an object of file stats for an Android file. |
| 422 | |
| 423 | The returned object supported limited attributes, but can be easily extended |
| 424 | if needed. Note that the value are all string. |
| 425 | |
| 426 | This uses _android_shell to run as root, so that it can access to all files |
| 427 | inside the container. On non-debuggable build, adb shell is not rootable. |
| 428 | """ |
| 429 | mapping = { |
| 430 | '%a': 'mode', |
| 431 | '%g': 'gid', |
| 432 | '%h': 'nlink', |
| 433 | '%u': 'uid', |
| 434 | } |
| 435 | output = _android_shell( |
| 436 | 'stat -c "%s" %s' % (' '.join(mapping.keys()), pipes.quote(filename))) |
| 437 | stats = output.split(' ') |
| 438 | if len(stats) != len(mapping): |
| 439 | raise error.TestError('Unexpected output from stat: %s' % output) |
| 440 | _Stats = collections.namedtuple('_Stats', mapping.values()) |
| 441 | return _Stats(*stats) |
| 442 | |
| 443 | |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 444 | def remove_android_file(filename): |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 445 | """Removes a file in Android filesystem. |
| 446 | |
| 447 | @param filename: File to remove. |
| 448 | """ |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 449 | adb_shell('rm -f %s' % pipes.quote(filename)) |
| 450 | |
| 451 | |
| 452 | def wait_for_android_boot(timeout=None): |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 453 | """Sleep until Android has completed booting or timeout occurs. |
| 454 | |
| 455 | @param timeout: Timeout in seconds. |
| 456 | """ |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 457 | arc_common.wait_for_android_boot(timeout) |
| 458 | |
| 459 | |
| 460 | def wait_for_android_process(process_name, |
| 461 | timeout=_WAIT_FOR_ANDROID_PROCESS_SECONDS): |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 462 | """Sleep until an Android process is running or timeout occurs. |
| 463 | |
| 464 | @param process_name: Process name. |
| 465 | @param timeout: Timeout in seconds. |
| 466 | """ |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 467 | condition = lambda: is_android_process_running(process_name) |
| 468 | utils.poll_for_condition(condition=condition, |
| 469 | desc='%s is running' % process_name, |
| 470 | timeout=timeout, |
| 471 | sleep_interval=_PROCESS_CHECK_INTERVAL_SECONDS) |
| 472 | |
| 473 | |
| 474 | def _android_shell(cmd, **kwargs): |
| 475 | """Execute cmd instead the Android container. |
| 476 | |
Ricardo Quesada | a55d47e | 2018-02-15 12:53:42 -0800 | [diff] [blame] | 477 | This function is strictly for internal use only, as commands do not run in |
| 478 | a fully consistent Android environment. Prefer adb_shell instead. |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 479 | """ |
| 480 | return utils.system_output('android-sh -c {}'.format(pipes.quote(cmd)), |
| 481 | **kwargs) |
| 482 | |
| 483 | |
| 484 | def is_android_container_alive(): |
| 485 | """Check if android container is alive.""" |
| 486 | try: |
| 487 | container_pid = get_container_pid() |
Yusuke Sato | 55a8100 | 2017-07-07 15:43:31 +0900 | [diff] [blame] | 488 | except Exception, e: |
| 489 | logging.error('is_android_container_alive failed: %r', e) |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 490 | return False |
| 491 | return utils.pid_is_alive(int(container_pid)) |
| 492 | |
| 493 | |
Ted Lai | de93c93 | 2017-09-05 12:36:20 +0800 | [diff] [blame] | 494 | def _is_in_installed_packages_list(package, option=None): |
| 495 | """Check if a package is in the list returned by pm list packages. |
| 496 | |
| 497 | adb must be ready. |
| 498 | |
| 499 | @param package: Package in request. |
| 500 | @param option: An option for the command adb shell pm list packages. |
| 501 | Valid values include '-s', '-3', '-d', and '-e'. |
| 502 | """ |
| 503 | command = 'pm list packages' |
| 504 | if option: |
| 505 | command += ' ' + option |
| 506 | packages = adb_shell(command).splitlines() |
| 507 | package_entry = 'package:' + package |
Ricardo Quesada | 0224279 | 2018-05-08 12:57:57 -0700 | [diff] [blame] | 508 | ret = package_entry in packages |
| 509 | |
Ricardo Quesada | 0224279 | 2018-05-08 12:57:57 -0700 | [diff] [blame] | 510 | if not ret: |
Risan | 5417a38 | 2018-11-14 09:54:24 +0900 | [diff] [blame] | 511 | logging.info('Could not find "%s" in %s', |
Cici Ruan | bed8aa6 | 2018-11-27 19:31:33 -0800 | [diff] [blame] | 512 | package_entry, str(packages)) |
Ricardo Quesada | 0224279 | 2018-05-08 12:57:57 -0700 | [diff] [blame] | 513 | return ret |
Ted Lai | de93c93 | 2017-09-05 12:36:20 +0800 | [diff] [blame] | 514 | |
| 515 | |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 516 | def is_package_installed(package): |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 517 | """Check if a package is installed. adb must be ready. |
| 518 | |
| 519 | @param package: Package in request. |
| 520 | """ |
Ted Lai | de93c93 | 2017-09-05 12:36:20 +0800 | [diff] [blame] | 521 | return _is_in_installed_packages_list(package) |
| 522 | |
| 523 | |
| 524 | def is_package_disabled(package): |
| 525 | """Check if an installed package is disabled. adb must be ready. |
| 526 | |
| 527 | @param package: Package in request. |
| 528 | """ |
| 529 | return _is_in_installed_packages_list(package, '-d') |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 530 | |
| 531 | |
Victor Hsieh | 189690b | 2018-05-15 14:04:05 -0700 | [diff] [blame] | 532 | def get_package_install_path(package): |
| 533 | """Returns the apk install location of the given package.""" |
| 534 | output = adb_shell('pm path {}'.format(pipes.quote(package))) |
| 535 | return output.split(':')[1] |
| 536 | |
| 537 | |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 538 | def _before_iteration_hook(obj): |
| 539 | """Executed by parent class before every iteration. |
| 540 | |
| 541 | This function resets the run_once_finished flag before every iteration |
| 542 | so we can detect failure on every single iteration. |
| 543 | |
| 544 | Args: |
| 545 | obj: the test itself |
| 546 | """ |
| 547 | obj.run_once_finished = False |
| 548 | |
| 549 | |
| 550 | def _after_iteration_hook(obj): |
| 551 | """Executed by parent class after every iteration. |
| 552 | |
| 553 | The parent class will handle exceptions and failures in the run and will |
| 554 | always call this hook afterwards. Take a screenshot if the run has not |
| 555 | been marked as finished (i.e. there was a failure/exception). |
| 556 | |
| 557 | Args: |
| 558 | obj: the test itself |
| 559 | """ |
| 560 | if not obj.run_once_finished: |
Ted Lai | 4656ea8 | 2017-09-19 17:03:36 +0800 | [diff] [blame] | 561 | if is_adb_connected(): |
| 562 | logging.debug('Recent activities dump:\n%s', |
| 563 | adb_shell('dumpsys activity recents')) |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 564 | if not os.path.exists(_SCREENSHOT_DIR_PATH): |
| 565 | os.mkdir(_SCREENSHOT_DIR_PATH, 0755) |
| 566 | obj.num_screenshots += 1 |
| 567 | if obj.num_screenshots <= _MAX_SCREENSHOT_NUM: |
| 568 | logging.warning('Iteration %d failed, taking a screenshot.', |
| 569 | obj.iteration) |
Ilja H. Friedel | ce8ebd6 | 2017-07-11 13:55:49 -0700 | [diff] [blame] | 570 | try: |
Shuhei Takahashi | 0192b77 | 2018-06-26 13:38:26 +0900 | [diff] [blame] | 571 | utils.run('screenshot "{}/{}_iter{}.png"'.format( |
| 572 | _SCREENSHOT_DIR_PATH, _SCREENSHOT_BASENAME, obj.iteration)) |
Hidehiko Abe | 1984650 | 2017-08-03 15:38:15 +0900 | [diff] [blame] | 573 | except Exception as e: |
| 574 | logging.warning('Unable to capture screenshot. %s', e) |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 575 | else: |
| 576 | logging.warning('Too many failures, no screenshot taken') |
| 577 | |
| 578 | |
Chen-Hao Chang@google.com | 1d6f3b4 | 2016-09-26 15:17:57 +0800 | [diff] [blame] | 579 | def send_keycode(keycode): |
| 580 | """Sends the given keycode to the container |
| 581 | |
| 582 | @param keycode: keycode to send. |
| 583 | """ |
| 584 | adb_shell('input keyevent {}'.format(keycode)) |
| 585 | |
| 586 | |
Shuhei Takahashi | 8f8524d | 2017-01-31 14:22:53 +0900 | [diff] [blame] | 587 | def get_android_sdk_version(): |
| 588 | """Returns the Android SDK version. |
| 589 | |
| 590 | This function can be called before Android container boots. |
| 591 | """ |
| 592 | with open('/etc/lsb-release') as f: |
| 593 | values = dict(line.split('=', 1) for line in f.read().splitlines()) |
| 594 | try: |
| 595 | return int(values['CHROMEOS_ARC_ANDROID_SDK_VERSION']) |
| 596 | except (KeyError, ValueError): |
| 597 | raise error.TestError('Could not determine Android SDK version') |
| 598 | |
| 599 | |
Ricardo Quesada | a55d47e | 2018-02-15 12:53:42 -0800 | [diff] [blame] | 600 | def set_device_mode(device_mode, use_fake_sensor_with_lifetime_secs=0): |
| 601 | """Sets the device in either Clamshell or Tablet mode. |
| 602 | |
| 603 | "inject_powerd_input_event" might fail if the DUT does not support Tablet |
| 604 | mode, and it will raise an |error.CmdError| exception. To prevent that, use |
| 605 | the |use_fake_sensor_with_lifetime_secs| parameter. |
| 606 | |
| 607 | @param device_mode: string with either 'clamshell' or 'tablet' |
| 608 | @param use_fake_sensor_with_lifetime_secs: if > 0, it will create the |
| 609 | input device with the given lifetime in seconds |
| 610 | @raise ValueError: if passed invalid parameters |
| 611 | @raise error.CmdError: if inject_powerd_input_event fails |
| 612 | """ |
| 613 | valid_value = ('tablet', 'clamshell') |
| 614 | if device_mode not in valid_value: |
| 615 | raise ValueError('Invalid device_mode parameter: %s' % device_mode) |
| 616 | |
| 617 | value = 1 if device_mode == 'tablet' else 0 |
| 618 | |
| 619 | args = ['--code=tablet', '--value=%d' % value] |
| 620 | |
| 621 | if use_fake_sensor_with_lifetime_secs > 0: |
| 622 | args.extend(['--create_dev', '--dev_lifetime=%d' % |
| 623 | use_fake_sensor_with_lifetime_secs]) |
Ricardo Quesada | f507b82 | 2018-10-03 15:51:04 -0700 | [diff] [blame] | 624 | |
| 625 | try: |
| 626 | utils.run('inject_powerd_input_event', args=args) |
| 627 | except error.CmdError as err: |
| 628 | # TODO: Fragile code ahead. Correct way to do it is to check |
| 629 | # if device is already in desired mode, and do nothing if so. |
| 630 | # ATM we don't have a way to check current device mode. |
| 631 | |
| 632 | # Assuming that CmdError means that device does not support |
| 633 | # --code=tablet parameter, meaning that device only supports clamshell |
| 634 | # mode. |
| 635 | if device_mode == 'clamshell' and \ |
| 636 | use_fake_sensor_with_lifetime_secs == 0: |
| 637 | return |
| 638 | raise err |
Ricardo Quesada | a55d47e | 2018-02-15 12:53:42 -0800 | [diff] [blame] | 639 | |
| 640 | |
Lloyd Pique | 5767c45 | 2018-07-11 18:25:43 -0700 | [diff] [blame] | 641 | def wait_for_userspace_ready(): |
| 642 | """Waits for userspace apps to be launchable. |
| 643 | |
| 644 | Launches and then closes Android settings as a way to ensure all basic |
| 645 | services are ready. This goes a bit beyond waiting for boot-up to complete, |
| 646 | as being able to launch an activity requires more of the framework to have |
| 647 | started. The boot-complete signal happens fairly early, and the framework |
| 648 | system server is still starting services. By waiting for ActivityManager to |
| 649 | respond, we automatically wait on more services to be ready. |
| 650 | """ |
| 651 | output = adb_shell('am start -W -a android.settings.SETTINGS') |
| 652 | if not output.endswith('Complete'): |
| 653 | logging.debug('Output was: %s', output) |
| 654 | raise error.TestError('Could not launch SETTINGS') |
| 655 | adb_shell('am force-stop com.android.settings') |
| 656 | |
| 657 | |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 658 | class ArcTest(test.test): |
| 659 | """ Base class of ARC Test. |
| 660 | |
| 661 | This class could be used as super class of an ARC test for saving |
| 662 | redundant codes for container bringup, autotest-dep package(s) including |
| 663 | uiautomator setup if required, and apks install/remove during |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 664 | arc_setup/arc_teardown, respectively. By default arc_setup() is called in |
Ricardo Quesada | 0224279 | 2018-05-08 12:57:57 -0700 | [diff] [blame] | 665 | initialize() after Android have been brought up. It could also be |
| 666 | overridden to perform non-default tasks. For example, a simple |
| 667 | ArcHelloWorldTest can be just implemented with print 'HelloWorld' in its |
| 668 | run_once() and no other functions are required. We could expect |
| 669 | ArcHelloWorldTest would bring up browser and wait for container up, then |
| 670 | print 'Hello World', and shutdown browser after. As a precaution, if you |
| 671 | overwrite initialize(), arc_setup(), or cleanup() function(s) in ARC test, |
| 672 | remember to call the corresponding function(s) in this base class as well. |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 673 | """ |
| 674 | version = 1 |
| 675 | _PKG_UIAUTOMATOR = 'uiautomator' |
| 676 | _FULL_PKG_NAME_UIAUTOMATOR = 'com.github.uiautomator' |
| 677 | |
| 678 | def __init__(self, *args, **kwargs): |
| 679 | """Initialize flag setting.""" |
| 680 | super(ArcTest, self).__init__(*args, **kwargs) |
| 681 | self.initialized = False |
| 682 | # Set the flag run_once_finished to detect if a test is executed |
| 683 | # successfully without any exception thrown. Otherwise, generate |
| 684 | # a screenshot in /var/log for debugging. |
| 685 | self.run_once_finished = False |
| 686 | self.logcat_proc = None |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 687 | self.dep_package = None |
| 688 | self.apks = None |
| 689 | self.full_pkg_names = [] |
| 690 | self.uiautomator = False |
Ted Lai | de93c93 | 2017-09-05 12:36:20 +0800 | [diff] [blame] | 691 | self._should_reenable_play_store = False |
Chen-Hao Chang@google.com | 1d6f3b4 | 2016-09-26 15:17:57 +0800 | [diff] [blame] | 692 | self._chrome = None |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 693 | if os.path.exists(_SCREENSHOT_DIR_PATH): |
| 694 | shutil.rmtree(_SCREENSHOT_DIR_PATH) |
| 695 | self.register_before_iteration_hook(_before_iteration_hook) |
| 696 | self.register_after_iteration_hook(_after_iteration_hook) |
| 697 | # Keep track of the number of debug screenshots taken and keep the |
| 698 | # total number sane to avoid issues. |
| 699 | self.num_screenshots = 0 |
| 700 | |
Ted Lai | 05fe230 | 2017-09-01 11:35:07 +0800 | [diff] [blame] | 701 | def initialize(self, extension_path=None, username=None, password=None, |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 702 | arc_mode=arc_common.ARC_MODE_ENABLED, **chrome_kargs): |
| 703 | """Log in to a test account.""" |
| 704 | extension_paths = [extension_path] if extension_path else [] |
| 705 | self._chrome = chrome.Chrome(extension_paths=extension_paths, |
Ted Lai | 05fe230 | 2017-09-01 11:35:07 +0800 | [diff] [blame] | 706 | username=username, |
| 707 | password=password, |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 708 | arc_mode=arc_mode, |
| 709 | **chrome_kargs) |
| 710 | if extension_path: |
| 711 | self._extension = self._chrome.get_extension(extension_path) |
| 712 | else: |
| 713 | self._extension = None |
| 714 | # With ARC enabled, Chrome will wait until container to boot up |
| 715 | # before returning here, see chrome.py. |
| 716 | self.initialized = True |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 717 | try: |
| 718 | if is_android_container_alive(): |
| 719 | self.arc_setup() |
| 720 | else: |
| 721 | logging.error('Container is alive?') |
| 722 | except Exception as err: |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 723 | raise error.TestFail(err) |
| 724 | |
| 725 | def after_run_once(self): |
| 726 | """Executed after run_once() only if there were no errors. |
| 727 | |
| 728 | This function marks the run as finished with a flag. If there was a |
| 729 | failure the flag won't be set and the failure can then be detected by |
| 730 | testing the run_once_finished flag. |
| 731 | """ |
| 732 | logging.info('After run_once') |
| 733 | self.run_once_finished = True |
| 734 | |
| 735 | def cleanup(self): |
| 736 | """Log out of Chrome.""" |
| 737 | if not self.initialized: |
| 738 | logging.info('Skipping ARC cleanup: not initialized') |
| 739 | return |
| 740 | logging.info('Starting ARC cleanup') |
| 741 | try: |
| 742 | if is_android_container_alive(): |
| 743 | self.arc_teardown() |
| 744 | except Exception as err: |
| 745 | raise error.TestFail(err) |
| 746 | finally: |
| 747 | try: |
Luis Hector Chavez | b740ab9 | 2018-05-17 14:10:51 -0700 | [diff] [blame] | 748 | if self.logcat_proc: |
| 749 | self.logcat_proc.close() |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 750 | finally: |
Chen-Hao Chang@google.com | 1d6f3b4 | 2016-09-26 15:17:57 +0800 | [diff] [blame] | 751 | if self._chrome is not None: |
| 752 | self._chrome.close() |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 753 | |
Ben Cheng | 42b8689 | 2017-10-25 17:22:25 +0800 | [diff] [blame] | 754 | def _install_apks(self, dep_package, apks, full_pkg_names): |
| 755 | """"Install apks fetched from the specified package folder. |
| 756 | |
| 757 | @param dep_package: A dependent package directory |
| 758 | @param apks: List of apk names to be installed |
| 759 | @param full_pkg_names: List of packages to be uninstalled at teardown |
| 760 | """ |
| 761 | apk_path = os.path.join(self.autodir, 'deps', dep_package) |
| 762 | if apks: |
| 763 | for apk in apks: |
| 764 | logging.info('Installing %s', apk) |
Ricardo Quesada | 0224279 | 2018-05-08 12:57:57 -0700 | [diff] [blame] | 765 | out = adb_install('%s/%s' % (apk_path, apk)) |
Risan | 5417a38 | 2018-11-14 09:54:24 +0900 | [diff] [blame] | 766 | logging.info('Install apk output: %s', str(out)) |
Ben Cheng | 42b8689 | 2017-10-25 17:22:25 +0800 | [diff] [blame] | 767 | # Verify if package(s) are installed correctly |
| 768 | if not full_pkg_names: |
| 769 | raise error.TestError('Package names of apks expected') |
| 770 | for pkg in full_pkg_names: |
| 771 | logging.info('Check if %s is installed', pkg) |
| 772 | if not is_package_installed(pkg): |
| 773 | raise error.TestError('Package %s not found' % pkg) |
| 774 | # Make sure full_pkg_names contains installed packages only |
| 775 | # so arc_teardown() knows what packages to uninstall. |
| 776 | self.full_pkg_names.append(pkg) |
| 777 | |
| 778 | def _count_nested_array_level(self, array): |
| 779 | """Count the level of a nested array.""" |
| 780 | if isinstance(array, list): |
| 781 | return 1 + self._count_nested_array_level(array[0]) |
| 782 | return 0 |
| 783 | |
| 784 | def _fix_nested_array_level(self, var_name, expected_level, array): |
| 785 | """Enclose array one level deeper if needed.""" |
| 786 | level = self._count_nested_array_level(array) |
| 787 | if level == expected_level: |
| 788 | return array |
| 789 | if level == expected_level - 1: |
| 790 | return [array] |
| 791 | |
| 792 | logging.error("Variable %s nested level is not fixable: " |
| 793 | "Expecting %d, seeing %d", |
Ricardo Quesada | a55d47e | 2018-02-15 12:53:42 -0800 | [diff] [blame] | 794 | var_name, expected_level, level) |
Ben Cheng | 42b8689 | 2017-10-25 17:22:25 +0800 | [diff] [blame] | 795 | raise error.TestError('Format error with variable %s' % var_name) |
| 796 | |
| 797 | def arc_setup(self, dep_packages=None, apks=None, full_pkg_names=None, |
Ted Lai | de93c93 | 2017-09-05 12:36:20 +0800 | [diff] [blame] | 798 | uiautomator=False, block_outbound=False, |
| 799 | disable_play_store=False): |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 800 | """ARC test setup: Setup dependencies and install apks. |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 801 | |
| 802 | This function disables package verification and enables non-market |
| 803 | APK installation. Then, it installs specified APK(s) and uiautomator |
| 804 | package and path if required in a test. |
| 805 | |
Ben Cheng | 42b8689 | 2017-10-25 17:22:25 +0800 | [diff] [blame] | 806 | @param dep_packages: Array of package names of autotest_deps APK |
| 807 | packages. |
| 808 | @param apks: Array of APK name arrays to be installed in dep_package. |
| 809 | @param full_pkg_names: Array of full package name arrays to be removed |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 810 | in teardown. |
| 811 | @param uiautomator: uiautomator python package is required or not. |
Chung-yih Wang | a67fce0 | 2017-03-28 16:19:17 +0800 | [diff] [blame] | 812 | @param block_outbound: block outbound network traffic during a test. |
Ted Lai | de93c93 | 2017-09-05 12:36:20 +0800 | [diff] [blame] | 813 | @param disable_play_store: Set this to True if you want to prevent |
| 814 | GMS Core from updating. |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 815 | """ |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 816 | if not self.initialized: |
| 817 | logging.info('Skipping ARC setup: not initialized') |
| 818 | return |
| 819 | logging.info('Starting ARC setup') |
Ben Cheng | 42b8689 | 2017-10-25 17:22:25 +0800 | [diff] [blame] | 820 | |
| 821 | # Sample parameters for multi-deps setup after fixup (if needed): |
| 822 | # dep_packages: ['Dep1-apk', 'Dep2-apk'] |
| 823 | # apks: [['com.dep1.arch1.apk', 'com.dep2.arch2.apk'], ['com.dep2.apk'] |
| 824 | # full_pkg_nmes: [['com.dep1.app'], ['com.dep2.app']] |
| 825 | # TODO(crbug/777787): once the parameters of all callers of arc_setup |
| 826 | # are refactored, we can delete the safety net here. |
| 827 | if dep_packages: |
| 828 | dep_packages = self._fix_nested_array_level( |
| 829 | 'dep_packages', 1, dep_packages) |
| 830 | apks = self._fix_nested_array_level('apks', 2, apks) |
| 831 | full_pkg_names = self._fix_nested_array_level( |
| 832 | 'full_pkg_names', 2, full_pkg_names) |
| 833 | if (len(dep_packages) != len(apks) or |
Ricardo Quesada | 0224279 | 2018-05-08 12:57:57 -0700 | [diff] [blame] | 834 | len(apks) != len(full_pkg_names)): |
Ben Cheng | 42b8689 | 2017-10-25 17:22:25 +0800 | [diff] [blame] | 835 | logging.info('dep_packages length is %d', len(dep_packages)) |
| 836 | logging.info('apks length is %d', len(apks)) |
Ricardo Quesada | 0224279 | 2018-05-08 12:57:57 -0700 | [diff] [blame] | 837 | logging.info('full_pkg_names length is %d', |
| 838 | len(full_pkg_names)) |
Ben Cheng | 42b8689 | 2017-10-25 17:22:25 +0800 | [diff] [blame] | 839 | raise error.TestFail( |
| 840 | 'dep_packages/apks/full_pkg_names format error') |
| 841 | |
| 842 | self.dep_packages = dep_packages |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 843 | self.apks = apks |
Ted Lai | de93c93 | 2017-09-05 12:36:20 +0800 | [diff] [blame] | 844 | self.uiautomator = uiautomator or disable_play_store |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 845 | # Setup dependent packages if required |
| 846 | packages = [] |
Ben Cheng | 42b8689 | 2017-10-25 17:22:25 +0800 | [diff] [blame] | 847 | if dep_packages: |
| 848 | packages = dep_packages[:] |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 849 | if self.uiautomator: |
| 850 | packages.append(self._PKG_UIAUTOMATOR) |
| 851 | if packages: |
| 852 | logging.info('Setting up dependent package(s) %s', packages) |
| 853 | self.job.setup_dep(packages) |
| 854 | |
Shuhei Takahashi | 375783b | 2018-07-03 15:53:02 +0900 | [diff] [blame] | 855 | self.logcat_proc = arc_common.Logcat() |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 856 | |
| 857 | wait_for_adb_ready() |
| 858 | |
Lloyd Pique | 7d1a6ab | 2017-11-15 16:46:38 -0800 | [diff] [blame] | 859 | # Setting verifier_verify_adb_installs to zero suppresses a dialog box |
| 860 | # that can appear asking for the user to consent to the install. |
| 861 | adb_shell('settings put global verifier_verify_adb_installs 0') |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 862 | |
Ben Cheng | 42b8689 | 2017-10-25 17:22:25 +0800 | [diff] [blame] | 863 | # Install apks based on dep_packages/apks/full_pkg_names tuples |
| 864 | if dep_packages: |
| 865 | for i in xrange(len(dep_packages)): |
| 866 | self._install_apks(dep_packages[i], apks[i], full_pkg_names[i]) |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 867 | |
| 868 | if self.uiautomator: |
| 869 | path = os.path.join(self.autodir, 'deps', self._PKG_UIAUTOMATOR) |
| 870 | sys.path.append(path) |
Ted Lai | 4656ea8 | 2017-09-19 17:03:36 +0800 | [diff] [blame] | 871 | self._add_ui_object_not_found_handler() |
Ted Lai | de93c93 | 2017-09-05 12:36:20 +0800 | [diff] [blame] | 872 | if disable_play_store and not is_package_disabled(_PLAY_STORE_PKG): |
| 873 | self._disable_play_store() |
| 874 | if not is_package_disabled(_PLAY_STORE_PKG): |
| 875 | raise error.TestFail('Failed to disable Google Play Store.') |
| 876 | self._should_reenable_play_store = True |
Chung-yih Wang | a67fce0 | 2017-03-28 16:19:17 +0800 | [diff] [blame] | 877 | if block_outbound: |
| 878 | self.block_outbound() |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 879 | |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 880 | def arc_teardown(self): |
| 881 | """ARC test teardown. |
| 882 | |
| 883 | This function removes all installed packages in arc_setup stage |
| 884 | first. Then, it restores package verification and disables non-market |
| 885 | APK installation. |
| 886 | |
| 887 | """ |
| 888 | if self.full_pkg_names: |
| 889 | for pkg in self.full_pkg_names: |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 890 | logging.info('Uninstalling %s', pkg) |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 891 | if not is_package_installed(pkg): |
| 892 | raise error.TestError('Package %s was not installed' % pkg) |
| 893 | adb_uninstall(pkg) |
| 894 | if self.uiautomator: |
Cheng-Yu Lee | a854fbf | 2016-09-09 22:25:14 +0800 | [diff] [blame] | 895 | logging.info('Uninstalling %s', self._FULL_PKG_NAME_UIAUTOMATOR) |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 896 | adb_uninstall(self._FULL_PKG_NAME_UIAUTOMATOR) |
Ted Lai | de93c93 | 2017-09-05 12:36:20 +0800 | [diff] [blame] | 897 | if self._should_reenable_play_store: |
| 898 | adb_shell('pm enable ' + _PLAY_STORE_PKG) |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 899 | adb_shell('settings put secure install_non_market_apps 0') |
| 900 | adb_shell('settings put global package_verifier_enable 1') |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 901 | adb_shell('settings put secure package_verifier_user_consent 0') |
| 902 | |
Luis Hector Chavez | b2e7cd7 | 2018-04-24 15:06:35 -0700 | [diff] [blame] | 903 | # Remove the adb keys without going through adb. This is because the |
| 904 | # 'rm' tool does not have permissions to remove the keys once they have |
| 905 | # been restorecon(8)ed. |
| 906 | utils.system_output('rm -f %s' % |
| 907 | pipes.quote(os.path.join( |
| 908 | get_android_data_root(), |
| 909 | os.path.relpath(_ANDROID_ADB_KEYS_PATH, '/')))) |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 910 | utils.system_output('adb kill-server') |
| 911 | |
| 912 | def block_outbound(self): |
| 913 | """ Blocks the connection from the container to outer network. |
| 914 | |
Abhishek Bhardwaj | b3b206f | 2017-04-04 15:52:47 -0700 | [diff] [blame] | 915 | The iptables settings accept only 100.115.92.2 port 5555 (adb) and |
Chung-yih Wang | a67fce0 | 2017-03-28 16:19:17 +0800 | [diff] [blame] | 916 | all local connections, e.g. uiautomator. |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 917 | """ |
| 918 | logging.info('Blocking outbound connection') |
Ricardo Quesada | 0abf315 | 2018-12-06 16:26:35 -0800 | [diff] [blame] | 919 | # ipv6 |
| 920 | _android_shell('ip6tables -I OUTPUT -j REJECT') |
| 921 | _android_shell('ip6tables -I OUTPUT -d ip6-localhost -j ACCEPT') |
| 922 | # ipv4 |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 923 | _android_shell('iptables -I OUTPUT -j REJECT') |
Ricardo Quesada | 0224279 | 2018-05-08 12:57:57 -0700 | [diff] [blame] | 924 | _android_shell('iptables -I OUTPUT -p tcp -s 100.115.92.2 ' |
| 925 | '--sport 5555 ' |
Ilja H. Friedel | ce8ebd6 | 2017-07-11 13:55:49 -0700 | [diff] [blame] | 926 | '-j ACCEPT') |
Ricardo Quesada | 0abf315 | 2018-12-06 16:26:35 -0800 | [diff] [blame] | 927 | _android_shell('iptables -I OUTPUT -d localhost -j ACCEPT') |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 928 | |
Victor Hsieh | 2eba892 | 2016-08-17 12:54:14 -0700 | [diff] [blame] | 929 | def unblock_outbound(self): |
| 930 | """ Unblocks the connection from the container to outer network. |
| 931 | |
| 932 | The iptables settings are not permanent which means they reset on |
| 933 | each instance invocation. But we can still use this function to |
| 934 | unblock the outbound connections during the test if needed. |
| 935 | """ |
| 936 | logging.info('Unblocking outbound connection') |
Ricardo Quesada | 0abf315 | 2018-12-06 16:26:35 -0800 | [diff] [blame] | 937 | # ipv4 |
| 938 | _android_shell('iptables -D OUTPUT -d localhost -j ACCEPT') |
Ricardo Quesada | 0224279 | 2018-05-08 12:57:57 -0700 | [diff] [blame] | 939 | _android_shell('iptables -D OUTPUT -p tcp -s 100.115.92.2 ' |
| 940 | '--sport 5555 ' |
Ilja H. Friedel | ce8ebd6 | 2017-07-11 13:55:49 -0700 | [diff] [blame] | 941 | '-j ACCEPT') |
Chung-yih Wang | c58c7ab | 2017-06-30 17:31:56 +0800 | [diff] [blame] | 942 | _android_shell('iptables -D OUTPUT -j REJECT') |
Ricardo Quesada | 0abf315 | 2018-12-06 16:26:35 -0800 | [diff] [blame] | 943 | # ipv6 |
| 944 | _android_shell('ip6tables -D OUTPUT -d ip6-localhost -j ACCEPT') |
| 945 | _android_shell('ip6tables -D OUTPUT -j REJECT') |
Ted Lai | de93c93 | 2017-09-05 12:36:20 +0800 | [diff] [blame] | 946 | |
Ted Lai | 4656ea8 | 2017-09-19 17:03:36 +0800 | [diff] [blame] | 947 | def _add_ui_object_not_found_handler(self): |
| 948 | """Logs the device dump upon uiautomator.UiObjectNotFoundException.""" |
| 949 | from uiautomator import device as d |
| 950 | d.handlers.on(lambda d: logging.debug('Device window dump:\n%s', |
| 951 | d.dump())) |
| 952 | |
Ted Lai | de93c93 | 2017-09-05 12:36:20 +0800 | [diff] [blame] | 953 | def _disable_play_store(self): |
| 954 | """Disables the Google Play Store app.""" |
| 955 | if is_package_disabled(_PLAY_STORE_PKG): |
| 956 | return |
Ted Lai | de93c93 | 2017-09-05 12:36:20 +0800 | [diff] [blame] | 957 | adb_shell('am force-stop ' + _PLAY_STORE_PKG) |
Lloyd Pique | 9368a8a | 2018-04-27 13:35:40 -0700 | [diff] [blame] | 958 | adb_shell('am start -a android.settings.APPLICATION_DETAILS_SETTINGS ' |
| 959 | '-d package:' + _PLAY_STORE_PKG) |
| 960 | |
| 961 | # Note: the straightforward "pm disable <package>" command would be |
| 962 | # better, but that requires root permissions, which aren't available on |
| 963 | # a pre-release image being tested. The only other way is through the |
| 964 | # Settings UI, but which might change. |
| 965 | from uiautomator import device as d |
| 966 | d(textMatches='(?i)DISABLE', packageName=_SETTINGS_PKG).wait.exists() |
| 967 | d(textMatches='(?i)DISABLE', packageName=_SETTINGS_PKG).click.wait() |
Ted Lai | de93c93 | 2017-09-05 12:36:20 +0800 | [diff] [blame] | 968 | d(textMatches='(?i)DISABLE APP').click.wait() |
| 969 | ok_button = d(textMatches='(?i)OK') |
| 970 | if ok_button.exists: |
| 971 | ok_button.click.wait() |
Hidehiko Abe | 755e8a3 | 2018-05-02 21:32:05 +0900 | [diff] [blame] | 972 | adb_shell('am force-stop ' + _SETTINGS_PKG) |