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