1#!/usr/bin/env python3 2# coding: utf-8 3# Copyright 2018 The Chromium Authors 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7import collections 8import os 9import sys 10import unittest 11 12sys.path.insert( 13 0, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))) 14from util import build_utils 15 16# Required because the following import needs build/android/gyp in the 17# Python path to import util.build_utils. 18_BUILD_ANDROID_GYP_ROOT = os.path.abspath( 19 os.path.join(os.path.dirname(__file__), os.pardir)) 20sys.path.insert(1, _BUILD_ANDROID_GYP_ROOT) 21 22import resource_utils 23 24# pylint: disable=line-too-long 25 26_TEST_XML_INPUT_1 = '''<?xml version="1.0" encoding="utf-8"?> 27<resources xmlns:android="http://schemas.android.com/apk/res/android"> 28<string name="copy_to_clipboard_failure_message">"Lõikelauale kopeerimine ebaõnnestus"</string> 29<string name="low_memory_error">"Eelmist toimingut ei saa vähese mälu tõttu lõpetada"</string> 30<string name="opening_file_error">"Valit. faili avamine ebaõnnestus"</string> 31<string name="structured_text">"This is <android:g id="STRUCTURED_TEXT">%s</android:g>"</string> 32</resources> 33''' 34 35_TEST_XML_OUTPUT_2 = '''<?xml version="1.0" encoding="utf-8"?> 36<resources xmlns:android="http://schemas.android.com/apk/res/android"> 37<string name="low_memory_error">"Eelmist toimingut ei saa vähese mälu tõttu lõpetada"</string> 38<string name="structured_text">"This is <android:g id="STRUCTURED_TEXT">%s</android:g>"</string> 39</resources> 40''' 41 42# pylint: enable=line-too-long 43 44_TEST_XML_OUTPUT_EMPTY = '''<?xml version="1.0" encoding="utf-8"?> 45<resources> 46<!-- this file intentionally empty --> 47</resources> 48''' 49 50_TEST_RESOURCES_MAP_1 = { 51 'low_memory_error': 'Eelmist toimingut ei saa vähese mälu tõttu lõpetada', 52 'opening_file_error': 'Valit. faili avamine ebaõnnestus', 53 'copy_to_clipboard_failure_message': 'Lõikelauale kopeerimine ebaõnnestus', 54 'structured_text': 'This is <android:g id="STRUCTURED_TEXT">%s</android:g>', 55} 56 57_TEST_NAMESPACES_1 = {'android': 'http://schemas.android.com/apk/res/android'} 58 59_TEST_RESOURCES_ALLOWLIST_1 = ['low_memory_error', 'structured_text'] 60 61# Extracted from one generated Chromium R.txt file, with string resource 62# names shuffled randomly. 63_TEST_R_TXT = r'''int anim abc_fade_in 0x7f050000 64int anim abc_fade_out 0x7f050001 65int anim abc_grow_fade_in_from_bottom 0x7f050002 66int array DefaultCookiesSettingEntries 0x7f120002 67int array DefaultCookiesSettingValues 0x7f120003 68int array DefaultGeolocationSettingEntries 0x7f120004 69int attr actionBarDivider 0x7f0100e7 70int attr actionBarStyle 0x7f0100e2 71int string AllowedDomainsForAppsDesc 0x7f0c0105 72int string AlternateErrorPagesEnabledDesc 0x7f0c0107 73int string AuthAndroidNegotiateAccountTypeDesc 0x7f0c0109 74int string AllowedDomainsForAppsTitle 0x7f0c0104 75int string AlternateErrorPagesEnabledTitle 0x7f0c0106 76int[] styleable SnackbarLayout { 0x0101011f, 0x7f010076, 0x7f0100ba } 77int styleable SnackbarLayout_android_maxWidth 0 78int styleable SnackbarLayout_elevation 2 79''' 80 81# Test allowlist R.txt file. Note that AlternateErrorPagesEnabledTitle is 82# listed as an 'anim' and should thus be skipped. Similarly the string 83# 'ThisStringDoesNotAppear' should not be in the final result. 84_TEST_ALLOWLIST_R_TXT = r'''int anim AlternateErrorPagesEnabledTitle 0x7f0eeeee 85int string AllowedDomainsForAppsDesc 0x7f0c0105 86int string AlternateErrorPagesEnabledDesc 0x7f0c0107 87int string ThisStringDoesNotAppear 0x7f0fffff 88''' 89 90_TEST_R_TEXT_RESOURCES_IDS = { 91 0x7f0c0105: 'AllowedDomainsForAppsDesc', 92 0x7f0c0107: 'AlternateErrorPagesEnabledDesc', 93} 94 95# Names of string resources in _TEST_R_TXT, should be sorted! 96_TEST_R_TXT_STRING_RESOURCE_NAMES = sorted([ 97 'AllowedDomainsForAppsDesc', 98 'AllowedDomainsForAppsTitle', 99 'AlternateErrorPagesEnabledDesc', 100 'AlternateErrorPagesEnabledTitle', 101 'AuthAndroidNegotiateAccountTypeDesc', 102]) 103 104 105def _CreateTestFile(tmp_dir, file_name, file_data): 106 file_path = os.path.join(tmp_dir, file_name) 107 with open(file_path, 'wt') as f: 108 f.write(file_data) 109 return file_path 110 111 112 113class ResourceUtilsTest(unittest.TestCase): 114 115 def test_GetRTxtStringResourceNames(self): 116 with build_utils.TempDir() as tmp_dir: 117 tmp_file = _CreateTestFile(tmp_dir, "test_R.txt", _TEST_R_TXT) 118 self.assertListEqual( 119 resource_utils.GetRTxtStringResourceNames(tmp_file), 120 _TEST_R_TXT_STRING_RESOURCE_NAMES) 121 122 def test_GenerateStringResourcesAllowList(self): 123 with build_utils.TempDir() as tmp_dir: 124 tmp_module_rtxt_file = _CreateTestFile(tmp_dir, "test_R.txt", _TEST_R_TXT) 125 tmp_allowlist_rtxt_file = _CreateTestFile(tmp_dir, "test_allowlist_R.txt", 126 _TEST_ALLOWLIST_R_TXT) 127 self.assertDictEqual( 128 resource_utils.GenerateStringResourcesAllowList( 129 tmp_module_rtxt_file, tmp_allowlist_rtxt_file), 130 _TEST_R_TEXT_RESOURCES_IDS) 131 132 def test_IsAndroidLocaleQualifier(self): 133 good_locales = [ 134 'en', 135 'en-rUS', 136 'fil', 137 'fil-rPH', 138 'iw', 139 'iw-rIL', 140 'b+en', 141 'b+en+US', 142 'b+ja+Latn', 143 'b+ja+JP+Latn', 144 'b+cmn+Hant-TW', 145 ] 146 bad_locales = [ 147 'e', 'english', 'en-US', 'en_US', 'en-rus', 'b+e', 'b+english', 'b+ja+' 148 ] 149 for locale in good_locales: 150 self.assertTrue( 151 resource_utils.IsAndroidLocaleQualifier(locale), 152 msg="'%s' should be a good locale!" % locale) 153 154 for locale in bad_locales: 155 self.assertFalse( 156 resource_utils.IsAndroidLocaleQualifier(locale), 157 msg="'%s' should be a bad locale!" % locale) 158 159 def test_ToAndroidLocaleName(self): 160 _TEST_CHROMIUM_TO_ANDROID_LOCALE_MAP = { 161 'en': 'en', 162 'en-US': 'en-rUS', 163 'en-FOO': 'en-rFOO', 164 'fil': 'tl', 165 'tl': 'tl', 166 'he': 'iw', 167 'he-IL': 'iw-rIL', 168 'id': 'in', 169 'id-BAR': 'in-rBAR', 170 'nb': 'nb', 171 'yi': 'ji' 172 } 173 for chromium_locale, android_locale in \ 174 _TEST_CHROMIUM_TO_ANDROID_LOCALE_MAP.items(): 175 result = resource_utils.ToAndroidLocaleName(chromium_locale) 176 self.assertEqual(result, android_locale) 177 178 def test_ToChromiumLocaleName(self): 179 _TEST_ANDROID_TO_CHROMIUM_LOCALE_MAP = { 180 'foo': 'foo', 181 'foo-rBAR': 'foo-BAR', 182 'b+lll': 'lll', 183 'b+ll+Extra': 'll', 184 'b+ll+RR': 'll-RR', 185 'b+lll+RR+Extra': 'lll-RR', 186 'b+ll+RRR+Extra': 'll-RRR', 187 'b+ll+Ssss': 'll-Ssss', 188 'b+ll+Ssss+Extra': 'll-Ssss', 189 'b+ll+Ssss+RR': 'll-Ssss-RR', 190 'b+ll+Ssss+RRR': 'll-Ssss-RRR', 191 'b+ll+Ssss+RRR+Extra': 'll-Ssss-RRR', 192 'b+ll+Whatever': 'll', 193 'en': 'en', 194 'en-rUS': 'en-US', 195 'en-US': None, 196 'en-FOO': None, 197 'en-rFOO': 'en-FOO', 198 'es-rES': 'es-ES', 199 'es-rUS': 'es-419', 200 'tl': 'fil', 201 'fil': 'fil', 202 'iw': 'he', 203 'iw-rIL': 'he-IL', 204 'b+iw+IL': 'he-IL', 205 'in': 'id', 206 'in-rBAR': 'id-BAR', 207 'id-rBAR': 'id-BAR', 208 'nb': 'nb', 209 'no': 'nb', # http://crbug.com/920960 210 } 211 for android_locale, chromium_locale in \ 212 _TEST_ANDROID_TO_CHROMIUM_LOCALE_MAP.items(): 213 result = resource_utils.ToChromiumLocaleName(android_locale) 214 self.assertEqual(result, chromium_locale) 215 216 def test_FindLocaleInStringResourceFilePath(self): 217 self.assertEqual( 218 None, 219 resource_utils.FindLocaleInStringResourceFilePath( 220 'res/values/whatever.xml')) 221 self.assertEqual( 222 'foo', 223 resource_utils.FindLocaleInStringResourceFilePath( 224 'res/values-foo/whatever.xml')) 225 self.assertEqual( 226 'foo-rBAR', 227 resource_utils.FindLocaleInStringResourceFilePath( 228 'res/values-foo-rBAR/whatever.xml')) 229 self.assertEqual( 230 None, 231 resource_utils.FindLocaleInStringResourceFilePath( 232 'res/values-foo/ignore-subdirs/whatever.xml')) 233 234 def test_ParseAndroidResourceStringsFromXml(self): 235 ret, namespaces = resource_utils.ParseAndroidResourceStringsFromXml( 236 _TEST_XML_INPUT_1) 237 self.assertDictEqual(ret, _TEST_RESOURCES_MAP_1) 238 self.assertDictEqual(namespaces, _TEST_NAMESPACES_1) 239 240 def test_GenerateAndroidResourceStringsXml(self): 241 # Fist, an empty strings map, with no namespaces 242 result = resource_utils.GenerateAndroidResourceStringsXml({}) 243 self.assertEqual(result.decode('utf8'), _TEST_XML_OUTPUT_EMPTY) 244 245 result = resource_utils.GenerateAndroidResourceStringsXml( 246 _TEST_RESOURCES_MAP_1, _TEST_NAMESPACES_1) 247 self.assertEqual(result.decode('utf8'), _TEST_XML_INPUT_1) 248 249 @staticmethod 250 def _CreateTestResourceFile(output_dir, locale, string_map, namespaces): 251 values_dir = os.path.join(output_dir, 'values-' + locale) 252 build_utils.MakeDirectory(values_dir) 253 file_path = os.path.join(values_dir, 'strings.xml') 254 with open(file_path, 'wb') as f: 255 file_data = resource_utils.GenerateAndroidResourceStringsXml( 256 string_map, namespaces) 257 f.write(file_data) 258 return file_path 259 260 def _CheckTestResourceFile(self, file_path, expected_data): 261 with open(file_path) as f: 262 file_data = f.read() 263 self.assertEqual(file_data, expected_data) 264 265 def test_FilterAndroidResourceStringsXml(self): 266 with build_utils.TempDir() as tmp_path: 267 test_file = self._CreateTestResourceFile( 268 tmp_path, 'foo', _TEST_RESOURCES_MAP_1, _TEST_NAMESPACES_1) 269 resource_utils.FilterAndroidResourceStringsXml( 270 test_file, lambda x: x in _TEST_RESOURCES_ALLOWLIST_1) 271 self._CheckTestResourceFile(test_file, _TEST_XML_OUTPUT_2) 272 273 274if __name__ == '__main__': 275 unittest.main() 276