• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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