| # Copyright 2014 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| from yaml import load |
| |
| from autotest_lib.client.bin import test, utils |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.client.common_lib.cros import chrome |
| from autotest_lib.client.cros import cros_ui |
| |
| class login_OobeLocalization(test.test): |
| """Tests different region configurations at OOBE.""" |
| version = 1 |
| |
| _LANGUAGE_SELECT = 'language-select' |
| _KEYBOARD_SELECT = 'keyboard-select' |
| _FALLBACK_KEYBOARD = 'xkb:us::eng' |
| |
| # dump_vpd_log reads the VPD cache in lieu of running `vpd -l`. |
| _VPD_FILENAME = '/var/cache/vpd/full-v2.txt' |
| # The filtered cache is created from the cache by dump_vpd_log. It is read |
| # at startup if the device is not owned. (Otherwise /tmp/machine-info is |
| # created by dump_vpd_log and read. See |
| # /platform/login_manager/init/machine-info.conf.) |
| _FILTERED_VPD_FILENAME = '/var/log/vpd_2.0.txt' |
| # regions.yaml has information for each region (locale, input method, etc.). |
| _REGIONS_FILENAME = '/usr/local/factory/share/regions.yaml' |
| # input_methods.txt lists supported input methods. |
| _INPUT_METHODS_FILENAME = ('/usr/share/chromeos-assets/input_methods/' |
| 'input_methods.txt') |
| |
| |
| def initialize(self): |
| self._login_keyboards = self._get_login_keyboards() |
| self._comp_ime_prefix = self._run_with_chrome( |
| self._get_comp_ime_prefix) |
| |
| |
| def run_once(self): |
| for region in self._get_regions(): |
| self._set_vpd(region['vpd_settings']) |
| self._run_with_chrome(self._run_localization_test, region) |
| |
| |
| def cleanup(self): |
| """Removes cache files so our changes don't persist.""" |
| cros_ui.stop() |
| utils.run('rm /home/chronos/Local\ State', ignore_status=True) |
| utils.run('dump_vpd_log --clean') |
| |
| |
| def _run_with_chrome(self, func, *args): |
| with chrome.Chrome(auto_login=False) as self._chrome: |
| utils.poll_for_condition( |
| self._is_oobe_ready, |
| exception=error.TestFail('OOBE not ready')) |
| return func(*args) |
| |
| |
| def _run_localization_test(self, region): |
| """Checks the network screen for the proper dropdown values.""" |
| |
| # Find the language(s), or acceptable alternate value(s). |
| initial_locale = region['vpd_settings']['initial_locale'] |
| if not self._verify_initial_options( |
| self._LANGUAGE_SELECT, |
| initial_locale, |
| alternate_values = self._resolve_language(initial_locale), |
| check_separator = True): |
| raise error.TestFail( |
| 'Language not found for region "%s".\n' |
| 'Actual value of %s:\n%s' % ( |
| region['region_code'], |
| self._LANGUAGE_SELECT, |
| self._dump_options(self._LANGUAGE_SELECT))) |
| |
| # We expect to see only login keyboards at OOBE. |
| keyboards = region['vpd_settings']['keyboard_layout'].split(',') |
| keyboards = [kbd for kbd in keyboards if kbd in self._login_keyboards] |
| |
| # If there are no login keyboards, expect only the fallback keyboard. |
| keyboards = keyboards or [self._FALLBACK_KEYBOARD] |
| |
| # Prepend each xkb value with the component extension id. |
| keyboard_ids = ','.join( |
| [self._comp_ime_prefix + xkb for xkb in keyboards]) |
| |
| # Find the keyboard layout(s). |
| if not self._verify_initial_options( |
| self._KEYBOARD_SELECT, |
| keyboard_ids): |
| raise error.TestFail( |
| 'Keyboard not found for region "%s".\n' |
| 'Actual value of %s:\n%s' % ( |
| region['region_code'], |
| self._KEYBOARD_SELECT, |
| self._dump_options(self._KEYBOARD_SELECT))) |
| |
| # Check that the fallback keyboard is present. |
| if self._FALLBACK_KEYBOARD not in keyboards: |
| if not self._verify_option_exists( |
| self._KEYBOARD_SELECT, |
| self._comp_ime_prefix + self._FALLBACK_KEYBOARD): |
| raise error.TestFail( |
| 'Fallback keyboard layout not found for region "%s".\n' |
| 'Actual value of %s:\n%s' % ( |
| region['region_code'], |
| self._KEYBOARD_SELECT, |
| self._dump_options(self._KEYBOARD_SELECT))) |
| |
| |
| def _set_vpd(self, vpd_settings): |
| """Changes VPD cache on disk. |
| @param vpd_settings: Dictionary of VPD key-value pairs. |
| """ |
| cros_ui.stop() |
| |
| vpd = {} |
| with open(self._VPD_FILENAME, 'r+') as vpd_log: |
| # Read the existing VPD info. |
| for line in vpd_log: |
| # Extract "key"="value" pair. |
| key, _, value = line.replace('"', '').partition('=') |
| vpd[key] = value |
| |
| vpd.update(vpd_settings); |
| |
| # Write the new set of settings to disk. |
| vpd_log.seek(0) |
| for key in vpd: |
| vpd_log.write('"%s"="%s"\n' % (key, vpd[key])) |
| vpd_log.truncate() |
| |
| # Remove filtered cache so dump_vpd_log recreates it from the cache we |
| # just updated. |
| utils.run('rm ' + self._FILTERED_VPD_FILENAME, ignore_status=True) |
| utils.run('dump_vpd_log') |
| |
| # Remove cached files to clear initial locale info. |
| utils.run('rm /home/chronos/Local\ State', ignore_status=True) |
| utils.run('rm /home/chronos/.oobe_completed', ignore_status=True) |
| cros_ui.start() |
| |
| |
| def _verify_initial_options(self, select_id, values, |
| alternate_values='', check_separator=False): |
| """Verifies that |values| are the initial elements of |select_id|. |
| |
| @param select_id: ID of the select element to check. |
| @param values: Comma-separated list of values that should appear, |
| in order, at the top of the select before any options group. |
| @param alternate_values: Optional comma-separated list of alternate |
| values for the corresponding items in values. |
| @param check_separator: If True, also verifies that an options group |
| label appears after the initial set of values. |
| |
| @returns whether the select fits the given constraints. |
| |
| @raises EvaluateException if the JS expression fails to evaluate. |
| """ |
| js_expression = """ |
| (function () { |
| var select = document.querySelector('#%s'); |
| if (!select || select.selectedIndex) |
| return false; |
| var values = '%s'.split(','); |
| var alternate_values = '%s'.split(','); |
| for (var i = 0; i < values.length; i++) { |
| if (select.options[i].value != values[i] && |
| (!alternate_values[i] || |
| select.options[i].value != alternate_values[i])) |
| return false; |
| } |
| if (%d) { |
| return select.children[values.length].tagName == |
| 'OPTGROUP'; |
| } |
| return true; |
| })()""" % (select_id, |
| values, |
| alternate_values, |
| check_separator) |
| |
| return self._chrome.browser.oobe.EvaluateJavaScript(js_expression) |
| |
| |
| def _verify_option_exists(self, select_id, value): |
| """Verifies that |value| exists in |select_id|. |
| |
| @param select_id: ID of the select element to check. |
| @param value: A single value to find in the select. |
| |
| @returns whether the value is found. |
| |
| @raises EvaluateException if the JS expression fails to evaluate. |
| """ |
| js_expression = """ |
| (function () { |
| return !!document.querySelector( |
| '#%s option[value=\\'%s\\']'); |
| })()""" % (select_id, value) |
| |
| return self._chrome.browser.oobe.EvaluateJavaScript(js_expression) |
| |
| |
| def _get_login_keyboards(self): |
| """Returns the set of login xkbs from the input methods file.""" |
| login_keyboards = set() |
| with open(self._INPUT_METHODS_FILENAME) as input_methods_file: |
| for line in input_methods_file: |
| columns = line.strip().split() |
| # The 5th column will be "login" if this keyboard layout will |
| # be used on login. |
| if len(columns) == 5 and columns[4] == 'login': |
| login_keyboards.add(columns[0]) |
| return login_keyboards |
| |
| |
| def _get_regions(self): |
| regions = {} |
| with open(self._REGIONS_FILENAME, 'r') as regions_file: |
| return load(regions_file).values() |
| |
| |
| def _get_comp_ime_prefix(self): |
| """Finds the xkb values' component extension id prefix, if any. |
| @returns the prefix if found, or an empty string |
| """ |
| return self._chrome.browser.oobe.EvaluateJavaScript(""" |
| var value = document.getElementById('%s').value; |
| value.substr(0, value.lastIndexOf('xkb:'))""" % |
| self._KEYBOARD_SELECT) |
| |
| |
| def _resolve_language(self, locale): |
| """Falls back to an existing locale if the given locale matches a |
| language but not the country. Mirrors |
| chromium:ui/base/l10n/l10n_util.cc. |
| """ |
| lang, _, region = map(str.lower, locale.partition('-')) |
| if not region: |
| return '' |
| |
| # Map from other countries to a localized country. |
| if lang == 'es' and region == 'es': |
| return 'es-419' |
| if lang == 'zh': |
| if region in ('hk', 'mo'): |
| return 'zh-TW' |
| return 'zh-CN' |
| if lang == 'en': |
| if region in ('au', 'ca', 'nz', 'za'): |
| return 'en-GB' |
| return 'en-US' |
| |
| # No mapping found. |
| return '' |
| |
| |
| def _is_oobe_ready(self): |
| return (self._chrome.browser.oobe and |
| self._chrome.browser.oobe.EvaluateJavaScript( |
| "var select = document.getElementById('%s');" |
| "select && select.children.length >= 2" % |
| self._LANGUAGE_SELECT)) |
| |
| |
| def _dump_options(self, select_id): |
| js_expression = """ |
| (function () { |
| var selector = '#%s'; |
| var divider = ','; |
| var select = document.querySelector(selector); |
| if (!select) |
| return 'document.querySelector(\\'' + selector + |
| '\\') failed.'; |
| var dumpOptgroup = function(group) { |
| var result = ''; |
| for (var i = 0; i < group.children.length; i++) { |
| if (i > 0) |
| result += divider; |
| if (group.children[i].value) |
| result += group.children[i].value; |
| else |
| result += '__NO_VALUE__'; |
| } |
| return result; |
| }; |
| var result = ''; |
| if (select.selectedIndex != 0) { |
| result += '(selectedIndex=' + select.selectedIndex + |
| ', selected \' + |
| select.options[select.selectedIndex].value + |
| '\)'; |
| } |
| var children = select.children; |
| for (var i = 0; i < children.length; i++) { |
| if (i > 0) |
| result += divider; |
| if (children[i].value) |
| result += children[i].value; |
| else if (children[i].tagName === 'OPTGROUP') |
| result += '[' + dumpOptgroup(children[i]) + ']'; |
| else |
| result += '__NO_VALUE__'; |
| } |
| return result; |
| })()""" % select_id |
| return self._chrome.browser.oobe.EvaluateJavaScript(js_expression) |