arc_util.py revision 747ea82f3b1a06e5e82964f42e909b217b2b6042
1# Copyright 2016 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#
5# arc_util.py is supposed to be called from chrome.py for ARC specific logic.
6# It should not import arc.py since it will create a import loop.
7
8import logging
9import os
10import select
11import tempfile
12import time
13
14from autotest_lib.client.common_lib import error
15from autotest_lib.client.common_lib import file_utils
16from autotest_lib.client.common_lib.cros import arc_common
17from telemetry.core import exceptions
18from telemetry.internal.browser import extension_page
19
20_ARC_SUPPORT_HOST_URL = 'chrome-extension://cnbgggchhmkkdmeppjobngjoejnihlei/'
21_ARC_SUPPORT_HOST_PAGENAME = '_generated_background_page.html'
22_DUMPSTATE_DEFAULT_TIMEOUT = 20
23_DUMPSTATE_PATH = '/var/log/arc-dumpstate.log'
24_DUMPSTATE_PIPE_PATH = '/run/arc/bugreport/pipe'
25_USERNAME = 'crosarcplusplustest@gmail.com'
26_ARCP_URL = 'https://sites.google.com/a/chromium.org/dev/chromium-os' \
27                '/testing/arcplusplus-testing/arcp'
28_OPT_IN_BEGIN = 'Initializing ARC opt-in flow.'
29_OPT_IN_FINISH = 'ARC opt-in flow complete.'
30
31def should_start_arc(arc_mode):
32    """
33    Determines whether ARC should be started.
34
35    @param arc_mode: mode as defined in arc_common.
36
37    @returns: True or False.
38
39    """
40    logging.debug('ARC is enabled in mode ' + str(arc_mode))
41    assert arc_mode is None or arc_mode in arc_common.ARC_MODES
42    return arc_mode in [arc_common.ARC_MODE_ENABLED,
43                        arc_common.ARC_MODE_ENABLED_ASYNC]
44
45
46def get_extra_chrome_flags():
47    """Returns extra Chrome flags for ARC tests to run"""
48    return ['--disable-arc-opt-in-verification']
49
50
51def post_processing_after_browser(chrome):
52    """
53    Called when a new browser instance has been initialized.
54
55    Note that this hook function is called regardless of arc_mode.
56
57    @param chrome: Chrome object.
58
59    """
60    # Remove any stale dumpstate files.
61    if os.path.isfile(_DUMPSTATE_PATH):
62        os.unlink(_DUMPSTATE_PATH)
63
64    # Wait for Android container ready if ARC is enabled.
65    if chrome.arc_mode == arc_common.ARC_MODE_ENABLED:
66        try:
67            arc_common.wait_for_android_boot()
68        except Exception:
69            # Save dumpstate so that we can figure out why boot does not
70            # complete.
71            _save_android_dumpstate()
72            raise
73
74
75def pre_processing_before_close(chrome):
76    """
77    Called when the browser instance is being closed.
78
79    Note that this hook function is called regardless of arc_mode.
80
81    @param chrome: Chrome object.
82
83    """
84    if not should_start_arc(chrome.arc_mode):
85        return
86    # TODO(b/29341443): Implement stopping of adb logcat when we start adb
87    # logcat for all tests
88
89    # Save dumpstate just before logout.
90    _save_android_dumpstate()
91
92
93def _save_android_dumpstate(timeout=_DUMPSTATE_DEFAULT_TIMEOUT):
94    """
95    Triggers a dumpstate and saves its contents to to /var/log/arc-dumpstate.log
96    with logging.
97
98    Exception thrown while doing dumpstate will be ignored.
99
100    @param timeout: The timeout in seconds.
101    """
102
103    try:
104        logging.info('Saving Android dumpstate.')
105        with open(_DUMPSTATE_PATH, 'w') as out:
106            # _DUMPSTATE_PIPE_PATH is a named pipe, so it permanently blocks if
107            # opened normally if the other end has not been opened. In order to
108            # avoid that, open the file with O_NONBLOCK and use a select loop to
109            # read from the file with a timeout.
110            fd = os.open(_DUMPSTATE_PIPE_PATH, os.O_RDONLY | os.O_NONBLOCK)
111            with os.fdopen(fd, 'r') as pipe:
112                end_time = time.time() + timeout
113                while True:
114                    remaining_time = end_time - time.time()
115                    if remaining_time <= 0:
116                        break
117                    rlist, _, _ = select.select([pipe], [], [], remaining_time)
118                    if pipe not in rlist:
119                        break
120                    buf = os.read(pipe.fileno(), 1024)
121                    if len(buf) == 0:
122                        break
123                    out.write(buf)
124        logging.info('Android dumpstate successfully saved.')
125    except Exception:
126        # Dumpstate is nice-to-have stuff. Do not make it as a fatal error.
127        logging.exception('Failed to save Android dumpstate.')
128
129
130def get_test_account_info():
131    """Retrieve test account information."""
132    with tempfile.NamedTemporaryFile() as pltp:
133        file_utils.download_file(_ARCP_URL, pltp.name)
134        password = pltp.read().rstrip()
135    return (_USERNAME, password)
136
137
138def set_browser_options_for_opt_in(b_options):
139    """
140    Setup Chrome for gaia login and opt_in.
141
142    @param b_options: browser options object used by chrome.Chrome.
143
144    """
145    b_options.username, b_options.password = get_test_account_info()
146    b_options.disable_default_apps = False
147    b_options.disable_component_extensions_with_background_pages = False
148    b_options.gaia_login = True
149
150
151def enable_play_store(autotest_ext, enabled):
152    """
153    Enable ARC++ Play Store
154
155    Do nothing if the account is managed.
156
157    @param autotest_ext: autotest extension object.
158
159    @param enabled: if True then perform opt-in, otherwise opt-out.
160
161    @returns: True if the opt-in should continue; else False.
162
163    """
164
165    if autotest_ext is None:
166         raise error.TestFail(
167                 'Could not change the Play Store enabled state because '
168                 'autotest API does not exist')
169
170    # Skip enabling for managed users, since value is policy enforced.
171    # Return early if a managed user has ArcEnabled set to false.
172    try:
173        autotest_ext.ExecuteJavaScript('''
174            chrome.autotestPrivate.getPlayStoreState(function(state) {
175              window.__play_store_state = state;
176            });
177        ''')
178        # Results must be available by the next invocation.
179        is_managed = autotest_ext.EvaluateJavaScript(
180            'window.__play_store_state.managed')
181        if is_managed:
182            logging.info('Determined that ARC is managed by user policy.')
183            policy_enabled = autotest_ext.EvaluateJavaScript(
184                'window.__play_store_state.enabled')
185            if enabled != policy_enabled:
186                logging.info(
187                    'Returning early since ARC is policy-enforced.')
188                return False
189        else:
190            autotest_ext.ExecuteJavaScript('''
191                    chrome.autotestPrivate.setPlayStoreEnabled(
192                        %s, function(enabled) {});
193                ''' % ('true' if enabled else 'false'))
194    except exceptions.EvaluateException as e:
195        raise error.TestFail('Could not change the Play Store enabled state '
196                             ' via autotest API. "%s".' % e)
197
198    return True
199
200
201def find_opt_in_extension_page(browser):
202    """
203    Find and verify the opt-in extension extension page.
204
205    @param browser: chrome.Chrome broswer object.
206
207    @returns: the extension page.
208
209    @raises: error.TestFail if extension is not found or is mal-formed.
210
211    """
212    opt_in_extension_id = extension_page.UrlToExtensionId(_ARC_SUPPORT_HOST_URL)
213    try:
214        extension_pages = browser.extensions.GetByExtensionId(
215            opt_in_extension_id)
216    except Exception, e:
217        raise error.TestFail('Could not locate extension for arc opt-in. '
218                             'Make sure disable_default_apps is False. '
219                             '"%s".' % e)
220
221    extension_main_page = None
222    for page in extension_pages:
223        url = page.EvaluateJavaScript('location.href;')
224        if url.endswith(_ARC_SUPPORT_HOST_PAGENAME):
225            extension_main_page = page
226            break
227    if not extension_main_page:
228        raise error.TestError('Found opt-in extension but not correct page!')
229    extension_main_page.WaitForDocumentReadyStateToBeComplete()
230
231    js_code_did_start_conditions = ['termsPage != null',
232            '(termsPage.isManaged_ || termsPage.state_ == LoadState.LOADED)']
233    try:
234        for condition in js_code_did_start_conditions:
235            extension_main_page.WaitForJavaScriptCondition(condition,
236                                                           timeout=60)
237    except Exception, e:
238        raise error.TestError('Error waiting for "%s": "%s".' % (condition, e))
239
240    return extension_main_page
241
242
243def opt_in_and_wait_for_completion(extension_main_page):
244    """
245    Step through the user input of the opt-in extension and wait for completion.
246
247    @param extension_main_page: opt-in extension object.
248
249    @raises error.TestFail if opt-in doesn't complete after timeout.
250
251    """
252    extension_main_page.ExecuteJavaScript('termsPage.onAgree()')
253
254    SIGN_IN_TIMEOUT = 120
255    try:
256        extension_main_page.WaitForJavaScriptCondition('!appWindow',
257                                                       timeout=SIGN_IN_TIMEOUT)
258    except Exception, e:
259        js_read_error_message = """
260            err = appWindow.contentWindow.document.getElementById(
261                    "error-message");
262            if (err) {
263                err.innerText;
264            }
265        """
266        err_msg = extension_main_page.EvaluateJavaScript(js_read_error_message)
267        err_msg = err_msg.strip()
268        logging.error('Error: %r', err_msg.encode('utf8'))
269        if err_msg:
270            raise error.TestFail('Opt-in app error: %s' % err_msg)
271        else:
272            raise error.TestFail('Opt-in app did not finish running after %s '
273                                 'seconds!' % SIGN_IN_TIMEOUT)
274    # Reset termsPage to be able to reuse OptIn page and wait condition for ToS
275    # are loaded.
276    extension_main_page.ExecuteJavaScript('termsPage = null')
277
278
279def opt_in(browser, autotest_ext):
280    """
281    Step through opt in and wait for it to complete.
282
283    Return early if the arc_setting cannot be set True.
284
285    @param browser: chrome.Chrome browser object.
286    @param autotest_ext: autotest extension object.
287
288    @raises: error.TestFail if opt in fails.
289
290    """
291
292    logging.info(_OPT_IN_BEGIN)
293    if not enable_play_store(autotest_ext, True):
294        return
295
296    extension_main_page = find_opt_in_extension_page(browser)
297    opt_in_and_wait_for_completion(extension_main_page)
298    logging.info(_OPT_IN_FINISH)
299