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.core import exceptions
     18 from 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 (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 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 
    138 def 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 
    151 def 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 
    201 def 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 
    243 def 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 
    279 def 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