Home | History | Annotate | Download | only in cros
      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 
      8 import logging
      9 import os
     10 import select
     11 import tempfile
     12 import time
     13 
     14 from autotest_lib.client.common_lib import error
     15 from autotest_lib.client.common_lib import file_utils
     16 from autotest_lib.client.common_lib.cros import arc_common
     17 from telemetry.internal.browser import extension_page
     18 
     19 _ARC_SUPPORT_HOST_URL = 'chrome-extension://cnbgggchhmkkdmeppjobngjoejnihlei/'
     20 _ARC_SUPPORT_HOST_PAGENAME = '_generated_background_page.html'
     21 _DUMPSTATE_DEFAULT_TIMEOUT = 20
     22 _DUMPSTATE_PATH = '/var/log/arc-dumpstate.log'
     23 _DUMPSTATE_PIPE_PATH = '/var/run/arc/bugreport/pipe'
     24 _USERNAME = 'arcplusplustest (at] gmail.com'
     25 _USERNAME_DISPLAY = 'arcplusplustest (at] 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 
     31 def 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 
     46 def get_extra_chrome_flags():
     47     """Returns extra Chrome flags for ARC tests to run"""
     48     return ['--disable-arc-opt-in-verification']
     49 
     50 
     51 def 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 
     75 def 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 
     93 def _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 
    130 def set_browser_options_for_opt_in(b_options):
    131     """
    132     Setup Chrome for gaia login and opt_in.
    133 
    134     @param b_options: browser options object used by chrome.Chrome.
    135 
    136     """
    137     b_options.username = _USERNAME
    138     with tempfile.NamedTemporaryFile() as pltp:
    139         file_utils.download_file(_ARCP_URL, pltp.name)
    140         b_options.password = pltp.read().rstrip()
    141     b_options.disable_default_apps = False
    142     b_options.disable_component_extensions_with_background_pages = False
    143     b_options.gaia_login = True
    144 
    145 
    146 def enable_arc_setting(browser):
    147     """
    148     Enable ARC++ via the settings page checkbox.
    149 
    150     Do nothing if the account is managed.
    151 
    152     @param browser: chrome.Chrome broswer object.
    153 
    154     @returns: True if the opt-in should continue; else False.
    155 
    156     """
    157     settings_tab = browser.tabs.New()
    158 
    159     try:
    160         settings_tab.Navigate('chrome://settings-frame')
    161         settings_tab.WaitForDocumentReadyStateToBeComplete()
    162 
    163         try:
    164             settings_tab.ExecuteJavaScript(
    165                     'assert(document.getElementById("android-apps-enabled"))')
    166         except Exception, e:
    167             raise error.TestFail('Could not locate section in chrome://settings'
    168                                  ' to enable arc. Make sure ARC is available.')
    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         is_managed = settings_tab.EvaluateJavaScript(
    173                 'document.getElementById("android-apps-enabled").disabled')
    174         if is_managed:
    175             logging.info('Determined that ARC is managed by user policy.')
    176             policy_value = settings_tab.EvaluateJavaScript(
    177                     'document.getElementById("android-apps-enabled").checked')
    178             if not policy_value:
    179                 logging.info(
    180                         'Returning early since ARC is policy-enforced off.')
    181                 return False
    182         else:
    183             settings_tab.ExecuteJavaScript(
    184                     'Preferences.setBooleanPref("arc.enabled", true, true)')
    185     finally:
    186         settings_tab.Close()
    187 
    188     return True
    189 
    190 
    191 def find_opt_in_extension_page(browser):
    192     """
    193     Find and verify the opt-in extension extension page.
    194 
    195     @param browser: chrome.Chrome broswer object.
    196 
    197     @returns: the extension page.
    198 
    199     @raises: error.TestFail if extension is not found or is mal-formed.
    200 
    201     """
    202     opt_in_extension_id = extension_page.UrlToExtensionId(_ARC_SUPPORT_HOST_URL)
    203     try:
    204         extension_pages = browser.extensions.GetByExtensionId(
    205             opt_in_extension_id)
    206     except Exception, e:
    207         raise error.TestFail('Could not locate extension for arc opt-in. '
    208                              'Make sure disable_default_apps is False. '
    209                              '"%s".' % e)
    210 
    211     extension_main_page = None
    212     for page in extension_pages:
    213         url = page.EvaluateJavaScript('location.href;')
    214         if url.endswith(_ARC_SUPPORT_HOST_PAGENAME):
    215             extension_main_page = page
    216             break
    217     if not extension_main_page:
    218         raise error.TestError('Found opt-in extension but not correct page!')
    219     extension_main_page.WaitForDocumentReadyStateToBeComplete()
    220 
    221     js_code_did_start_conditions = ['termsPage != null',
    222             '(termsPage.isManaged_ || termsPage.state_ == LoadState.LOADED)']
    223     try:
    224         for condition in js_code_did_start_conditions:
    225             extension_main_page.WaitForJavaScriptCondition(condition,
    226                                                            timeout=60)
    227     except Exception, e:
    228         raise error.TestError('Error waiting for "%s": "%s".' % (condition, e))
    229 
    230     return extension_main_page
    231 
    232 
    233 def opt_in_and_wait_for_completion(extension_main_page):
    234     """
    235     Step through the user input of the opt-in extension and wait for completion.
    236 
    237     @param extension_main_page: opt-in extension object.
    238 
    239     @raises error.TestFail if opt-in doesn't complete after timeout.
    240 
    241     """
    242     js_code_click_agree = """
    243         doc = appWindow.contentWindow.document;
    244         agree_button_element = doc.getElementById('button-agree');
    245         agree_button_element.click();
    246     """
    247     extension_main_page.ExecuteJavaScript(js_code_click_agree)
    248 
    249     SIGN_IN_TIMEOUT = 120
    250     try:
    251         extension_main_page.WaitForJavaScriptCondition('!appWindow',
    252                                                        timeout=SIGN_IN_TIMEOUT)
    253     except Exception, e:
    254         js_read_error_message = """
    255             err = appWindow.contentWindow.document.getElementById(
    256                     "error-message");
    257             if (err) {
    258                 err.innerText;
    259             }
    260         """
    261         err_msg = extension_main_page.EvaluateJavaScript(js_read_error_message)
    262         err_msg = err_msg.strip()
    263         logging.error('Error: %s', err_msg.strip())
    264         if err_msg:
    265             raise error.TestFail('Opt-in app error: %s' % err_msg)
    266         else:
    267             raise error.TestFail('Opt-in app did not finish running after %s '
    268                                  'seconds!' % SIGN_IN_TIMEOUT)
    269 
    270 
    271 def opt_in(browser):
    272     """
    273     Step through opt in and wait for it to complete.
    274 
    275     Return early if the arc_setting cannot be set True.
    276 
    277     @param browser: chrome.Chrome broswer object.
    278 
    279     @raises: error.TestFail if opt in fails.
    280 
    281     """
    282     logging.info(_OPT_IN_BEGIN)
    283     if not enable_arc_setting(browser):
    284         return
    285     extension_main_page = find_opt_in_extension_page(browser)
    286     opt_in_and_wait_for_completion(extension_main_page)
    287     logging.info(_OPT_IN_FINISH)
    288