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