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