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