blob: 1bb6d8f84276b56cd828a6d6a70d213b0878a9cd [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
24class RegionTest(unittest.TestCase):
25 """Tests for the Region class."""
26
27 @classmethod
28 def _ReadTestData(cls, name):
29 """Reads a YAML-formatted test data file.
30
31 Args:
32 name: Name of file in the testdata directory.
33
34 Returns:
35 The parsed value.
36 """
37 with open(os.path.join(os.path.dirname(__file__),
38 'testdata', name + '.yaml')) as f:
39 return yaml.load(f)
40
41 @classmethod
42 def setUpClass(cls):
43 cls.locales = cls._ReadTestData('locales')
44 cls.time_zones = cls._ReadTestData('time_zones')
45 cls.migration_map = cls._ReadTestData('migration_map')
46 cls.input_methods = cls._ReadTestData('input_methods')
47
48 def _ResolveInputMethod(self, method):
49 """Resolves an input method using the migration map.
50
51 Args:
52 method: An input method ID that may contain prefixes from the
53 migration map. (E.g., "m17n:ar", which contains the "m17n:" prefix.)
54
55 Returns:
56 The input method ID after mapping any prefixes. (E.g., "m17n:ar" will
57 be mapped to "vkd_".)
58 """
59 for k, v in self.migration_map:
60 if method.startswith(k):
61 method = v + method[len(k):]
62 return method
63
64 def testZoneInfo(self):
65 all_regions = regions.BuildRegionsDict(include_all=True)
66
67 # Make sure all time zones are present in /usr/share/zoneinfo.
68 all_zoneinfos = [os.path.join('/usr/share/zoneinfo', tz)
69 for r in all_regions.values() for tz in r.time_zones]
70 missing = [z for z in all_zoneinfos if not os.path.exists(z)]
71 self.assertFalse(missing,
72 ('Missing zoneinfo files; are timezones misspelled?: %r' %
73 missing))
74
75 def testBadLocales(self):
76 self.assertRaisesRegexp(
77 AssertionError, "Locale 'en-us' does not match", regions.Region,
78 'us', 'xkb:us::eng', 'America/Los_Angeles', 'en-us', 'ANSI')
79
80 def testBadKeyboard(self):
81 self.assertRaisesRegexp(
82 AssertionError, "Keyboard pattern 'xkb:us::' does not match",
83 regions.Region, 'us', 'xkb:us::', 'America/Los_Angeles', 'en-US',
84 'ANSI')
85
86 def testKeyboardRegexp(self):
87 self.assertTrue(regions.KEYBOARD_PATTERN.match('xkb:us::eng'))
88 self.assertTrue(regions.KEYBOARD_PATTERN.match('ime:ko:korean'))
89 self.assertTrue(regions.KEYBOARD_PATTERN.match('m17n:ar'))
90 self.assertFalse(regions.KEYBOARD_PATTERN.match('m17n:'))
91 self.assertFalse(regions.KEYBOARD_PATTERN.match('foo_bar'))
92
93 def testTimeZones(self):
94 for r in regions.BuildRegionsDict(include_all=True).values():
95 for tz in r.time_zones:
96 if tz not in self.time_zones:
97 if r.region_code in regions.REGIONS:
98 self.fail(
99 'Missing time zone %r; does a new time zone need to be added '
100 'to CrOS, or does testdata need to be updated?' % tz)
101 else:
102 # This is an unconfirmed region; just print a warning.
103 logging.warn('Missing time zone %r; does a new time zone need to '
104 'be added to CrOS, or does testdata need to '
105 'be updated? (just a warning, since region '
106 '%r is not a confirmed region)', tz, r.region_code)
107
108 def testLocales(self):
109 missing = []
110 for r in regions.BuildRegionsDict(include_all=True).values():
111 for l in r.locales:
112 if l not in self.locales:
113 missing.append(l)
114 self.assertFalse(
115 missing,
116 ('Missing locale; does testdata need to be updated?: %r' %
117 missing))
118
119 def testInputMethods(self):
120 # Verify that every region is present in the dict.
121 for r in regions.BuildRegionsDict(include_all=True).values():
122 for k in r.keyboards:
123 resolved_method = self._ResolveInputMethod(k)
124 # Make sure the keyboard method is present.
125 self.assertIn(
126 resolved_method, self.input_methods,
127 'Missing keyboard layout %r (resolved from %r)' % (
128 resolved_method, k))
129
130 def testFirmwareLocales(self):
131 bmpblk_dir = os.path.join(
132 os.environ.get('CROS_WORKON_SRCROOT'), 'src', 'platform', 'bmpblk')
133 if not os.path.exists(bmpblk_dir):
134 logging.warn('Skipping testFirmwareLocales, since %r is missing',
135 bmpblk_dir)
136 return
137
138 bmp_locale_dir = os.path.join(bmpblk_dir, 'strings', 'locale')
139 for r in regions.BuildRegionsDict(include_all=True).values():
140 for l in r.locales:
141 paths = [os.path.join(bmp_locale_dir, l)]
142 if '-' in l:
143 paths.append(os.path.join(bmp_locale_dir, l.partition('-')[0]))
144 if not any([os.path.exists(x) for x in paths]):
145 if r.region_code in regions.REGIONS:
146 self.fail(
147 'For region %r, none of %r exists' % (r.region_code, paths))
148 else:
149 logging.warn('For region %r, none of %r exists; '
150 'just a warning since this region is not confirmed',
151 r.region_code, paths)
152
153 def testYAMLOutput(self):
154 output = StringIO.StringIO()
155 regions.main(['--format', 'yaml'], output)
156 data = yaml.load(output.getvalue())
157 self.assertEquals(
158 {'keyboards': ['xkb:us::eng'],
159 'keyboard_mechanical_layout': 'ANSI',
160 'locales': ['en-US'],
161 'region_code': 'us',
162 'numeric_id': 29,
163 'description': 'United States',
Hung-Te Lin2c89ccd2015-04-07 15:20:35 +0800164 'regulatory_domain': 'US',
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800165 'time_zones': ['America/Los_Angeles']},
166 data['us'])
167
168 def testFieldsDict(self):
169 # 'description' and 'notes' should be missing.
170 self.assertEquals(
171 {'keyboards': ['xkb:b::b'],
172 'keyboard_mechanical_layout': 'e',
173 'description': 'description',
174 'locales': ['d'],
175 'numeric_id': 11,
Hung-Te Lin2c89ccd2015-04-07 15:20:35 +0800176 'region_code': 'aa',
177 'regulatory_domain': 'AA',
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800178 'time_zones': ['c']},
Hung-Te Lin2c89ccd2015-04-07 15:20:35 +0800179 (regions.Region('aa', 'xkb:b::b', 'c', 'd', 'e', 'description', 'notes',
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800180 11).GetFieldsDict()))
181
182 def testConsolidateRegionsDups(self):
183 """Test duplicate handling. Two identical Regions are OK."""
184 # Make two copies of the same region.
Hung-Te Lin2c89ccd2015-04-07 15:20:35 +0800185 region_list = [regions.Region('aa', 'xkb:b::b', 'c', 'd', 'e')
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800186 for _ in range(2)]
187 # It's OK.
188 self.assertEquals(
Hung-Te Lin2c89ccd2015-04-07 15:20:35 +0800189 {'aa': region_list[0]}, regions.ConsolidateRegions(region_list))
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800190
191 # Modify the second copy.
192 region_list[1].keyboards = ['f']
193 # Not OK anymore!
194 self.assertRaisesRegexp(
Hung-Te Lin2c89ccd2015-04-07 15:20:35 +0800195 regions.RegionException, "Conflicting definitions for region 'aa':",
Hung-Te Lin76c55b22015-03-31 14:47:14 +0800196 regions.ConsolidateRegions, region_list)
197
198 def testNumericIds(self):
199 """Make sure numeric IDs are unique and all regions have a numeric ID."""
200 numeric_ids = set()
201 for region in regions.BuildRegionsDict(include_all=True).values():
202 if region.numeric_id is not None:
203 self.assertNotIn(region.numeric_id, numeric_ids,
204 'Duplicate numeric ID %d in %s' % (
205 region.numeric_id, region.region_code))
206 numeric_ids.add(region.numeric_id)
207
208 # Confirmed regions only
209 if region.region_code in regions.REGIONS:
210 self.assertIsNotNone(region.numeric_id,
211 'Region %s has no numeric ID assigned' % (
212 region.region_code))
213
214if __name__ == '__main__':
215 unittest.main()