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 collections 9import logging 10import os 11import tempfile 12 13from autotest_lib.client.bin import utils 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_PATH = '/var/log/arc-dumpstate.log' 23_USERNAME = 'crosarcplusplustest@gmail.com' 24_ARCP_URL = 'https://sites.google.com/a/chromium.org/dev/chromium-os' \ 25 '/testing/arcplusplus-testing/arcp' 26_OPT_IN_BEGIN = 'Initializing ARC opt-in flow.' 27_OPT_IN_SKIP = 'Skip waiting provisioning completed.' 28_OPT_IN_FINISH = 'ARC opt-in flow complete.' 29 30_SIGN_IN_TIMEOUT = 120 31_SIGN_IN_CHECK_INTERVAL = 1 32 33 34# Describes ARC session state. 35# - provisioned is set to True once ARC is provisioned. 36# - tos_needed is set to True in case ARC Terms of Service need to be shown. 37# This depends on ARC Terms of Service acceptance and policy for 38# ARC preferences, such as backup and restore and use of location 39# services. 40ArcSessionState = collections.namedtuple( 41 'ArcSessionState', ['provisioned', 'tos_needed']) 42 43 44def get_arc_session_state(autotest_ext): 45 """Returns the runtime state of ARC session. 46 47 @param autotest_ext private autotest API. 48 49 @raises error.TestFail in case autotest API failure or if ARC is not 50 allowed for the device. 51 52 @return ArcSessionState that describes the state of current ARC session. 53 54 """ 55 get_arc_state = ''' 56 new Promise((resolve, reject) => { 57 chrome.autotestPrivate.getArcState((state) => { 58 if (chrome.runtime.lastError) { 59 reject(new Error(chrome.runtime.lastError.message)); 60 return; 61 } 62 resolve(state); 63 }); 64 }) 65 ''' 66 try: 67 state = autotest_ext.EvaluateJavaScript(get_arc_state, promise=True) 68 return ArcSessionState(state['provisioned'], state['tosNeeded']) 69 except exceptions.EvaluateException as e: 70 raise error.TestFail('Could not get ARC session state "%s".' % e) 71 72 73def _wait_arc_provisioned(autotest_ext): 74 """Waits until ARC provisioning is completed. 75 76 @param autotest_ext private autotest API. 77 78 @raises: error.TimeoutException if provisioning is not complete. 79 80 """ 81 utils.poll_for_condition( 82 condition=lambda: get_arc_session_state(autotest_ext).provisioned, 83 desc='Wait for ARC is provisioned', 84 timeout=_SIGN_IN_TIMEOUT, 85 sleep_interval=_SIGN_IN_CHECK_INTERVAL) 86 87 88def should_start_arc(arc_mode): 89 """ 90 Determines whether ARC should be started. 91 92 @param arc_mode: mode as defined in arc_common. 93 94 @returns: True or False. 95 96 """ 97 logging.debug('ARC is enabled in mode %s', arc_mode) 98 assert arc_mode is None or arc_mode in arc_common.ARC_MODES 99 return arc_mode in [arc_common.ARC_MODE_ENABLED, 100 arc_common.ARC_MODE_ENABLED_ASYNC] 101 102 103def post_processing_after_browser(chrome, timeout): 104 """ 105 Called when a new browser instance has been initialized. 106 107 Note that this hook function is called regardless of arc_mode. 108 109 @param chrome: Chrome object. 110 111 """ 112 # Remove any stale dumpstate files. 113 if os.path.isfile(_DUMPSTATE_PATH): 114 os.unlink(_DUMPSTATE_PATH) 115 116 # Wait for Android container ready if ARC is enabled. 117 if chrome.arc_mode == arc_common.ARC_MODE_ENABLED: 118 try: 119 arc_common.wait_for_android_boot(timeout) 120 except Exception: 121 # Save dumpstate so that we can figure out why boot does not 122 # complete. 123 _save_android_dumpstate() 124 raise 125 126 127def pre_processing_before_close(chrome): 128 """ 129 Called when the browser instance is being closed. 130 131 Note that this hook function is called regardless of arc_mode. 132 133 @param chrome: Chrome object. 134 135 """ 136 if not should_start_arc(chrome.arc_mode): 137 return 138 # TODO(b/29341443): Implement stopping of adb logcat when we start adb 139 # logcat for all tests 140 141 # Save dumpstate just before logout. 142 _save_android_dumpstate() 143 144 145def _save_android_dumpstate(): 146 """ 147 Triggers a dumpstate and saves its contents to to /var/log/arc-dumpstate.log 148 with logging. 149 150 Exception thrown while doing dumpstate will be ignored. 151 """ 152 153 try: 154 logging.info('Saving Android dumpstate.') 155 with open(_DUMPSTATE_PATH, 'w') as out: 156 log = utils.system_output('android-sh -c arc-bugreport') 157 out.write(log) 158 logging.info('Android dumpstate successfully saved.') 159 except Exception: 160 # Dumpstate is nice-to-have stuff. Do not make it as a fatal error. 161 logging.exception('Failed to save Android dumpstate.') 162 163 164def get_test_account_info(): 165 """Retrieve test account information.""" 166 with tempfile.NamedTemporaryFile() as pltp: 167 file_utils.download_file(_ARCP_URL, pltp.name) 168 password = pltp.read().rstrip() 169 return (_USERNAME, password) 170 171 172def set_browser_options_for_opt_in(b_options): 173 """ 174 Setup Chrome for gaia login and opt_in. 175 176 @param b_options: browser options object used by chrome.Chrome. 177 178 """ 179 b_options.username, b_options.password = get_test_account_info() 180 b_options.disable_default_apps = False 181 b_options.disable_component_extensions_with_background_pages = False 182 b_options.gaia_login = True 183 184 185def enable_play_store(autotest_ext, enabled, enable_managed_policy=True): 186 """ 187 Enable ARC++ Play Store 188 189 Do nothing if the account is managed and enable_managed_policy is set to 190 True. 191 192 @param autotest_ext: autotest extension object. 193 194 @param enabled: if True then perform opt-in, otherwise opt-out. 195 196 @param enable_managed_policy: If False then policy check is ignored for 197 managed user case and ARC++ is forced enabled or disabled. 198 199 @returns: True if the opt-in should continue; else False. 200 201 """ 202 203 if autotest_ext is None: 204 raise error.TestFail( 205 'Could not change the Play Store enabled state because ' 206 'autotest API does not exist') 207 208 # Skip enabling for managed users, since value is policy enforced. 209 # Return early if a managed user has ArcEnabled set to false. 210 get_play_store_state = ''' 211 new Promise((resolve, reject) => { 212 chrome.autotestPrivate.getPlayStoreState((state) => { 213 if (chrome.runtime.lastError) { 214 reject(new Error(chrome.runtime.lastError.message)); 215 return; 216 } 217 resolve(state); 218 }); 219 }) 220 ''' 221 try: 222 state = autotest_ext.EvaluateJavaScript(get_play_store_state, 223 promise=True) 224 if state['managed']: 225 # Handle managed case. 226 logging.info('Determined that ARC is managed by user policy.') 227 if enable_managed_policy: 228 if enabled == state['enabled']: 229 return True 230 logging.info('Returning early since ARC is policy-enforced.') 231 return False 232 logging.info('Forcing ARC %s, ignore managed state.', 233 ('enabled' if enabled else 'disabled')) 234 235 autotest_ext.ExecuteJavaScript(''' 236 chrome.autotestPrivate.setPlayStoreEnabled( 237 %s, function(enabled) {}); 238 ''' % ('true' if enabled else 'false')) 239 except exceptions.EvaluateException as e: 240 raise error.TestFail('Could not change the Play Store enabled state ' 241 ' via autotest API. "%s".' % e) 242 243 return True 244 245 246def find_opt_in_extension_page(browser): 247 """ 248 Find and verify the opt-in extension extension page. 249 250 @param browser: chrome.Chrome broswer object. 251 252 @returns: the extension page. 253 254 @raises: error.TestFail if extension is not found or is mal-formed. 255 256 """ 257 opt_in_extension_id = extension_page.UrlToExtensionId(_ARC_SUPPORT_HOST_URL) 258 try: 259 extension_pages = browser.extensions.GetByExtensionId( 260 opt_in_extension_id) 261 except Exception, e: 262 raise error.TestFail('Could not locate extension for arc opt-in. ' 263 'Make sure disable_default_apps is False. ' 264 '"%s".' % e) 265 266 extension_main_page = None 267 for page in extension_pages: 268 url = page.EvaluateJavaScript('location.href;') 269 if url.endswith(_ARC_SUPPORT_HOST_PAGENAME): 270 extension_main_page = page 271 break 272 if not extension_main_page: 273 raise error.TestError('Found opt-in extension but not correct page!') 274 extension_main_page.WaitForDocumentReadyStateToBeComplete() 275 276 js_code_did_start_conditions = ['termsPage != null', 277 '(termsPage.isManaged_ || termsPage.state_ == LoadState.LOADED)'] 278 try: 279 for condition in js_code_did_start_conditions: 280 extension_main_page.WaitForJavaScriptCondition(condition, 281 timeout=60) 282 except Exception, e: 283 raise error.TestError('Error waiting for "%s": "%s".' % (condition, e)) 284 285 return extension_main_page 286 287 288def opt_in_and_wait_for_completion(extension_main_page): 289 """ 290 Step through the user input of the opt-in extension and wait for completion. 291 292 @param extension_main_page: opt-in extension object. 293 294 @raises error.TestFail if opt-in doesn't complete after timeout. 295 296 """ 297 extension_main_page.ExecuteJavaScript('termsPage.onAgree()') 298 299 try: 300 extension_main_page.WaitForJavaScriptCondition('!appWindow', 301 timeout=_SIGN_IN_TIMEOUT) 302 except Exception, e: 303 js_read_error_message = """ 304 err = appWindow.contentWindow.document.getElementById( 305 "error-message"); 306 if (err) { 307 err.innerText; 308 } 309 """ 310 err_msg = extension_main_page.EvaluateJavaScript(js_read_error_message) 311 err_msg = err_msg.strip() 312 logging.error('Error: %r', err_msg) 313 if err_msg: 314 raise error.TestFail('Opt-in app error: %r' % err_msg) 315 else: 316 raise error.TestFail('Opt-in app did not finish running after %s ' 317 'seconds!' % _SIGN_IN_TIMEOUT) 318 # Reset termsPage to be able to reuse OptIn page and wait condition for ToS 319 # are loaded. 320 extension_main_page.ExecuteJavaScript('termsPage = null') 321 322 323def opt_in(browser, 324 autotest_ext, 325 enable_managed_policy=True, 326 wait_for_provisioning=True): 327 """ 328 Step through opt in and wait for it to complete. 329 330 Return early if the arc_setting cannot be set True. 331 332 @param browser: chrome.Chrome browser object. 333 @param autotest_ext: autotest extension object. 334 @param enable_managed_policy: If False then policy check is ignored for 335 managed user case and ARC++ is forced enabled. 336 @param wait_for_provisioning: True in case we have to wait for provisioning 337 to complete. 338 339 @raises: error.TestFail if opt in fails. 340 341 """ 342 343 logging.info(_OPT_IN_BEGIN) 344 345 # TODO(b/124391451): Enterprise tests have race, when ARC policy appears 346 # after profile prefs sync completed. That means they may appear as 347 # non-managed first, treated like this but finally turns as managed. This 348 # leads to test crash. Solution is to wait until prefs are synced or, if 349 # possible tune test accounts to wait sync is completed before starting the 350 # Chrome session. 351 352 # Enabling Play Store may affect this flag, capture it before. 353 tos_needed = get_arc_session_state(autotest_ext).tos_needed 354 355 if not enable_play_store(autotest_ext, True, enable_managed_policy): 356 return 357 358 if not wait_for_provisioning: 359 logging.info(_OPT_IN_SKIP) 360 return 361 362 if tos_needed: 363 extension_main_page = find_opt_in_extension_page(browser) 364 opt_in_and_wait_for_completion(extension_main_page) 365 else: 366 _wait_arc_provisioned(autotest_ext) 367 368 logging.info(_OPT_IN_FINISH) 369