blob: 4234270b5b084f14bd4718a321da32d81ca92166 [file] [log] [blame]
Hung-Te Lin98501ae2017-06-02 15:53:33 +08001#!/usr/bin/env python2
Hung-Te Lin76c55b22015-03-31 14:47:14 +08002#
3# Copyright 2015 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Tests for regions.py.
8
9These tests ensure that all regions in regions.py are valid. The tests use
10testdata pulled from the Chromium sources.
11"""
12
13from __future__ import print_function
14
15import os
16import StringIO
17import unittest
Hung-Te Lin98501ae2017-06-02 15:53:33 +080018import logging
Hung-Te Lin76c55b22015-03-31 14:47:14 +080019
Hung-Te Lin76c55b22015-03-31 14:47:14 +080020import regions
21import yaml
22
23
Hung-Te Lin8f8e0c42015-04-13 11:42:42 +080024_WARN_UNKNOWN_DATA_IN_UNCONFIRMED_REGION = (
25 'Missing %s %r; does this new data need to be added to CrOS, or '
26 'does testdata need to be updated? (just a warning, since region '
27 '%r is not a confirmed region)')
28
Hung-Te Lin76c55b22015-03-31 14:47:14 +080029class RegionTest(unittest.TestCase):
30 """Tests for the Region class."""
31
32 @classmethod
33 def _ReadTestData(cls, name):
34 """Reads a YAML-formatted test data file.
35
36 Args:
37 name: Name of file in the testdata directory.
38
39 Returns:
40 The parsed value.
41 """
42 with open(os.path.join(os.path.dirname(__file__),
43 'testdata', name + '.yaml')) as f:
44 return yaml.load(f)
45
46 @classmethod
47 def setUpClass(cls):
48 cls.locales = cls._ReadTestData('locales')
49 cls.time_zones = cls._ReadTestData('time_zones')
50 cls.migration_map = cls._ReadTestData('migration_map')
51 cls.input_methods = cls._ReadTestData('input_methods')
52
53 def _ResolveInputMethod(self, method):
54 """Resolves an input method using the migration map.
55
56 Args:
57 method: An input method ID that may contain prefixes from the
58 migration map. (E.g., "m17n:ar", which contains the "m17n:" prefix.)
59
60 Returns:
61 The input method ID after mapping any prefixes. (E.g., "m17n:ar" will
62 be mapped to "vkd_".)
63 """
64 for k, v in self.migration_map:
65 if method.startswith(k):
66 method = v + method[len(k):]
67 return method
68
69 def testZoneInfo(self):
70 all_regions = regions.BuildRegionsDict(include_all=True)
71
72 # Make sure all time zones are present in /usr/share/zoneinfo.
73 all_zoneinfos = [os.path.join('/usr/share/zoneinfo', tz)
74 for r in all_regions.values() for tz in r.time_zones]
75 missing = [z for z in all_zoneinfos if not os.path.exists(z)]
76 self.assertFalse(missing,
77 ('Missing zoneinfo files; are timezones misspelled?: %r' %
78 missing))
79
80 def testBadLocales(self):
81 self.assertRaisesRegexp(
82 AssertionError, "Locale 'en-us' does not match", regions.Region,
83 'us', 'xkb:us::eng', 'America/Los_Angeles', 'en-us', 'ANSI')
84
85 def testBadKeyboard(self):
86 self.assertRaisesRegexp(
87 AssertionError, "Keyboard pattern 'xkb:us::' does not match",
88 regions.Region, 'us', 'xkb:us::', 'America/Los_Angeles', 'en-US',
89 'ANSI')
90
91 def testKeyboardRegexp(self):
92 self.assertTrue(regions.KEYBOARD_PATTERN.match('xkb:us::eng'))
93 self.assertTrue(regions.KEYBOARD_PATTERN.match('ime:ko:korean'))
94 self.assertTrue(regions.KEYBOARD_PATTERN.match('m17n:ar'))
95 self.assertFalse(regions.KEYBOARD_PATTERN.match('m17n:'))
96 self.assertFalse(regions.KEYBOARD_PATTERN.match('foo_bar'))
97
98 def testTimeZones(self):
99 for r in regions.BuildRegionsDict(include_all=True).values():
100 for tz in r.time_zones:
101 if tz not in self.time_zones:
102 if r.region_code in regions.REGIONS:
103 self.fail(
Hung-Te Lin8f8e0c42015-04-13 11:42:42 +0800104 'Missing time zones: %r; does a new time zone need to be added '
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800105 'to CrOS, or does testdata need to be updated?' % tz)
106 else:
107 # This is an unconfirmed region; just print a warning.
Hung-Te Lin8f8e0c42015-04-13 11:42:42 +0800108 logging.warn(_WARN_UNKNOWN_DATA_IN_UNCONFIRMED_REGION, 'time zone',
109 tz, r.region_code)
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800110
111 def testLocales(self):
112 missing = []
113 for r in regions.BuildRegionsDict(include_all=True).values():
114 for l in r.locales:
115 if l not in self.locales:
Hung-Te Lin8f8e0c42015-04-13 11:42:42 +0800116 if r.region_code in regions.REGIONS:
117 missing.append(l)
118 else:
119 logging.warn(_WARN_UNKNOWN_DATA_IN_UNCONFIRMED_REGION, 'locale', l,
120 r.region_code)
121 self.assertFalse(missing,
122 ('Missing locale; does testdata need to be updated?: %r' %
123 missing))
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800124
125 def testInputMethods(self):
126 # Verify that every region is present in the dict.
127 for r in regions.BuildRegionsDict(include_all=True).values():
128 for k in r.keyboards:
129 resolved_method = self._ResolveInputMethod(k)
130 # Make sure the keyboard method is present.
Hung-Te Lin8f8e0c42015-04-13 11:42:42 +0800131 if resolved_method not in self.input_methods:
132 if r.region_code in regions.REGIONS:
133 self.fail('Missing keyboard layout %r (resolved from %r)' % (
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800134 resolved_method, k))
Hung-Te Lin8f8e0c42015-04-13 11:42:42 +0800135 else:
136 # This is an unconfirmed region; just print a warning.
137 logging.warn(_WARN_UNKNOWN_DATA_IN_UNCONFIRMED_REGION, 'keyboard',
138 k, r.region_code)
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800139
140 def testFirmwareLocales(self):
Hung-Te Lin98501ae2017-06-02 15:53:33 +0800141 # This file is probably in src/platform2/regions
142 src_root = os.environ.get('CROS_WORKON_SRCROOT',
143 os.path.join(os.path.dirname(__file__), '..',
144 '..', '..'))
145 bmpblk_dir = os.path.join(src_root, 'src', 'platform', 'bmpblk')
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800146 if not os.path.exists(bmpblk_dir):
147 logging.warn('Skipping testFirmwareLocales, since %r is missing',
148 bmpblk_dir)
149 return
150
151 bmp_locale_dir = os.path.join(bmpblk_dir, 'strings', 'locale')
152 for r in regions.BuildRegionsDict(include_all=True).values():
Marco Chen95a5a872019-05-13 23:52:56 +0800153 checked_paths = []
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800154 for l in r.locales:
155 paths = [os.path.join(bmp_locale_dir, l)]
156 if '-' in l:
157 paths.append(os.path.join(bmp_locale_dir, l.partition('-')[0]))
Marco Chen95a5a872019-05-13 23:52:56 +0800158 if any([os.path.exists(x) for x in paths]):
159 break
160 checked_paths += paths
161 else:
162 if r.region_code in regions.REGIONS:
163 self.fail(
164 'For region %r, none of %r exists' % (r.region_code,
165 checked_paths))
166 else:
167 logging.warn('For region %r, none of %r exists; '
168 'just a warning since this region is not confirmed',
169 r.region_code, checked_paths)
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800170
171 def testYAMLOutput(self):
172 output = StringIO.StringIO()
173 regions.main(['--format', 'yaml'], output)
174 data = yaml.load(output.getvalue())
175 self.assertEquals(
176 {'keyboards': ['xkb:us::eng'],
177 'keyboard_mechanical_layout': 'ANSI',
178 'locales': ['en-US'],
179 'region_code': 'us',
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800180 'description': 'United States',
Hung-Te Lin2c89ccd2015-04-07 15:20:35 +0800181 'regulatory_domain': 'US',
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800182 'time_zones': ['America/Los_Angeles']},
183 data['us'])
184
185 def testFieldsDict(self):
186 # 'description' and 'notes' should be missing.
187 self.assertEquals(
188 {'keyboards': ['xkb:b::b'],
189 'keyboard_mechanical_layout': 'e',
190 'description': 'description',
191 'locales': ['d'],
Hung-Te Lin2c89ccd2015-04-07 15:20:35 +0800192 'region_code': 'aa',
193 'regulatory_domain': 'AA',
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800194 'time_zones': ['c']},
Hung-Te Lin98501ae2017-06-02 15:53:33 +0800195 (regions.Region('aa', 'xkb:b::b', 'c', 'd', 'e', 'description',
196 'notes').GetFieldsDict()))
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800197
198 def testConsolidateRegionsDups(self):
199 """Test duplicate handling. Two identical Regions are OK."""
200 # Make two copies of the same region.
Hung-Te Lin2c89ccd2015-04-07 15:20:35 +0800201 region_list = [regions.Region('aa', 'xkb:b::b', 'c', 'd', 'e')
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800202 for _ in range(2)]
203 # It's OK.
204 self.assertEquals(
Hung-Te Lin2c89ccd2015-04-07 15:20:35 +0800205 {'aa': region_list[0]}, regions.ConsolidateRegions(region_list))
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800206
207 # Modify the second copy.
208 region_list[1].keyboards = ['f']
209 # Not OK anymore!
210 self.assertRaisesRegexp(
Hung-Te Lin2c89ccd2015-04-07 15:20:35 +0800211 regions.RegionException, "Conflicting definitions for region 'aa':",
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800212 regions.ConsolidateRegions, region_list)
213
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800214if __name__ == '__main__':
Hung-Te Lin98501ae2017-06-02 15:53:33 +0800215 logging.basicConfig(format='%(message)s', level=logging.WARNING)
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800216 unittest.main()