• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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