Home | History | Annotate | Download | only in chromeos
      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