arc_util.py revision d33c21d5d384efdb53643bf1168b17628905ac68
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.bin import utils
15from autotest_lib.client.common_lib import error
16from autotest_lib.client.common_lib import file_utils
17from autotest_lib.client.common_lib.cros import arc_common
18from telemetry.internal.browser import extension_page
19
20_ARC_SUPPORT_HOST_URL = 'chrome-extension://cnbgggchhmkkdmeppjobngjoejnihlei/'
21_DUMPSTATE_DEFAULT_TIMEOUT = 20
22_DUMPSTATE_PATH = '/var/log/arc-dumpstate.log'
23_DUMPSTATE_PIPE_PATH = '/var/run/arc/bugreport/pipe'
24_USERNAME = 'powerloadtest@gmail.com'
25_USERNAME_DISPLAY = 'power.loadtest@gmail.com'
26_PLTP_URL = 'https://sites.google.com/a/chromium.org/dev/chromium-os' \
27                '/testing/power-testing/pltp/pltp'
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    # Wait for Android container ready if ARC is enabled.
61    if chrome.arc_mode == arc_common.ARC_MODE_ENABLED:
62        arc_common.wait_for_android_boot()
63    # Remove any stale dumpstate files.
64    if os.path.isfile(_DUMPSTATE_PATH):
65        os.unlink(_DUMPSTATE_PATH)
66
67
68def pre_processing_before_close(chrome):
69    """
70    Called when the browser instance is being closed.
71
72    Note that this hook function is called regardless of arc_mode.
73
74    @param chrome: Chrome object.
75
76    """
77    if not should_start_arc(chrome.arc_mode):
78        return
79    # TODO(b/29341443): Implement stopping of adb logcat when we start adb
80    # logcat for all tests
81
82    # Save dumpstate just before logout.
83    try:
84        logging.info('Saving Android dumpstate.')
85        _save_android_dumpstate()
86        logging.info('Android dumpstate successfully saved.')
87    except Exception:
88        # Dumpstate is nice-to-have stuff. Do not make it as a fatal error.
89        logging.exception('Failed to save Android dumpstate.')
90
91
92def _save_android_dumpstate(timeout=_DUMPSTATE_DEFAULT_TIMEOUT):
93    """
94    Triggers a dumpstate and saves its contents to to /var/log/arc-dumpstate.log
95
96    @param timeout: The timeout in seconds.
97    """
98
99    with open(_DUMPSTATE_PATH, 'w') as out:
100        # _DUMPSTATE_PIPE_PATH is a named pipe, so it permanently blocks if
101        # opened normally if the other end has not been opened. In order to
102        # avoid that, open the file with O_NONBLOCK and use a select loop to
103        # read from the file with a timeout.
104        fd = os.open(_DUMPSTATE_PIPE_PATH, os.O_RDONLY | os.O_NONBLOCK)
105        with os.fdopen(fd, 'r') as pipe:
106            end_time = time.time() + timeout
107            while True:
108                remaining_time = end_time - time.time()
109                if remaining_time <= 0:
110                    break
111                rlist, _, _ = select.select([pipe], [], [], remaining_time)
112                if pipe not in rlist:
113                    break
114                buf = os.read(pipe.fileno(), 1024)
115                if len(buf) == 0:
116                    break
117                out.write(buf)
118
119
120def set_browser_options_for_opt_in(b_options):
121    """
122    Setup Chrome for gaia login and opt_in.
123
124    @param b_options: browser options object used by chrome.Chrome.
125
126    """
127    b_options.username = _USERNAME
128    with tempfile.NamedTemporaryFile() as pltp:
129        file_utils.download_file(_PLTP_URL, pltp.name)
130        b_options.password = pltp.read().rstrip()
131    b_options.disable_default_apps = False
132    b_options.disable_component_extensions_with_background_pages = False
133    b_options.gaia_login = True
134
135
136def opt_in(browser):
137    """
138    Step through opt in and wait for it to complete.
139
140    @param browser: chrome.Chrome broswer object.
141
142    @raises: error.TestFail if opt in fails.
143
144    """
145    logging.info(_OPT_IN_BEGIN)
146
147    opt_in_extension_id = extension_page.UrlToExtensionId(_ARC_SUPPORT_HOST_URL)
148    try:
149        extension_main_page = browser.extensions.GetByExtensionId(
150            opt_in_extension_id)[0]
151    except Exception, e:
152        raise error.TestFail('Could not locate extension for arc opt-in.' +
153                             'Make sure disable_default_apps is False.')
154
155    settings_tab = browser.tabs[0]
156    settings_tab.Navigate('chrome://settings')
157    settings_tab.WaitForDocumentReadyStateToBeComplete()
158
159    try:
160        js_code_assert_arc_option_available = """
161            assert(document.getElementById('android-apps-enabled'));
162        """
163        settings_tab.ExecuteJavaScript(js_code_assert_arc_option_available)
164    except Exception, e:
165        raise error.TestFail('Could not locate section in chrome://settings' +
166                             ' to enable arc. Make sure arc is available.')
167
168    # Skip enabling for managed users, since value is policy enforced.
169    # Return early if a managed user has ArcEnabled set to false.
170    js_code_is_managed = ('document.getElementById('
171                          '"android-apps-enabled").disabled')
172    is_managed = settings_tab.EvaluateJavaScript(js_code_is_managed)
173    if is_managed:
174        logging.info('Determined that ARC++ is managed by user policy.')
175        js_code_policy_value = ('document.getElementById('
176                                '"android-apps-enabled").checked')
177        policy_value = settings_tab.EvaluateJavaScript(js_code_policy_value)
178        if not policy_value:
179            logging.info('Returning early since ARC++ is policy enforced off.')
180            return
181    else:
182        js_code_enable_arc = ('Preferences.setBooleanPref(\'arc.enabled\', '
183                                                          'true, true)')
184        settings_tab.ExecuteJavaScript(js_code_enable_arc)
185
186    js_code_did_start_conditions = ['appWindow', 'termsView',
187            ('!appWindow.contentWindow.document'
188             '.getElementById(\'terms\').hidden')]
189
190    extension_main_page.WaitForDocumentReadyStateToBeComplete()
191    for condition in js_code_did_start_conditions:
192        extension_main_page.WaitForJavaScriptExpression(condition, 60.0)
193
194    js_code_click_agree = """
195        doc = appWindow.contentWindow.document;
196        agree_button_element = doc.getElementById('button-agree');
197        agree_button_element.click();
198    """
199    extension_main_page.ExecuteJavaScript(js_code_click_agree)
200
201    js_code_is_lso_section_active = """
202        !appWindow.contentWindow.document.getElementById('lso').hidden
203    """
204    try:
205        extension_main_page.WaitForJavaScriptExpression(
206            js_code_is_lso_section_active, 120)
207    except Exception, e:
208        raise error.TestFail('Error occured while waiting for lso session. This' +
209                             'may have been caused if gaia login was not used.')
210
211    web_views = utils.poll_for_condition(
212            extension_main_page.GetWebviewContexts, timeout=60,
213            exception=error.TestError('WebviewContexts error during opt in!'))
214
215    js_code_is_sign_in_button_enabled = """
216        !document.getElementById('submit_approve_access')
217            .hasAttribute('disabled')
218    """
219    web_views[0].WaitForJavaScriptExpression(
220            js_code_is_sign_in_button_enabled, 60.0)
221
222    js_code_click_sign_in = """
223        sign_in_button_element = document.getElementById('submit_approve_access');
224        sign_in_button_element.click();
225    """
226    web_views[0].ExecuteJavaScript(js_code_click_sign_in)
227
228    # Wait for app to close (i.e. complete sign in).
229    SIGN_IN_TIMEOUT = 120
230    try:
231        extension_main_page.WaitForJavaScriptExpression('!appWindow',
232                                                        SIGN_IN_TIMEOUT)
233    except Exception, e:
234        js_read_error_message = """
235            err = appWindow.contentWindow.document.getElementById(
236                    "error-message");
237            if (err) {
238                err.innerText;
239            }
240        """
241        err_msg = extension_main_page.EvaluateJavaScript(js_read_error_message)
242        err_msg = err_msg.strip()
243        logging.error('Error: %s', err_msg.strip())
244        if err_msg:
245            raise error.TestFail('Opt-in app error: %s' % err_msg)
246        else:
247            raise error.TestFail('Opt-in app did not finish running after %s '
248                                 'seconds!' % SIGN_IN_TIMEOUT)
249
250    logging.info(_OPT_IN_FINISH)
251