1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /*
4 *******************************************************************************
5 * Copyright (C) 2007-2014, International Business Machines Corporation and
6 * others. All Rights Reserved.
7 ********************************************************************************
8
9 * File PLURULTS.cpp
10 *
11 ********************************************************************************
12 */
13
14 #include "unicode/utypes.h"
15
16 #if !UCONFIG_NO_FORMATTING
17
18 #include <stdlib.h>
19 #include <stdarg.h>
20 #include <string.h>
21
22 #include "unicode/localpointer.h"
23 #include "unicode/plurrule.h"
24 #include "unicode/stringpiece.h"
25 #include "unicode/numberformatter.h"
26 #include "unicode/numberrangeformatter.h"
27
28 #include "cmemory.h"
29 #include "cstr.h"
30 #include "plurrule_impl.h"
31 #include "plurults.h"
32 #include "uhash.h"
33 #include "number_decimalquantity.h"
34
35 using icu::number::impl::DecimalQuantity;
36 using namespace icu::number;
37
38 void setupResult(const int32_t testSource[], char result[], int32_t* max);
39 UBool checkEqual(const PluralRules &test, char *result, int32_t max);
40 UBool testEquality(const PluralRules &test);
41
42 // This is an API test, not a unit test. It doesn't test very many cases, and doesn't
43 // try to test the full functionality. It just calls each function in the class and
44 // verifies that it works on a basic level.
45
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)46 void PluralRulesTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
47 {
48 if (exec) logln("TestSuite PluralRulesAPI");
49 TESTCASE_AUTO_BEGIN;
50 TESTCASE_AUTO(testAPI);
51 // TESTCASE_AUTO(testGetUniqueKeywordValue);
52 TESTCASE_AUTO(testGetSamples);
53 TESTCASE_AUTO(testGetFixedDecimalSamples);
54 TESTCASE_AUTO(testSamplesWithExponent);
55 TESTCASE_AUTO(testSamplesWithCompactNotation);
56 TESTCASE_AUTO(testWithin);
57 TESTCASE_AUTO(testGetAllKeywordValues);
58 TESTCASE_AUTO(testScientificPluralKeyword);
59 TESTCASE_AUTO(testCompactDecimalPluralKeyword);
60 TESTCASE_AUTO(testDoubleValue);
61 TESTCASE_AUTO(testLongValue);
62 TESTCASE_AUTO(testOrdinal);
63 TESTCASE_AUTO(testSelect);
64 TESTCASE_AUTO(testSelectRange);
65 TESTCASE_AUTO(testAvailableLocales);
66 TESTCASE_AUTO(testParseErrors);
67 TESTCASE_AUTO(testFixedDecimal);
68 TESTCASE_AUTO(testSelectTrailingZeros);
69 TESTCASE_AUTO(testLocaleExtension);
70 TESTCASE_AUTO_END;
71 }
72
73
74 // Quick and dirty class for putting UnicodeStrings in char * messages.
75 // TODO: something like this should be generally available.
76 class US {
77 private:
78 char *buf;
79 public:
US(const UnicodeString & us)80 US(const UnicodeString &us) {
81 int32_t bufLen = us.extract((int32_t)0, us.length(), (char *)NULL, (uint32_t)0) + 1;
82 buf = (char *)uprv_malloc(bufLen);
83 us.extract(0, us.length(), buf, bufLen); }
cstr()84 const char *cstr() {return buf;}
~US()85 ~US() { uprv_free(buf);}
86 };
87
88
89
90
91
92 #define PLURAL_TEST_NUM 18
93 /**
94 * Test various generic API methods of PluralRules for API coverage.
95 */
testAPI()96 void PluralRulesTest::testAPI(/*char *par*/)
97 {
98 UnicodeString pluralTestData[PLURAL_TEST_NUM] = {
99 UNICODE_STRING_SIMPLE("a: n is 1"),
100 UNICODE_STRING_SIMPLE("a: n mod 10 is 2"),
101 UNICODE_STRING_SIMPLE("a: n is not 1"),
102 UNICODE_STRING_SIMPLE("a: n mod 3 is not 1"),
103 UNICODE_STRING_SIMPLE("a: n in 2..5"),
104 UNICODE_STRING_SIMPLE("a: n within 2..5"),
105 UNICODE_STRING_SIMPLE("a: n not in 2..5"),
106 UNICODE_STRING_SIMPLE("a: n not within 2..5"),
107 UNICODE_STRING_SIMPLE("a: n mod 10 in 2..5"),
108 UNICODE_STRING_SIMPLE("a: n mod 10 within 2..5"),
109 UNICODE_STRING_SIMPLE("a: n mod 10 is 2 and n is not 12"),
110 UNICODE_STRING_SIMPLE("a: n mod 10 in 2..3 or n mod 10 is 5"),
111 UNICODE_STRING_SIMPLE("a: n mod 10 within 2..3 or n mod 10 is 5"),
112 UNICODE_STRING_SIMPLE("a: n is 1 or n is 4 or n is 23"),
113 UNICODE_STRING_SIMPLE("a: n mod 2 is 1 and n is not 3 and n in 1..11"),
114 UNICODE_STRING_SIMPLE("a: n mod 2 is 1 and n is not 3 and n within 1..11"),
115 UNICODE_STRING_SIMPLE("a: n mod 2 is 1 or n mod 5 is 1 and n is not 6"),
116 "",
117 };
118 static const int32_t pluralTestResult[PLURAL_TEST_NUM][30] = {
119 {1, 0},
120 {2,12,22, 0},
121 {0,2,3,4,5,0},
122 {0,2,3,5,6,8,9,0},
123 {2,3,4,5,0},
124 {2,3,4,5,0},
125 {0,1,6,7,8, 0},
126 {0,1,6,7,8, 0},
127 {2,3,4,5,12,13,14,15,22,23,24,25,0},
128 {2,3,4,5,12,13,14,15,22,23,24,25,0},
129 {2,22,32,42,0},
130 {2,3,5,12,13,15,22,23,25,0},
131 {2,3,5,12,13,15,22,23,25,0},
132 {1,4,23,0},
133 {1,5,7,9,11,0},
134 {1,5,7,9,11,0},
135 {1,3,5,7,9,11,13,15,16,0},
136 };
137 UErrorCode status = U_ZERO_ERROR;
138
139 // ======= Test constructors
140 logln("Testing PluralRules constructors");
141
142
143 logln("\n start default locale test case ..\n");
144
145 PluralRules defRule(status);
146 LocalPointer<PluralRules> test(new PluralRules(status), status);
147 if(U_FAILURE(status)) {
148 dataerrln("ERROR: Could not create PluralRules (default) - exiting");
149 return;
150 }
151 LocalPointer<PluralRules> newEnPlural(test->forLocale(Locale::getEnglish(), status), status);
152 if(U_FAILURE(status)) {
153 dataerrln("ERROR: Could not create PluralRules (English) - exiting");
154 return;
155 }
156
157 // ======= Test clone, assignment operator && == operator.
158 LocalPointer<PluralRules> dupRule(defRule.clone());
159 if (dupRule==NULL) {
160 errln("ERROR: clone plural rules test failed!");
161 return;
162 } else {
163 if ( *dupRule != defRule ) {
164 errln("ERROR: clone plural rules test failed!");
165 }
166 }
167 *dupRule = *newEnPlural;
168 if (dupRule!=NULL) {
169 if ( *dupRule != *newEnPlural ) {
170 errln("ERROR: clone plural rules test failed!");
171 }
172 }
173
174 // ======= Test empty plural rules
175 logln("Testing Simple PluralRules");
176
177 LocalPointer<PluralRules> empRule(test->createRules(UNICODE_STRING_SIMPLE("a:n"), status));
178 UnicodeString key;
179 for (int32_t i=0; i<10; ++i) {
180 key = empRule->select(i);
181 if ( key.charAt(0)!= 0x61 ) { // 'a'
182 errln("ERROR: empty plural rules test failed! - exiting");
183 }
184 }
185
186 // ======= Test simple plural rules
187 logln("Testing Simple PluralRules");
188
189 char result[100];
190 int32_t max;
191
192 for (int32_t i=0; i<PLURAL_TEST_NUM-1; ++i) {
193 LocalPointer<PluralRules> newRules(test->createRules(pluralTestData[i], status));
194 setupResult(pluralTestResult[i], result, &max);
195 if ( !checkEqual(*newRules, result, max) ) {
196 errln("ERROR: simple plural rules failed! - exiting");
197 return;
198 }
199 }
200
201 // ======= Test complex plural rules
202 logln("Testing Complex PluralRules");
203 // TODO: the complex test data is hard coded. It's better to implement
204 // a parser to parse the test data.
205 UnicodeString complexRule = UNICODE_STRING_SIMPLE("a: n in 2..5; b: n in 5..8; c: n mod 2 is 1");
206 UnicodeString complexRule2 = UNICODE_STRING_SIMPLE("a: n within 2..5; b: n within 5..8; c: n mod 2 is 1");
207 char cRuleResult[] =
208 {
209 0x6F, // 'o'
210 0x63, // 'c'
211 0x61, // 'a'
212 0x61, // 'a'
213 0x61, // 'a'
214 0x61, // 'a'
215 0x62, // 'b'
216 0x62, // 'b'
217 0x62, // 'b'
218 0x63, // 'c'
219 0x6F, // 'o'
220 0x63 // 'c'
221 };
222 LocalPointer<PluralRules> newRules(test->createRules(complexRule, status));
223 if ( !checkEqual(*newRules, cRuleResult, 12) ) {
224 errln("ERROR: complex plural rules failed! - exiting");
225 return;
226 }
227 newRules.adoptInstead(test->createRules(complexRule2, status));
228 if ( !checkEqual(*newRules, cRuleResult, 12) ) {
229 errln("ERROR: complex plural rules failed! - exiting");
230 return;
231 }
232
233 // ======= Test decimal fractions plural rules
234 UnicodeString decimalRule= UNICODE_STRING_SIMPLE("a: n not in 0..100;");
235 UnicodeString KEYWORD_A = UNICODE_STRING_SIMPLE("a");
236 status = U_ZERO_ERROR;
237 newRules.adoptInstead(test->createRules(decimalRule, status));
238 if (U_FAILURE(status)) {
239 dataerrln("ERROR: Could not create PluralRules for testing fractions - exiting");
240 return;
241 }
242 double fData[] = {-101, -100, -1, -0.0, 0, 0.1, 1, 1.999, 2.0, 100, 100.001 };
243 bool isKeywordA[] = {true, false, false, false, false, true, false, true, false, false, true };
244 for (int32_t i=0; i<UPRV_LENGTHOF(fData); i++) {
245 if ((newRules->select(fData[i])== KEYWORD_A) != isKeywordA[i]) {
246 errln("File %s, Line %d, ERROR: plural rules for decimal fractions test failed!\n"
247 " number = %g, expected %s", __FILE__, __LINE__, fData[i], isKeywordA[i]?"TRUE":"FALSE");
248 }
249 }
250
251 // ======= Test Equality
252 logln("Testing Equality of PluralRules");
253
254 if ( !testEquality(*test) ) {
255 errln("ERROR: complex plural rules failed! - exiting");
256 return;
257 }
258
259
260 // ======= Test getStaticClassID()
261 logln("Testing getStaticClassID()");
262
263 if(test->getDynamicClassID() != PluralRules::getStaticClassID()) {
264 errln("ERROR: getDynamicClassID() didn't return the expected value");
265 }
266 // ====== Test fallback to parent locale
267 LocalPointer<PluralRules> en_UK(test->forLocale(Locale::getUK(), status));
268 LocalPointer<PluralRules> en(test->forLocale(Locale::getEnglish(), status));
269 if (en_UK.isValid() && en.isValid()) {
270 if ( *en_UK != *en ) {
271 errln("ERROR: test locale fallback failed!");
272 }
273 }
274
275 LocalPointer<PluralRules> zh_Hant(test->forLocale(Locale::getTaiwan(), status));
276 LocalPointer<PluralRules> zh(test->forLocale(Locale::getChinese(), status));
277 if (zh_Hant.isValid() && zh.isValid()) {
278 if ( *zh_Hant != *zh ) {
279 errln("ERROR: test locale fallback failed!");
280 }
281 }
282 }
283
setupResult(const int32_t testSource[],char result[],int32_t * max)284 void setupResult(const int32_t testSource[], char result[], int32_t* max) {
285 int32_t i=0;
286 int32_t curIndex=0;
287
288 do {
289 while (curIndex < testSource[i]) {
290 result[curIndex++]=0x6F; //'o' other
291 }
292 result[curIndex++]=0x61; // 'a'
293
294 } while(testSource[++i]>0);
295 *max=curIndex;
296 }
297
298
checkEqual(const PluralRules & test,char * result,int32_t max)299 UBool checkEqual(const PluralRules &test, char *result, int32_t max) {
300 UnicodeString key;
301 UBool isEqual = TRUE;
302 for (int32_t i=0; i<max; ++i) {
303 key= test.select(i);
304 if ( key.charAt(0)!=result[i] ) {
305 isEqual = FALSE;
306 }
307 }
308 return isEqual;
309 }
310
311
312
313 static const int32_t MAX_EQ_ROW = 2;
314 static const int32_t MAX_EQ_COL = 5;
testEquality(const PluralRules & test)315 UBool testEquality(const PluralRules &test) {
316 UnicodeString testEquRules[MAX_EQ_ROW][MAX_EQ_COL] = {
317 { UNICODE_STRING_SIMPLE("a: n in 2..3"),
318 UNICODE_STRING_SIMPLE("a: n is 2 or n is 3"),
319 UNICODE_STRING_SIMPLE( "a:n is 3 and n in 2..5 or n is 2"),
320 "",
321 },
322 { UNICODE_STRING_SIMPLE("a: n is 12; b:n mod 10 in 2..3"),
323 UNICODE_STRING_SIMPLE("b: n mod 10 in 2..3 and n is not 12; a: n in 12..12"),
324 UNICODE_STRING_SIMPLE("b: n is 13; a: n in 12..13; b: n mod 10 is 2 or n mod 10 is 3"),
325 "",
326 }
327 };
328 UErrorCode status = U_ZERO_ERROR;
329 UnicodeString key[MAX_EQ_COL];
330 UBool ret=TRUE;
331 for (int32_t i=0; i<MAX_EQ_ROW; ++i) {
332 PluralRules* rules[MAX_EQ_COL];
333
334 for (int32_t j=0; j<MAX_EQ_COL; ++j) {
335 rules[j]=NULL;
336 }
337 int32_t totalRules=0;
338 while((totalRules<MAX_EQ_COL) && (testEquRules[i][totalRules].length()>0) ) {
339 rules[totalRules]=test.createRules(testEquRules[i][totalRules], status);
340 totalRules++;
341 }
342 for (int32_t n=0; n<300 && ret ; ++n) {
343 for(int32_t j=0; j<totalRules;++j) {
344 key[j] = rules[j]->select(n);
345 }
346 for(int32_t j=0; j<totalRules-1;++j) {
347 if (key[j]!=key[j+1]) {
348 ret= FALSE;
349 break;
350 }
351 }
352
353 }
354 for (int32_t j=0; j<MAX_EQ_COL; ++j) {
355 if (rules[j]!=NULL) {
356 delete rules[j];
357 }
358 }
359 }
360
361 return ret;
362 }
363
364 void
assertRuleValue(const UnicodeString & rule,double expected)365 PluralRulesTest::assertRuleValue(const UnicodeString& rule, double expected) {
366 assertRuleKeyValue("a:" + rule, "a", expected);
367 }
368
369 void
assertRuleKeyValue(const UnicodeString & rule,const UnicodeString & key,double expected)370 PluralRulesTest::assertRuleKeyValue(const UnicodeString& rule,
371 const UnicodeString& key, double expected) {
372 UErrorCode status = U_ZERO_ERROR;
373 PluralRules *pr = PluralRules::createRules(rule, status);
374 double result = pr->getUniqueKeywordValue(key);
375 delete pr;
376 if (expected != result) {
377 errln("expected %g but got %g", expected, result);
378 }
379 }
380
381 // TODO: UniqueKeywordValue() is not currently supported.
382 // If it never will be, this test code should be removed.
testGetUniqueKeywordValue()383 void PluralRulesTest::testGetUniqueKeywordValue() {
384 assertRuleValue("n is 1", 1);
385 assertRuleValue("n in 2..2", 2);
386 assertRuleValue("n within 2..2", 2);
387 assertRuleValue("n in 3..4", UPLRULES_NO_UNIQUE_VALUE);
388 assertRuleValue("n within 3..4", UPLRULES_NO_UNIQUE_VALUE);
389 assertRuleValue("n is 2 or n is 2", 2);
390 assertRuleValue("n is 2 and n is 2", 2);
391 assertRuleValue("n is 2 or n is 3", UPLRULES_NO_UNIQUE_VALUE);
392 assertRuleValue("n is 2 and n is 3", UPLRULES_NO_UNIQUE_VALUE);
393 assertRuleValue("n is 2 or n in 2..3", UPLRULES_NO_UNIQUE_VALUE);
394 assertRuleValue("n is 2 and n in 2..3", 2);
395 assertRuleKeyValue("a: n is 1", "not_defined", UPLRULES_NO_UNIQUE_VALUE); // key not defined
396 assertRuleKeyValue("a: n is 1", "other", UPLRULES_NO_UNIQUE_VALUE); // key matches default rule
397 }
398
testGetSamples()399 void PluralRulesTest::testGetSamples() {
400 // TODO: fix samples, re-enable this test.
401
402 // no get functional equivalent API in ICU4C, so just
403 // test every locale...
404 UErrorCode status = U_ZERO_ERROR;
405 int32_t numLocales;
406 const Locale* locales = Locale::getAvailableLocales(numLocales);
407
408 double values[1000];
409 for (int32_t i = 0; U_SUCCESS(status) && i < numLocales; ++i) {
410 //if (uprv_strcmp(locales[i].getLanguage(), "fr") == 0 &&
411 // logKnownIssue("21322", "PluralRules::getSamples cannot distinguish 1e5 from 100000")) {
412 // continue;
413 //}
414 LocalPointer<PluralRules> rules(PluralRules::forLocale(locales[i], status));
415 if (U_FAILURE(status)) {
416 break;
417 }
418 LocalPointer<StringEnumeration> keywords(rules->getKeywords(status));
419 if (U_FAILURE(status)) {
420 break;
421 }
422 const UnicodeString* keyword;
423 while (NULL != (keyword = keywords->snext(status))) {
424 int32_t count = rules->getSamples(*keyword, values, UPRV_LENGTHOF(values), status);
425 if (U_FAILURE(status)) {
426 errln(UnicodeString(u"getSamples() failed for locale ") +
427 locales[i].getName() +
428 UnicodeString(u", keyword ") + *keyword);
429 continue;
430 }
431 if (count == 0) {
432 // TODO: Lots of these.
433 // errln(UnicodeString(u"no samples for keyword ") + *keyword + UnicodeString(u" in locale ") + locales[i].getName() );
434 }
435 if (count > UPRV_LENGTHOF(values)) {
436 errln(UnicodeString(u"getSamples()=") + count +
437 UnicodeString(u", too many values, for locale ") +
438 locales[i].getName() +
439 UnicodeString(u", keyword ") + *keyword);
440 count = UPRV_LENGTHOF(values);
441 }
442 for (int32_t j = 0; j < count; ++j) {
443 if (values[j] == UPLRULES_NO_UNIQUE_VALUE) {
444 errln("got 'no unique value' among values");
445 } else {
446 UnicodeString resultKeyword = rules->select(values[j]);
447 // if (strcmp(locales[i].getName(), "uk") == 0) { // Debug only.
448 // std::cout << " uk " << US(resultKeyword).cstr() << " " << values[j] << std::endl;
449 // }
450 if (*keyword != resultKeyword) {
451 errln("file %s, line %d, Locale %s, sample for keyword \"%s\": %g, select(%g) returns keyword \"%s\"",
452 __FILE__, __LINE__, locales[i].getName(), US(*keyword).cstr(), values[j], values[j], US(resultKeyword).cstr());
453 }
454 }
455 }
456 }
457 }
458 }
459
testGetFixedDecimalSamples()460 void PluralRulesTest::testGetFixedDecimalSamples() {
461 // TODO: fix samples, re-enable this test.
462
463 // no get functional equivalent API in ICU4C, so just
464 // test every locale...
465 UErrorCode status = U_ZERO_ERROR;
466 int32_t numLocales;
467 const Locale* locales = Locale::getAvailableLocales(numLocales);
468
469 FixedDecimal values[1000];
470 for (int32_t i = 0; U_SUCCESS(status) && i < numLocales; ++i) {
471 //if (uprv_strcmp(locales[i].getLanguage(), "fr") == 0 &&
472 // logKnownIssue("21322", "PluralRules::getSamples cannot distinguish 1e5 from 100000")) {
473 // continue;
474 //}
475 LocalPointer<PluralRules> rules(PluralRules::forLocale(locales[i], status));
476 if (U_FAILURE(status)) {
477 break;
478 }
479 LocalPointer<StringEnumeration> keywords(rules->getKeywords(status));
480 if (U_FAILURE(status)) {
481 break;
482 }
483 const UnicodeString* keyword;
484 while (NULL != (keyword = keywords->snext(status))) {
485 int32_t count = rules->getSamples(*keyword, values, UPRV_LENGTHOF(values), status);
486 if (U_FAILURE(status)) {
487 errln(UnicodeString(u"getSamples() failed for locale ") +
488 locales[i].getName() +
489 UnicodeString(u", keyword ") + *keyword);
490 continue;
491 }
492 if (count == 0) {
493 // TODO: Lots of these.
494 // errln(UnicodeString(u"no samples for keyword ") + *keyword + UnicodeString(u" in locale ") + locales[i].getName() );
495 }
496 if (count > UPRV_LENGTHOF(values)) {
497 errln(UnicodeString(u"getSamples()=") + count +
498 UnicodeString(u", too many values, for locale ") +
499 locales[i].getName() +
500 UnicodeString(u", keyword ") + *keyword);
501 count = UPRV_LENGTHOF(values);
502 }
503 for (int32_t j = 0; j < count; ++j) {
504 if (values[j] == UPLRULES_NO_UNIQUE_VALUE_DECIMAL) {
505 errln("got 'no unique value' among values");
506 } else {
507 UnicodeString resultKeyword = rules->select(values[j]);
508 // if (strcmp(locales[i].getName(), "uk") == 0) { // Debug only.
509 // std::cout << " uk " << US(resultKeyword).cstr() << " " << values[j] << std::endl;
510 // }
511 if (*keyword != resultKeyword) {
512 if (values[j].exponent == 0 || !logKnownIssue("21714", "PluralRules::select treats 1c6 as 1")) {
513 UnicodeString valueString(values[j].toString());
514 char valueBuf[16];
515 valueString.extract(0, valueString.length(), valueBuf, sizeof(valueBuf));
516 errln("file %s, line %d, Locale %s, sample for keyword \"%s\": %s, select(%s) returns keyword \"%s\"",
517 __FILE__, __LINE__, locales[i].getName(), US(*keyword).cstr(), valueBuf, valueBuf, US(resultKeyword).cstr());
518 }
519 }
520 }
521 }
522 }
523 }
524 }
525
testSamplesWithExponent()526 void PluralRulesTest::testSamplesWithExponent() {
527 // integer samples
528 UErrorCode status = U_ZERO_ERROR;
529 UnicodeString description(
530 u"one: i = 0,1 @integer 0, 1, 1e5 @decimal 0.0~1.5, 1.1e5; "
531 u"many: e = 0 and i != 0 and i % 1000000 = 0 and v = 0 or e != 0..5"
532 u" @integer 1000000, 2e6, 3e6, 4e6, 5e6, 6e6, 7e6, … @decimal 2.1e6, 3.1e6, 4.1e6, 5.1e6, 6.1e6, 7.1e6, …; "
533 u"other: @integer 2~17, 100, 1000, 10000, 100000, 2e5, 3e5, 4e5, 5e5, 6e5, 7e5, …"
534 u" @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 2.1e5, 3.1e5, 4.1e5, 5.1e5, 6.1e5, 7.1e5, …"
535 );
536 LocalPointer<PluralRules> test(PluralRules::createRules(description, status));
537 if (U_FAILURE(status)) {
538 errln("Couldn't create plural rules from a string using exponent notation, with error = %s", u_errorName(status));
539 return;
540 }
541 checkNewSamples(description, test, u"one", u"@integer 0, 1, 1e5", FixedDecimal(0));
542 checkNewSamples(description, test, u"many", u"@integer 1000000, 2e6, 3e6, 4e6, 5e6, 6e6, 7e6, …", FixedDecimal(1000000));
543 checkNewSamples(description, test, u"other", u"@integer 2~17, 100, 1000, 10000, 100000, 2e5, 3e5, 4e5, 5e5, 6e5, 7e5, …", FixedDecimal(2));
544
545 // decimal samples
546 status = U_ZERO_ERROR;
547 UnicodeString description2(
548 u"one: i = 0,1 @decimal 0.0~1.5, 1.1e5; "
549 u"many: e = 0 and i != 0 and i % 1000000 = 0 and v = 0 or e != 0..5"
550 u" @decimal 2.1e6, 3.1e6, 4.1e6, 5.1e6, 6.1e6, 7.1e6, …; "
551 u"other: @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 2.1e5, 3.1e5, 4.1e5, 5.1e5, 6.1e5, 7.1e5, …"
552 );
553 LocalPointer<PluralRules> test2(PluralRules::createRules(description2, status));
554 if (U_FAILURE(status)) {
555 errln("Couldn't create plural rules from a string using exponent notation, with error = %s", u_errorName(status));
556 return;
557 }
558 checkNewSamples(description2, test2, u"one", u"@decimal 0.0~1.5, 1.1e5", FixedDecimal(0, 1));
559 checkNewSamples(description2, test2, u"many", u"@decimal 2.1e6, 3.1e6, 4.1e6, 5.1e6, 6.1e6, 7.1e6, …", FixedDecimal::createWithExponent(2.1, 1, 6));
560 checkNewSamples(description2, test2, u"other", u"@decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 2.1e5, 3.1e5, 4.1e5, 5.1e5, 6.1e5, 7.1e5, …", FixedDecimal(2.0, 1));
561 }
562
563
testSamplesWithCompactNotation()564 void PluralRulesTest::testSamplesWithCompactNotation() {
565 // integer samples
566 UErrorCode status = U_ZERO_ERROR;
567 UnicodeString description(
568 u"one: i = 0,1 @integer 0, 1, 1c5 @decimal 0.0~1.5, 1.1c5; "
569 u"many: c = 0 and i != 0 and i % 1000000 = 0 and v = 0 or c != 0..5"
570 u" @integer 1000000, 2c6, 3c6, 4c6, 5c6, 6c6, 7c6, … @decimal 2.1c6, 3.1c6, 4.1c6, 5.1c6, 6.1c6, 7.1c6, …; "
571 u"other: @integer 2~17, 100, 1000, 10000, 100000, 2c5, 3c5, 4c5, 5c5, 6c5, 7c5, …"
572 u" @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 2.1c5, 3.1c5, 4.1c5, 5.1c5, 6.1c5, 7.1c5, …"
573 );
574 LocalPointer<PluralRules> test(PluralRules::createRules(description, status));
575 if (U_FAILURE(status)) {
576 errln("Couldn't create plural rules from a string using exponent notation, with error = %s", u_errorName(status));
577 return;
578 }
579 checkNewSamples(description, test, u"one", u"@integer 0, 1, 1c5", FixedDecimal(0));
580 checkNewSamples(description, test, u"many", u"@integer 1000000, 2c6, 3c6, 4c6, 5c6, 6c6, 7c6, …", FixedDecimal(1000000));
581 checkNewSamples(description, test, u"other", u"@integer 2~17, 100, 1000, 10000, 100000, 2c5, 3c5, 4c5, 5c5, 6c5, 7c5, …", FixedDecimal(2));
582
583 // decimal samples
584 status = U_ZERO_ERROR;
585 UnicodeString description2(
586 u"one: i = 0,1 @decimal 0.0~1.5, 1.1c5; "
587 u"many: c = 0 and i != 0 and i % 1000000 = 0 and v = 0 or c != 0..5"
588 u" @decimal 2.1c6, 3.1c6, 4.1c6, 5.1c6, 6.1c6, 7.1c6, …; "
589 u"other: @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 2.1c5, 3.1c5, 4.1c5, 5.1c5, 6.1c5, 7.1c5, …"
590 );
591 LocalPointer<PluralRules> test2(PluralRules::createRules(description2, status));
592 if (U_FAILURE(status)) {
593 errln("Couldn't create plural rules from a string using exponent notation, with error = %s", u_errorName(status));
594 return;
595 }
596 checkNewSamples(description2, test2, u"one", u"@decimal 0.0~1.5, 1.1c5", FixedDecimal(0, 1));
597 checkNewSamples(description2, test2, u"many", u"@decimal 2.1c6, 3.1c6, 4.1c6, 5.1c6, 6.1c6, 7.1c6, …", FixedDecimal::createWithExponent(2.1, 1, 6));
598 checkNewSamples(description2, test2, u"other", u"@decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 2.1c5, 3.1c5, 4.1c5, 5.1c5, 6.1c5, 7.1c5, …", FixedDecimal(2.0, 1));
599 }
600
checkNewSamples(UnicodeString description,const LocalPointer<PluralRules> & test,UnicodeString keyword,UnicodeString samplesString,FixedDecimal firstInRange)601 void PluralRulesTest::checkNewSamples(
602 UnicodeString description,
603 const LocalPointer<PluralRules> &test,
604 UnicodeString keyword,
605 UnicodeString samplesString,
606 FixedDecimal firstInRange) {
607
608 UErrorCode status = U_ZERO_ERROR;
609 FixedDecimal samples[1000];
610
611 test->getSamples(keyword, samples, UPRV_LENGTHOF(samples), status);
612 if (U_FAILURE(status)) {
613 errln("Couldn't retrieve plural samples, with error = %s", u_errorName(status));
614 return;
615 }
616 FixedDecimal actualFirstSample = samples[0];
617
618 if (!(firstInRange == actualFirstSample)) {
619 CStr descCstr(description);
620 CStr samplesCstr(samplesString);
621 char errMsg[1000];
622 snprintf(errMsg, sizeof(errMsg), "First parsed sample FixedDecimal not equal to expected for samples: %s in rule string: %s\n", descCstr(), samplesCstr());
623 errln(errMsg);
624 }
625 }
626
testWithin()627 void PluralRulesTest::testWithin() {
628 // goes to show you what lack of testing will do.
629 // of course, this has been broken for two years and no one has noticed...
630 UErrorCode status = U_ZERO_ERROR;
631 PluralRules *rules = PluralRules::createRules("a: n mod 10 in 5..8", status);
632 if (!rules) {
633 errln("couldn't instantiate rules");
634 return;
635 }
636
637 UnicodeString keyword = rules->select((int32_t)26);
638 if (keyword != "a") {
639 errln("expected 'a' for 26 but didn't get it.");
640 }
641
642 keyword = rules->select(26.5);
643 if (keyword != "other") {
644 errln("expected 'other' for 26.5 but didn't get it.");
645 }
646
647 delete rules;
648 }
649
650 void
testGetAllKeywordValues()651 PluralRulesTest::testGetAllKeywordValues() {
652 const char* data[] = {
653 "a: n in 2..5", "a: 2,3,4,5; other: null; b:",
654 "a: n not in 2..5", "a: null; other: null",
655 "a: n within 2..5", "a: null; other: null",
656 "a: n not within 2..5", "a: null; other: null",
657 "a: n in 2..5 or n within 6..8", "a: null", // ignore 'other' here on out, always null
658 "a: n in 2..5 and n within 6..8", "a:",
659 "a: n in 2..5 and n within 5..8", "a: 5",
660 "a: n within 2..5 and n within 6..8", "a:", // our sampling catches these
661 "a: n within 2..5 and n within 5..8", "a: 5", // ''
662 "a: n within 1..2 and n within 2..3 or n within 3..4 and n within 4..5", "a: 2,4",
663 "a: n within 1..2 and n within 2..3 or n within 3..4 and n within 4..5 "
664 "or n within 5..6 and n within 6..7", "a: null", // but not this...
665 "a: n mod 3 is 0", "a: null",
666 "a: n mod 3 is 0 and n within 1..2", "a:",
667 "a: n mod 3 is 0 and n within 0..5", "a: 0,3",
668 "a: n mod 3 is 0 and n within 0..6", "a: null", // similarly with mod, we don't catch...
669 "a: n mod 3 is 0 and n in 3..12", "a: 3,6,9,12",
670 NULL
671 };
672
673 for (int i = 0; data[i] != NULL; i += 2) {
674 UErrorCode status = U_ZERO_ERROR;
675 UnicodeString ruleDescription(data[i], -1, US_INV);
676 const char* result = data[i+1];
677
678 logln("[%d] %s", i >> 1, data[i]);
679
680 PluralRules *p = PluralRules::createRules(ruleDescription, status);
681 if (p == NULL || U_FAILURE(status)) {
682 errln("file %s, line %d: could not create rules from '%s'\n"
683 " ErrorCode: %s\n",
684 __FILE__, __LINE__, data[i], u_errorName(status));
685 continue;
686 }
687
688 // TODO: fix samples implementation, re-enable test.
689 (void)result;
690 #if 0
691
692 const char* rp = result;
693 while (*rp) {
694 while (*rp == ' ') ++rp;
695 if (!rp) {
696 break;
697 }
698
699 const char* ep = rp;
700 while (*ep && *ep != ':') ++ep;
701
702 status = U_ZERO_ERROR;
703 UnicodeString keyword(rp, ep - rp, US_INV);
704 double samples[4]; // no test above should have more samples than 4
705 int32_t count = p->getAllKeywordValues(keyword, &samples[0], 4, status);
706 if (U_FAILURE(status)) {
707 errln("error getting samples for %s", rp);
708 break;
709 }
710
711 if (count > 4) {
712 errln("count > 4 for keyword %s", rp);
713 count = 4;
714 }
715
716 if (*ep) {
717 ++ep; // skip colon
718 while (*ep && *ep == ' ') ++ep; // and spaces
719 }
720
721 UBool ok = TRUE;
722 if (count == -1) {
723 if (*ep != 'n') {
724 errln("expected values for keyword %s but got -1 (%s)", rp, ep);
725 ok = FALSE;
726 }
727 } else if (*ep == 'n') {
728 errln("expected count of -1, got %d, for keyword %s (%s)", count, rp, ep);
729 ok = FALSE;
730 }
731
732 // We'll cheat a bit here. The samples happened to be in order and so are our
733 // expected values, so we'll just test in order until a failure. If the
734 // implementation changes to return samples in an arbitrary order, this test
735 // must change. There's no actual restriction on the order of the samples.
736
737 for (int j = 0; ok && j < count; ++j ) { // we've verified count < 4
738 double val = samples[j];
739 if (*ep == 0 || *ep == ';') {
740 errln("got unexpected value[%d]: %g", j, val);
741 ok = FALSE;
742 break;
743 }
744 char* xp;
745 double expectedVal = strtod(ep, &xp);
746 if (xp == ep) {
747 // internal error
748 errln("yikes!");
749 ok = FALSE;
750 break;
751 }
752 ep = xp;
753 if (expectedVal != val) {
754 errln("expected %g but got %g", expectedVal, val);
755 ok = FALSE;
756 break;
757 }
758 if (*ep == ',') ++ep;
759 }
760
761 if (ok && count != -1) {
762 if (!(*ep == 0 || *ep == ';')) {
763 errln("file: %s, line %d, didn't get expected value: %s", __FILE__, __LINE__, ep);
764 ok = FALSE;
765 }
766 }
767
768 while (*ep && *ep != ';') ++ep;
769 if (*ep == ';') ++ep;
770 rp = ep;
771 }
772 #endif
773 delete p;
774 }
775 }
776
777 // For the time being, the compact notation exponent operand `c` is an alias
778 // for the scientific exponent operand `e` and compact notation.
779 void
testScientificPluralKeyword()780 PluralRulesTest::testScientificPluralKeyword() {
781 IcuTestErrorCode errorCode(*this, "testScientificPluralKeyword");
782
783 LocalPointer<PluralRules> rules(PluralRules::createRules(
784 u"one: i = 0,1 @integer 0, 1 @decimal 0.0~1.5; "
785 u"many: e = 0 and i % 1000000 = 0 and v = 0 or e != 0 .. 5; "
786 u"other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, "
787 u" @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", errorCode));
788
789 if (U_FAILURE(errorCode)) {
790 errln("Couldn't instantiate plurals rules from string, with error = %s", u_errorName(errorCode));
791 return;
792 }
793
794 const char* localeName = "fr-FR";
795 Locale locale = Locale::createFromName(localeName);
796
797 struct TestCase {
798 const char16_t* skeleton;
799 const int input;
800 const char16_t* expectedFormattedOutput;
801 const char16_t* expectedPluralRuleKeyword;
802 } cases[] = {
803 // unlocalized formatter skeleton, input, string output, plural rule keyword
804 {u"", 0, u"0", u"one"},
805 {u"scientific", 0, u"0", u"one"},
806
807 {u"", 1, u"1", u"one"},
808 {u"scientific", 1, u"1", u"one"},
809
810 {u"", 2, u"2", u"other"},
811 {u"scientific", 2, u"2", u"other"},
812
813 {u"", 1000000, u"1 000 000", u"many"},
814 {u"scientific", 1000000, u"1 million", u"many"},
815
816 {u"", 1000001, u"1 000 001", u"other"},
817 {u"scientific", 1000001, u"1 million", u"many"},
818
819 {u"", 120000, u"1 200 000", u"other"},
820 {u"scientific", 1200000, u"1,2 millions", u"many"},
821
822 {u"", 1200001, u"1 200 001", u"other"},
823 {u"scientific", 1200001, u"1,2 millions", u"many"},
824
825 {u"", 2000000, u"2 000 000", u"many"},
826 {u"scientific", 2000000, u"2 millions", u"many"},
827 };
828 for (const auto& cas : cases) {
829 const char16_t* skeleton = cas.skeleton;
830 const int input = cas.input;
831 const char16_t* expectedPluralRuleKeyword = cas.expectedPluralRuleKeyword;
832
833 UnicodeString actualPluralRuleKeyword =
834 getPluralKeyword(rules, locale, input, skeleton);
835
836 UnicodeString message(UnicodeString(localeName) + u" " + DoubleToUnicodeString(input));
837 assertEquals(message, expectedPluralRuleKeyword, actualPluralRuleKeyword);
838 }
839 }
840
841 void
testCompactDecimalPluralKeyword()842 PluralRulesTest::testCompactDecimalPluralKeyword() {
843 IcuTestErrorCode errorCode(*this, "testCompactDecimalPluralKeyword");
844
845 LocalPointer<PluralRules> rules(PluralRules::createRules(
846 u"one: i = 0,1 @integer 0, 1 @decimal 0.0~1.5; "
847 u"many: c = 0 and i % 1000000 = 0 and v = 0 or c != 0 .. 5; "
848 u"other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, "
849 u" @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …", errorCode));
850
851 if (U_FAILURE(errorCode)) {
852 errln("Couldn't instantiate plurals rules from string, with error = %s", u_errorName(errorCode));
853 return;
854 }
855
856 const char* localeName = "fr-FR";
857 Locale locale = Locale::createFromName(localeName);
858
859 struct TestCase {
860 const char16_t* skeleton;
861 const int input;
862 const char16_t* expectedFormattedOutput;
863 const char16_t* expectedPluralRuleKeyword;
864 } cases[] = {
865 // unlocalized formatter skeleton, input, string output, plural rule keyword
866 {u"", 0, u"0", u"one"},
867 {u"compact-long", 0, u"0", u"one"},
868
869 {u"", 1, u"1", u"one"},
870 {u"compact-long", 1, u"1", u"one"},
871
872 {u"", 2, u"2", u"other"},
873 {u"compact-long", 2, u"2", u"other"},
874
875 {u"", 1000000, u"1 000 000", u"many"},
876 {u"compact-long", 1000000, u"1 million", u"many"},
877
878 {u"", 1000001, u"1 000 001", u"other"},
879 {u"compact-long", 1000001, u"1 million", u"many"},
880
881 {u"", 120000, u"1 200 000", u"other"},
882 {u"compact-long", 1200000, u"1,2 millions", u"many"},
883
884 {u"", 1200001, u"1 200 001", u"other"},
885 {u"compact-long", 1200001, u"1,2 millions", u"many"},
886
887 {u"", 2000000, u"2 000 000", u"many"},
888 {u"compact-long", 2000000, u"2 millions", u"many"},
889 };
890 for (const auto& cas : cases) {
891 const char16_t* skeleton = cas.skeleton;
892 const int input = cas.input;
893 const char16_t* expectedPluralRuleKeyword = cas.expectedPluralRuleKeyword;
894
895 UnicodeString actualPluralRuleKeyword =
896 getPluralKeyword(rules, locale, input, skeleton);
897
898 UnicodeString message(UnicodeString(localeName) + u" " + DoubleToUnicodeString(input));
899 assertEquals(message, expectedPluralRuleKeyword, actualPluralRuleKeyword);
900 }
901 }
902
903 void
testDoubleValue()904 PluralRulesTest::testDoubleValue() {
905 IcuTestErrorCode errorCode(*this, "testDoubleValue");
906
907 struct IntTestCase {
908 const int64_t inputNum;
909 const double expVal;
910 } intCases[] = {
911 {-101, -101.0},
912 {-100, -100.0},
913 {-1, -1.0},
914 {0, 0.0},
915 {1, 1.0},
916 {100, 100.0}
917 };
918 for (const auto& cas : intCases) {
919 const int64_t inputNum = cas.inputNum;
920 const double expVal = cas.expVal;
921
922 FixedDecimal fd(inputNum);
923 UnicodeString message(u"FixedDecimal::doubleValue() for" + Int64ToUnicodeString(inputNum));
924 assertEquals(message, expVal, fd.doubleValue());
925 }
926
927 struct DoubleTestCase {
928 const double inputNum;
929 const double expVal;
930 } dblCases[] = {
931 {-0.0, -0.0},
932 {0.1, 0.1},
933 {1.999, 1.999},
934 {2.0, 2.0},
935 {100.001, 100.001}
936 };
937 for (const auto & cas : dblCases) {
938 const double inputNum = cas.inputNum;
939 const double expVal = cas.expVal;
940
941 FixedDecimal fd(inputNum);
942 UnicodeString message(u"FixedDecimal::doubleValue() for" + DoubleToUnicodeString(inputNum));
943 assertEquals(message, expVal, fd.doubleValue());
944 }
945 }
946
947 void
testLongValue()948 PluralRulesTest::testLongValue() {
949 IcuTestErrorCode errorCode(*this, "testLongValue");
950
951 struct IntTestCase {
952 const int64_t inputNum;
953 const int64_t expVal;
954 } intCases[] = {
955 {-101, 101},
956 {-100, 100},
957 {-1, 1},
958 {0, 0},
959 {1, 1},
960 {100, 100}
961 };
962 for (const auto& cas : intCases) {
963 const int64_t inputNum = cas.inputNum;
964 const int64_t expVal = cas.expVal;
965
966 FixedDecimal fd(inputNum);
967 UnicodeString message(u"FixedDecimal::longValue() for" + Int64ToUnicodeString(inputNum));
968 assertEquals(message, expVal, fd.longValue());
969 }
970
971 struct DoubleTestCase {
972 const double inputNum;
973 const int64_t expVal;
974 } dblCases[] = {
975 {-0.0, 0},
976 {0.1, 0},
977 {1.999, 1},
978 {2.0, 2},
979 {100.001, 100}
980 };
981 for (const auto & cas : dblCases) {
982 const double inputNum = cas.inputNum;
983 const int64_t expVal = cas.expVal;
984
985 FixedDecimal fd(inputNum);
986 UnicodeString message(u"FixedDecimal::longValue() for" + DoubleToUnicodeString(inputNum));
987 assertEquals(message, expVal, fd.longValue());
988 }
989 }
990
getPluralKeyword(const LocalPointer<PluralRules> & rules,Locale locale,double number,const char16_t * skeleton)991 UnicodeString PluralRulesTest::getPluralKeyword(const LocalPointer<PluralRules> &rules, Locale locale, double number, const char16_t* skeleton) {
992 IcuTestErrorCode errorCode(*this, "getPluralKeyword");
993 UnlocalizedNumberFormatter ulnf = NumberFormatter::forSkeleton(skeleton, errorCode);
994 if (errorCode.errIfFailureAndReset("PluralRules::getPluralKeyword(<PluralRules>, <locale>, %d, %s) failed", number, skeleton)) {
995 return nullptr;
996 }
997 LocalizedNumberFormatter formatter = ulnf.locale(locale);
998
999 const FormattedNumber fn = formatter.formatDouble(number, errorCode);
1000 if (errorCode.errIfFailureAndReset("NumberFormatter::formatDouble(%d) failed", number)) {
1001 return nullptr;
1002 }
1003
1004 UnicodeString pluralKeyword = rules->select(fn, errorCode);
1005 if (errorCode.errIfFailureAndReset("PluralRules->select(FormattedNumber of %d) failed", number)) {
1006 return nullptr;
1007 }
1008 return pluralKeyword;
1009 }
1010
testOrdinal()1011 void PluralRulesTest::testOrdinal() {
1012 IcuTestErrorCode errorCode(*this, "testOrdinal");
1013 LocalPointer<PluralRules> pr(PluralRules::forLocale("en", UPLURAL_TYPE_ORDINAL, errorCode));
1014 if (errorCode.errIfFailureAndReset("PluralRules::forLocale(en, UPLURAL_TYPE_ORDINAL) failed")) {
1015 return;
1016 }
1017 UnicodeString keyword = pr->select(2.);
1018 if (keyword != UNICODE_STRING("two", 3)) {
1019 dataerrln("PluralRules(en-ordinal).select(2) failed");
1020 }
1021 }
1022
1023
1024 static const char * END_MARK = "999.999"; // Mark end of varargs data.
1025
checkSelect(const LocalPointer<PluralRules> & rules,UErrorCode & status,int32_t line,const char * keyword,...)1026 void PluralRulesTest::checkSelect(const LocalPointer<PluralRules> &rules, UErrorCode &status,
1027 int32_t line, const char *keyword, ...) {
1028 // The varargs parameters are a const char* strings, each being a decimal number.
1029 // The formatting of the numbers as strings is significant, e.g.
1030 // the difference between "2" and "2.0" can affect which rule matches (which keyword is selected).
1031 // Note: rules parameter is a LocalPointer reference rather than a PluralRules * to avoid having
1032 // to write getAlias() at every (numerous) call site.
1033
1034 if (U_FAILURE(status)) {
1035 errln("file %s, line %d, ICU error status: %s.", __FILE__, line, u_errorName(status));
1036 status = U_ZERO_ERROR;
1037 return;
1038 }
1039
1040 if (rules == NULL) {
1041 errln("file %s, line %d: rules pointer is NULL", __FILE__, line);
1042 return;
1043 }
1044
1045 va_list ap;
1046 va_start(ap, keyword);
1047 for (;;) {
1048 const char *num = va_arg(ap, const char *);
1049 if (strcmp(num, END_MARK) == 0) {
1050 break;
1051 }
1052
1053 // DigitList is a convenient way to parse the decimal number string and get a double.
1054 DecimalQuantity dl;
1055 dl.setToDecNumber(StringPiece(num), status);
1056 if (U_FAILURE(status)) {
1057 errln("file %s, line %d, ICU error status: %s.", __FILE__, line, u_errorName(status));
1058 status = U_ZERO_ERROR;
1059 continue;
1060 }
1061 double numDbl = dl.toDouble();
1062 const char *decimalPoint = strchr(num, '.');
1063 int fractionDigitCount = decimalPoint == NULL ? 0 : static_cast<int>((num + strlen(num) - 1) - decimalPoint);
1064 int fractionDigits = fractionDigitCount == 0 ? 0 : atoi(decimalPoint + 1);
1065 FixedDecimal ni(numDbl, fractionDigitCount, fractionDigits);
1066
1067 UnicodeString actualKeyword = rules->select(ni);
1068 if (actualKeyword != UnicodeString(keyword)) {
1069 errln("file %s, line %d, select(%s) returned incorrect keyword. Expected %s, got %s",
1070 __FILE__, line, num, keyword, US(actualKeyword).cstr());
1071 }
1072 }
1073 va_end(ap);
1074 }
1075
testSelect()1076 void PluralRulesTest::testSelect() {
1077 UErrorCode status = U_ZERO_ERROR;
1078 LocalPointer<PluralRules> pr(PluralRules::createRules("s: n in 1,3,4,6", status));
1079 checkSelect(pr, status, __LINE__, "s", "1.0", "3.0", "4.0", "6.0", END_MARK);
1080 checkSelect(pr, status, __LINE__, "other", "0.0", "2.0", "3.1", "7.0", END_MARK);
1081
1082 pr.adoptInstead(PluralRules::createRules("s: n not in 1,3,4,6", status));
1083 checkSelect(pr, status, __LINE__, "other", "1.0", "3.0", "4.0", "6.0", END_MARK);
1084 checkSelect(pr, status, __LINE__, "s", "0.0", "2.0", "3.1", "7.0", END_MARK);
1085
1086 pr.adoptInstead(PluralRules::createRules("r: n in 1..4, 7..10, 14 .. 17;"
1087 "s: n is 29;", status));
1088 checkSelect(pr, status, __LINE__, "r", "1.0", "3.0", "7.0", "8.0", "10.0", "14.0", "17.0", END_MARK);
1089 checkSelect(pr, status, __LINE__, "s", "29.0", END_MARK);
1090 checkSelect(pr, status, __LINE__, "other", "28.0", "29.1", END_MARK);
1091
1092 pr.adoptInstead(PluralRules::createRules("a: n mod 10 is 1; b: n mod 100 is 0 ", status));
1093 checkSelect(pr, status, __LINE__, "a", "1", "11", "41", "101", "301.00", END_MARK);
1094 checkSelect(pr, status, __LINE__, "b", "0", "100", "200.0", "300.", "1000", "1100", "110000", END_MARK);
1095 checkSelect(pr, status, __LINE__, "other", "0.01", "1.01", "0.99", "2", "3", "99", "102", END_MARK);
1096
1097 // Rules that end with or without a ';' and with or without trailing spaces.
1098 // (There was a rule parser bug here with these.)
1099 pr.adoptInstead(PluralRules::createRules("a: n is 1", status));
1100 checkSelect(pr, status, __LINE__, "a", "1", END_MARK);
1101 checkSelect(pr, status, __LINE__, "other", "2", END_MARK);
1102
1103 pr.adoptInstead(PluralRules::createRules("a: n is 1 ", status));
1104 checkSelect(pr, status, __LINE__, "a", "1", END_MARK);
1105 checkSelect(pr, status, __LINE__, "other", "2", END_MARK);
1106
1107 pr.adoptInstead(PluralRules::createRules("a: n is 1;", status));
1108 checkSelect(pr, status, __LINE__, "a", "1", END_MARK);
1109 checkSelect(pr, status, __LINE__, "other", "2", END_MARK);
1110
1111 pr.adoptInstead(PluralRules::createRules("a: n is 1 ; ", status));
1112 checkSelect(pr, status, __LINE__, "a", "1", END_MARK);
1113 checkSelect(pr, status, __LINE__, "other", "2", END_MARK);
1114
1115 // First match when rules for different keywords are not disjoint.
1116 // Also try spacing variations around ':' and '..'
1117 pr.adoptInstead(PluralRules::createRules("c: n in 5..15; b : n in 1..10 ;a:n in 10 .. 20", status));
1118 checkSelect(pr, status, __LINE__, "a", "20", END_MARK);
1119 checkSelect(pr, status, __LINE__, "b", "1", END_MARK);
1120 checkSelect(pr, status, __LINE__, "c", "10", END_MARK);
1121 checkSelect(pr, status, __LINE__, "other", "0", "21", "10.1", END_MARK);
1122
1123 // in vs within
1124 pr.adoptInstead(PluralRules::createRules("a: n in 2..10; b: n within 8..15", status));
1125 checkSelect(pr, status, __LINE__, "a", "2", "8", "10", END_MARK);
1126 checkSelect(pr, status, __LINE__, "b", "8.01", "9.5", "11", "14.99", "15", END_MARK);
1127 checkSelect(pr, status, __LINE__, "other", "1", "7.7", "15.01", "16", END_MARK);
1128
1129 // OR and AND chains.
1130 pr.adoptInstead(PluralRules::createRules("a: n in 2..10 and n in 4..12 and n not in 5..7", status));
1131 checkSelect(pr, status, __LINE__, "a", "4", "8", "9", "10", END_MARK);
1132 checkSelect(pr, status, __LINE__, "other", "2", "3", "5", "7", "11", END_MARK);
1133 pr.adoptInstead(PluralRules::createRules("a: n is 2 or n is 5 or n in 7..11 and n in 11..13", status));
1134 checkSelect(pr, status, __LINE__, "a", "2", "5", "11", END_MARK);
1135 checkSelect(pr, status, __LINE__, "other", "3", "4", "6", "8", "10", "12", "13", END_MARK);
1136
1137 // Number attributes -
1138 // n: the number itself
1139 // i: integer digits
1140 // f: visible fraction digits
1141 // t: f with trailing zeros removed.
1142 // v: number of visible fraction digits
1143 // j: = n if there are no visible fraction digits
1144 // != anything if there are visible fraction digits
1145
1146 pr.adoptInstead(PluralRules::createRules("a: i is 123", status));
1147 checkSelect(pr, status, __LINE__, "a", "123", "123.0", "123.1", "0123.99", END_MARK);
1148 checkSelect(pr, status, __LINE__, "other", "124", "122.0", END_MARK);
1149
1150 pr.adoptInstead(PluralRules::createRules("a: f is 120", status));
1151 checkSelect(pr, status, __LINE__, "a", "1.120", "0.120", "11123.120", "0123.120", END_MARK);
1152 checkSelect(pr, status, __LINE__, "other", "1.121", "122.1200", "1.12", "120", END_MARK);
1153
1154 pr.adoptInstead(PluralRules::createRules("a: t is 12", status));
1155 checkSelect(pr, status, __LINE__, "a", "1.120", "0.12", "11123.12000", "0123.1200000", END_MARK);
1156 checkSelect(pr, status, __LINE__, "other", "1.121", "122.1200001", "1.11", "12", END_MARK);
1157
1158 pr.adoptInstead(PluralRules::createRules("a: v is 3", status));
1159 checkSelect(pr, status, __LINE__, "a", "1.120", "0.000", "11123.100", "0123.124", ".666", END_MARK);
1160 checkSelect(pr, status, __LINE__, "other", "1.1212", "122.12", "1.1", "122", "0.0000", END_MARK);
1161
1162 pr.adoptInstead(PluralRules::createRules("a: v is 0 and i is 123", status));
1163 checkSelect(pr, status, __LINE__, "a", "123", "123.", END_MARK);
1164 checkSelect(pr, status, __LINE__, "other", "123.0", "123.1", "123.123", "0.123", END_MARK);
1165
1166 // The reserved words from the rule syntax will also function as keywords.
1167 pr.adoptInstead(PluralRules::createRules("a: n is 21; n: n is 22; i: n is 23; f: n is 24;"
1168 "t: n is 25; v: n is 26; w: n is 27; j: n is 28"
1169 , status));
1170 checkSelect(pr, status, __LINE__, "other", "20", "29", END_MARK);
1171 checkSelect(pr, status, __LINE__, "a", "21", END_MARK);
1172 checkSelect(pr, status, __LINE__, "n", "22", END_MARK);
1173 checkSelect(pr, status, __LINE__, "i", "23", END_MARK);
1174 checkSelect(pr, status, __LINE__, "f", "24", END_MARK);
1175 checkSelect(pr, status, __LINE__, "t", "25", END_MARK);
1176 checkSelect(pr, status, __LINE__, "v", "26", END_MARK);
1177 checkSelect(pr, status, __LINE__, "w", "27", END_MARK);
1178 checkSelect(pr, status, __LINE__, "j", "28", END_MARK);
1179
1180
1181 pr.adoptInstead(PluralRules::createRules("not: n=31; and: n=32; or: n=33; mod: n=34;"
1182 "in: n=35; within: n=36;is:n=37"
1183 , status));
1184 checkSelect(pr, status, __LINE__, "other", "30", "39", END_MARK);
1185 checkSelect(pr, status, __LINE__, "not", "31", END_MARK);
1186 checkSelect(pr, status, __LINE__, "and", "32", END_MARK);
1187 checkSelect(pr, status, __LINE__, "or", "33", END_MARK);
1188 checkSelect(pr, status, __LINE__, "mod", "34", END_MARK);
1189 checkSelect(pr, status, __LINE__, "in", "35", END_MARK);
1190 checkSelect(pr, status, __LINE__, "within", "36", END_MARK);
1191 checkSelect(pr, status, __LINE__, "is", "37", END_MARK);
1192
1193 // Test cases from ICU4J PluralRulesTest.parseTestData
1194
1195 pr.adoptInstead(PluralRules::createRules("a: n is 1", status));
1196 checkSelect(pr, status, __LINE__, "a", "1", END_MARK);
1197 pr.adoptInstead(PluralRules::createRules("a: n mod 10 is 2", status));
1198 checkSelect(pr, status, __LINE__, "a", "2", "12", "22", END_MARK);
1199 pr.adoptInstead(PluralRules::createRules("a: n is not 1", status));
1200 checkSelect(pr, status, __LINE__, "a", "0", "2", "3", "4", "5", END_MARK);
1201 pr.adoptInstead(PluralRules::createRules("a: n mod 3 is not 1", status));
1202 checkSelect(pr, status, __LINE__, "a", "0", "2", "3", "5", "6", "8", "9", END_MARK);
1203 pr.adoptInstead(PluralRules::createRules("a: n in 2..5", status));
1204 checkSelect(pr, status, __LINE__, "a", "2", "3", "4", "5", END_MARK);
1205 pr.adoptInstead(PluralRules::createRules("a: n within 2..5", status));
1206 checkSelect(pr, status, __LINE__, "a", "2", "3", "4", "5", END_MARK);
1207 pr.adoptInstead(PluralRules::createRules("a: n not in 2..5", status));
1208 checkSelect(pr, status, __LINE__, "a", "0", "1", "6", "7", "8", END_MARK);
1209 pr.adoptInstead(PluralRules::createRules("a: n not within 2..5", status));
1210 checkSelect(pr, status, __LINE__, "a", "0", "1", "6", "7", "8", END_MARK);
1211 pr.adoptInstead(PluralRules::createRules("a: n mod 10 in 2..5", status));
1212 checkSelect(pr, status, __LINE__, "a", "2", "3", "4", "5", "12", "13", "14", "15", "22", "23", "24", "25", END_MARK);
1213 pr.adoptInstead(PluralRules::createRules("a: n mod 10 within 2..5", status));
1214 checkSelect(pr, status, __LINE__, "a", "2", "3", "4", "5", "12", "13", "14", "15", "22", "23", "24", "25", END_MARK);
1215 pr.adoptInstead(PluralRules::createRules("a: n mod 10 is 2 and n is not 12", status));
1216 checkSelect(pr, status, __LINE__, "a", "2", "22", "32", "42", END_MARK);
1217 pr.adoptInstead(PluralRules::createRules("a: n mod 10 in 2..3 or n mod 10 is 5", status));
1218 checkSelect(pr, status, __LINE__, "a", "2", "3", "5", "12", "13", "15", "22", "23", "25", END_MARK);
1219 pr.adoptInstead(PluralRules::createRules("a: n mod 10 within 2..3 or n mod 10 is 5", status));
1220 checkSelect(pr, status, __LINE__, "a", "2", "3", "5", "12", "13", "15", "22", "23", "25", END_MARK);
1221 pr.adoptInstead(PluralRules::createRules("a: n is 1 or n is 4 or n is 23", status));
1222 checkSelect(pr, status, __LINE__, "a", "1", "4", "23", END_MARK);
1223 pr.adoptInstead(PluralRules::createRules("a: n mod 2 is 1 and n is not 3 and n in 1..11", status));
1224 checkSelect(pr, status, __LINE__, "a", "1", "5", "7", "9", "11", END_MARK);
1225 pr.adoptInstead(PluralRules::createRules("a: n mod 2 is 1 and n is not 3 and n within 1..11", status));
1226 checkSelect(pr, status, __LINE__, "a", "1", "5", "7", "9", "11", END_MARK);
1227 pr.adoptInstead(PluralRules::createRules("a: n mod 2 is 1 or n mod 5 is 1 and n is not 6", status));
1228 checkSelect(pr, status, __LINE__, "a", "1", "3", "5", "7", "9", "11", "13", "15", "16", END_MARK);
1229 pr.adoptInstead(PluralRules::createRules("a: n in 2..5; b: n in 5..8; c: n mod 2 is 1", status));
1230 checkSelect(pr, status, __LINE__, "a", "2", "3", "4", "5", END_MARK);
1231 checkSelect(pr, status, __LINE__, "b", "6", "7", "8", END_MARK);
1232 checkSelect(pr, status, __LINE__, "c", "1", "9", "11", END_MARK);
1233 pr.adoptInstead(PluralRules::createRules("a: n within 2..5; b: n within 5..8; c: n mod 2 is 1", status));
1234 checkSelect(pr, status, __LINE__, "a", "2", "3", "4", "5", END_MARK);
1235 checkSelect(pr, status, __LINE__, "b", "6", "7", "8", END_MARK);
1236 checkSelect(pr, status, __LINE__, "c", "1", "9", "11", END_MARK);
1237 pr.adoptInstead(PluralRules::createRules("a: n in 2, 4..6; b: n within 7..9,11..12,20", status));
1238 checkSelect(pr, status, __LINE__, "a", "2", "4", "5", "6", END_MARK);
1239 checkSelect(pr, status, __LINE__, "b", "7", "8", "9", "11", "12", "20", END_MARK);
1240 pr.adoptInstead(PluralRules::createRules("a: n in 2..8, 12 and n not in 4..6", status));
1241 checkSelect(pr, status, __LINE__, "a", "2", "3", "7", "8", "12", END_MARK);
1242 pr.adoptInstead(PluralRules::createRules("a: n mod 10 in 2, 3,5..7 and n is not 12", status));
1243 checkSelect(pr, status, __LINE__, "a", "2", "3", "5", "6", "7", "13", "15", "16", "17", END_MARK);
1244 pr.adoptInstead(PluralRules::createRules("a: n in 2..6, 3..7", status));
1245 checkSelect(pr, status, __LINE__, "a", "2", "3", "4", "5", "6", "7", END_MARK);
1246
1247 // Extended Syntax, with '=', '!=' and '%' operators.
1248 pr.adoptInstead(PluralRules::createRules("a: n = 1..8 and n!= 2,3,4,5", status));
1249 checkSelect(pr, status, __LINE__, "a", "1", "6", "7", "8", END_MARK);
1250 checkSelect(pr, status, __LINE__, "other", "0", "2", "3", "4", "5", "9", END_MARK);
1251 pr.adoptInstead(PluralRules::createRules("a:n % 10 != 1", status));
1252 checkSelect(pr, status, __LINE__, "a", "2", "6", "7", "8", END_MARK);
1253 checkSelect(pr, status, __LINE__, "other", "1", "21", "211", "91", END_MARK);
1254 }
1255
1256
testSelectRange()1257 void PluralRulesTest::testSelectRange() {
1258 IcuTestErrorCode status(*this, "testSelectRange");
1259
1260 int32_t d1 = 102;
1261 int32_t d2 = 201;
1262 Locale locale("sl");
1263
1264 // Locale sl has interesting data: one + two => few
1265 auto range = NumberRangeFormatter::withLocale(locale).formatFormattableRange(d1, d2, status);
1266 auto rules = LocalPointer<PluralRules>(PluralRules::forLocale(locale, status), status);
1267 if (status.errIfFailureAndReset()) {
1268 return;
1269 }
1270
1271 // For testing: get plural form of first and second numbers
1272 auto a = NumberFormatter::withLocale(locale).formatDouble(d1, status);
1273 auto b = NumberFormatter::withLocale(locale).formatDouble(d2, status);
1274 assertEquals("First plural", u"two", rules->select(a, status));
1275 assertEquals("Second plural", u"one", rules->select(b, status));
1276
1277 // Check the range plural now:
1278 auto form = rules->select(range, status);
1279 assertEquals("Range plural", u"few", form);
1280
1281 // Test after copying:
1282 PluralRules copy(*rules);
1283 form = copy.select(range, status);
1284 assertEquals("Range plural after copying", u"few", form);
1285
1286 // Test when plural ranges data is unavailable:
1287 auto bare = LocalPointer<PluralRules>(
1288 PluralRules::createRules(u"a: i = 0,1", status), status);
1289 if (status.errIfFailureAndReset()) { return; }
1290 form = bare->select(range, status);
1291 status.expectErrorAndReset(U_UNSUPPORTED_ERROR);
1292
1293 // However, they should not set an error when no data is available for a language.
1294 auto xyz = LocalPointer<PluralRules>(
1295 PluralRules::forLocale("xyz", status));
1296 form = xyz->select(range, status);
1297 assertEquals("Fallback form", u"other", form);
1298 }
1299
1300
testAvailableLocales()1301 void PluralRulesTest::testAvailableLocales() {
1302
1303 // Hash set of (char *) strings.
1304 UErrorCode status = U_ZERO_ERROR;
1305 UHashtable *localeSet = uhash_open(uhash_hashUnicodeString, uhash_compareUnicodeString, uhash_compareLong, &status);
1306 uhash_setKeyDeleter(localeSet, uprv_deleteUObject);
1307 if (U_FAILURE(status)) {
1308 errln("file %s, line %d: Error status = %s", __FILE__, __LINE__, u_errorName(status));
1309 return;
1310 }
1311
1312 // Check that each locale returned by the iterator is unique.
1313 StringEnumeration *localesEnum = PluralRules::getAvailableLocales(status);
1314 int localeCount = 0;
1315 for (;;) {
1316 const char *locale = localesEnum->next(NULL, status);
1317 if (U_FAILURE(status)) {
1318 dataerrln("file %s, line %d: Error status = %s", __FILE__, __LINE__, u_errorName(status));
1319 return;
1320 }
1321 if (locale == NULL) {
1322 break;
1323 }
1324 localeCount++;
1325 int32_t oldVal = uhash_puti(localeSet, new UnicodeString(locale), 1, &status);
1326 if (oldVal != 0) {
1327 errln("file %s, line %d: locale %s was seen before.", __FILE__, __LINE__, locale);
1328 }
1329 }
1330
1331 // Reset the iterator, verify that we get the same count.
1332 localesEnum->reset(status);
1333 int32_t localeCount2 = 0;
1334 while (localesEnum->next(NULL, status) != NULL) {
1335 if (U_FAILURE(status)) {
1336 errln("file %s, line %d: Error status = %s", __FILE__, __LINE__, u_errorName(status));
1337 break;
1338 }
1339 localeCount2++;
1340 }
1341 if (localeCount != localeCount2) {
1342 errln("file %s, line %d: locale counts differ. They are (%d, %d)",
1343 __FILE__, __LINE__, localeCount, localeCount2);
1344 }
1345
1346 // Instantiate plural rules for each available locale.
1347 localesEnum->reset(status);
1348 for (;;) {
1349 status = U_ZERO_ERROR;
1350 const char *localeName = localesEnum->next(NULL, status);
1351 if (U_FAILURE(status)) {
1352 errln("file %s, line %d: Error status = %s, locale = %s",
1353 __FILE__, __LINE__, u_errorName(status), localeName);
1354 return;
1355 }
1356 if (localeName == NULL) {
1357 break;
1358 }
1359 Locale locale = Locale::createFromName(localeName);
1360 PluralRules *pr = PluralRules::forLocale(locale, status);
1361 if (U_FAILURE(status)) {
1362 errln("file %s, line %d: Error %s creating plural rules for locale %s",
1363 __FILE__, __LINE__, u_errorName(status), localeName);
1364 continue;
1365 }
1366 if (pr == NULL) {
1367 errln("file %s, line %d: Null plural rules for locale %s", __FILE__, __LINE__, localeName);
1368 continue;
1369 }
1370
1371 // Pump some numbers through the plural rules. Can't check for correct results,
1372 // mostly this to tickle any asserts or crashes that may be lurking.
1373 for (double n=0; n<120.0; n+=0.5) {
1374 UnicodeString keyword = pr->select(n);
1375 if (keyword.length() == 0) {
1376 errln("file %s, line %d, empty keyword for n = %g, locale %s",
1377 __FILE__, __LINE__, n, localeName);
1378 }
1379 }
1380 delete pr;
1381 }
1382
1383 uhash_close(localeSet);
1384 delete localesEnum;
1385
1386 }
1387
1388
testParseErrors()1389 void PluralRulesTest::testParseErrors() {
1390 // Test rules with syntax errors.
1391 // Creation of PluralRules from them should fail.
1392
1393 static const char *testCases[] = {
1394 "a: n mod 10, is 1",
1395 "a: q is 13",
1396 "a n is 13",
1397 "a: n is 13,",
1398 "a: n is 13, 15, b: n is 4",
1399 "a: n is 1, 3, 4.. ",
1400 "a: n within 5..4",
1401 "A: n is 13", // Uppercase keywords not allowed.
1402 "a: n ! = 3", // spaces in != operator
1403 "a: n = not 3", // '=' not exact equivalent of 'is'
1404 "a: n ! in 3..4", // '!' not exact equivalent of 'not'
1405 "a: n % 37 ! in 3..4"
1406
1407 };
1408 for (int i=0; i<UPRV_LENGTHOF(testCases); i++) {
1409 const char *rules = testCases[i];
1410 UErrorCode status = U_ZERO_ERROR;
1411 PluralRules *pr = PluralRules::createRules(UnicodeString(rules), status);
1412 if (U_SUCCESS(status)) {
1413 errln("file %s, line %d, expected failure with \"%s\".", __FILE__, __LINE__, rules);
1414 }
1415 if (pr != NULL) {
1416 errln("file %s, line %d, expected NULL. Rules: \"%s\"", __FILE__, __LINE__, rules);
1417 }
1418 }
1419 return;
1420 }
1421
1422
testFixedDecimal()1423 void PluralRulesTest::testFixedDecimal() {
1424 struct DoubleTestCase {
1425 double n;
1426 int32_t fractionDigitCount;
1427 int64_t fractionDigits;
1428 };
1429
1430 // Check that the internal functions for extracting the decimal fraction digits from
1431 // a double value are working.
1432 static DoubleTestCase testCases[] = {
1433 {1.0, 0, 0},
1434 {123456.0, 0, 0},
1435 {1.1, 1, 1},
1436 {1.23, 2, 23},
1437 {1.234, 3, 234},
1438 {1.2345, 4, 2345},
1439 {1.23456, 5, 23456},
1440 {.1234, 4, 1234},
1441 {.01234, 5, 1234},
1442 {.001234, 6, 1234},
1443 {.0001234, 7, 1234},
1444 {100.1234, 4, 1234},
1445 {100.01234, 5, 1234},
1446 {100.001234, 6, 1234},
1447 {100.0001234, 7, 1234}
1448 };
1449
1450 for (int i=0; i<UPRV_LENGTHOF(testCases); ++i) {
1451 DoubleTestCase &tc = testCases[i];
1452 int32_t numFractionDigits = FixedDecimal::decimals(tc.n);
1453 if (numFractionDigits != tc.fractionDigitCount) {
1454 errln("file %s, line %d: decimals(%g) expected %d, actual %d",
1455 __FILE__, __LINE__, tc.n, tc.fractionDigitCount, numFractionDigits);
1456 continue;
1457 }
1458 int64_t actualFractionDigits = FixedDecimal::getFractionalDigits(tc.n, numFractionDigits);
1459 if (actualFractionDigits != tc.fractionDigits) {
1460 errln("file %s, line %d: getFractionDigits(%g, %d): expected %ld, got %ld",
1461 __FILE__, __LINE__, tc.n, numFractionDigits, tc.fractionDigits, actualFractionDigits);
1462 }
1463 }
1464 }
1465
1466
testSelectTrailingZeros()1467 void PluralRulesTest::testSelectTrailingZeros() {
1468 IcuTestErrorCode status(*this, "testSelectTrailingZeros");
1469 number::UnlocalizedNumberFormatter unf = number::NumberFormatter::with()
1470 .precision(number::Precision::fixedFraction(2));
1471 struct TestCase {
1472 const char* localeName;
1473 const char16_t* expectedDoubleKeyword;
1474 const char16_t* expectedFormattedKeyword;
1475 double number;
1476 } cases[] = {
1477 {"bs", u"few", u"other", 5.2}, // 5.2 => two, but 5.20 => other
1478 {"si", u"one", u"one", 0.0},
1479 {"si", u"one", u"one", 1.0},
1480 {"si", u"one", u"other", 0.1}, // 0.1 => one, but 0.10 => other
1481 {"si", u"one", u"one", 0.01}, // 0.01 => one
1482 {"hsb", u"few", u"few", 1.03}, // (f % 100 == 3) => few
1483 {"hsb", u"few", u"other", 1.3}, // 1.3 => few, but 1.30 => other
1484 };
1485 for (const auto& cas : cases) {
1486 UnicodeString message(UnicodeString(cas.localeName) + u" " + DoubleToUnicodeString(cas.number));
1487 status.setScope(message);
1488 Locale locale(cas.localeName);
1489 LocalPointer<PluralRules> rules(PluralRules::forLocale(locale, status));
1490 if (U_FAILURE(status)) {
1491 dataerrln("Failed to create PluralRules by PluralRules::forLocale(%s): %s\n",
1492 cas.localeName, u_errorName(status));
1493 return;
1494 }
1495 assertEquals(message, cas.expectedDoubleKeyword, rules->select(cas.number));
1496 number::FormattedNumber fn = unf.locale(locale).formatDouble(cas.number, status);
1497 assertEquals(message, cas.expectedFormattedKeyword, rules->select(fn, status));
1498 status.errIfFailureAndReset();
1499 }
1500 }
1501
compareLocaleResults(const char * loc1,const char * loc2,const char * loc3)1502 void PluralRulesTest::compareLocaleResults(const char* loc1, const char* loc2, const char* loc3) {
1503 UErrorCode status = U_ZERO_ERROR;
1504 LocalPointer<PluralRules> rules1(PluralRules::forLocale(loc1, status));
1505 LocalPointer<PluralRules> rules2(PluralRules::forLocale(loc2, status));
1506 LocalPointer<PluralRules> rules3(PluralRules::forLocale(loc3, status));
1507 if (U_FAILURE(status)) {
1508 dataerrln("Failed to create PluralRules for one of %s, %s, %s: %s\n", loc1, loc2, loc3, u_errorName(status));
1509 return;
1510 }
1511 for (int32_t value = 0; value <= 12; value++) {
1512 UnicodeString result1 = rules1->select(value);
1513 UnicodeString result2 = rules2->select(value);
1514 UnicodeString result3 = rules3->select(value);
1515 if (result1 != result2 || result1 != result3) {
1516 errln("PluralRules.select(%d) does not return the same values for %s, %s, %s\n", value, loc1, loc2, loc3);
1517 }
1518 }
1519 }
1520
testLocaleExtension()1521 void PluralRulesTest::testLocaleExtension() {
1522 IcuTestErrorCode errorCode(*this, "testLocaleExtension");
1523 LocalPointer<PluralRules> rules(PluralRules::forLocale("pt@calendar=gregorian", errorCode));
1524 if (errorCode.errIfFailureAndReset("PluralRules::forLocale()")) { return; }
1525 UnicodeString key = rules->select(1);
1526 assertEquals("pt@calendar=gregorian select(1)", u"one", key);
1527 compareLocaleResults("ar", "ar_SA", "ar_SA@calendar=gregorian");
1528 compareLocaleResults("ru", "ru_UA", "ru-u-cu-RUB");
1529 compareLocaleResults("fr", "fr_CH", "fr@ms=uksystem");
1530 }
1531
1532 #endif /* #if !UCONFIG_NO_FORMATTING */
1533