blob: cff6c447b90ff5f0ead0e6a63f00fdfb02ee52bd [file] [log] [blame]
Hung-Te Lin76c55b22015-03-31 14:47:14 +08001#!/usr/bin/python2
2#
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
18
19from chromite.lib import cros_logging as logging
20import 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):
141 bmpblk_dir = os.path.join(
142 os.environ.get('CROS_WORKON_SRCROOT'), 'src', 'platform', 'bmpblk')
143 if not os.path.exists(bmpblk_dir):
144 logging.warn('Skipping testFirmwareLocales, since %r is missing',
145 bmpblk_dir)
146 return
147
148 bmp_locale_dir = os.path.join(bmpblk_dir, 'strings', 'locale')
149 for r in regions.BuildRegionsDict(include_all=True).values():
150 for l in r.locales:
151 paths = [os.path.join(bmp_locale_dir, l)]
152 if '-' in l:
153 paths.append(os.path.join(bmp_locale_dir, l.partition('-')[0]))
154 if not any([os.path.exists(x) for x in paths]):
155 if r.region_code in regions.REGIONS:
156 self.fail(
157 'For region %r, none of %r exists' % (r.region_code, paths))
158 else:
159 logging.warn('For region %r, none of %r exists; '
160 'just a warning since this region is not confirmed',
161 r.region_code, paths)
162
163 def testYAMLOutput(self):
164 output = StringIO.StringIO()
165 regions.main(['--format', 'yaml'], output)
166 data = yaml.load(output.getvalue())
167 self.assertEquals(
168 {'keyboards': ['xkb:us::eng'],
169 'keyboard_mechanical_layout': 'ANSI',
170 'locales': ['en-US'],
171 'region_code': 'us',
172 'numeric_id': 29,
173 'description': 'United States',
Hung-Te Lin2c89ccd2015-04-07 15:20:35 +0800174 'regulatory_domain': 'US',
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800175 'time_zones': ['America/Los_Angeles']},
176 data['us'])
177
178 def testFieldsDict(self):
179 # 'description' and 'notes' should be missing.
180 self.assertEquals(
181 {'keyboards': ['xkb:b::b'],
182 'keyboard_mechanical_layout': 'e',
183 'description': 'description',
184 'locales': ['d'],
185 'numeric_id': 11,
Hung-Te Lin2c89ccd2015-04-07 15:20:35 +0800186 'region_code': 'aa',
187 'regulatory_domain': 'AA',
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800188 'time_zones': ['c']},
Hung-Te Lin2c89ccd2015-04-07 15:20:35 +0800189 (regions.Region('aa', 'xkb:b::b', 'c', 'd', 'e', 'description', 'notes',
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800190 11).GetFieldsDict()))
191
192 def testConsolidateRegionsDups(self):
193 """Test duplicate handling. Two identical Regions are OK."""
194 # Make two copies of the same region.
Hung-Te Lin2c89ccd2015-04-07 15:20:35 +0800195 region_list = [regions.Region('aa', 'xkb:b::b', 'c', 'd', 'e')
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800196 for _ in range(2)]
197 # It's OK.
198 self.assertEquals(
Hung-Te Lin2c89ccd2015-04-07 15:20:35 +0800199 {'aa': region_list[0]}, regions.ConsolidateRegions(region_list))
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800200
201 # Modify the second copy.
202 region_list[1].keyboards = ['f']
203 # Not OK anymore!
204 self.assertRaisesRegexp(
Hung-Te Lin2c89ccd2015-04-07 15:20:35 +0800205 regions.RegionException, "Conflicting definitions for region 'aa':",
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800206 regions.ConsolidateRegions, region_list)
207
208 def testNumericIds(self):
209 """Make sure numeric IDs are unique and all regions have a numeric ID."""
210 numeric_ids = set()
211 for region in regions.BuildRegionsDict(include_all=True).values():
212 if region.numeric_id is not None:
213 self.assertNotIn(region.numeric_id, numeric_ids,
214 'Duplicate numeric ID %d in %s' % (
215 region.numeric_id, region.region_code))
216 numeric_ids.add(region.numeric_id)
217
218 # Confirmed regions only
219 if region.region_code in regions.REGIONS:
220 self.assertIsNotNone(region.numeric_id,
221 'Region %s has no numeric ID assigned' % (
222 region.region_code))
223
224if __name__ == '__main__':
225 unittest.main()