# Copyright 2018 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import contextlib import logging from autotest_lib.client.common_lib import error from autotest_lib.server import autotest from autotest_lib.server.cros.tradefed import tradefed_constants as constants class ChromeLogin(object): """Context manager to handle Chrome login state.""" def need_reboot(self, hard_reboot=False): """Marks state as "dirty" - reboot needed during/after test.""" logging.info('Will reboot DUT when Chrome stops.') self._need_reboot = True if hard_reboot and self._host.servo: self._hard_reboot_on_failure = True def __init__(self, host, board=None, dont_override_profile=False, enable_default_apps=False, toggle_ndk=False, nativebridge64=False): """Initializes the ChromeLogin object. @param board: optional parameter to extend timeout for login for slow DUTs. Used in particular for virtual machines. @param dont_override_profile: reuses the existing test profile if any @param enable_default_apps: enables default apps (like Files app) @param toggle_ndk: toggles native bridge engine switch. @param nativebridge64: enables 64-bit native bridge experiment. """ self._host = host self._timeout = constants.LOGIN_BOARD_TIMEOUT.get( board, constants.LOGIN_DEFAULT_TIMEOUT) self._dont_override_profile = dont_override_profile self._enable_default_apps = enable_default_apps self._need_reboot = False self._hard_reboot_on_failure = False self._toggle_ndk = toggle_ndk self._nativebridge64 = nativebridge64 def _cmd_builder(self, verbose=False): """Gets remote command to start browser with ARC enabled.""" # If autotest is not installed on the host, as with moblab at times, # getting the autodir will raise an exception. cmd = autotest.Autotest.get_installed_autodir(self._host) cmd += '/bin/autologin.py --arc' # We want to suppress the Google doodle as it is not part of the image # and can be different content every day interacting with testing. cmd += ' --no-startup-window' # Disable several forms of auto-installation to stablize the tests. cmd += ' --no-arc-syncs' # Toggle the translation from houdini to ndk if self._toggle_ndk: cmd += ' --toggle_ndk' if self._dont_override_profile: logging.info('Starting Chrome with a possibly reused profile.') cmd += ' --dont_override_profile' else: logging.info('Starting Chrome with a fresh profile.') if self._enable_default_apps: logging.info('Using --enable_default_apps to start Chrome.') cmd += ' --enable_default_apps' if self._nativebridge64: cmd += ' --nativebridge64' if not verbose: cmd += ' > /dev/null 2>&1' return cmd def _login_by_script(self, timeout, verbose): """Runs the autologin.py script on the DUT to log in.""" self._host.run( self._cmd_builder(verbose=verbose), ignore_status=False, verbose=verbose, timeout=timeout) def _login(self, timeout, verbose=False, install_autotest=False): """Logs into Chrome. Raises an exception on failure.""" if not install_autotest: try: # Assume autotest to be already installed. self._login_by_script(timeout=timeout, verbose=verbose) except autotest.AutodirNotFoundError: logging.warning('Autotest not installed, forcing install...') install_autotest = True if install_autotest: # Installs the autotest client to the DUT by running a no-op test. autotest.Autotest(self._host).run_timed_test( 'dummy_Pass', timeout=2 * timeout, check_client_result=True) # The (re)run the login script. self._login_by_script(timeout=timeout, verbose=verbose) # Quick check if Android has really started. When autotest client # installed on the DUT was partially broken, the script may succeed # without actually logging into Chrome/Android. See b/129382439. self._host.run( # "/data/anr" is an arbitrary directory accessible only after # proper login and data mount. 'android-sh -c "ls /data/anr"', ignore_status=False, timeout=9) def enter(self): """Logs into Chrome with retry.""" timeout = self._timeout try: logging.info('Ensure Android is running (timeout=%d)...', timeout) self._login(timeout=timeout) except Exception as e: logging.error('Login failed.', exc_info=e) # Retry with more time, with refreshed client autotest installation, # and the DUT cleanup by rebooting. This can hide some failures. self._reboot() timeout *= 2 logging.info('Retrying failed login (timeout=%d)...', timeout) try: self._login(timeout=timeout, verbose=True, install_autotest=True) except Exception as e2: logging.error('Failed to login to Chrome', exc_info=e2) raise error.TestError('Failed to login to Chrome') def exit(self): """On exit restart the browser or reboot the machine.""" if not self._need_reboot: try: self._restart() except: logging.error('Restarting browser has failed.') self.need_reboot() if self._need_reboot: self._reboot() def _restart(self): """Restart Chrome browser.""" # We clean up /tmp (which is memory backed) from crashes and # other files. A reboot would have cleaned /tmp as well. # TODO(ihf): Remove "start ui" which is a nicety to non-ARC tests (i.e. # now we wait on login screen, but login() above will 'stop ui' again # before launching Chrome with ARC enabled). logging.info('Skipping reboot, restarting browser.') script = 'stop ui' script += '&& find /tmp/ -mindepth 1 -delete ' script += '&& start ui' self._host.run(script, ignore_status=False, verbose=False, timeout=120) def _reboot(self): """Reboot the machine.""" if self._hard_reboot_on_failure: logging.info('Powering OFF the DUT: %s', self._host) self._host.servo.get_power_state_controller().power_off() logging.info('Powering ON the DUT: %s', self._host) self._host.servo.get_power_state_controller().power_on() else: logging.info('Rebooting...') self._host.reboot() @contextlib.contextmanager def login_chrome(hosts, **kwargs): """Returns Chrome log-in context manager for multiple hosts. """ # TODO(pwang): Chromelogin takes 10+ seconds for it to successfully # enter. Parallelize if this becomes a bottleneck. instances = [ChromeLogin(host, **kwargs) for host in hosts] for instance in instances: instance.enter() yield instances for instance in instances: instance.exit()