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 glob 6import logging 7import os 8import pipes 9import re 10import shutil 11import subprocess 12import sys 13import tempfile 14 15from autotest_lib.client.bin import test, utils 16from autotest_lib.client.common_lib import error 17from autotest_lib.client.common_lib.cros import chrome, arc_common 18 19_ADB_KEYS_PATH = '/tmp/adb_keys' 20_ADB_VENDOR_KEYS = 'ADB_VENDOR_KEYS' 21_ANDROID_CONTAINER_PID_PATH = '/var/run/containers/android_*/container.pid' 22_SCREENSHOT_DIR_PATH = '/var/log/arc-screenshots' 23_SCREENSHOT_BASENAME = 'arc-screenshot' 24_MAX_SCREENSHOT_NUM = 10 25_SDCARD_PID_PATH = '/var/run/arc/sdcard.pid' 26_ANDROID_ADB_KEYS_PATH = '/data/misc/adb/adb_keys' 27_PROCESS_CHECK_INTERVAL_SECONDS = 1 28_WAIT_FOR_ADB_READY = 60 29_WAIT_FOR_ANDROID_PROCESS_SECONDS = 60 30_WAIT_FOR_DATA_MOUNTED_SECONDS = 60 31_VAR_LOGCAT_PATH = '/var/log/logcat' 32 33 34def setup_adb_host(): 35 """Setup ADB host keys. 36 37 This sets up the files and environment variables that wait_for_adb_ready() 38 needs""" 39 if _ADB_VENDOR_KEYS in os.environ: 40 return 41 if not os.path.exists(_ADB_KEYS_PATH): 42 os.mkdir(_ADB_KEYS_PATH) 43 # adb expects $HOME to be writable. 44 os.environ['HOME'] = _ADB_KEYS_PATH 45 46 # Generate and save keys for adb if needed 47 key_path = os.path.join(_ADB_KEYS_PATH, 'test_key') 48 if not os.path.exists(key_path): 49 utils.system('adb keygen ' + pipes.quote(key_path)) 50 os.environ[_ADB_VENDOR_KEYS] = key_path 51 52 53def adb_connect(): 54 """Attempt to connect ADB to the Android container. 55 56 Returns true if successful. Do not call this function directly. Call 57 wait_for_adb_ready() instead.""" 58 if not is_android_booted(): 59 return False 60 if utils.system('adb connect localhost:22', ignore_status=True) != 0: 61 return False 62 return is_adb_connected() 63 64 65def is_adb_connected(): 66 """Return true if adb is connected to the container.""" 67 output = utils.system_output('adb get-state', ignore_status=True) 68 logging.debug('adb get-state: %s', output) 69 return output.strip() == 'device' 70 71 72def is_partial_boot_enabled(): 73 """Return true if partial boot is enabled. 74 75 When partial boot is enabled, Android is started at login screen without 76 any persistent state (e.g. /data is not mounted). 77 """ 78 return _android_shell('getprop ro.boot.partial_boot') == '1' 79 80 81def _is_android_data_mounted(): 82 """Return true if Android's /data is mounted with partial boot enabled.""" 83 return _android_shell('getprop ro.data_mounted') == '1' 84 85 86def _wait_for_data_mounted(timeout=_WAIT_FOR_DATA_MOUNTED_SECONDS): 87 utils.poll_for_condition( 88 condition=_is_android_data_mounted, 89 desc='Wait for /data mounted', 90 timeout=timeout, 91 sleep_interval=_PROCESS_CHECK_INTERVAL_SECONDS) 92 93 94def wait_for_adb_ready(timeout=_WAIT_FOR_ADB_READY): 95 """Wait for the ADB client to connect to the ARC container. 96 97 @param timeout: Timeout in seconds. 98 """ 99 # When partial boot is enabled, although adbd is started at login screen, 100 # we still need /data to be mounted to set up key-based authentication. 101 # /data should be mounted once the user has logged in. 102 if is_partial_boot_enabled(): 103 _wait_for_data_mounted() 104 105 setup_adb_host() 106 if is_adb_connected(): 107 return 108 109 # Push keys for adb. 110 pubkey_path = os.environ[_ADB_VENDOR_KEYS] + '.pub' 111 with open(pubkey_path, 'r') as f: 112 _write_android_file(_ANDROID_ADB_KEYS_PATH, f.read()) 113 _android_shell('restorecon ' + pipes.quote(_ANDROID_ADB_KEYS_PATH)) 114 115 # This starts adbd. 116 _android_shell('setprop sys.usb.config mtp,adb') 117 118 # Kill existing adb server to ensure that a full reconnect is performed. 119 utils.system('adb kill-server', ignore_status=True) 120 121 exception = error.TestFail('adb is not ready in %d seconds.' % timeout) 122 utils.poll_for_condition(adb_connect, 123 exception, 124 timeout) 125 126 127def grant_permissions(package, permissions): 128 """Grants permissions to a package. 129 130 @param package: Package name. 131 @param permissions: A list of permissions. 132 133 """ 134 for permission in permissions: 135 adb_shell('pm grant %s android.permission.%s' % ( 136 pipes.quote(package), pipes.quote(permission))) 137 138 139def adb_cmd(cmd, **kwargs): 140 """Executed cmd using adb. Must wait for adb ready. 141 142 @param cmd: Command to run. 143 """ 144 wait_for_adb_ready() 145 return utils.system_output('adb %s' % cmd, **kwargs) 146 147 148def adb_shell(cmd, **kwargs): 149 """Executed shell command with adb. 150 151 @param cmd: Command to run. 152 """ 153 output = adb_cmd('shell %s' % pipes.quote(cmd), **kwargs) 154 # Some android commands include a trailing CRLF in their output. 155 if kwargs.pop('strip_trailing_whitespace', True): 156 output = output.rstrip() 157 return output 158 159 160def adb_install(apk): 161 """Install an apk into container. You must connect first. 162 163 @param apk: Package to install. 164 """ 165 return adb_cmd('install -r %s' % apk) 166 167 168def adb_uninstall(apk): 169 """Remove an apk from container. You must connect first. 170 171 @param apk: Package to uninstall. 172 """ 173 return adb_cmd('uninstall %s' % apk) 174 175 176def adb_reboot(): 177 """Reboots the container. You must connect first.""" 178 adb_root() 179 return adb_cmd('reboot', ignore_status=True) 180 181 182def adb_root(): 183 """Restart adbd with root permission.""" 184 adb_cmd('root') 185 186 187def get_container_root(): 188 """Returns path to Android container root directory. 189 190 Raises: 191 TestError if no container root directory is found, or 192 more than one container root directories are found. 193 """ 194 # Find the PID file rather than the android_XXXXXX/ directory to ignore 195 # stale and empty android_XXXXXX/ directories when they exist. 196 # TODO(yusukes): Investigate why libcontainer sometimes fails to remove 197 # the directory. See b/63376749 for more details. 198 arc_container_pid_files = glob.glob(_ANDROID_CONTAINER_PID_PATH) 199 200 if len(arc_container_pid_files) == 0: 201 raise error.TestError('Android container not available') 202 203 if len(arc_container_pid_files) > 1: 204 raise error.TestError('Multiple Android containers found: %r. ' 205 'Reboot your DUT to recover.' % ( 206 arc_container_pid_files)) 207 208 return os.path.dirname(arc_container_pid_files[0]) 209 210 211def get_job_pid(job_name): 212 """Returns the PID of an upstart job.""" 213 status = utils.system_output('status %s' % job_name) 214 match = re.match(r'^%s start/running, process (\d+)$' % job_name, 215 status) 216 if not match: 217 raise error.TestError('Unexpected status: "%s"' % status) 218 return match.group(1) 219 220 221def get_container_pid(): 222 """Returns the PID of the container.""" 223 container_root = get_container_root() 224 pid_path = os.path.join(container_root, 'container.pid') 225 return utils.read_one_line(pid_path) 226 227 228def get_sdcard_pid(): 229 """Returns the PID of the sdcard container.""" 230 return utils.read_one_line(_SDCARD_PID_PATH) 231 232 233def get_removable_media_pid(): 234 """Returns the PID of the arc-removable-media FUSE daemon.""" 235 job_pid = get_job_pid('arc-removable-media') 236 # |job_pid| is the minijail process, obtain the PID of the process running 237 # inside the mount namespace. 238 # FUSE process is the only process running as chronos in the process group. 239 return utils.system_output('pgrep -u chronos -g %s' % job_pid) 240 241 242def get_obb_mounter_pid(): 243 """Returns the PID of the OBB mounter.""" 244 return utils.system_output('pgrep -f -u root ^/usr/bin/arc-obb-mounter') 245 246 247def is_android_booted(): 248 """Return whether Android has completed booting.""" 249 # We used to check sys.boot_completed system property to detect Android has 250 # booted in Android M, but in Android N it is set long before BOOT_COMPLETED 251 # intent is broadcast. So we read event logs instead. 252 log = _android_shell( 253 'logcat -d -b events *:S arc_system_event', ignore_status=True) 254 return 'ArcAppLauncher:started' in log 255 256 257def is_android_process_running(process_name): 258 """Return whether Android has completed booting. 259 260 @param process_name: Process name. 261 """ 262 output = adb_shell('ps | grep %s' % pipes.quote(' %s$' % process_name)) 263 return bool(output) 264 265 266def check_android_file_exists(filename): 267 """Checks whether the given file exists in the Android filesystem 268 269 @param filename: File to check. 270 """ 271 return adb_shell('test -e {} && echo FileExists'.format( 272 pipes.quote(filename))).find("FileExists") >= 0 273 274 275def read_android_file(filename): 276 """Reads a file in Android filesystem. 277 278 @param filename: File to read. 279 """ 280 with tempfile.NamedTemporaryFile() as tmpfile: 281 adb_cmd('pull %s %s' % (pipes.quote(filename), 282 pipes.quote(tmpfile.name))) 283 with open(tmpfile.name) as f: 284 return f.read() 285 286 return None 287 288 289def write_android_file(filename, data): 290 """Writes to a file in Android filesystem. 291 292 @param filename: File to write. 293 @param data: Data to write. 294 """ 295 with tempfile.NamedTemporaryFile() as tmpfile: 296 tmpfile.write(data) 297 tmpfile.flush() 298 299 adb_cmd('push %s %s' % (pipes.quote(tmpfile.name), 300 pipes.quote(filename))) 301 302 303def _write_android_file(filename, data): 304 """Writes to a file in Android filesystem. 305 306 This is an internal function used to bootstrap adb. 307 Tests should use write_android_file instead. 308 """ 309 android_cmd = 'cat > %s' % pipes.quote(filename) 310 cros_cmd = 'android-sh -c %s' % pipes.quote(android_cmd) 311 utils.run(cros_cmd, stdin=data) 312 313 314def remove_android_file(filename): 315 """Removes a file in Android filesystem. 316 317 @param filename: File to remove. 318 """ 319 adb_shell('rm -f %s' % pipes.quote(filename)) 320 321 322def wait_for_android_boot(timeout=None): 323 """Sleep until Android has completed booting or timeout occurs. 324 325 @param timeout: Timeout in seconds. 326 """ 327 arc_common.wait_for_android_boot(timeout) 328 329 330def wait_for_android_process(process_name, 331 timeout=_WAIT_FOR_ANDROID_PROCESS_SECONDS): 332 """Sleep until an Android process is running or timeout occurs. 333 334 @param process_name: Process name. 335 @param timeout: Timeout in seconds. 336 """ 337 condition = lambda: is_android_process_running(process_name) 338 utils.poll_for_condition(condition=condition, 339 desc='%s is running' % process_name, 340 timeout=timeout, 341 sleep_interval=_PROCESS_CHECK_INTERVAL_SECONDS) 342 343 344def _android_shell(cmd, **kwargs): 345 """Execute cmd instead the Android container. 346 347 This function is strictly for internal use only, as commands do not run in a 348 fully consistent Android environment. Prefer adb_shell instead. 349 """ 350 return utils.system_output('android-sh -c {}'.format(pipes.quote(cmd)), 351 **kwargs) 352 353 354def is_android_container_alive(): 355 """Check if android container is alive.""" 356 try: 357 container_pid = get_container_pid() 358 except Exception, e: 359 logging.error('is_android_container_alive failed: %r', e) 360 return False 361 return utils.pid_is_alive(int(container_pid)) 362 363 364def is_package_installed(package): 365 """Check if a package is installed. adb must be ready. 366 367 @param package: Package in request. 368 """ 369 packages = adb_shell('pm list packages').splitlines() 370 package_entry = 'package:{}'.format(package) 371 return package_entry in packages 372 373 374def _before_iteration_hook(obj): 375 """Executed by parent class before every iteration. 376 377 This function resets the run_once_finished flag before every iteration 378 so we can detect failure on every single iteration. 379 380 Args: 381 obj: the test itself 382 """ 383 obj.run_once_finished = False 384 385 386def _after_iteration_hook(obj): 387 """Executed by parent class after every iteration. 388 389 The parent class will handle exceptions and failures in the run and will 390 always call this hook afterwards. Take a screenshot if the run has not 391 been marked as finished (i.e. there was a failure/exception). 392 393 Args: 394 obj: the test itself 395 """ 396 if not obj.run_once_finished: 397 if not os.path.exists(_SCREENSHOT_DIR_PATH): 398 os.mkdir(_SCREENSHOT_DIR_PATH, 0755) 399 obj.num_screenshots += 1 400 if obj.num_screenshots <= _MAX_SCREENSHOT_NUM: 401 logging.warning('Iteration %d failed, taking a screenshot.', 402 obj.iteration) 403 from cros.graphics.gbm import crtcScreenshot 404 try: 405 image = crtcScreenshot() 406 image.save('{}/{}_iter{}.png'.format(_SCREENSHOT_DIR_PATH, 407 _SCREENSHOT_BASENAME, 408 obj.iteration)) 409 except Exception: 410 e = sys.exc_info()[0] 411 logging.warning('Unable to capture screenshot. %s' % e) 412 else: 413 logging.warning('Too many failures, no screenshot taken') 414 415 416def send_keycode(keycode): 417 """Sends the given keycode to the container 418 419 @param keycode: keycode to send. 420 """ 421 adb_shell('input keyevent {}'.format(keycode)) 422 423 424def get_android_sdk_version(): 425 """Returns the Android SDK version. 426 427 This function can be called before Android container boots. 428 """ 429 with open('/etc/lsb-release') as f: 430 values = dict(line.split('=', 1) for line in f.read().splitlines()) 431 try: 432 return int(values['CHROMEOS_ARC_ANDROID_SDK_VERSION']) 433 except (KeyError, ValueError): 434 raise error.TestError('Could not determine Android SDK version') 435 436 437class ArcTest(test.test): 438 """ Base class of ARC Test. 439 440 This class could be used as super class of an ARC test for saving 441 redundant codes for container bringup, autotest-dep package(s) including 442 uiautomator setup if required, and apks install/remove during 443 arc_setup/arc_teardown, respectively. By default arc_setup() is called in 444 initialize() after Android have been brought up. It could also be overridden 445 to perform non-default tasks. For example, a simple ArcHelloWorldTest can be 446 just implemented with print 'HelloWorld' in its run_once() and no other 447 functions are required. We could expect ArcHelloWorldTest would bring up 448 browser and wait for container up, then print 'Hello World', and shutdown 449 browser after. As a precaution, if you overwrite initialize(), arc_setup(), 450 or cleanup() function(s) in ARC test, remember to call the corresponding 451 function(s) in this base class as well. 452 453 """ 454 version = 1 455 _PKG_UIAUTOMATOR = 'uiautomator' 456 _FULL_PKG_NAME_UIAUTOMATOR = 'com.github.uiautomator' 457 458 def __init__(self, *args, **kwargs): 459 """Initialize flag setting.""" 460 super(ArcTest, self).__init__(*args, **kwargs) 461 self.initialized = False 462 # Set the flag run_once_finished to detect if a test is executed 463 # successfully without any exception thrown. Otherwise, generate 464 # a screenshot in /var/log for debugging. 465 self.run_once_finished = False 466 self.logcat_proc = None 467 self.dep_package = None 468 self.apks = None 469 self.full_pkg_names = [] 470 self.uiautomator = False 471 self.email_id = None 472 self.password = None 473 self._chrome = None 474 if os.path.exists(_SCREENSHOT_DIR_PATH): 475 shutil.rmtree(_SCREENSHOT_DIR_PATH) 476 self.register_before_iteration_hook(_before_iteration_hook) 477 self.register_after_iteration_hook(_after_iteration_hook) 478 # Keep track of the number of debug screenshots taken and keep the 479 # total number sane to avoid issues. 480 self.num_screenshots = 0 481 482 def initialize(self, extension_path=None, 483 arc_mode=arc_common.ARC_MODE_ENABLED, **chrome_kargs): 484 """Log in to a test account.""" 485 extension_paths = [extension_path] if extension_path else [] 486 self._chrome = chrome.Chrome(extension_paths=extension_paths, 487 arc_mode=arc_mode, 488 **chrome_kargs) 489 if extension_path: 490 self._extension = self._chrome.get_extension(extension_path) 491 else: 492 self._extension = None 493 # With ARC enabled, Chrome will wait until container to boot up 494 # before returning here, see chrome.py. 495 self.initialized = True 496 try: 497 if is_android_container_alive(): 498 self.arc_setup() 499 else: 500 logging.error('Container is alive?') 501 except Exception as err: 502 self.cleanup() 503 raise error.TestFail(err) 504 505 def after_run_once(self): 506 """Executed after run_once() only if there were no errors. 507 508 This function marks the run as finished with a flag. If there was a 509 failure the flag won't be set and the failure can then be detected by 510 testing the run_once_finished flag. 511 """ 512 logging.info('After run_once') 513 self.run_once_finished = True 514 515 def cleanup(self): 516 """Log out of Chrome.""" 517 if not self.initialized: 518 logging.info('Skipping ARC cleanup: not initialized') 519 return 520 logging.info('Starting ARC cleanup') 521 try: 522 if is_android_container_alive(): 523 self.arc_teardown() 524 except Exception as err: 525 raise error.TestFail(err) 526 finally: 527 try: 528 self._stop_logcat() 529 finally: 530 if self._chrome is not None: 531 self._chrome.close() 532 533 def arc_setup(self, dep_package=None, apks=None, full_pkg_names=None, 534 uiautomator=False, email_id=None, password=None, 535 block_outbound=False): 536 """ARC test setup: Setup dependencies and install apks. 537 538 This function disables package verification and enables non-market 539 APK installation. Then, it installs specified APK(s) and uiautomator 540 package and path if required in a test. 541 542 @param dep_package: Package name of autotest_deps APK package. 543 @param apks: Array of APK names to be installed in dep_package. 544 @param full_pkg_names: Array of full package names to be removed 545 in teardown. 546 @param uiautomator: uiautomator python package is required or not. 547 548 @param email_id: email id to be attached to the android. Only used 549 when account_util is set to true. 550 @param password: password related to the email_id. 551 @param block_outbound: block outbound network traffic during a test. 552 """ 553 if not self.initialized: 554 logging.info('Skipping ARC setup: not initialized') 555 return 556 logging.info('Starting ARC setup') 557 self.dep_package = dep_package 558 self.apks = apks 559 self.uiautomator = uiautomator 560 self.email_id = email_id 561 self.password = password 562 # Setup dependent packages if required 563 packages = [] 564 if dep_package: 565 packages.append(dep_package) 566 if self.uiautomator: 567 packages.append(self._PKG_UIAUTOMATOR) 568 if packages: 569 logging.info('Setting up dependent package(s) %s', packages) 570 self.job.setup_dep(packages) 571 572 # TODO(b/29341443): Run logcat on non ArcTest test cases too. 573 with open(_VAR_LOGCAT_PATH, 'w') as f: 574 self.logcat_proc = subprocess.Popen( 575 ['android-sh', '-c', 'logcat -v threadtime'], 576 stdout=f, 577 stderr=subprocess.STDOUT, 578 close_fds=True) 579 580 wait_for_adb_ready() 581 582 # package_verifier_user_consent == -1 means to reject Google's 583 # verification on the server side through Play Store. This suppress a 584 # consent dialog from the system. 585 adb_shell('settings put secure package_verifier_user_consent -1') 586 adb_shell('settings put global package_verifier_enable 0') 587 adb_shell('settings put secure install_non_market_apps 1') 588 589 if self.dep_package: 590 apk_path = os.path.join(self.autodir, 'deps', self.dep_package) 591 if self.apks: 592 for apk in self.apks: 593 logging.info('Installing %s', apk) 594 adb_install('%s/%s' % (apk_path, apk)) 595 # Verify if package(s) are installed correctly 596 if not full_pkg_names: 597 raise error.TestError('Package names of apks expected') 598 for pkg in full_pkg_names: 599 logging.info('Check if %s is installed', pkg) 600 if not is_package_installed(pkg): 601 raise error.TestError('Package %s not found' % pkg) 602 # Make sure full_pkg_names contains installed packages only 603 # so arc_teardown() knows what packages to uninstall. 604 self.full_pkg_names.append(pkg) 605 606 if self.uiautomator: 607 path = os.path.join(self.autodir, 'deps', self._PKG_UIAUTOMATOR) 608 sys.path.append(path) 609 if block_outbound: 610 self.block_outbound() 611 612 def _stop_logcat(self): 613 """Stop the adb logcat process gracefully.""" 614 if not self.logcat_proc: 615 return 616 # Running `adb kill-server` should have killed `adb logcat` 617 # process, but just in case also send termination signal. 618 self.logcat_proc.terminate() 619 620 class TimeoutException(Exception): 621 """Termination timeout timed out.""" 622 623 try: 624 utils.poll_for_condition( 625 condition=lambda: self.logcat_proc.poll() is not None, 626 exception=TimeoutException, 627 timeout=10, 628 sleep_interval=0.1, 629 desc='Waiting for adb logcat to terminate') 630 except TimeoutException: 631 logging.info('Killing adb logcat due to timeout') 632 self.logcat_proc.kill() 633 self.logcat_proc.wait() 634 635 def arc_teardown(self): 636 """ARC test teardown. 637 638 This function removes all installed packages in arc_setup stage 639 first. Then, it restores package verification and disables non-market 640 APK installation. 641 642 """ 643 if self.full_pkg_names: 644 for pkg in self.full_pkg_names: 645 logging.info('Uninstalling %s', pkg) 646 if not is_package_installed(pkg): 647 raise error.TestError('Package %s was not installed' % pkg) 648 adb_uninstall(pkg) 649 if self.uiautomator: 650 logging.info('Uninstalling %s', self._FULL_PKG_NAME_UIAUTOMATOR) 651 adb_uninstall(self._FULL_PKG_NAME_UIAUTOMATOR) 652 adb_shell('settings put secure install_non_market_apps 0') 653 adb_shell('settings put global package_verifier_enable 1') 654 adb_shell('settings put secure package_verifier_user_consent 0') 655 656 remove_android_file(_ANDROID_ADB_KEYS_PATH) 657 utils.system_output('adb kill-server') 658 659 def block_outbound(self): 660 """ Blocks the connection from the container to outer network. 661 662 The iptables settings accept only 100.115.92.2 port 5555 (adb) and 663 all local connections, e.g. uiautomator. 664 """ 665 logging.info('Blocking outbound connection') 666 _android_shell('iptables -I OUTPUT -j REJECT') 667 _android_shell('iptables -I OUTPUT -p tcp -s 100.115.92.2 --sport 5555 ' 668 '-j ACCEPT') 669 _android_shell('iptables -I OUTPUT -p tcp -d localhost -j ACCEPT') 670 671 def unblock_outbound(self): 672 """ Unblocks the connection from the container to outer network. 673 674 The iptables settings are not permanent which means they reset on 675 each instance invocation. But we can still use this function to 676 unblock the outbound connections during the test if needed. 677 """ 678 logging.info('Unblocking outbound connection') 679 _android_shell('iptables -D OUTPUT -p tcp -d localhost -j ACCEPT') 680 _android_shell('iptables -D OUTPUT -p tcp -s 100.115.92.2 --sport 5555 ' 681 '-j ACCEPT') 682 _android_shell('iptables -D OUTPUT -j REJECT') 683