1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "utils/resources.h"
18
19 #include "utils/i18n/locale.h"
20 #include "utils/resources_generated.h"
21 #include "gmock/gmock.h"
22 #include "gtest/gtest.h"
23
24 namespace libtextclassifier3 {
25 namespace {
26
27 class ResourcesTest : public testing::Test {
28 protected:
ResourcesTest()29 ResourcesTest() {}
30
BuildTestResources(bool add_default_language=true) const31 std::string BuildTestResources(bool add_default_language = true) const {
32 ResourcePoolT test_resources;
33
34 // Test locales.
35 test_resources.locale.emplace_back(new LanguageTagT);
36 test_resources.locale.back()->language = "en";
37 test_resources.locale.back()->region = "US";
38 test_resources.locale.emplace_back(new LanguageTagT);
39 test_resources.locale.back()->language = "en";
40 test_resources.locale.back()->region = "GB";
41 test_resources.locale.emplace_back(new LanguageTagT);
42 test_resources.locale.back()->language = "de";
43 test_resources.locale.back()->region = "DE";
44 test_resources.locale.emplace_back(new LanguageTagT);
45 test_resources.locale.back()->language = "fr";
46 test_resources.locale.back()->region = "FR";
47 test_resources.locale.emplace_back(new LanguageTagT);
48 test_resources.locale.back()->language = "pt";
49 test_resources.locale.back()->region = "PT";
50 test_resources.locale.emplace_back(new LanguageTagT);
51 test_resources.locale.back()->language = "pt";
52 test_resources.locale.emplace_back(new LanguageTagT);
53 test_resources.locale.back()->language = "zh";
54 test_resources.locale.back()->script = "Hans";
55 test_resources.locale.back()->region = "CN";
56 test_resources.locale.emplace_back(new LanguageTagT);
57 test_resources.locale.back()->language = "zh";
58 test_resources.locale.emplace_back(new LanguageTagT);
59 test_resources.locale.back()->language = "fr";
60 test_resources.locale.back()->region = "CA";
61 test_resources.locale.emplace_back(new LanguageTagT);
62 test_resources.locale.back()->language = "in";
63 if (add_default_language) {
64 test_resources.locale.emplace_back(new LanguageTagT); // default
65 }
66
67 // Test entries.
68 test_resources.resource_entry.emplace_back(new ResourceEntryT);
69 test_resources.resource_entry.back()->name = /*resource_name=*/"A";
70
71 // en-US, default
72 test_resources.resource_entry.back()->resource.emplace_back(new ResourceT);
73 test_resources.resource_entry.back()->resource.back()->content = "localize";
74 test_resources.resource_entry.back()->resource.back()->locale.push_back(0);
75 if (add_default_language) {
76 test_resources.resource_entry.back()->resource.back()->locale.push_back(
77 10);
78 }
79
80 // en-GB
81 test_resources.resource_entry.back()->resource.emplace_back(new ResourceT);
82 test_resources.resource_entry.back()->resource.back()->content = "localise";
83 test_resources.resource_entry.back()->resource.back()->locale.push_back(1);
84
85 // de-DE
86 test_resources.resource_entry.back()->resource.emplace_back(new ResourceT);
87 test_resources.resource_entry.back()->resource.back()->content =
88 "lokalisieren";
89 test_resources.resource_entry.back()->resource.back()->locale.push_back(2);
90
91 // fr-FR, fr-CA
92 test_resources.resource_entry.back()->resource.emplace_back(new ResourceT);
93 test_resources.resource_entry.back()->resource.back()->content =
94 "localiser";
95 test_resources.resource_entry.back()->resource.back()->locale.push_back(3);
96 test_resources.resource_entry.back()->resource.back()->locale.push_back(8);
97
98 // pt-PT
99 test_resources.resource_entry.back()->resource.emplace_back(new ResourceT);
100 test_resources.resource_entry.back()->resource.back()->content =
101 "localizar";
102 test_resources.resource_entry.back()->resource.back()->locale.push_back(4);
103
104 // pt
105 test_resources.resource_entry.back()->resource.emplace_back(new ResourceT);
106 test_resources.resource_entry.back()->resource.back()->content =
107 "concentrar";
108 test_resources.resource_entry.back()->resource.back()->locale.push_back(5);
109
110 // zh-Hans-CN
111 test_resources.resource_entry.back()->resource.emplace_back(new ResourceT);
112 test_resources.resource_entry.back()->resource.back()->content = "龙";
113 test_resources.resource_entry.back()->resource.back()->locale.push_back(6);
114
115 // zh
116 test_resources.resource_entry.back()->resource.emplace_back(new ResourceT);
117 test_resources.resource_entry.back()->resource.back()->content = "龍";
118 test_resources.resource_entry.back()->resource.back()->locale.push_back(7);
119
120 // in
121 test_resources.resource_entry.back()->resource.emplace_back(new ResourceT);
122 test_resources.resource_entry.back()->resource.back()->content =
123 "Apa kabar";
124 test_resources.resource_entry.back()->resource.back()->locale.push_back(9);
125
126 flatbuffers::FlatBufferBuilder builder;
127 builder.Finish(ResourcePool::Pack(builder, &test_resources));
128
129 return std::string(
130 reinterpret_cast<const char*>(builder.GetBufferPointer()),
131 builder.GetSize());
132 }
133 };
134
TEST_F(ResourcesTest,CorrectlyHandlesExactMatch)135 TEST_F(ResourcesTest, CorrectlyHandlesExactMatch) {
136 std::string test_resources = BuildTestResources();
137 Resources resources(
138 flatbuffers::GetRoot<ResourcePool>(test_resources.data()));
139 std::string content;
140 EXPECT_TRUE(resources.GetResourceContent({Locale::FromBCP47("en-US")},
141 /*resource_name=*/"A", &content));
142 EXPECT_EQ("localize", content);
143 EXPECT_TRUE(resources.GetResourceContent({Locale::FromBCP47("en-GB")},
144 /*resource_name=*/"A", &content));
145 EXPECT_EQ("localise", content);
146 EXPECT_TRUE(resources.GetResourceContent({Locale::FromBCP47("pt-PT")},
147 /*resource_name=*/"A", &content));
148 EXPECT_EQ("localizar", content);
149 EXPECT_TRUE(resources.GetResourceContent({Locale::FromBCP47("zh-Hans-CN")},
150 /*resource_name=*/"A", &content));
151 EXPECT_EQ("龙", content);
152 EXPECT_TRUE(resources.GetResourceContent({Locale::FromBCP47("zh")},
153 /*resource_name=*/"A", &content));
154 EXPECT_EQ("龍", content);
155 EXPECT_TRUE(resources.GetResourceContent({Locale::FromBCP47("fr-CA")},
156 /*resource_name=*/"A", &content));
157 EXPECT_EQ("localiser", content);
158 EXPECT_TRUE(resources.GetResourceContent({Locale::FromBCP47("id")},
159 /*resource_name=*/"A", &content));
160 EXPECT_EQ("Apa kabar", content);
161 }
162
TEST_F(ResourcesTest,CorrectlyHandlesTie)163 TEST_F(ResourcesTest, CorrectlyHandlesTie) {
164 std::string test_resources = BuildTestResources();
165 Resources resources(
166 flatbuffers::GetRoot<ResourcePool>(test_resources.data()));
167 // Uses first best match in case of a tie.
168 std::string content;
169 EXPECT_TRUE(resources.GetResourceContent({Locale::FromBCP47("en-CA")},
170 /*resource_name=*/"A", &content));
171 EXPECT_EQ("localize", content);
172 }
173
TEST_F(ResourcesTest,RequiresLanguageMatch)174 TEST_F(ResourcesTest, RequiresLanguageMatch) {
175 {
176 std::string test_resources =
177 BuildTestResources(/*add_default_language=*/false);
178 Resources resources(
179 flatbuffers::GetRoot<ResourcePool>(test_resources.data()));
180 EXPECT_FALSE(resources.GetResourceContent({Locale::FromBCP47("es-US")},
181 /*resource_name=*/"A",
182 /*result=*/nullptr));
183 }
184 {
185 std::string test_resources =
186 BuildTestResources(/*add_default_language=*/true);
187 Resources resources(
188 flatbuffers::GetRoot<ResourcePool>(test_resources.data()));
189 std::string content;
190 EXPECT_TRUE(resources.GetResourceContent({Locale::FromBCP47("es-US")},
191 /*resource_name=*/"A",
192 /*result=*/&content));
193 EXPECT_EQ("localize", content);
194 }
195 }
196
TEST_F(ResourcesTest,HandlesFallback)197 TEST_F(ResourcesTest, HandlesFallback) {
198 std::string test_resources = BuildTestResources();
199 Resources resources(
200 flatbuffers::GetRoot<ResourcePool>(test_resources.data()));
201 std::string content;
202 EXPECT_TRUE(resources.GetResourceContent({Locale::FromBCP47("fr-CH")},
203 /*resource_name=*/"A", &content));
204 EXPECT_EQ("localiser", content);
205 EXPECT_TRUE(resources.GetResourceContent({Locale::FromBCP47("zh-Hans")},
206 /*resource_name=*/"A", &content));
207 EXPECT_EQ("龙", content);
208 EXPECT_TRUE(resources.GetResourceContent({Locale::FromBCP47("zh-Hans-ZZ")},
209 /*resource_name=*/"A", &content));
210 EXPECT_EQ("龙", content);
211
212 // Fallback to default, en-US.
213 EXPECT_TRUE(resources.GetResourceContent({Locale::FromBCP47("ru")},
214 /*resource_name=*/"A", &content));
215 EXPECT_EQ("localize", content);
216 }
217
TEST_F(ResourcesTest,HandlesFallbackMultipleLocales)218 TEST_F(ResourcesTest, HandlesFallbackMultipleLocales) {
219 std::string test_resources = BuildTestResources();
220 Resources resources(
221 flatbuffers::GetRoot<ResourcePool>(test_resources.data()));
222 std::string content;
223
224 // Still use inexact match with primary locale if language matches,
225 // even though secondary locale would match exactly.
226 EXPECT_TRUE(resources.GetResourceContent(
227 {Locale::FromBCP47("fr-CH"), Locale::FromBCP47("en-US")},
228 /*resource_name=*/"A", &content));
229 EXPECT_EQ("localiser", content);
230
231 // Use secondary language instead of default fallback if that is an exact
232 // language match.
233 EXPECT_TRUE(resources.GetResourceContent(
234 {Locale::FromBCP47("ru"), Locale::FromBCP47("de")},
235 /*resource_name=*/"A", &content));
236 EXPECT_EQ("lokalisieren", content);
237
238 // Use tertiary language.
239 EXPECT_TRUE(resources.GetResourceContent(
240 {Locale::FromBCP47("ru"), Locale::FromBCP47("it-IT"),
241 Locale::FromBCP47("de")},
242 /*resource_name=*/"A", &content));
243 EXPECT_EQ("lokalisieren", content);
244
245 // Default fallback if no locale matches.
246 EXPECT_TRUE(resources.GetResourceContent(
247 {Locale::FromBCP47("ru"), Locale::FromBCP47("it-IT"),
248 Locale::FromBCP47("es")},
249 /*resource_name=*/"A", &content));
250 EXPECT_EQ("localize", content);
251 }
252
TEST_F(ResourcesTest,PreferGenericCallback)253 TEST_F(ResourcesTest, PreferGenericCallback) {
254 std::string test_resources = BuildTestResources();
255 Resources resources(
256 flatbuffers::GetRoot<ResourcePool>(test_resources.data()));
257 std::string content;
258 EXPECT_TRUE(resources.GetResourceContent({Locale::FromBCP47("pt-BR")},
259 /*resource_name=*/"A", &content));
260 EXPECT_EQ("concentrar", content); // Falls back to pt, not pt-PT.
261 EXPECT_TRUE(resources.GetResourceContent({Locale::FromBCP47("zh-Hant")},
262 /*resource_name=*/"A", &content));
263 EXPECT_EQ("龍", content); // Falls back to zh, not zh-Hans-CN.
264 EXPECT_TRUE(resources.GetResourceContent({Locale::FromBCP47("zh-Hant-CN")},
265 /*resource_name=*/"A", &content));
266 EXPECT_EQ("龍", content); // Falls back to zh, not zh-Hans-CN.
267 EXPECT_TRUE(resources.GetResourceContent({Locale::FromBCP47("zh-CN")},
268 /*resource_name=*/"A", &content));
269 EXPECT_EQ("龍", content); // Falls back to zh, not zh-Hans-CN.
270 }
271
TEST_F(ResourcesTest,PreferGenericWhenGeneric)272 TEST_F(ResourcesTest, PreferGenericWhenGeneric) {
273 std::string test_resources = BuildTestResources();
274 Resources resources(
275 flatbuffers::GetRoot<ResourcePool>(test_resources.data()));
276 std::string content;
277 EXPECT_TRUE(resources.GetResourceContent({Locale::FromBCP47("pt")},
278 /*resource_name=*/"A", &content));
279
280 // Uses pt, not pt-PT.
281 EXPECT_EQ("concentrar", content);
282 }
283
284 } // namespace
285 } // namespace libtextclassifier3
286