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