blob: 171c26f19591104501d145cd1123e7e94eef8753 [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',
164 'time_zones': ['America/Los_Angeles']},
165 data['us'])
166
167 def testFieldsDict(self):
168 # 'description' and 'notes' should be missing.
169 self.assertEquals(
170 {'keyboards': ['xkb:b::b'],
171 'keyboard_mechanical_layout': 'e',
172 'description': 'description',
173 'locales': ['d'],
174 'numeric_id': 11,
175 'region_code': 'a',
176 'time_zones': ['c']},
177 (regions.Region('a', 'xkb:b::b', 'c', 'd', 'e', 'description', 'notes',
178 11).GetFieldsDict()))
179
180 def testConsolidateRegionsDups(self):
181 """Test duplicate handling. Two identical Regions are OK."""
182 # Make two copies of the same region.
183 region_list = [regions.Region('a', 'xkb:b::b', 'c', 'd', 'e')
184 for _ in range(2)]
185 # It's OK.
186 self.assertEquals(
187 {'a': region_list[0]}, regions.ConsolidateRegions(region_list))
188
189 # Modify the second copy.
190 region_list[1].keyboards = ['f']
191 # Not OK anymore!
192 self.assertRaisesRegexp(
193 regions.RegionException, "Conflicting definitions for region 'a':",
194 regions.ConsolidateRegions, region_list)
195
196 def testNumericIds(self):
197 """Make sure numeric IDs are unique and all regions have a numeric ID."""
198 numeric_ids = set()
199 for region in regions.BuildRegionsDict(include_all=True).values():
200 if region.numeric_id is not None:
201 self.assertNotIn(region.numeric_id, numeric_ids,
202 'Duplicate numeric ID %d in %s' % (
203 region.numeric_id, region.region_code))
204 numeric_ids.add(region.numeric_id)
205
206 # Confirmed regions only
207 if region.region_code in regions.REGIONS:
208 self.assertIsNotNone(region.numeric_id,
209 'Region %s has no numeric ID assigned' % (
210 region.region_code))
211
212if __name__ == '__main__':
213 unittest.main()