blob: ec864ee8ea41f510124d9256d7de13cd43f16c70 [file] [log] [blame]
# 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.
import json
import logging
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'
# cros-regions.json has information for each region (locale, input method,
# etc.) in JSON format.
_REGIONS_FILENAME = '/usr/share/misc/cros-regions.json'
# 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():
# Unconfirmed regions may have incorrect data. The 'confirm'
# property is optional when all regions in database are confirmed so
# we have to check explicit 'False'.
if region.get('confirmed', True) is False:
logging.info('Skip unconfirmed region: %s',
region['region_code'])
continue
# TODO(hungte) When OOBE supports cros-regions.json
# (crosbug.com/p/34536) we can remove initial_locale,
# initial_timezone, and keyboard_layout.
self._set_vpd({'region': region['region_code'],
'initial_locale': ','.join(region['locales']),
'initial_timezone': ','.join(region['time_zones']),
'keyboard_layout': ','.join(region['keyboards'])})
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 = ','.join(region['locales'])
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['keyboards']
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 json.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, str(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)