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