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