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