1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /********************************************************************
4 * COPYRIGHT:
5 * Copyright (c) 1997-2016, International Business Machines Corporation and
6 * others. All Rights Reserved.
7 ********************************************************************/
8
9 #include "unicode/utypes.h"
10
11 #include "cmemory.h"
12 #include "cstring.h"
13 #include "unicode/unistr.h"
14 #include "unicode/uniset.h"
15 #include "unicode/resbund.h"
16 #include "restest.h"
17
18 #include "uresimp.h"
19 #include "ureslocs.h"
20
21 #include <stdlib.h>
22 #include <time.h>
23 #include <string.h>
24 #include <limits.h>
25
26 #include <map>
27 #include <set>
28 #include <string>
29
30 //***************************************************************************************
31
32 static const char16_t kErrorUChars[] = { 0x45, 0x52, 0x52, 0x4f, 0x52, 0 };
33 static const int32_t kErrorLength = 5;
34
35 //***************************************************************************************
36
37 enum E_Where
38 {
39 e_Root,
40 e_te,
41 e_te_IN,
42 e_Where_count
43 };
44
45 //***************************************************************************************
46
47 #define CONFIRM_EQ(actual, expected, myAction) UPRV_BLOCK_MACRO_BEGIN { \
48 if ((expected)==(actual)) { \
49 record_pass(myAction); \
50 } else { \
51 record_fail(myAction + (UnicodeString)" returned " + (actual) + (UnicodeString)" instead of " + (expected) + "\n"); \
52 } \
53 } UPRV_BLOCK_MACRO_END
54 #define CONFIRM_GE(actual, expected, myAction) UPRV_BLOCK_MACRO_BEGIN { \
55 if ((actual)>=(expected)) { \
56 record_pass(myAction); \
57 } else { \
58 record_fail(myAction + (UnicodeString)" returned " + (actual) + (UnicodeString)" instead of x >= " + (expected) + "\n"); \
59 } \
60 } UPRV_BLOCK_MACRO_END
61 #define CONFIRM_NE(actual, expected, myAction) UPRV_BLOCK_MACRO_BEGIN { \
62 if ((expected)!=(actual)) { \
63 record_pass(myAction); \
64 } else { \
65 record_fail(myAction + (UnicodeString)" returned " + (actual) + (UnicodeString)" instead of x != " + (expected) + "\n"); \
66 } \
67 } UPRV_BLOCK_MACRO_END
68
69 #define CONFIRM_UErrorCode(actual, expected, myAction) UPRV_BLOCK_MACRO_BEGIN { \
70 if ((expected)==(actual)) { \
71 record_pass(myAction); \
72 } else { \
73 record_fail(myAction + (UnicodeString)" returned " + u_errorName(actual) + " instead of " + u_errorName(expected) + "\n"); \
74 } \
75 } UPRV_BLOCK_MACRO_END
76
77 //***************************************************************************************
78
79 /**
80 * Convert an integer, positive or negative, to a character string radix 10.
81 */
82 char*
itoa(int32_t i,char * buf)83 itoa(int32_t i, char* buf)
84 {
85 char* result = buf;
86
87 // Handle negative
88 if (i < 0)
89 {
90 *buf++ = '-';
91 i = -i;
92 }
93
94 // Output digits in reverse order
95 char* p = buf;
96 do
97 {
98 *p++ = static_cast<char>('0' + (i % 10));
99 i /= 10;
100 }
101 while (i);
102 *p-- = 0;
103
104 // Reverse the string
105 while (buf < p)
106 {
107 char c = *buf;
108 *buf++ = *p;
109 *p-- = c;
110 }
111
112 return result;
113 }
114
115
116
117 //***************************************************************************************
118
119 // Array of our test objects
120
121 static struct
122 {
123 const char* name;
124 Locale *locale;
125 UErrorCode expected_constructor_status;
126 E_Where where;
127 UBool like[e_Where_count];
128 UBool inherits[e_Where_count];
129 }
130 param[] =
131 {
132 // "te" means test
133 // "IN" means inherits
134 // "NE" or "ne" means "does not exist"
135
136 { "root", nullptr, U_ZERO_ERROR, e_Root, { true, false, false }, { true, false, false } },
137 { "te", nullptr, U_ZERO_ERROR, e_te, { false, true, false }, { true, true, false } },
138 { "te_IN", nullptr, U_ZERO_ERROR, e_te_IN, { false, false, true }, { true, true, true } },
139 { "te_NE", nullptr, U_USING_FALLBACK_WARNING, e_te, { false, true, false }, { true, true, false } },
140 { "te_IN_NE", nullptr, U_USING_FALLBACK_WARNING, e_te_IN, { false, false, true }, { true, true, true } },
141 { "ne", nullptr, U_USING_DEFAULT_WARNING, e_Root, { true, false, false }, { true, false, false } }
142 };
143
144 static const int32_t bundles_count = UPRV_LENGTHOF(param);
145
146 //***************************************************************************************
147
148 /**
149 * Return a random unsigned long l where 0N <= l <= ULONG_MAX.
150 */
151
152 uint32_t
randul()153 randul()
154 {
155 static UBool initialized = false;
156 if (!initialized)
157 {
158 srand(static_cast<unsigned>(time(nullptr)));
159 initialized = true;
160 }
161 // Assume rand has at least 12 bits of precision
162 uint32_t l = 0;
163 for (uint32_t i=0; i<sizeof(l); ++i)
164 reinterpret_cast<char*>(&l)[i] = static_cast<char>((rand() & 0x0FF0) >> 4);
165 return l;
166 }
167
168 /**
169 * Return a random double x where 0.0 <= x < 1.0.
170 */
171 double
randd()172 randd()
173 {
174 return static_cast<double>(randul() / ULONG_MAX);
175 }
176
177 /**
178 * Return a random integer i where 0 <= i < n.
179 */
randi(int32_t n)180 int32_t randi(int32_t n)
181 {
182 return static_cast<int32_t>(randd() * n);
183 }
184
185 //***************************************************************************************
186
187 /*
188 Don't use more than one of these at a time because of the Locale names
189 */
ResourceBundleTest()190 ResourceBundleTest::ResourceBundleTest()
191 : pass(0),
192 fail(0)
193 {
194 if (param[5].locale == nullptr) {
195 param[0].locale = new Locale("root");
196 param[1].locale = new Locale("te");
197 param[2].locale = new Locale("te", "IN");
198 param[3].locale = new Locale("te", "NE");
199 param[4].locale = new Locale("te", "IN", "NE");
200 param[5].locale = new Locale("ne");
201 }
202 }
203
~ResourceBundleTest()204 ResourceBundleTest::~ResourceBundleTest()
205 {
206 if (param[5].locale) {
207 int idx;
208 for (idx = 0; idx < UPRV_LENGTHOF(param); idx++) {
209 delete param[idx].locale;
210 param[idx].locale = nullptr;
211 }
212 }
213 }
214
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)215 void ResourceBundleTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
216 {
217 if (exec) logln("TestSuite ResourceBundleTest: ");
218 switch (index) {
219 #if !UCONFIG_NO_FILE_IO && !UCONFIG_NO_LEGACY_CONVERSION
220 case 0: name = "TestResourceBundles"; if (exec) TestResourceBundles(); break;
221 case 1: name = "TestConstruction"; if (exec) TestConstruction(); break;
222 case 2: name = "TestGetSize"; if (exec) TestGetSize(); break;
223 case 3: name = "TestGetLocaleByType"; if (exec) TestGetLocaleByType(); break;
224 #else
225 case 0: case 1: case 2: case 3: name = "skip"; break;
226 #endif
227
228 case 4: name = "TestExemplar"; if (exec) TestExemplar(); break;
229 case 5: name = "TestPersonUnits"; if (exec) TestPersonUnits(); break;
230 case 6: name = "TestZuluFields"; if (exec) TestZuluFields(); break;
231 default: name = ""; break; //needed to end loop
232 }
233 }
234
235 //***************************************************************************************
236
237 void
TestResourceBundles()238 ResourceBundleTest::TestResourceBundles()
239 {
240 UErrorCode status = U_ZERO_ERROR;
241
242 loadTestData(status);
243 if(U_FAILURE(status))
244 {
245 dataerrln("Could not load testdata.dat %s " + UnicodeString(u_errorName(status)));
246 return;
247 }
248
249 /* Make sure that users using te_IN for the default locale don't get test failures. */
250 Locale originalDefault;
251 if (Locale::getDefault() == Locale("te_IN")) {
252 Locale::setDefault(Locale("en_US"), status);
253 }
254
255 testTag("only_in_Root", true, false, false);
256 testTag("only_in_te", false, true, false);
257 testTag("only_in_te_IN", false, false, true);
258 testTag("in_Root_te", true, true, false);
259 testTag("in_Root_te_te_IN", true, true, true);
260 testTag("in_Root_te_IN", true, false, true);
261 testTag("in_te_te_IN", false, true, true);
262 testTag("nonexistent", false, false, false);
263 logln("Passed: %d\nFailed: %d", pass, fail);
264
265 /* Restore the default locale for the other tests. */
266 Locale::setDefault(originalDefault, status);
267 }
268
269 void
TestConstruction()270 ResourceBundleTest::TestConstruction()
271 {
272 UErrorCode err = U_ZERO_ERROR;
273 Locale locale("te", "IN");
274
275 const char* testdatapath=loadTestData(err);
276 if(U_FAILURE(err))
277 {
278 dataerrln("Could not load testdata.dat " + UnicodeString(testdatapath) + ", " + UnicodeString(u_errorName(err)));
279 return;
280 }
281
282 /* Make sure that users using te_IN for the default locale don't get test failures. */
283 Locale originalDefault;
284 if (Locale::getDefault() == Locale("te_IN")) {
285 Locale::setDefault(Locale("en_US"), err);
286 }
287
288 ResourceBundle test1(UnicodeString(testdatapath), err);
289 ResourceBundle test2(testdatapath, locale, err);
290 //ResourceBundle test1("c:\\icu\\icu\\source\\test\\testdata\\testdata", err);
291 //ResourceBundle test2("c:\\icu\\icu\\source\\test\\testdata\\testdata", locale, err);
292
293 UnicodeString result1(test1.getStringEx("string_in_Root_te_te_IN", err));
294 UnicodeString result2(test2.getStringEx("string_in_Root_te_te_IN", err));
295
296 if (U_FAILURE(err)) {
297 errln("Something threw an error in TestConstruction()");
298 return;
299 }
300
301 logln("for string_in_Root_te_te_IN, default.txt had " + result1);
302 logln("for string_in_Root_te_te_IN, te_IN.txt had " + result2);
303
304 if (result1 != "ROOT" || result2 != "TE_IN")
305 errln("Construction test failed; run verbose for more information");
306
307 const char* version1;
308 const char* version2;
309
310 version1 = test1.getVersionNumber();
311 version2 = test2.getVersionNumber();
312
313 char *versionID1 = new char[1+strlen(version1)]; // + 1 for zero byte
314 char *versionID2 = new char[1+ strlen(version2)]; // + 1 for zero byte
315
316 strcpy(versionID1, "45.0"); // hardcoded, please change if the default.txt file or ResourceBundle::kVersionSeparater is changed.
317
318 strcpy(versionID2, "55.0"); // hardcoded, please change if the te_IN.txt file or ResourceBundle::kVersionSeparater is changed.
319
320 logln(UnicodeString("getVersionNumber on default.txt returned ") + version1);
321 logln(UnicodeString("getVersionNumber on te_IN.txt returned ") + version2);
322
323 if (strcmp(version1, versionID1) != 0 || strcmp(version2, versionID2) != 0)
324 errln("getVersionNumber() failed");
325
326 delete[] versionID1;
327 delete[] versionID2;
328
329 /* Restore the default locale for the other tests. */
330 Locale::setDefault(originalDefault, err);
331 }
332
333 //***************************************************************************************
334
335 UBool
testTag(const char * frag,UBool in_Root,UBool in_te,UBool in_te_IN)336 ResourceBundleTest::testTag(const char* frag,
337 UBool in_Root,
338 UBool in_te,
339 UBool in_te_IN)
340 {
341 int32_t failOrig = fail;
342
343 // Make array from input params
344
345 UBool is_in[] = { in_Root, in_te, in_te_IN };
346
347 const char* NAME[] = { "ROOT", "TE", "TE_IN" };
348
349 // Now try to load the desired items
350
351 char tag[100];
352 UnicodeString action;
353
354 int32_t i,j,actual_bundle;
355 // int32_t row,col;
356 int32_t index;
357 UErrorCode status = U_ZERO_ERROR;
358 const char* testdatapath;
359 testdatapath=loadTestData(status);
360 if(U_FAILURE(status))
361 {
362 dataerrln("Could not load testdata.dat %s " + UnicodeString(u_errorName(status)));
363 return false;
364 }
365
366 for (i=0; i<bundles_count; ++i)
367 {
368 action = "Constructor for ";
369 action += param[i].name;
370
371 status = U_ZERO_ERROR;
372 ResourceBundle theBundle( testdatapath, *param[i].locale, status);
373 //ResourceBundle theBundle( "c:\\icu\\icu\\source\\test\\testdata\\testdata", *param[i].locale, status);
374 CONFIRM_UErrorCode(status, param[i].expected_constructor_status, action);
375
376 if(i == 5)
377 actual_bundle = 0; /* ne -> default */
378 else if(i == 3)
379 actual_bundle = 1; /* te_NE -> te */
380 else if(i == 4)
381 actual_bundle = 2; /* te_IN_NE -> te_IN */
382 else
383 actual_bundle = i;
384
385
386 UErrorCode expected_resource_status = U_MISSING_RESOURCE_ERROR;
387 for (j=e_te_IN; j>=e_Root; --j)
388 {
389 if (is_in[j] && param[i].inherits[j])
390 {
391 if(j == actual_bundle) /* it's in the same bundle OR it's a nonexistent=default bundle (5) */
392 expected_resource_status = U_ZERO_ERROR;
393 else if(j == 0)
394 expected_resource_status = U_USING_DEFAULT_WARNING;
395 else
396 expected_resource_status = U_USING_FALLBACK_WARNING;
397
398 break;
399 }
400 }
401
402 UErrorCode expected_status;
403
404 UnicodeString base;
405 for (j=param[i].where; j>=0; --j)
406 {
407 if (is_in[j])
408 {
409 base = NAME[j];
410 break;
411 }
412 }
413
414 //--------------------------------------------------------------------------
415 // string
416
417 uprv_strcpy(tag, "string_");
418 uprv_strcat(tag, frag);
419
420 action = param[i].name;
421 action += ".getString(";
422 action += tag;
423 action += ")";
424
425
426 status = U_ZERO_ERROR;
427
428 UnicodeString string(theBundle.getStringEx(tag, status));
429
430 if(U_FAILURE(status)) {
431 string.setTo(true, kErrorUChars, kErrorLength);
432 }
433
434 CONFIRM_UErrorCode(status, expected_resource_status, action);
435
436 UnicodeString expected_string(kErrorUChars);
437 if (U_SUCCESS(status)) {
438 expected_string = base;
439 }
440
441 CONFIRM_EQ(string, expected_string, action);
442
443 //--------------------------------------------------------------------------
444 // array
445
446 uprv_strcpy(tag, "array_");
447 uprv_strcat(tag, frag);
448
449 action = param[i].name;
450 action += ".get(";
451 action += tag;
452 action += ")";
453
454 status = U_ZERO_ERROR;
455 ResourceBundle arrayBundle(theBundle.get(tag, status));
456 CONFIRM_UErrorCode(status, expected_resource_status, action);
457 int32_t count = arrayBundle.getSize();
458
459 if (U_SUCCESS(status))
460 {
461 CONFIRM_GE(count, 1, action);
462
463 for (j=0; j < count; ++j)
464 {
465 char buf[32];
466 UnicodeString value(arrayBundle.getStringEx(j, status));
467 expected_string = base;
468 expected_string += itoa(j,buf);
469 CONFIRM_EQ(value, expected_string, action);
470 }
471
472 action = param[i].name;
473 action += ".getStringEx(";
474 action += tag;
475 action += ")";
476
477 for (j=0; j<100; ++j)
478 {
479 index = count ? (randi(count * 3) - count) : (randi(200) - 100);
480 status = U_ZERO_ERROR;
481 string = kErrorUChars;
482 UnicodeString t(arrayBundle.getStringEx(index, status));
483 expected_status = (index >= 0 && index < count) ? expected_resource_status : U_MISSING_RESOURCE_ERROR;
484 CONFIRM_UErrorCode(status, expected_status, action);
485
486 if (U_SUCCESS(status))
487 {
488 char buf[32];
489 expected_string = base;
490 expected_string += itoa(index,buf);
491 }
492 else
493 {
494 expected_string = kErrorUChars;
495 }
496 CONFIRM_EQ(string, expected_string, action);
497 }
498 }
499 else if (status != expected_resource_status)
500 {
501 record_fail("Error getting " + UnicodeString(tag));
502 return failOrig != fail;
503 }
504
505 }
506
507 return failOrig != fail;
508 }
509
510 void
record_pass(UnicodeString passMessage)511 ResourceBundleTest::record_pass(UnicodeString passMessage)
512 {
513 logln(passMessage);
514 ++pass;
515 }
516 void
record_fail(UnicodeString errMessage)517 ResourceBundleTest::record_fail(UnicodeString errMessage)
518 {
519 err(errMessage);
520 ++fail;
521 }
522
523 void
TestExemplar()524 ResourceBundleTest::TestExemplar(){
525
526 int32_t locCount = uloc_countAvailable();
527 int32_t locIndex=0;
528 int num=0;
529 UErrorCode status = U_ZERO_ERROR;
530 for(;locIndex<locCount;locIndex++){
531 const char* locale = uloc_getAvailable(locIndex);
532 UResourceBundle *resb =ures_open(nullptr,locale,&status);
533 if(U_SUCCESS(status) && status!=U_USING_FALLBACK_WARNING && status!=U_USING_DEFAULT_WARNING){
534 int32_t len=0;
535 const char16_t* strSet = ures_getStringByKey(resb,"ExemplarCharacters",&len,&status);
536 UnicodeSet set(strSet,status);
537 if(U_FAILURE(status)){
538 errln("Could not construct UnicodeSet from pattern for ExemplarCharacters in locale : %s. Error: %s",locale,u_errorName(status));
539 status=U_ZERO_ERROR;
540 }
541 num++;
542 }
543 ures_close(resb);
544 }
545 logln("Number of installed locales with exemplar characters that could be tested: %d",num);
546
547 }
548
549 void
TestGetSize()550 ResourceBundleTest::TestGetSize()
551 {
552 const struct {
553 const char* key;
554 int32_t size;
555 } test[] = {
556 { "zerotest", 1},
557 { "one", 1},
558 { "importtest", 1},
559 { "integerarray", 1},
560 { "emptyarray", 0},
561 { "emptytable", 0},
562 { "emptystring", 1}, /* empty string is still a string */
563 { "emptyint", 1},
564 { "emptybin", 1},
565 { "testinclude", 1},
566 { "collations", 1}, /* not 2 - there is hidden %%CollationBin */
567 };
568
569 UErrorCode status = U_ZERO_ERROR;
570
571 const char* testdatapath = loadTestData(status);
572 int32_t i = 0, j = 0;
573 int32_t size = 0;
574
575 if(U_FAILURE(status))
576 {
577 dataerrln("Could not load testdata.dat %s\n", u_errorName(status));
578 return;
579 }
580
581 ResourceBundle rb(testdatapath, "testtypes", status);
582 if(U_FAILURE(status))
583 {
584 err("Could not testtypes resource bundle %s\n", u_errorName(status));
585 return;
586 }
587
588 for(i = 0; i < UPRV_LENGTHOF(test); i++) {
589 ResourceBundle res = rb.get(test[i].key, status);
590 if(U_FAILURE(status))
591 {
592 err("Couldn't find the key %s. Error: %s\n", u_errorName(status));
593 return;
594 }
595 size = res.getSize();
596 if(size != test[i].size) {
597 err("Expected size %i, got size %i for key %s\n", test[i].size, size, test[i].key);
598 for(j = 0; j < size; j++) {
599 ResourceBundle helper = res.get(j, status);
600 err("%s\n", helper.getKey());
601 }
602 }
603 }
604 }
605
606 void
TestGetLocaleByType()607 ResourceBundleTest::TestGetLocaleByType()
608 {
609 const struct {
610 const char *requestedLocale;
611 const char *resourceKey;
612 const char *validLocale;
613 const char *actualLocale;
614 } test[] = {
615 { "te_IN_BLAH", "string_only_in_te_IN", "te_IN", "te_IN" },
616 { "te_IN_BLAH", "string_only_in_te", "te_IN", "te" },
617 { "te_IN_BLAH", "string_only_in_Root", "te_IN", "" },
618 { "te_IN_BLAH_01234567890_01234567890_01234567890_01234567890_01234567890_01234567890", "array_2d_only_in_Root", "te_IN", "" },
619 { "te_IN_BLAH@currency=euro", "array_2d_only_in_te_IN", "te_IN", "te_IN" },
620 { "te_IN_BLAH@calendar=thai;collation=phonebook", "array_2d_only_in_te", "te_IN", "te" }
621 };
622
623 UErrorCode status = U_ZERO_ERROR;
624
625 const char* testdatapath = loadTestData(status);
626 int32_t i = 0;
627 Locale locale;
628
629 if(U_FAILURE(status))
630 {
631 dataerrln("Could not load testdata.dat %s\n", u_errorName(status));
632 return;
633 }
634
635 for(i = 0; i < UPRV_LENGTHOF(test); i++) {
636 ResourceBundle rb(testdatapath, test[i].requestedLocale, status);
637 if(U_FAILURE(status))
638 {
639 err("Could not open resource bundle %s (error %s)\n", test[i].requestedLocale, u_errorName(status));
640 status = U_ZERO_ERROR;
641 continue;
642 }
643
644 ResourceBundle res = rb.get(test[i].resourceKey, status);
645 if(U_FAILURE(status))
646 {
647 err("Couldn't find the key %s. Error: %s\n", test[i].resourceKey, u_errorName(status));
648 status = U_ZERO_ERROR;
649 continue;
650 }
651
652 locale = res.getLocale(ULOC_REQUESTED_LOCALE, status);
653 if(U_SUCCESS(status) && locale != Locale::getDefault()) {
654 err("Expected requested locale to be %s. Got %s\n", test[i].requestedLocale, locale.getName());
655 }
656 status = U_ZERO_ERROR;
657 locale = res.getLocale(ULOC_VALID_LOCALE, status);
658 if(strcmp(locale.getName(), test[i].validLocale) != 0) {
659 err("Expected valid locale to be %s. Got %s\n", test[i].requestedLocale, locale.getName());
660 }
661 locale = res.getLocale(ULOC_ACTUAL_LOCALE, status);
662 if(strcmp(locale.getName(), test[i].actualLocale) != 0) {
663 err("Expected actual locale to be %s. Got %s\n", test[i].requestedLocale, locale.getName());
664 }
665 }
666 }
667
668 void
TestPersonUnits()669 ResourceBundleTest::TestPersonUnits() {
670 // Test for ICU-21877: ICUResourceBundle.getAllChildrenWithFallback() doesn't return all of the children of the resource
671 // that's passed into it. If you have to follow an alias to get some of the children, we get the resources in the
672 // bundle we're aliasing to, and its children, but things that the bundle we're aliasing to inherits from its parent
673 // don't show up.
674 // This example is from the en_CA resource in the "unit" tree: The four test cases below show what we get when we call
675 // getStringWithFallback():
676 // - units/duration/day-person/other doesn't exist in either en_CA or en, so we fall back on root.
677 // - root/units aliases over to LOCALE/unitsShort.
678 // - unitsShort/duration/day-person/other also doesn't exist in either en_CA or en, so we fall back to root again.
679 // - root/unitsShort/duration/day-person aliases over to LOCALE/unitsShort/duration/day.
680 // - unitsShort/duration/day/other also doesn't exist in en_CA, so we fallback to en.
681 // - en/unitsShort/duration/day/other DOES exist and has "{0} days", so we return that.
682 // It's the same basic story for week-person, month-person, and year-person, except that:
683 // - unitsShort/duration/day doesn't exist at all in en_CA
684 // - unitsShort/duration/week DOES exist in en_CA, but only contains "per", so we inherit "other" from en
685 // - unitsShort/duration/month/other DOES exist in en_CA (it overrides "{0} mths" with "{0} mos")
686 // - unitsShort/duration/year DOES exist in en_CA, but only contains "per", so we inherit "other" from en
687 UErrorCode err = U_ZERO_ERROR;
688 LocalUResourceBundlePointer en_ca(ures_open(U_ICUDATA_UNIT, "en_CA", &err));
689
690 if (!assertSuccess("Failed to load en_CA resource in units tree", err)) {
691 return;
692 }
693 assertEquals("Wrong result for units/duration/day-person/other", u"{0} days", ures_getStringByKeyWithFallback(en_ca.getAlias(), "units/duration/day-person/other", nullptr, &err));
694 assertEquals("Wrong result for units/duration/week-person/other", u"{0} wks", ures_getStringByKeyWithFallback(en_ca.getAlias(), "units/duration/week-person/other", nullptr, &err));
695 assertEquals("Wrong result for units/duration/month-person/other", u"{0} mos", ures_getStringByKeyWithFallback(en_ca.getAlias(), "units/duration/month-person/other", nullptr, &err));
696 assertEquals("Wrong result for units/duration/year-person/other", u"{0} yrs", ures_getStringByKeyWithFallback(en_ca.getAlias(), "units/duration/year-person/other", nullptr, &err));
697
698 // getAllChildrenWithFallback() wasn't bringing all of those things back, however. When en_CA/units/year-person
699 // aliased over to en_CA/unitsShort/year, the sink would only be called on en_CA/unitsShort/year, which means it'd
700 // only see en_CA/unitsShort/year/per, because "per" was the only thing contained in en_CA/unitsShort/year. But
701 // en_CA/unitsShort/year should be inheriting "dnam", "one", and "other" from en/unitsShort/year.
702 // getAllChildrenWithFallback() had to be modified to walk the parent chain and call the sink again with
703 // en/unitsShort/year and root/unitsShort/year.
704 struct TestPersonUnitsSink : public ResourceSink {
705 typedef std::set<std::string> FoundKeysType;
706 typedef std::map<std::string, FoundKeysType > FoundResourcesType;
707 FoundResourcesType foundResources;
708 IntlTest& owningTestCase;
709
710 TestPersonUnitsSink(IntlTest& owningTestCase) : owningTestCase(owningTestCase) {}
711
712 virtual void put(const char *key, ResourceValue &value, UBool /*noFallback*/,
713 UErrorCode &errorCode) override {
714 if (value.getType() != URES_TABLE) {
715 owningTestCase.errln("%s is not a table!", key);
716 return;
717 }
718
719 FoundKeysType& foundKeys(foundResources[key]);
720
721 ResourceTable childTable = value.getTable(errorCode);
722 if (owningTestCase.assertSuccess("value.getTable() didn't work", errorCode)) {
723 const char* childKey;
724 for (int32_t i = 0; i < childTable.getSize(); i++) {
725 childTable.getKeyAndValue(i, childKey, value);
726 foundKeys.insert(childKey);
727 }
728 }
729 }
730 };
731
732 TestPersonUnitsSink sink(*this);
733 ures_getAllChildrenWithFallback(en_ca.getAlias(), "units/duration", sink, err);
734
735 if (assertSuccess("ures_getAllChildrenWithFallback() failed", err)) {
736 for (auto foundResourcesEntry : sink.foundResources) {
737 std::string foundResourcesKey = foundResourcesEntry.first;
738 if (foundResourcesKey.rfind("-person") == std::string::npos) {
739 continue;
740 }
741
742 TestPersonUnitsSink::FoundKeysType foundKeys = foundResourcesEntry.second;
743 if (foundKeys.find("dnam") == foundKeys.end()) {
744 errln("%s doesn't contain 'dnam'!", foundResourcesKey.c_str());
745 }
746 if (foundKeys.find("one") == foundKeys.end()) {
747 errln("%s doesn't contain 'one'!", foundResourcesKey.c_str());
748 }
749 if (foundKeys.find("other") == foundKeys.end()) {
750 errln("%s doesn't contain 'other'!", foundResourcesKey.c_str());
751 }
752 if (foundKeys.find("per") == foundKeys.end()) {
753 errln("%s doesn't contain 'per'!", foundResourcesKey.c_str());
754 }
755 }
756 }
757 }
758
759 void
TestZuluFields()760 ResourceBundleTest::TestZuluFields() {
761 // Test for ICU-21877: Similar to the above test, except that here we're bringing back the wrong values.
762 // In some resources under the "locales" tree, some resources under "fields" would either have no display
763 // names or bring back the _wrong_ display names. The underlying cause was the same as in TestPersonUnits()
764 // above, except that there were two levels of indirection: At the root level, *-narrow aliased to *-short,
765 // which in turn aliased to *. If (say) day-narrow and day-short both didn't have "dn" resources, you could
766 // iterate over fields/day-short and find the "dn" resource that should have been inherited over from
767 // fields/day, but if you iterated over fields/day-narrow, you WOULDN'T get back the "dn" resource from
768 // fields/day (or, with my original fix for ICU-21877, you'd get back the wrong value). This test verifies
769 // that the double indirection works correctly.
770 UErrorCode err = U_ZERO_ERROR;
771 LocalUResourceBundlePointer zu(ures_open(nullptr, "zu", &err));
772
773 if (!assertSuccess("Failed to load zu resource in locales tree", err)) {
774 return;
775 }
776 assertEquals("Wrong getStringWithFallback() result for fields/day/dn", u"Usuku", ures_getStringByKeyWithFallback(zu.getAlias(), "fields/day/dn", nullptr, &err));
777 assertEquals("Wrong getStringWithFallback() result for fields/day-short/dn", u"Usuku", ures_getStringByKeyWithFallback(zu.getAlias(), "fields/day-short/dn", nullptr, &err));
778 assertEquals("Wrong getStringWithFallback() result for fields/day-narrow/dn", u"Usuku", ures_getStringByKeyWithFallback(zu.getAlias(), "fields/day-narrow/dn", nullptr, &err));
779
780 assertEquals("Wrong getStringWithFallback() result for fields/month/dn", u"Inyanga", ures_getStringByKeyWithFallback(zu.getAlias(), "fields/month/dn", nullptr, &err));
781 assertEquals("Wrong getStringWithFallback() result for fields/month-short/dn", u"Inyanga", ures_getStringByKeyWithFallback(zu.getAlias(), "fields/month-short/dn", nullptr, &err));
782 assertEquals("Wrong getStringWithFallback() result for fields/month-narrow/dn", u"Inyanga", ures_getStringByKeyWithFallback(zu.getAlias(), "fields/month-narrow/dn", nullptr, &err));
783
784 struct TestZuluFieldsSink : public ResourceSink {
785 typedef std::map<std::string, const UChar* > FoundResourcesType;
786 FoundResourcesType foundResources;
787 IntlTest& owningTestCase;
788
789 TestZuluFieldsSink(IntlTest& owningTestCase) : owningTestCase(owningTestCase) {}
790
791 virtual void put(const char *key, ResourceValue &value, UBool /*noFallback*/,
792 UErrorCode &errorCode) override {
793 if (value.getType() != URES_TABLE) {
794 owningTestCase.errln("%s is not a table!", key);
795 return;
796 }
797
798 ResourceTable childTable = value.getTable(errorCode);
799 if (owningTestCase.assertSuccess("value.getTable() didn't work", errorCode)) {
800 if (childTable.findValue("dn", value)) {
801 int32_t dummyLength;
802 if (foundResources.find(key) == foundResources.end()) {
803 foundResources.insert(std::make_pair(key, value.getString(dummyLength, errorCode)));
804 }
805 }
806 }
807 }
808 };
809
810 TestZuluFieldsSink sink(*this);
811 ures_getAllChildrenWithFallback(zu.getAlias(), "fields", sink, err);
812
813 if (assertSuccess("ures_getAllChildrenWithFallback() failed", err)) {
814 assertEquals("Wrong getAllChildrenWithFallback() result for fields/day/dn", u"Usuku", sink.foundResources["day"]);
815 assertEquals("Wrong getAllChildrenWithFallback() result for fields/day-short/dn", u"Usuku", sink.foundResources["day-short"]);
816 assertEquals("Wrong getAllChildrenWithFallback() result for fields/day-narrow/dn", u"Usuku", sink.foundResources["day-narrow"]);
817 assertEquals("Wrong getAllChildrenWithFallback() result for fields/month/dn", u"Inyanga", sink.foundResources["month"]);
818 assertEquals("Wrong getAllChildrenWithFallback() result for fields/month-short/dn", u"Inyanga", sink.foundResources["month-short"]);
819 assertEquals("Wrong getAllChildrenWithFallback() result for fields/month-narrow/dn", u"Inyanga", sink.foundResources["month-narrow"]);
820 }
821 }
822