1 /*
2  * Copyright (C) 2016 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 "compile/PseudolocaleGenerator.h"
18 
19 #include "test/Test.h"
20 #include "util/Util.h"
21 
22 using ::android::ConfigDescription;
23 
24 namespace aapt {
25 
TEST(PseudolocaleGeneratorTest,PseudolocalizeStyledString)26 TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) {
27   android::StringPool pool;
28   android::StyleString original_style;
29   original_style.str = "Hello world!";
30   original_style.spans = {android::Span{"i", 1, 10}, android::Span{"b", 2, 3},
31                           android::Span{"b", 6, 7}};
32 
33   std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString(
34       util::make_unique<StyledString>(pool.MakeRef(original_style)).get(),
35       Pseudolocalizer::Method::kNone, &pool);
36 
37   EXPECT_EQ(original_style.str, new_string->value->value);
38   ASSERT_EQ(original_style.spans.size(), new_string->value->spans.size());
39 
40   EXPECT_EQ(std::string("i"), *new_string->value->spans[0].name);
41   EXPECT_EQ(std::u16string(u"H").size(), new_string->value->spans[0].first_char);
42   EXPECT_EQ(std::u16string(u"Hello worl").size(), new_string->value->spans[0].last_char);
43 
44   EXPECT_EQ(std::string("b"), *new_string->value->spans[1].name);
45   EXPECT_EQ(std::u16string(u"He").size(), new_string->value->spans[1].first_char);
46   EXPECT_EQ(std::u16string(u"Hel").size(), new_string->value->spans[1].last_char);
47 
48   EXPECT_EQ(std::string("b"), *new_string->value->spans[2].name);
49   EXPECT_EQ(std::u16string(u"Hello ").size(), new_string->value->spans[2].first_char);
50   EXPECT_EQ(std::u16string(u"Hello w").size(), new_string->value->spans[2].last_char);
51 
52   original_style.spans.insert(original_style.spans.begin(), android::Span{"em", 0, 11u});
53 
54   new_string = PseudolocalizeStyledString(
55       util::make_unique<StyledString>(pool.MakeRef(original_style)).get(),
56       Pseudolocalizer::Method::kAccent, &pool);
57 
58   EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), new_string->value->value);
59   ASSERT_EQ(original_style.spans.size(), new_string->value->spans.size());
60 
61   EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char);
62   EXPECT_EQ(std::u16string(u"[Ĥéļļö ŵöŕļð").size(), new_string->value->spans[0].last_char);
63 
64   EXPECT_EQ(std::u16string(u"[Ĥ").size(), new_string->value->spans[1].first_char);
65   EXPECT_EQ(std::u16string(u"[Ĥéļļö ŵöŕļ").size(), new_string->value->spans[1].last_char);
66 
67   EXPECT_EQ(std::u16string(u"[Ĥé").size(), new_string->value->spans[2].first_char);
68   EXPECT_EQ(std::u16string(u"[Ĥéļ").size(), new_string->value->spans[2].last_char);
69 
70   EXPECT_EQ(std::u16string(u"[Ĥéļļö ").size(), new_string->value->spans[3].first_char);
71   EXPECT_EQ(std::u16string(u"[Ĥéļļö ŵ").size(), new_string->value->spans[3].last_char);
72 }
73 
TEST(PseudolocaleGeneratorTest,PseudolocalizeAdjacentNestedTags)74 TEST(PseudolocaleGeneratorTest, PseudolocalizeAdjacentNestedTags) {
75   android::StringPool pool;
76   android::StyleString original_style;
77   original_style.str = "bold";
78   original_style.spans = {android::Span{"b", 0, 3}, android::Span{"i", 0, 3}};
79 
80   std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString(
81       util::make_unique<StyledString>(pool.MakeRef(original_style)).get(),
82       Pseudolocalizer::Method::kAccent, &pool);
83   ASSERT_NE(nullptr, new_string);
84   ASSERT_EQ(2u, new_string->value->spans.size());
85   EXPECT_EQ(std::string("[ɓöļð one]"), new_string->value->value);
86 
87   EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name);
88   EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char);
89   EXPECT_EQ(std::u16string(u"[ɓöļ").size(), new_string->value->spans[0].last_char);
90 
91   EXPECT_EQ(std::string("i"), *new_string->value->spans[1].name);
92   EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[1].first_char);
93   EXPECT_EQ(std::u16string(u"[ɓöļ").size(), new_string->value->spans[1].last_char);
94 }
95 
TEST(PseudolocaleGeneratorTest,PseudolocalizeAdjacentTagsUnsorted)96 TEST(PseudolocaleGeneratorTest, PseudolocalizeAdjacentTagsUnsorted) {
97   android::StringPool pool;
98   android::StyleString original_style;
99   original_style.str = "bold";
100   original_style.spans = {android::Span{"i", 2, 3}, android::Span{"b", 0, 1}};
101 
102   std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString(
103       util::make_unique<StyledString>(pool.MakeRef(original_style)).get(),
104       Pseudolocalizer::Method::kAccent, &pool);
105   ASSERT_NE(nullptr, new_string);
106   ASSERT_EQ(2u, new_string->value->spans.size());
107   EXPECT_EQ(std::string("[ɓöļð one]"), new_string->value->value);
108 
109   EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name);
110   EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char);
111   EXPECT_EQ(std::u16string(u"[ɓ").size(), new_string->value->spans[0].last_char);
112 
113   EXPECT_EQ(std::string("i"), *new_string->value->spans[1].name);
114   EXPECT_EQ(std::u16string(u"[ɓö").size(), new_string->value->spans[1].first_char);
115   EXPECT_EQ(std::u16string(u"[ɓöļ").size(), new_string->value->spans[1].last_char);
116 }
117 
TEST(PseudolocaleGeneratorTest,PseudolocalizeNestedAndAdjacentTags)118 TEST(PseudolocaleGeneratorTest, PseudolocalizeNestedAndAdjacentTags) {
119   android::StringPool pool;
120   android::StyleString original_style;
121   original_style.str = "This sentence is not what you think it is at all.";
122   original_style.spans = {android::Span{"b", 16u, 19u}, android::Span{"em", 29u, 47u},
123                           android::Span{"i", 38u, 40u}, android::Span{"b", 44u, 47u}};
124 
125   std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString(
126       util::make_unique<StyledString>(pool.MakeRef(original_style)).get(),
127       Pseudolocalizer::Method::kAccent, &pool);
128   ASSERT_NE(nullptr, new_string);
129   ASSERT_EQ(4u, new_string->value->spans.size());
130   EXPECT_EQ(std::string(
131                 "[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ åļļ. one two three four five six]"),
132             new_string->value->value);
133 
134   EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name);
135   EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš").size(), new_string->value->spans[0].first_char);
136   EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñö").size(), new_string->value->spans[0].last_char);
137 
138   EXPECT_EQ(std::string("em"), *new_string->value->spans[1].name);
139   EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû").size(),
140             new_string->value->spans[1].first_char);
141   EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ åļ").size(),
142             new_string->value->spans[1].last_char);
143 
144   EXPECT_EQ(std::string("i"), *new_string->value->spans[2].name);
145   EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ").size(),
146             new_string->value->spans[2].first_char);
147   EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ î").size(),
148             new_string->value->spans[2].last_char);
149 
150   EXPECT_EQ(std::string("b"), *new_string->value->spans[3].name);
151   EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ").size(),
152             new_string->value->spans[3].first_char);
153   EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ åļ").size(),
154             new_string->value->spans[3].last_char);
155 }
156 
TEST(PseudolocaleGeneratorTest,PseudolocalizePartsOfString)157 TEST(PseudolocaleGeneratorTest, PseudolocalizePartsOfString) {
158   android::StringPool pool;
159   android::StyleString original_style;
160   original_style.str = "This should NOT be pseudolocalized.";
161   original_style.spans = {android::Span{"em", 4u, 14u}, android::Span{"i", 18u, 33u}};
162   std::unique_ptr<StyledString> original_string =
163       util::make_unique<StyledString>(pool.MakeRef(original_style));
164   original_string->untranslatable_sections = {UntranslatableSection{11u, 15u}};
165 
166   std::unique_ptr<StyledString> new_string =
167       PseudolocalizeStyledString(original_string.get(), Pseudolocalizer::Method::kAccent, &pool);
168   ASSERT_NE(nullptr, new_string);
169   ASSERT_EQ(2u, new_string->value->spans.size());
170   EXPECT_EQ(std::string("[Ţĥîš šĥöûļð NOT ɓé þšéûðöļöçåļîžéð. one two three four]"),
171             new_string->value->value);
172 
173   EXPECT_EQ(std::string("em"), *new_string->value->spans[0].name);
174   EXPECT_EQ(std::u16string(u"[Ţĥîš").size(), new_string->value->spans[0].first_char);
175   EXPECT_EQ(std::u16string(u"[Ţĥîš šĥöûļð NO").size(), new_string->value->spans[0].last_char);
176 
177   EXPECT_EQ(std::string("i"), *new_string->value->spans[1].name);
178   EXPECT_EQ(std::u16string(u"[Ţĥîš šĥöûļð NOT ɓé").size(), new_string->value->spans[1].first_char);
179   EXPECT_EQ(std::u16string(u"[Ţĥîš šĥöûļð NOT ɓé þšéûðöļöçåļîžé").size(),
180             new_string->value->spans[1].last_char);
181 }
182 
TEST(PseudolocaleGeneratorTest,PseudolocalizeOnlyDefaultConfigs)183 TEST(PseudolocaleGeneratorTest, PseudolocalizeOnlyDefaultConfigs) {
184   std::unique_ptr<ResourceTable> table =
185       test::ResourceTableBuilder()
186           .AddString("android:string/one", "one")
187           .AddString("android:string/two", ResourceId{},
188                      test::ParseConfigOrDie("en"), "two")
189           .AddString("android:string/three", "three")
190           .AddString("android:string/three", ResourceId{},
191                      test::ParseConfigOrDie("en-rXA"), "three")
192           .AddString("android:string/four", "four")
193           .Build();
194 
195   String* val = test::GetValue<String>(table.get(), "android:string/four");
196   ASSERT_NE(nullptr, val);
197   val->SetTranslatable(false);
198 
199   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
200   PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0"));
201   ASSERT_TRUE(generator.Consume(context.get(), table.get()));
202 
203   // Normal pseudolocalization should take place.
204   ASSERT_NE(nullptr,
205             test::GetValueForConfig<String>(table.get(), "android:string/one",
206                                             test::ParseConfigOrDie("en-rXA")));
207   ASSERT_NE(nullptr,
208             test::GetValueForConfig<String>(table.get(), "android:string/one",
209                                             test::ParseConfigOrDie("ar-rXB")));
210 
211   // No default config for android:string/two, so no pseudlocales should exist.
212   ASSERT_EQ(nullptr,
213             test::GetValueForConfig<String>(table.get(), "android:string/two",
214                                             test::ParseConfigOrDie("en-rXA")));
215   ASSERT_EQ(nullptr,
216             test::GetValueForConfig<String>(table.get(), "android:string/two",
217                                             test::ParseConfigOrDie("ar-rXB")));
218 
219   // Check that we didn't override manual pseudolocalization.
220   val = test::GetValueForConfig<String>(table.get(), "android:string/three",
221                                         test::ParseConfigOrDie("en-rXA"));
222   ASSERT_NE(nullptr, val);
223   EXPECT_EQ(std::string("three"), *val->value);
224 
225   ASSERT_NE(nullptr,
226             test::GetValueForConfig<String>(table.get(), "android:string/three",
227                                             test::ParseConfigOrDie("ar-rXB")));
228 
229   // Check that four's translateable marker was honored.
230   ASSERT_EQ(nullptr,
231             test::GetValueForConfig<String>(table.get(), "android:string/four",
232                                             test::ParseConfigOrDie("en-rXA")));
233   ASSERT_EQ(nullptr,
234             test::GetValueForConfig<String>(table.get(), "android:string/four",
235                                             test::ParseConfigOrDie("ar-rXB")));
236 }
237 
TEST(PseudolocaleGeneratorTest,PluralsArePseudolocalized)238 TEST(PseudolocaleGeneratorTest, PluralsArePseudolocalized) {
239   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
240   std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder().Build();
241   std::unique_ptr<Plural> plural = util::make_unique<Plural>();
242   plural->values = {util::make_unique<String>(table->string_pool.MakeRef("zero")),
243                     util::make_unique<String>(table->string_pool.MakeRef("one"))};
244   ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.pkg:plurals/foo"))
245                                      .SetValue(std::move(plural))
246                                      .Build(),
247                                  context->GetDiagnostics()));
248   std::unique_ptr<Plural> expected = util::make_unique<Plural>();
249   expected->values = {util::make_unique<String>(table->string_pool.MakeRef("[žéŕö one]")),
250                       util::make_unique<String>(table->string_pool.MakeRef("[öñé one]"))};
251 
252   PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0"));
253   ASSERT_TRUE(generator.Consume(context.get(), table.get()));
254 
255   const auto* actual = test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo",
256                                                        test::ParseConfigOrDie("en-rXA"));
257   ASSERT_NE(nullptr, actual);
258   EXPECT_TRUE(actual->Equals(expected.get()));
259 }
260 
TEST(PseudolocaleGeneratorTest,RespectUntranslateableSections)261 TEST(PseudolocaleGeneratorTest, RespectUntranslateableSections) {
262   std::unique_ptr<IAaptContext> context =
263       test::ContextBuilder().SetCompilationPackage("android").Build();
264   std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
265 
266   {
267     android::StyleString original_style;
268     original_style.str = "Hello world!";
269     original_style.spans = {android::Span{"i", 1, 10}, android::Span{"b", 2, 3},
270                             android::Span{"b", 6, 7}};
271 
272     auto styled_string =
273         util::make_unique<StyledString>(table->string_pool.MakeRef(original_style));
274     styled_string->untranslatable_sections.push_back(UntranslatableSection{6u, 8u});
275     styled_string->untranslatable_sections.push_back(UntranslatableSection{8u, 11u});
276 
277     auto string = util::make_unique<String>(table->string_pool.MakeRef(original_style.str));
278     string->untranslatable_sections.push_back(UntranslatableSection{6u, 11u});
279 
280     ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("android:string/foo"))
281                                        .SetValue(std::move(styled_string))
282                                        .Build(),
283                                    context->GetDiagnostics()));
284     ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("android:string/bar"))
285                                        .SetValue(std::move(string))
286                                        .Build(),
287                                    context->GetDiagnostics()));
288   }
289 
290   PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0"));
291   ASSERT_TRUE(generator.Consume(context.get(), table.get()));
292 
293   StyledString* new_styled_string = test::GetValueForConfig<StyledString>(
294       table.get(), "android:string/foo", test::ParseConfigOrDie("en-rXA"));
295   ASSERT_NE(nullptr, new_styled_string);
296 
297   // "world" should be untranslated.
298   EXPECT_NE(std::string::npos, new_styled_string->value->value.find("world"));
299 
300   String* new_string = test::GetValueForConfig<String>(table.get(), "android:string/bar",
301                                                        test::ParseConfigOrDie("en-rXA"));
302   ASSERT_NE(nullptr, new_string);
303 
304   // "world" should be untranslated.
305   EXPECT_NE(std::string::npos, new_string->value->find("world"));
306 }
307 
TEST(PseudolocaleGeneratorTest,PseudolocalizeGrammaticalGenderForString)308 TEST(PseudolocaleGeneratorTest, PseudolocalizeGrammaticalGenderForString) {
309   std::unique_ptr<ResourceTable> table =
310       test::ResourceTableBuilder().AddString("android:string/foo", "foo").Build();
311 
312   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
313   PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0"));
314   ASSERT_TRUE(generator.Consume(context.get(), table.get()));
315 
316   String* locale = test::GetValueForConfig<String>(table.get(), "android:string/foo",
317                                                    test::ParseConfigOrDie("en-rXA"));
318   ASSERT_NE(nullptr, locale);
319 
320   // Grammatical gendered string
321   auto config_feminine = test::ParseConfigOrDie("en-rXA-feminine");
322   config_feminine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
323   String* feminine =
324       test::GetValueForConfig<String>(table.get(), "android:string/foo", config_feminine);
325   ASSERT_NE(nullptr, feminine);
326   EXPECT_EQ(std::string("(F)") + *locale->value, *feminine->value);
327 
328   auto config_masculine = test::ParseConfigOrDie("en-rXA-masculine");
329   config_masculine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
330   String* masculine =
331       test::GetValueForConfig<String>(table.get(), "android:string/foo", config_masculine);
332   ASSERT_NE(nullptr, masculine);
333   EXPECT_EQ(std::string("(M)") + *locale->value, *masculine->value);
334 
335   auto config_neuter = test::ParseConfigOrDie("en-rXA-neuter");
336   config_neuter.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
337   String* neuter =
338       test::GetValueForConfig<String>(table.get(), "android:string/foo", config_neuter);
339   ASSERT_NE(nullptr, neuter);
340   EXPECT_EQ(std::string("(N)") + *locale->value, *neuter->value);
341 }
342 
TEST(PseudolocaleGeneratorTest,PseudolocalizeGrammaticalGenderForPlural)343 TEST(PseudolocaleGeneratorTest, PseudolocalizeGrammaticalGenderForPlural) {
344   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
345   std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder().Build();
346   std::unique_ptr<Plural> plural = util::make_unique<Plural>();
347   plural->values = {util::make_unique<String>(table->string_pool.MakeRef("zero")),
348                     util::make_unique<String>(table->string_pool.MakeRef("one"))};
349   ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.pkg:plurals/foo"))
350                                      .SetValue(std::move(plural))
351                                      .Build(),
352                                  context->GetDiagnostics()));
353   PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0"));
354   ASSERT_TRUE(generator.Consume(context.get(), table.get()));
355 
356   Plural* actual = test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo",
357                                                    test::ParseConfigOrDie("en-rXA"));
358   ASSERT_NE(nullptr, actual);
359 
360   // Grammatical gendered Plural
361   auto config_feminine = test::ParseConfigOrDie("en-rXA-feminine");
362   config_feminine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
363   Plural* actual_feminine =
364       test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo", config_feminine);
365   for (size_t i = 0; i < actual->values.size(); i++) {
366     if (actual->values[i]) {
367       String* locale = ValueCast<String>(actual->values[i].get());
368       String* feminine = ValueCast<String>(actual_feminine->values[i].get());
369       EXPECT_EQ(std::string("(F)") + *locale->value, *feminine->value);
370     }
371   }
372 
373   auto config_masculine = test::ParseConfigOrDie("en-rXA-masculine");
374   config_masculine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
375   Plural* actual_masculine =
376       test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo", config_masculine);
377   ASSERT_NE(nullptr, actual_masculine);
378   for (size_t i = 0; i < actual->values.size(); i++) {
379     if (actual->values[i]) {
380       String* locale = ValueCast<String>(actual->values[i].get());
381       String* masculine = ValueCast<String>(actual_masculine->values[i].get());
382       EXPECT_EQ(std::string("(M)") + *locale->value, *masculine->value);
383     }
384   }
385 
386   auto config_neuter = test::ParseConfigOrDie("en-rXA-neuter");
387   config_neuter.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
388   Plural* actual_neuter =
389       test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo", config_neuter);
390   for (size_t i = 0; i < actual->values.size(); i++) {
391     if (actual->values[i]) {
392       String* locale = ValueCast<String>(actual->values[i].get());
393       String* neuter = ValueCast<String>(actual_neuter->values[i].get());
394       EXPECT_EQ(std::string("(N)") + *locale->value, *neuter->value);
395     }
396   }
397 }
398 
TEST(PseudolocaleGeneratorTest,PseudolocalizeGrammaticalGenderForStyledString)399 TEST(PseudolocaleGeneratorTest, PseudolocalizeGrammaticalGenderForStyledString) {
400   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
401   std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder().Build();
402   android::StyleString original_style;
403   original_style.str = "Hello world!";
404   original_style.spans = {android::Span{"i", 1, 10}};
405 
406   std::unique_ptr<StyledString> original =
407       util::make_unique<StyledString>(table->string_pool.MakeRef(original_style));
408   ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("android:string/foo"))
409                                      .SetValue(std::move(original))
410                                      .Build(),
411                                  context->GetDiagnostics()));
412   PseudolocaleGenerator generator(std::string("f,m,n"), std::string("1.0"));
413   ASSERT_TRUE(generator.Consume(context.get(), table.get()));
414 
415   StyledString* locale = test::GetValueForConfig<StyledString>(table.get(), "android:string/foo",
416                                                                test::ParseConfigOrDie("en-rXA"));
417   ASSERT_NE(nullptr, locale);
418   EXPECT_EQ(1, locale->value->spans.size());
419   EXPECT_EQ(std::string("i"), *locale->value->spans[0].name);
420 
421   // Grammatical gendered StyledString
422   auto config_feminine = test::ParseConfigOrDie("en-rXA-feminine");
423   config_feminine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
424   StyledString* feminine =
425       test::GetValueForConfig<StyledString>(table.get(), "android:string/foo", config_feminine);
426   ASSERT_NE(nullptr, feminine);
427   EXPECT_EQ(1, feminine->value->spans.size());
428   EXPECT_EQ(std::string("i"), *feminine->value->spans[0].name);
429   EXPECT_EQ(std::string("(F)") + locale->value->value, feminine->value->value);
430 
431   auto config_masculine = test::ParseConfigOrDie("en-rXA-masculine");
432   config_masculine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
433   StyledString* masculine =
434       test::GetValueForConfig<StyledString>(table.get(), "android:string/foo", config_masculine);
435   ASSERT_NE(nullptr, masculine);
436   EXPECT_EQ(1, masculine->value->spans.size());
437   EXPECT_EQ(std::string("i"), *masculine->value->spans[0].name);
438   EXPECT_EQ(std::string("(M)") + locale->value->value, masculine->value->value);
439 
440   auto config_neuter = test::ParseConfigOrDie("en-rXA-neuter");
441   config_neuter.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
442   StyledString* neuter =
443       test::GetValueForConfig<StyledString>(table.get(), "android:string/foo", config_neuter);
444   ASSERT_NE(nullptr, neuter);
445   EXPECT_EQ(1, neuter->value->spans.size());
446   EXPECT_EQ(std::string("i"), *neuter->value->spans[0].name);
447   EXPECT_EQ(std::string("(N)") + locale->value->value, neuter->value->value);
448 }
449 
TEST(PseudolocaleGeneratorTest,GrammaticalGenderForCertainValues)450 TEST(PseudolocaleGeneratorTest, GrammaticalGenderForCertainValues) {
451   // single gender value
452   std::unique_ptr<ResourceTable> table_0 =
453       test::ResourceTableBuilder().AddString("android:string/foo", "foo").Build();
454 
455   std::unique_ptr<IAaptContext> context_0 = test::ContextBuilder().Build();
456   PseudolocaleGenerator generator_0(std::string("f"), std::string("1.0"));
457   ASSERT_TRUE(generator_0.Consume(context_0.get(), table_0.get()));
458 
459   String* locale_0 = test::GetValueForConfig<String>(table_0.get(), "android:string/foo",
460                                                      test::ParseConfigOrDie("en-rXA"));
461   ASSERT_NE(nullptr, locale_0);
462 
463   auto config_feminine = test::ParseConfigOrDie("en-rXA-feminine");
464   config_feminine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
465   String* feminine_0 =
466       test::GetValueForConfig<String>(table_0.get(), "android:string/foo", config_feminine);
467   ASSERT_NE(nullptr, feminine_0);
468   EXPECT_EQ(std::string("(F)") + *locale_0->value, *feminine_0->value);
469 
470   auto config_masculine = test::ParseConfigOrDie("en-rXA-masculine");
471   config_masculine.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
472   String* masculine_0 =
473       test::GetValueForConfig<String>(table_0.get(), "android:string/foo", config_masculine);
474   EXPECT_EQ(nullptr, masculine_0);
475 
476   auto config_neuter = test::ParseConfigOrDie("en-rXA-neuter");
477   config_neuter.sdkVersion = android::ResTable_config::SDKVERSION_ANY;
478   String* neuter_0 =
479       test::GetValueForConfig<String>(table_0.get(), "android:string/foo", config_neuter);
480   EXPECT_EQ(nullptr, neuter_0);
481 
482   // multiple gender values
483   std::unique_ptr<ResourceTable> table_1 =
484       test::ResourceTableBuilder().AddString("android:string/foo", "foo").Build();
485 
486   std::unique_ptr<IAaptContext> context_1 = test::ContextBuilder().Build();
487   PseudolocaleGenerator generator_1(std::string("f,n"), std::string("1.0"));
488   ASSERT_TRUE(generator_1.Consume(context_1.get(), table_1.get()));
489 
490   String* locale_1 = test::GetValueForConfig<String>(table_1.get(), "android:string/foo",
491                                                      test::ParseConfigOrDie("en-rXA"));
492   ASSERT_NE(nullptr, locale_1);
493 
494   String* feminine_1 =
495       test::GetValueForConfig<String>(table_1.get(), "android:string/foo", config_feminine);
496   ASSERT_NE(nullptr, feminine_1);
497   EXPECT_EQ(std::string("(F)") + *locale_1->value, *feminine_1->value);
498 
499   String* masculine_1 =
500       test::GetValueForConfig<String>(table_1.get(), "android:string/foo", config_masculine);
501   EXPECT_EQ(nullptr, masculine_1);
502 
503   String* neuter_1 =
504       test::GetValueForConfig<String>(table_1.get(), "android:string/foo", config_neuter);
505   ASSERT_NE(nullptr, neuter_1);
506   EXPECT_EQ(std::string("(N)") + *locale_1->value, *neuter_1->value);
507 
508   // invalid gender value
509   std::unique_ptr<ResourceTable> table_2 =
510       test::ResourceTableBuilder().AddString("android:string/foo", "foo").Build();
511 
512   std::unique_ptr<IAaptContext> context_2 = test::ContextBuilder().Build();
513   PseudolocaleGenerator generator_2(std::string("invald,"), std::string("1.0"));
514   ASSERT_FALSE(generator_2.Consume(context_2.get(), table_2.get()));
515 }
516 
517 }  // namespace aapt
518