1 # Copyright (c) 2012 The Chromium 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 import json 5 import logging 6 import os 7 import unittest 8 9 from telemetry.core import browser_finder 10 from telemetry.core import exceptions 11 from telemetry.core import extension_to_load 12 from telemetry.core import util 13 from telemetry.core.backends.chrome import cros_interface 14 from telemetry.unittest import options_for_unittests 15 16 class CrOSAutoTest(unittest.TestCase): 17 def setUp(self): 18 options = options_for_unittests.GetCopy() 19 self._cri = cros_interface.CrOSInterface(options.cros_remote, 20 options.cros_ssh_identity) 21 self._is_guest = options.browser_type == 'cros-chrome-guest' 22 self._username = '' if self._is_guest else options.browser_options.username 23 self._password = options.browser_options.password 24 25 def _IsCryptohomeMounted(self): 26 """Returns True if cryptohome is mounted""" 27 cryptohomeJSON, _ = self._cri.RunCmdOnDevice(['/usr/sbin/cryptohome', 28 '--action=status']) 29 cryptohomeStatus = json.loads(cryptohomeJSON) 30 return (cryptohomeStatus['mounts'] and 31 cryptohomeStatus['mounts'][0]['mounted']) 32 33 def _CreateBrowser(self, autotest_ext=False, auto_login=True): 34 """Finds and creates a browser for tests. if autotest_ext is True, 35 also loads the autotest extension""" 36 options = options_for_unittests.GetCopy() 37 38 if autotest_ext: 39 extension_path = os.path.join(os.path.dirname(__file__), 'autotest_ext') 40 self._load_extension = extension_to_load.ExtensionToLoad( 41 path=extension_path, 42 browser_type=options.browser_type, 43 is_component=True) 44 options.extensions_to_load = [self._load_extension] 45 46 browser_to_create = browser_finder.FindBrowser(options) 47 self.assertTrue(browser_to_create) 48 options.browser_options.create_browser_with_oobe = True 49 options.browser_options.auto_login = auto_login 50 b = browser_to_create.Create() 51 b.Start() 52 return b 53 54 def _GetAutotestExtension(self, browser): 55 """Returns the autotest extension instance""" 56 extension = browser.extensions[self._load_extension] 57 self.assertTrue(extension) 58 return extension 59 60 def _GetLoginStatus(self, browser): 61 extension = self._GetAutotestExtension(browser) 62 self.assertTrue(extension.EvaluateJavaScript( 63 "typeof('chrome.autotestPrivate') != 'undefined'")) 64 extension.ExecuteJavaScript(''' 65 window.__login_status = null; 66 chrome.autotestPrivate.loginStatus(function(s) { 67 window.__login_status = s; 68 }); 69 ''') 70 return util.WaitFor( 71 lambda: extension.EvaluateJavaScript('window.__login_status'), 10) 72 73 def testCryptohomeMounted(self): 74 """Verifies cryptohome mount status for regular and guest user and when 75 logged out""" 76 with self._CreateBrowser() as b: 77 self.assertEquals(1, len(b.tabs)) 78 self.assertTrue(b.tabs[0].url) 79 self.assertTrue(self._IsCryptohomeMounted()) 80 81 chronos_fs = self._cri.FilesystemMountedAt('/home/chronos/user') 82 self.assertTrue(chronos_fs) 83 if self._is_guest: 84 self.assertEquals(chronos_fs, 'guestfs') 85 else: 86 home, _ = self._cri.RunCmdOnDevice(['/usr/sbin/cryptohome-path', 87 'user', self._username]) 88 self.assertEquals(self._cri.FilesystemMountedAt(home.rstrip()), 89 chronos_fs) 90 91 self.assertFalse(self._IsCryptohomeMounted()) 92 self.assertEquals(self._cri.FilesystemMountedAt('/home/chronos/user'), 93 '/dev/mapper/encstateful') 94 95 def testLoginStatus(self): 96 """Tests autotestPrivate.loginStatus""" 97 with self._CreateBrowser(autotest_ext=True) as b: 98 login_status = self._GetLoginStatus(b) 99 self.assertEquals(type(login_status), dict) 100 101 self.assertEquals(not self._is_guest, login_status['isRegularUser']) 102 self.assertEquals(self._is_guest, login_status['isGuest']) 103 self.assertEquals(login_status['email'], self._username) 104 self.assertFalse(login_status['isScreenLocked']) 105 106 def _IsScreenLocked(self, browser): 107 return self._GetLoginStatus(browser)['isScreenLocked'] 108 109 def _LockScreen(self, browser): 110 self.assertFalse(self._IsScreenLocked(browser)) 111 112 extension = self._GetAutotestExtension(browser) 113 self.assertTrue(extension.EvaluateJavaScript( 114 "typeof chrome.autotestPrivate.lockScreen == 'function'")) 115 logging.info('Locking screen') 116 extension.ExecuteJavaScript('chrome.autotestPrivate.lockScreen();') 117 118 logging.info('Waiting for the lock screen') 119 def ScreenLocked(): 120 return (browser.oobe and 121 browser.oobe.EvaluateJavaScript("typeof Oobe == 'function'") and 122 browser.oobe.EvaluateJavaScript( 123 "typeof Oobe.authenticateForTesting == 'function'")) 124 util.WaitFor(ScreenLocked, 10) 125 self.assertTrue(self._IsScreenLocked(browser)) 126 127 def _AttemptUnlockBadPassword(self, browser): 128 logging.info('Trying a bad password') 129 def ErrorBubbleVisible(): 130 return not browser.oobe.EvaluateJavaScript(''' 131 document.getElementById('bubble').hidden 132 ''') 133 self.assertFalse(ErrorBubbleVisible()) 134 browser.oobe.ExecuteJavaScript(''' 135 Oobe.authenticateForTesting('%s', 'bad'); 136 ''' % self._username) 137 util.WaitFor(ErrorBubbleVisible, 10) 138 self.assertTrue(self._IsScreenLocked(browser)) 139 140 def _UnlockScreen(self, browser): 141 logging.info('Unlocking') 142 browser.oobe.ExecuteJavaScript(''' 143 Oobe.authenticateForTesting('%s', '%s'); 144 ''' % (self._username, self._password)) 145 util.WaitFor(lambda: not browser.oobe, 10) 146 self.assertFalse(self._IsScreenLocked(browser)) 147 148 def testScreenLock(self): 149 """Tests autotestPrivate.screenLock""" 150 with self._CreateBrowser(autotest_ext=True) as browser: 151 self._LockScreen(browser) 152 self._AttemptUnlockBadPassword(browser) 153 self._UnlockScreen(browser) 154 155 def testLogout(self): 156 """Tests autotestPrivate.logout""" 157 with self._CreateBrowser(autotest_ext=True) as b: 158 extension = self._GetAutotestExtension(b) 159 try: 160 extension.ExecuteJavaScript('chrome.autotestPrivate.logout();') 161 except (exceptions.BrowserConnectionGoneException, 162 exceptions.BrowserGoneException): 163 pass 164 util.WaitFor(lambda: not self._IsCryptohomeMounted(), 20) 165 166 def _SwitchRegion(self, region): 167 self._cri.RunCmdOnDevice(['stop', 'ui']) 168 169 # Change VPD (requires RW-enabled firmware). 170 # To save time, region and initial_timezone are not set. 171 vpd = {'initial_locale': region.language_code, 172 'keyboard_layout': region.keyboard} 173 174 for (key, value) in vpd.items(): 175 self._cri.RunCmdOnDevice(['vpd', '-s', '"%s"="%s"' % (key, value)]) 176 177 # Remove cached files to clear initial locale info and force regeneration. 178 self._cri.RunCmdOnDevice(['rm', '/home/chronos/Local\ State']) 179 self._cri.RunCmdOnDevice(['rm', '/home/chronos/.oobe_completed']) 180 self._cri.RunCmdOnDevice(['dump_vpd_log', '--force']) 181 182 self._cri.RunCmdOnDevice(['start', 'ui']) 183 184 def _OobeHasOption(self, browser, selectId, value): 185 hasOptionJs = ''' 186 // Check that the option is present, and selected if it is the default. 187 (function hasOption(selectId, value, isDefault) { 188 var options = document.getElementById(selectId).options; 189 for (var i = 0; i < options.length; i++) { 190 if (options[i].value == value) { 191 // The option is present. Make sure it's selected if necessary. 192 return !isDefault || options.selectedIndex == i; 193 } 194 } 195 return false; 196 })("%s", "%s", %s); 197 ''' 198 return browser.oobe.EvaluateJavaScript( 199 hasOptionJs % (selectId, value, 'true')) 200 201 def _ResolveLanguage(self, locale): 202 # If the locale matches a language but not the country, fall back to 203 # an existing locale. See ui/base/l10n/l10n_util.cc. 204 lang, _, region = map(str.lower, locale.partition('-')) 205 if not region: 206 return "" 207 208 # Map from other countries to a localized country 209 if lang == 'es' and region == 'es': 210 return 'es-419' 211 if lang == 'zh': 212 if region in ('hk', 'mo'): 213 return 'zh-TW' 214 return 'zh-CN' 215 if lang == 'en': 216 if region in ('au', 'ca', 'nz', 'za'): 217 return 'en-GB' 218 return 'en-US' 219 220 # No mapping found 221 return "" 222 223 def testOobeLocalization(self): 224 """Tests different region configurations at OOBE""" 225 # Save the original device localization settings. 226 # To save time, only read initial_locale and keyboard_layout. 227 initial_region = self.Region('', '', '', '', '') 228 initial_region.language_code, _ = self._cri.RunCmdOnDevice( 229 ['vpd', '-g', 'initial_locale']) 230 initial_region.keyboard, _ = self._cri.RunCmdOnDevice( 231 ['vpd', '-g', 'keyboard_layout']) 232 233 for region in self.REGIONS_LIST: 234 self._SwitchRegion(region) 235 with self._CreateBrowser(auto_login=False) as browser: 236 # Ensure the dropdown lists have been created. 237 util.WaitFor(lambda: browser.oobe.EvaluateJavaScript( 238 'document.getElementById("language-select") != null'), 239 10) 240 241 # Find the language, or an acceptable fallback value. 242 languageFound = self._OobeHasOption(browser, 243 'language-select', 244 region.language_code) 245 if not languageFound: 246 fallback = self._ResolveLanguage(region.language_code) 247 self.assertTrue(fallback and 248 self._OobeHasOption(browser, 249 'language-select', 250 fallback)) 251 252 # Find the keyboard layout. 253 self.assertTrue(self._OobeHasOption( 254 browser, 'keyboard-select', region.keyboard)) 255 256 # Test is finished. Restore original region settings. 257 self._SwitchRegion(initial_region) 258 259 # The Region class and region list will be available in regions.py. 260 class Region(object): 261 def __init__(self, region_code, keyboard, time_zone, language_code, 262 keyboard_mechanical_layout, description=None, notes=None): 263 self.region_code = region_code 264 self.keyboard = keyboard 265 self.time_zone = time_zone 266 self.language_code = language_code 267 self.keyboard_mechanical_layout = keyboard_mechanical_layout 268 self.description = description or region_code 269 self.notes = notes 270 271 class Enum(frozenset): 272 def __getattr__(self, name): 273 if name in self: 274 return name 275 raise AttributeError 276 277 KeyboardMechanicalLayout = Enum(['ANSI', 'ISO', 'JIS', 'ABNT2']) 278 _KML = KeyboardMechanicalLayout 279 REGIONS_LIST = [ 280 Region('au', 'xkb:us::eng', 'Australia/Sydney', 'en-AU', _KML.ANSI, 281 'Australia'), 282 Region('ca.ansi', 'xkb:us::eng', 'America/Toronto', 'en-CA', _KML.ANSI, 283 'Canada (US keyboard)', 284 'Canada with US (ANSI) keyboard; see http://goto/cros-canada'), 285 Region('ca.fr', 'xkb:ca::fra', 'America/Toronto', 'fr-CA', _KML.ISO, 286 'Canada (French keyboard)', 287 ('Canadian French (ISO) keyboard. The most common configuration for ' 288 'Canadian French SKUs. See http://goto/cros-canada')), 289 Region('ca.hybrid', 'xkb:ca:eng:eng', 'America/Toronto', 'en-CA', _KML.ISO, 290 'Canada (hybrid)', 291 ('Canada with hybrid xkb:ca:eng:eng + xkb:ca::fra keyboard (ISO), ' 292 'defaulting to English language and keyboard. Used only if there ' 293 'needs to be a single SKU for all of Canada. See ' 294 'http://goto/cros-canada')), 295 Region('ca.multix', 'xkb:ca:multix:fra', 'America/Toronto', 'fr-CA', 296 _KML.ISO, 'Canada (multilingual)', 297 ("Canadian Multilingual keyboard; you probably don't want this. See " 298 "http://goto/cros-canada")), 299 Region('de', 'xkb:de::ger', 'Europe/Berlin', 'de', _KML.ISO, 'Germany'), 300 Region('fi', 'xkb:fi::fin', 'Europe/Helsinki', 'fi', _KML.ISO, 'Finland'), 301 Region('fr', 'xkb:fr::fra', 'Europe/Paris', 'fr', _KML.ISO, 'France'), 302 Region('gb', 'xkb:gb:extd:eng', 'Europe/London', 'en-GB', _KML.ISO, 'UK'), 303 Region('ie', 'xkb:gb:extd:eng', 'Europe/Dublin', 'en-GB', _KML.ISO, 304 'Ireland'), 305 Region('in', 'xkb:us::eng', 'Asia/Calcutta', 'en-US', _KML.ANSI, 'India'), 306 Region('my', 'xkb:us::eng', 'Asia/Kuala_Lumpur', 'ms', _KML.ANSI, 307 'Malaysia'), 308 Region('nl', 'xkb:us:intl:eng', 'Europe/Amsterdam', 'nl', _KML.ANSI, 309 'Netherlands'), 310 Region('nordic', 'xkb:se::swe', 'Europe/Stockholm', 'en-US', _KML.ISO, 311 'Nordics', 312 ('Unified SKU for Sweden, Norway, and Denmark. This defaults ' 313 'to Swedish keyboard layout, but starts with US English language ' 314 'for neutrality. Use if there is a single combined SKU for Nordic ' 315 'countries.')), 316 Region('se', 'xkb:se::swe', 'Europe/Stockholm', 'sv', _KML.ISO, 'Sweden', 317 ("Use this if there separate SKUs for Nordic countries (Sweden, " 318 "Norway, and Denmark), or the device is only shipping to Sweden. " 319 "If there is a single unified SKU, use 'nordic' instead.")), 320 Region('sg', 'xkb:us::eng', 'Asia/Singapore', 'en-GB', _KML.ANSI, 321 'Singapore'), 322 Region('us', 'xkb:us::eng', 'America/Los_Angeles', 'en-US', _KML.ANSI, 323 'United States'), 324 ] 325