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 # We used to check sys.boot_completed system property to detect Android has 210 # booted in Android M, but in Android N it is set long before BOOT_COMPLETED 211 # intent is broadcast. So we read event logs instead. 212 log = _android_shell( 213 'logcat -d -b events *:S arc_system_event', ignore_status=True) 214 return 'ArcAppLauncher:started' in log 215 216 217def is_android_process_running(process_name): 218 """Return whether Android has completed booting. 219 220 @param process_name: Process name. 221 """ 222 output = adb_shell('ps | grep %s' % pipes.quote(' %s$' % process_name)) 223 return bool(output) 224 225 226def check_android_file_exists(filename): 227 """Checks whether the given file exists in the Android filesystem 228 229 @param filename: File to check. 230 """ 231 return adb_shell('test -e {} && echo FileExists'.format( 232 pipes.quote(filename))).find("FileExists") >= 0 233 234 235def read_android_file(filename): 236 """Reads a file in Android filesystem. 237 238 @param filename: File to read. 239 """ 240 with tempfile.NamedTemporaryFile() as tmpfile: 241 adb_cmd('pull %s %s' % (pipes.quote(filename), pipes.quote(tmpfile.name))) 242 with open(tmpfile.name) as f: 243 return f.read() 244 245 return None 246 247 248def write_android_file(filename, data): 249 """Writes to a file in Android filesystem. 250 251 @param filename: File to write. 252 @param data: Data to write. 253 """ 254 with tempfile.NamedTemporaryFile() as tmpfile: 255 tmpfile.write(data) 256 tmpfile.flush() 257 258 adb_cmd('push %s %s' % (pipes.quote(tmpfile.name), pipes.quote(filename))) 259 260 261def _write_android_file(filename, data): 262 """Writes to a file in Android filesystem. 263 264 This is an internal function used to bootstrap adb. 265 Tests should use write_android_file instead. 266 """ 267 android_cmd = 'cat > %s' % pipes.quote(filename) 268 cros_cmd = 'android-sh -c %s' % pipes.quote(android_cmd) 269 utils.run(cros_cmd, stdin=data) 270 271 272def remove_android_file(filename): 273 """Removes a file in Android filesystem. 274 275 @param filename: File to remove. 276 """ 277 adb_shell('rm -f %s' % pipes.quote(filename)) 278 279 280def wait_for_android_boot(timeout=None): 281 """Sleep until Android has completed booting or timeout occurs. 282 283 @param timeout: Timeout in seconds. 284 """ 285 arc_common.wait_for_android_boot(timeout) 286 287 288def wait_for_android_process(process_name, 289 timeout=_WAIT_FOR_ANDROID_PROCESS_SECONDS): 290 """Sleep until an Android process is running or timeout occurs. 291 292 @param process_name: Process name. 293 @param timeout: Timeout in seconds. 294 """ 295 condition = lambda: is_android_process_running(process_name) 296 utils.poll_for_condition(condition=condition, 297 desc='%s is running' % process_name, 298 timeout=timeout, 299 sleep_interval=_PROCESS_CHECK_INTERVAL_SECONDS) 300 301 302def _android_shell(cmd, **kwargs): 303 """Execute cmd instead the Android container. 304 305 This function is strictly for internal use only, as commands do not run in a 306 fully consistent Android environment. Prefer adb_shell instead. 307 """ 308 return utils.system_output('android-sh -c {}'.format(pipes.quote(cmd)), 309 **kwargs) 310 311 312def is_android_container_alive(): 313 """Check if android container is alive.""" 314 try: 315 container_pid = get_container_pid() 316 except Exception: 317 return False 318 return utils.pid_is_alive(int(container_pid)) 319 320 321def is_package_installed(package): 322 """Check if a package is installed. adb must be ready. 323 324 @param package: Package in request. 325 """ 326 packages = adb_shell('pm list packages').splitlines() 327 package_entry = 'package:{}'.format(package) 328 return package_entry in packages 329 330 331def _before_iteration_hook(obj): 332 """Executed by parent class before every iteration. 333 334 This function resets the run_once_finished flag before every iteration 335 so we can detect failure on every single iteration. 336 337 Args: 338 obj: the test itself 339 """ 340 obj.run_once_finished = False 341 342 343def _after_iteration_hook(obj): 344 """Executed by parent class after every iteration. 345 346 The parent class will handle exceptions and failures in the run and will 347 always call this hook afterwards. Take a screenshot if the run has not 348 been marked as finished (i.e. there was a failure/exception). 349 350 Args: 351 obj: the test itself 352 """ 353 if not obj.run_once_finished: 354 if not os.path.exists(_SCREENSHOT_DIR_PATH): 355 os.mkdir(_SCREENSHOT_DIR_PATH, 0755) 356 obj.num_screenshots += 1 357 if obj.num_screenshots <= _MAX_SCREENSHOT_NUM: 358 logging.warning('Iteration %d failed, taking a screenshot.', 359 obj.iteration) 360 from cros.graphics.drm import crtcScreenshot 361 image = crtcScreenshot() 362 image.save('{}/{}_iter{}.png'.format(_SCREENSHOT_DIR_PATH, 363 _SCREENSHOT_BASENAME, 364 obj.iteration)) 365 else: 366 logging.warning('Too many failures, no screenshot taken') 367 368 369def send_keycode(keycode): 370 """Sends the given keycode to the container 371 372 @param keycode: keycode to send. 373 """ 374 adb_shell('input keyevent {}'.format(keycode)) 375 376 377def get_android_sdk_version(): 378 """Returns the Android SDK version. 379 380 This function can be called before Android container boots. 381 """ 382 with open('/etc/lsb-release') as f: 383 values = dict(line.split('=', 1) for line in f.read().splitlines()) 384 try: 385 return int(values['CHROMEOS_ARC_ANDROID_SDK_VERSION']) 386 except (KeyError, ValueError): 387 raise error.TestError('Could not determine Android SDK version') 388 389 390class ArcTest(test.test): 391 """ Base class of ARC Test. 392 393 This class could be used as super class of an ARC test for saving 394 redundant codes for container bringup, autotest-dep package(s) including 395 uiautomator setup if required, and apks install/remove during 396 arc_setup/arc_teardown, respectively. By default arc_setup() is called in 397 initialize() after Android have been brought up. It could also be overridden 398 to perform non-default tasks. For example, a simple ArcHelloWorldTest can be 399 just implemented with print 'HelloWorld' in its run_once() and no other 400 functions are required. We could expect ArcHelloWorldTest would bring up 401 browser and wait for container up, then print 'Hello World', and shutdown 402 browser after. As a precaution, if you overwrite initialize(), arc_setup(), 403 or cleanup() function(s) in ARC test, remember to call the corresponding 404 function(s) in this base class as well. 405 406 """ 407 version = 1 408 _PKG_UIAUTOMATOR = 'uiautomator' 409 _FULL_PKG_NAME_UIAUTOMATOR = 'com.github.uiautomator' 410 411 def __init__(self, *args, **kwargs): 412 """Initialize flag setting.""" 413 super(ArcTest, self).__init__(*args, **kwargs) 414 self.initialized = False 415 # Set the flag run_once_finished to detect if a test is executed 416 # successfully without any exception thrown. Otherwise, generate 417 # a screenshot in /var/log for debugging. 418 self.run_once_finished = False 419 self.logcat_proc = None 420 self.dep_package = None 421 self.apks = None 422 self.full_pkg_names = [] 423 self.uiautomator = False 424 self.email_id = None 425 self.password = None 426 self._chrome = None 427 if os.path.exists(_SCREENSHOT_DIR_PATH): 428 shutil.rmtree(_SCREENSHOT_DIR_PATH) 429 self.register_before_iteration_hook(_before_iteration_hook) 430 self.register_after_iteration_hook(_after_iteration_hook) 431 # Keep track of the number of debug screenshots taken and keep the 432 # total number sane to avoid issues. 433 self.num_screenshots = 0 434 435 def initialize(self, extension_path=None, 436 arc_mode=arc_common.ARC_MODE_ENABLED, **chrome_kargs): 437 """Log in to a test account.""" 438 extension_paths = [extension_path] if extension_path else [] 439 self._chrome = chrome.Chrome(extension_paths=extension_paths, 440 arc_mode=arc_mode, 441 **chrome_kargs) 442 if extension_path: 443 self._extension = self._chrome.get_extension(extension_path) 444 else: 445 self._extension = None 446 # With ARC enabled, Chrome will wait until container to boot up 447 # before returning here, see chrome.py. 448 self.initialized = True 449 try: 450 if is_android_container_alive(): 451 self.arc_setup() 452 else: 453 logging.error('Container is alive?') 454 except Exception as err: 455 self.cleanup() 456 raise error.TestFail(err) 457 458 def after_run_once(self): 459 """Executed after run_once() only if there were no errors. 460 461 This function marks the run as finished with a flag. If there was a 462 failure the flag won't be set and the failure can then be detected by 463 testing the run_once_finished flag. 464 """ 465 logging.info('After run_once') 466 self.run_once_finished = True 467 468 def cleanup(self): 469 """Log out of Chrome.""" 470 if not self.initialized: 471 logging.info('Skipping ARC cleanup: not initialized') 472 return 473 logging.info('Starting ARC cleanup') 474 try: 475 if is_android_container_alive(): 476 self.arc_teardown() 477 except Exception as err: 478 raise error.TestFail(err) 479 finally: 480 try: 481 self._stop_logcat() 482 finally: 483 if self._chrome is not None: 484 self._chrome.close() 485 486 def arc_setup(self, dep_package=None, apks=None, full_pkg_names=[], 487 uiautomator=False, email_id=None, password=None): 488 """ARC test setup: Setup dependencies and install apks. 489 490 This function disables package verification and enables non-market 491 APK installation. Then, it installs specified APK(s) and uiautomator 492 package and path if required in a test. 493 494 @param dep_package: Package name of autotest_deps APK package. 495 @param apks: Array of APK names to be installed in dep_package. 496 @param full_pkg_names: Array of full package names to be removed 497 in teardown. 498 @param uiautomator: uiautomator python package is required or not. 499 500 @param email_id: email id to be attached to the android. Only used 501 when account_util is set to true. 502 @param password: password related to the email_id. 503 """ 504 if not self.initialized: 505 logging.info('Skipping ARC setup: not initialized') 506 return 507 logging.info('Starting ARC setup') 508 self.dep_package = dep_package 509 self.apks = apks 510 self.uiautomator = uiautomator 511 self.email_id = email_id 512 self.password = password 513 # Setup dependent packages if required 514 packages = [] 515 if dep_package: 516 packages.append(dep_package) 517 if self.uiautomator: 518 packages.append(self._PKG_UIAUTOMATOR) 519 if packages: 520 logging.info('Setting up dependent package(s) %s', packages) 521 self.job.setup_dep(packages) 522 523 # TODO(b/29341443): Run logcat on non ArcTest test cases too. 524 with open(_VAR_LOGCAT_PATH, 'w') as f: 525 self.logcat_proc = subprocess.Popen( 526 ['android-sh', '-c', 'logcat -v threadtime'], 527 stdout=f, 528 stderr=subprocess.STDOUT, 529 close_fds=True) 530 531 wait_for_adb_ready() 532 533 # package_verifier_user_consent == -1 means to reject Google's 534 # verification on the server side through Play Store. This suppress a 535 # consent dialog from the system. 536 adb_shell('settings put secure package_verifier_user_consent -1') 537 adb_shell('settings put global package_verifier_enable 0') 538 adb_shell('settings put secure install_non_market_apps 1') 539 540 if self.dep_package: 541 apk_path = os.path.join(self.autodir, 'deps', self.dep_package) 542 if self.apks: 543 for apk in self.apks: 544 logging.info('Installing %s', apk) 545 adb_install('%s/%s' % (apk_path, apk)) 546 # Verify if package(s) are installed correctly 547 if not full_pkg_names: 548 raise error.TestError('Package names of apks expected') 549 for pkg in full_pkg_names: 550 logging.info('Check if %s is installed', pkg) 551 if not is_package_installed(pkg): 552 raise error.TestError('Package %s not found' % pkg) 553 # Make sure full_pkg_names contains installed packages only 554 # so arc_teardown() knows what packages to uninstall. 555 self.full_pkg_names.append(pkg) 556 557 if self.uiautomator: 558 path = os.path.join(self.autodir, 'deps', self._PKG_UIAUTOMATOR) 559 sys.path.append(path) 560 561 def _stop_logcat(self): 562 """Stop the adb logcat process gracefully.""" 563 if not self.logcat_proc: 564 return 565 # Running `adb kill-server` should have killed `adb logcat` 566 # process, but just in case also send termination signal. 567 self.logcat_proc.terminate() 568 569 class TimeoutException(Exception): 570 """Termination timeout timed out.""" 571 572 try: 573 utils.poll_for_condition( 574 condition=lambda: self.logcat_proc.poll() is not None, 575 exception=TimeoutException, 576 timeout=10, 577 sleep_interval=0.1, 578 desc='Waiting for adb logcat to terminate') 579 except TimeoutException: 580 logging.info('Killing adb logcat due to timeout') 581 self.logcat_proc.kill() 582 self.logcat_proc.wait() 583 584 def arc_teardown(self): 585 """ARC test teardown. 586 587 This function removes all installed packages in arc_setup stage 588 first. Then, it restores package verification and disables non-market 589 APK installation. 590 591 """ 592 if self.full_pkg_names: 593 for pkg in self.full_pkg_names: 594 logging.info('Uninstalling %s', pkg) 595 if not is_package_installed(pkg): 596 raise error.TestError('Package %s was not installed' % pkg) 597 adb_uninstall(pkg) 598 if self.uiautomator: 599 logging.info('Uninstalling %s', self._FULL_PKG_NAME_UIAUTOMATOR) 600 adb_uninstall(self._FULL_PKG_NAME_UIAUTOMATOR) 601 adb_shell('settings put secure install_non_market_apps 0') 602 adb_shell('settings put global package_verifier_enable 1') 603 adb_shell('settings put secure package_verifier_user_consent 0') 604 605 remove_android_file(_ANDROID_ADB_KEYS_PATH) 606 utils.system_output('adb kill-server') 607 608 def block_outbound(self): 609 """ Blocks the connection from the container to outer network. 610 611 The iptables settings accept only 192.168.254.2 port 5555 (adb) and 612 localhost port 9008 (uiautomator) 613 """ 614 logging.info('Blocking outbound connection') 615 _android_shell('iptables -I OUTPUT -j REJECT') 616 _android_shell('iptables -I OUTPUT -p tcp -s 192.168.254.2 --sport 5555 -j ACCEPT') 617 _android_shell('iptables -I OUTPUT -p tcp -d localhost --dport 9008 -j ACCEPT') 618 _android_shell('iptables -I OUTPUT -p tcp -s localhost --sport 9008 -j ACCEPT') 619 620 621 def unblock_outbound(self): 622 """ Unblocks the connection from the container to outer network. 623 624 The iptables settings are not permanent which means they reset on 625 each instance invocation. But we can still use this function to 626 unblock the outbound connections during the test if needed. 627 """ 628 logging.info('Unblocking outbound connection') 629 _android_shell('iptables -D OUTPUT -p tcp -s localhost --sport 9008 -j ACCEPT') 630 _android_shell('iptables -D OUTPUT -p tcp -d localhost --dport 9008 -j ACCEPT') 631 _android_shell('iptables -D OUTPUT -p tcp -s 192.168.254.2 --sport 5555 -j ACCEPT') 632 _android_shell('iptables -D OUTPUT -j REJECT') 633