1# Copyright 2018 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 contextlib 6import logging 7 8from autotest_lib.client.common_lib import error 9from autotest_lib.server import autotest 10from autotest_lib.server.cros.tradefed import tradefed_constants as constants 11 12 13class ChromeLogin(object): 14 """Context manager to handle Chrome login state.""" 15 16 def need_reboot(self, hard_reboot=False): 17 """Marks state as "dirty" - reboot needed during/after test.""" 18 logging.info('Will reboot DUT when Chrome stops.') 19 self._need_reboot = True 20 if hard_reboot and self._host.servo: 21 self._hard_reboot_on_failure = True 22 23 def __init__(self, host, board=None, dont_override_profile=False, 24 enable_default_apps=False, toggle_ndk=False, 25 nativebridge64=False): 26 """Initializes the ChromeLogin object. 27 28 @param board: optional parameter to extend timeout for login for slow 29 DUTs. Used in particular for virtual machines. 30 @param dont_override_profile: reuses the existing test profile if any 31 @param enable_default_apps: enables default apps (like Files app) 32 @param toggle_ndk: toggles native bridge engine switch. 33 @param nativebridge64: enables 64-bit native bridge experiment. 34 """ 35 self._host = host 36 self._timeout = constants.LOGIN_BOARD_TIMEOUT.get( 37 board, constants.LOGIN_DEFAULT_TIMEOUT) 38 self._dont_override_profile = dont_override_profile 39 self._enable_default_apps = enable_default_apps 40 self._need_reboot = False 41 self._hard_reboot_on_failure = False 42 self._toggle_ndk = toggle_ndk 43 self._nativebridge64 = nativebridge64 44 45 def _cmd_builder(self, verbose=False): 46 """Gets remote command to start browser with ARC enabled.""" 47 # If autotest is not installed on the host, as with moblab at times, 48 # getting the autodir will raise an exception. 49 cmd = autotest.Autotest.get_installed_autodir(self._host) 50 cmd += '/bin/autologin.py --arc' 51 52 # We want to suppress the Google doodle as it is not part of the image 53 # and can be different content every day interacting with testing. 54 cmd += ' --no-startup-window' 55 # Disable several forms of auto-installation to stablize the tests. 56 cmd += ' --no-arc-syncs' 57 # Toggle the translation from houdini to ndk 58 if self._toggle_ndk: 59 cmd += ' --toggle_ndk' 60 if self._dont_override_profile: 61 logging.info('Starting Chrome with a possibly reused profile.') 62 cmd += ' --dont_override_profile' 63 else: 64 logging.info('Starting Chrome with a fresh profile.') 65 if self._enable_default_apps: 66 logging.info('Using --enable_default_apps to start Chrome.') 67 cmd += ' --enable_default_apps' 68 if self._nativebridge64: 69 cmd += ' --nativebridge64' 70 if not verbose: 71 cmd += ' > /dev/null 2>&1' 72 return cmd 73 74 def _login_by_script(self, timeout, verbose): 75 """Runs the autologin.py script on the DUT to log in.""" 76 self._host.run( 77 self._cmd_builder(verbose=verbose), 78 ignore_status=False, 79 verbose=verbose, 80 timeout=timeout) 81 82 def _login(self, timeout, verbose=False, install_autotest=False): 83 """Logs into Chrome. Raises an exception on failure.""" 84 if not install_autotest: 85 try: 86 # Assume autotest to be already installed. 87 self._login_by_script(timeout=timeout, verbose=verbose) 88 except autotest.AutodirNotFoundError: 89 logging.warning('Autotest not installed, forcing install...') 90 install_autotest = True 91 92 if install_autotest: 93 # Installs the autotest client to the DUT by running a no-op test. 94 autotest.Autotest(self._host).run_timed_test( 95 'dummy_Pass', timeout=2 * timeout, check_client_result=True) 96 # The (re)run the login script. 97 self._login_by_script(timeout=timeout, verbose=verbose) 98 99 # Quick check if Android has really started. When autotest client 100 # installed on the DUT was partially broken, the script may succeed 101 # without actually logging into Chrome/Android. See b/129382439. 102 self._host.run( 103 # "/data/anr" is an arbitrary directory accessible only after 104 # proper login and data mount. 105 'android-sh -c "ls /data/anr"', 106 ignore_status=False, timeout=9) 107 108 def enter(self): 109 """Logs into Chrome with retry.""" 110 timeout = self._timeout 111 try: 112 logging.info('Ensure Android is running (timeout=%d)...', timeout) 113 self._login(timeout=timeout) 114 except Exception as e: 115 logging.error('Login failed.', exc_info=e) 116 # Retry with more time, with refreshed client autotest installation, 117 # and the DUT cleanup by rebooting. This can hide some failures. 118 self._reboot() 119 timeout *= 2 120 logging.info('Retrying failed login (timeout=%d)...', timeout) 121 try: 122 self._login(timeout=timeout, 123 verbose=True, 124 install_autotest=True) 125 except Exception as e2: 126 logging.error('Failed to login to Chrome', exc_info=e2) 127 raise error.TestError('Failed to login to Chrome') 128 129 def exit(self): 130 """On exit restart the browser or reboot the machine.""" 131 if not self._need_reboot: 132 try: 133 self._restart() 134 except: 135 logging.error('Restarting browser has failed.') 136 self.need_reboot() 137 if self._need_reboot: 138 self._reboot() 139 140 def _restart(self): 141 """Restart Chrome browser.""" 142 # We clean up /tmp (which is memory backed) from crashes and 143 # other files. A reboot would have cleaned /tmp as well. 144 # TODO(ihf): Remove "start ui" which is a nicety to non-ARC tests (i.e. 145 # now we wait on login screen, but login() above will 'stop ui' again 146 # before launching Chrome with ARC enabled). 147 logging.info('Skipping reboot, restarting browser.') 148 script = 'stop ui' 149 script += '&& find /tmp/ -mindepth 1 -delete ' 150 script += '&& start ui' 151 self._host.run(script, ignore_status=False, verbose=False, timeout=120) 152 153 def _reboot(self): 154 """Reboot the machine.""" 155 if self._hard_reboot_on_failure: 156 logging.info('Powering OFF the DUT: %s', self._host) 157 self._host.servo.get_power_state_controller().power_off() 158 logging.info('Powering ON the DUT: %s', self._host) 159 self._host.servo.get_power_state_controller().power_on() 160 else: 161 logging.info('Rebooting...') 162 self._host.reboot() 163 164 165@contextlib.contextmanager 166def login_chrome(hosts, **kwargs): 167 """Returns Chrome log-in context manager for multiple hosts. """ 168 # TODO(pwang): Chromelogin takes 10+ seconds for it to successfully 169 # enter. Parallelize if this becomes a bottleneck. 170 instances = [ChromeLogin(host, **kwargs) for host in hosts] 171 for instance in instances: 172 instance.enter() 173 yield instances 174 for instance in instances: 175 instance.exit() 176