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