1 /* 2 ******************************************************************************* 3 * Copyright (C) 2007-2014, International Business Machines Corporation and 4 * others. All Rights Reserved. 5 ******************************************************************************** 6 7 * File PLURRULTS.cpp 8 * 9 ******************************************************************************** 10 */ 11 12 #include "unicode/utypes.h" 13 14 #if !UCONFIG_NO_FORMATTING 15 16 #include <stdlib.h> 17 #include <stdarg.h> 18 #include <string.h> 19 20 #include "unicode/localpointer.h" 21 #include "unicode/plurrule.h" 22 #include "unicode/stringpiece.h" 23 24 #include "cmemory.h" 25 #include "digitlst.h" 26 #include "plurrule_impl.h" 27 #include "plurults.h" 28 #include "uhash.h" 29 30 void setupResult(const int32_t testSource[], char result[], int32_t* max); 31 UBool checkEqual(const PluralRules &test, char *result, int32_t max); 32 UBool testEquality(const PluralRules &test); 33 34 // This is an API test, not a unit test. It doesn't test very many cases, and doesn't 35 // try to test the full functionality. It just calls each function in the class and 36 // verifies that it works on a basic level. 37 38 void PluralRulesTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) 39 { 40 if (exec) logln("TestSuite PluralRulesAPI"); 41 TESTCASE_AUTO_BEGIN; 42 TESTCASE_AUTO(testAPI); 43 // TESTCASE_AUTO(testGetUniqueKeywordValue); 44 TESTCASE_AUTO(testGetSamples); 45 TESTCASE_AUTO(testWithin); 46 TESTCASE_AUTO(testGetAllKeywordValues); 47 TESTCASE_AUTO(testOrdinal); 48 TESTCASE_AUTO(testSelect); 49 TESTCASE_AUTO(testAvailbleLocales); 50 TESTCASE_AUTO(testParseErrors); 51 TESTCASE_AUTO(testFixedDecimal); 52 TESTCASE_AUTO_END; 53 } 54 55 56 // Quick and dirty class for putting UnicodeStrings in char * messages. 57 // TODO: something like this should be generally available. 58 class US { 59 private: 60 char *buf; 61 public: 62 US(const UnicodeString &us) { 63 int32_t bufLen = us.extract((int32_t)0, us.length(), (char *)NULL, (uint32_t)0) + 1; 64 buf = (char *)uprv_malloc(bufLen); 65 us.extract(0, us.length(), buf, bufLen); }; 66 const char *cstr() {return buf;}; 67 ~US() { uprv_free(buf);}; 68 }; 69 70 71 72 73 74 #define PLURAL_TEST_NUM 18 75 /** 76 * Test various generic API methods of PluralRules for API coverage. 77 */ 78 void PluralRulesTest::testAPI(/*char *par*/) 79 { 80 UnicodeString pluralTestData[PLURAL_TEST_NUM] = { 81 UNICODE_STRING_SIMPLE("a: n is 1"), 82 UNICODE_STRING_SIMPLE("a: n mod 10 is 2"), 83 UNICODE_STRING_SIMPLE("a: n is not 1"), 84 UNICODE_STRING_SIMPLE("a: n mod 3 is not 1"), 85 UNICODE_STRING_SIMPLE("a: n in 2..5"), 86 UNICODE_STRING_SIMPLE("a: n within 2..5"), 87 UNICODE_STRING_SIMPLE("a: n not in 2..5"), 88 UNICODE_STRING_SIMPLE("a: n not within 2..5"), 89 UNICODE_STRING_SIMPLE("a: n mod 10 in 2..5"), 90 UNICODE_STRING_SIMPLE("a: n mod 10 within 2..5"), 91 UNICODE_STRING_SIMPLE("a: n mod 10 is 2 and n is not 12"), 92 UNICODE_STRING_SIMPLE("a: n mod 10 in 2..3 or n mod 10 is 5"), 93 UNICODE_STRING_SIMPLE("a: n mod 10 within 2..3 or n mod 10 is 5"), 94 UNICODE_STRING_SIMPLE("a: n is 1 or n is 4 or n is 23"), 95 UNICODE_STRING_SIMPLE("a: n mod 2 is 1 and n is not 3 and n in 1..11"), 96 UNICODE_STRING_SIMPLE("a: n mod 2 is 1 and n is not 3 and n within 1..11"), 97 UNICODE_STRING_SIMPLE("a: n mod 2 is 1 or n mod 5 is 1 and n is not 6"), 98 "", 99 }; 100 static const int32_t pluralTestResult[PLURAL_TEST_NUM][30] = { 101 {1, 0}, 102 {2,12,22, 0}, 103 {0,2,3,4,5,0}, 104 {0,2,3,5,6,8,9,0}, 105 {2,3,4,5,0}, 106 {2,3,4,5,0}, 107 {0,1,6,7,8, 0}, 108 {0,1,6,7,8, 0}, 109 {2,3,4,5,12,13,14,15,22,23,24,25,0}, 110 {2,3,4,5,12,13,14,15,22,23,24,25,0}, 111 {2,22,32,42,0}, 112 {2,3,5,12,13,15,22,23,25,0}, 113 {2,3,5,12,13,15,22,23,25,0}, 114 {1,4,23,0}, 115 {1,5,7,9,11,0}, 116 {1,5,7,9,11,0}, 117 {1,3,5,7,9,11,13,15,16,0}, 118 }; 119 UErrorCode status = U_ZERO_ERROR; 120 121 // ======= Test constructors 122 logln("Testing PluralRules constructors"); 123 124 125 logln("\n start default locale test case ..\n"); 126 127 PluralRules defRule(status); 128 LocalPointer<PluralRules> test(new PluralRules(status), status); 129 if(U_FAILURE(status)) { 130 dataerrln("ERROR: Could not create PluralRules (default) - exitting"); 131 return; 132 } 133 LocalPointer<PluralRules> newEnPlural(test->forLocale(Locale::getEnglish(), status), status); 134 if(U_FAILURE(status)) { 135 dataerrln("ERROR: Could not create PluralRules (English) - exitting"); 136 return; 137 } 138 139 // ======= Test clone, assignment operator && == operator. 140 LocalPointer<PluralRules> dupRule(defRule.clone()); 141 if (dupRule==NULL) { 142 errln("ERROR: clone plural rules test failed!"); 143 return; 144 } else { 145 if ( *dupRule != defRule ) { 146 errln("ERROR: clone plural rules test failed!"); 147 } 148 } 149 *dupRule = *newEnPlural; 150 if (dupRule!=NULL) { 151 if ( *dupRule != *newEnPlural ) { 152 errln("ERROR: clone plural rules test failed!"); 153 } 154 } 155 156 // ======= Test empty plural rules 157 logln("Testing Simple PluralRules"); 158 159 LocalPointer<PluralRules> empRule(test->createRules(UNICODE_STRING_SIMPLE("a:n"), status)); 160 UnicodeString key; 161 for (int32_t i=0; i<10; ++i) { 162 key = empRule->select(i); 163 if ( key.charAt(0)!= 0x61 ) { // 'a' 164 errln("ERROR: empty plural rules test failed! - exitting"); 165 } 166 } 167 168 // ======= Test simple plural rules 169 logln("Testing Simple PluralRules"); 170 171 char result[100]; 172 int32_t max; 173 174 for (int32_t i=0; i<PLURAL_TEST_NUM-1; ++i) { 175 LocalPointer<PluralRules> newRules(test->createRules(pluralTestData[i], status)); 176 setupResult(pluralTestResult[i], result, &max); 177 if ( !checkEqual(*newRules, result, max) ) { 178 errln("ERROR: simple plural rules failed! - exitting"); 179 return; 180 } 181 } 182 183 // ======= Test complex plural rules 184 logln("Testing Complex PluralRules"); 185 // TODO: the complex test data is hard coded. It's better to implement 186 // a parser to parse the test data. 187 UnicodeString complexRule = UNICODE_STRING_SIMPLE("a: n in 2..5; b: n in 5..8; c: n mod 2 is 1"); 188 UnicodeString complexRule2 = UNICODE_STRING_SIMPLE("a: n within 2..5; b: n within 5..8; c: n mod 2 is 1"); 189 char cRuleResult[] = 190 { 191 0x6F, // 'o' 192 0x63, // 'c' 193 0x61, // 'a' 194 0x61, // 'a' 195 0x61, // 'a' 196 0x61, // 'a' 197 0x62, // 'b' 198 0x62, // 'b' 199 0x62, // 'b' 200 0x63, // 'c' 201 0x6F, // 'o' 202 0x63 // 'c' 203 }; 204 LocalPointer<PluralRules> newRules(test->createRules(complexRule, status)); 205 if ( !checkEqual(*newRules, cRuleResult, 12) ) { 206 errln("ERROR: complex plural rules failed! - exitting"); 207 return; 208 } 209 newRules.adoptInstead(test->createRules(complexRule2, status)); 210 if ( !checkEqual(*newRules, cRuleResult, 12) ) { 211 errln("ERROR: complex plural rules failed! - exitting"); 212 return; 213 } 214 215 // ======= Test decimal fractions plural rules 216 UnicodeString decimalRule= UNICODE_STRING_SIMPLE("a: n not in 0..100;"); 217 UnicodeString KEYWORD_A = UNICODE_STRING_SIMPLE("a"); 218 status = U_ZERO_ERROR; 219 newRules.adoptInstead(test->createRules(decimalRule, status)); 220 if (U_FAILURE(status)) { 221 dataerrln("ERROR: Could not create PluralRules for testing fractions - exitting"); 222 return; 223 } 224 double fData[] = {-101, -100, -1, -0.0, 0, 0.1, 1, 1.999, 2.0, 100, 100.001 }; 225 UBool isKeywordA[] = {TRUE, FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, TRUE }; 226 for (int32_t i=0; i<UPRV_LENGTHOF(fData); i++) { 227 if ((newRules->select(fData[i])== KEYWORD_A) != isKeywordA[i]) { 228 errln("File %s, Line %d, ERROR: plural rules for decimal fractions test failed!\n" 229 " number = %g, expected %s", __FILE__, __LINE__, fData[i], isKeywordA[i]?"TRUE":"FALSE"); 230 } 231 } 232 233 // ======= Test Equality 234 logln("Testing Equality of PluralRules"); 235 236 if ( !testEquality(*test) ) { 237 errln("ERROR: complex plural rules failed! - exitting"); 238 return; 239 } 240 241 242 // ======= Test getStaticClassID() 243 logln("Testing getStaticClassID()"); 244 245 if(test->getDynamicClassID() != PluralRules::getStaticClassID()) { 246 errln("ERROR: getDynamicClassID() didn't return the expected value"); 247 } 248 // ====== Test fallback to parent locale 249 LocalPointer<PluralRules> en_UK(test->forLocale(Locale::getUK(), status)); 250 LocalPointer<PluralRules> en(test->forLocale(Locale::getEnglish(), status)); 251 if (en_UK.isValid() && en.isValid()) { 252 if ( *en_UK != *en ) { 253 errln("ERROR: test locale fallback failed!"); 254 } 255 } 256 257 LocalPointer<PluralRules> zh_Hant(test->forLocale(Locale::getTaiwan(), status)); 258 LocalPointer<PluralRules> zh(test->forLocale(Locale::getChinese(), status)); 259 if (zh_Hant.isValid() && zh.isValid()) { 260 if ( *zh_Hant != *zh ) { 261 errln("ERROR: test locale fallback failed!"); 262 } 263 } 264 } 265 266 void setupResult(const int32_t testSource[], char result[], int32_t* max) { 267 int32_t i=0; 268 int32_t curIndex=0; 269 270 do { 271 while (curIndex < testSource[i]) { 272 result[curIndex++]=0x6F; //'o' other 273 } 274 result[curIndex++]=0x61; // 'a' 275 276 } while(testSource[++i]>0); 277 *max=curIndex; 278 } 279 280 281 UBool checkEqual(const PluralRules &test, char *result, int32_t max) { 282 UnicodeString key; 283 UBool isEqual = TRUE; 284 for (int32_t i=0; i<max; ++i) { 285 key= test.select(i); 286 if ( key.charAt(0)!=result[i] ) { 287 isEqual = FALSE; 288 } 289 } 290 return isEqual; 291 } 292 293 294 295 static const int32_t MAX_EQ_ROW = 2; 296 static const int32_t MAX_EQ_COL = 5; 297 UBool testEquality(const PluralRules &test) { 298 UnicodeString testEquRules[MAX_EQ_ROW][MAX_EQ_COL] = { 299 { UNICODE_STRING_SIMPLE("a: n in 2..3"), 300 UNICODE_STRING_SIMPLE("a: n is 2 or n is 3"), 301 UNICODE_STRING_SIMPLE( "a:n is 3 and n in 2..5 or n is 2"), 302 "", 303 }, 304 { UNICODE_STRING_SIMPLE("a: n is 12; b:n mod 10 in 2..3"), 305 UNICODE_STRING_SIMPLE("b: n mod 10 in 2..3 and n is not 12; a: n in 12..12"), 306 UNICODE_STRING_SIMPLE("b: n is 13; a: n in 12..13; b: n mod 10 is 2 or n mod 10 is 3"), 307 "", 308 } 309 }; 310 UErrorCode status = U_ZERO_ERROR; 311 UnicodeString key[MAX_EQ_COL]; 312 UBool ret=TRUE; 313 for (int32_t i=0; i<MAX_EQ_ROW; ++i) { 314 PluralRules* rules[MAX_EQ_COL]; 315 316 for (int32_t j=0; j<MAX_EQ_COL; ++j) { 317 rules[j]=NULL; 318 } 319 int32_t totalRules=0; 320 while((totalRules<MAX_EQ_COL) && (testEquRules[i][totalRules].length()>0) ) { 321 rules[totalRules]=test.createRules(testEquRules[i][totalRules], status); 322 totalRules++; 323 } 324 for (int32_t n=0; n<300 && ret ; ++n) { 325 for(int32_t j=0; j<totalRules;++j) { 326 key[j] = rules[j]->select(n); 327 } 328 for(int32_t j=0; j<totalRules-1;++j) { 329 if (key[j]!=key[j+1]) { 330 ret= FALSE; 331 break; 332 } 333 } 334 335 } 336 for (int32_t j=0; j<MAX_EQ_COL; ++j) { 337 if (rules[j]!=NULL) { 338 delete rules[j]; 339 } 340 } 341 } 342 343 return ret; 344 } 345 346 void 347 PluralRulesTest::assertRuleValue(const UnicodeString& rule, double expected) { 348 assertRuleKeyValue("a:" + rule, "a", expected); 349 } 350 351 void 352 PluralRulesTest::assertRuleKeyValue(const UnicodeString& rule, 353 const UnicodeString& key, double expected) { 354 UErrorCode status = U_ZERO_ERROR; 355 PluralRules *pr = PluralRules::createRules(rule, status); 356 double result = pr->getUniqueKeywordValue(key); 357 delete pr; 358 if (expected != result) { 359 errln("expected %g but got %g", expected, result); 360 } 361 } 362 363 // TODO: UniqueKeywordValue() is not currently supported. 364 // If it never will be, this test code should be removed. 365 void PluralRulesTest::testGetUniqueKeywordValue() { 366 assertRuleValue("n is 1", 1); 367 assertRuleValue("n in 2..2", 2); 368 assertRuleValue("n within 2..2", 2); 369 assertRuleValue("n in 3..4", UPLRULES_NO_UNIQUE_VALUE); 370 assertRuleValue("n within 3..4", UPLRULES_NO_UNIQUE_VALUE); 371 assertRuleValue("n is 2 or n is 2", 2); 372 assertRuleValue("n is 2 and n is 2", 2); 373 assertRuleValue("n is 2 or n is 3", UPLRULES_NO_UNIQUE_VALUE); 374 assertRuleValue("n is 2 and n is 3", UPLRULES_NO_UNIQUE_VALUE); 375 assertRuleValue("n is 2 or n in 2..3", UPLRULES_NO_UNIQUE_VALUE); 376 assertRuleValue("n is 2 and n in 2..3", 2); 377 assertRuleKeyValue("a: n is 1", "not_defined", UPLRULES_NO_UNIQUE_VALUE); // key not defined 378 assertRuleKeyValue("a: n is 1", "other", UPLRULES_NO_UNIQUE_VALUE); // key matches default rule 379 } 380 381 void PluralRulesTest::testGetSamples() { 382 // TODO: fix samples, re-enable this test. 383 384 // no get functional equivalent API in ICU4C, so just 385 // test every locale... 386 UErrorCode status = U_ZERO_ERROR; 387 int32_t numLocales; 388 const Locale* locales = Locale::getAvailableLocales(numLocales); 389 390 double values[1000]; 391 for (int32_t i = 0; U_SUCCESS(status) && i < numLocales; ++i) { 392 PluralRules *rules = PluralRules::forLocale(locales[i], status); 393 if (U_FAILURE(status)) { 394 break; 395 } 396 StringEnumeration *keywords = rules->getKeywords(status); 397 if (U_FAILURE(status)) { 398 delete rules; 399 break; 400 } 401 const UnicodeString* keyword; 402 while (NULL != (keyword = keywords->snext(status))) { 403 int32_t count = rules->getSamples(*keyword, values, UPRV_LENGTHOF(values), status); 404 if (U_FAILURE(status)) { 405 errln(UNICODE_STRING_SIMPLE("getSamples() failed for locale ") + 406 locales[i].getName() + 407 UNICODE_STRING_SIMPLE(", keyword ") + *keyword); 408 continue; 409 } 410 if (count == 0) { 411 // TODO: Lots of these. 412 // errln(UNICODE_STRING_SIMPLE("no samples for keyword ") + *keyword + UNICODE_STRING_SIMPLE(" in locale ") + locales[i].getName() ); 413 } 414 if (count > UPRV_LENGTHOF(values)) { 415 errln(UNICODE_STRING_SIMPLE("getSamples()=") + count + 416 UNICODE_STRING_SIMPLE(", too many values, for locale ") + 417 locales[i].getName() + 418 UNICODE_STRING_SIMPLE(", keyword ") + *keyword); 419 count = UPRV_LENGTHOF(values); 420 } 421 for (int32_t j = 0; j < count; ++j) { 422 if (values[j] == UPLRULES_NO_UNIQUE_VALUE) { 423 errln("got 'no unique value' among values"); 424 } else { 425 UnicodeString resultKeyword = rules->select(values[j]); 426 // if (strcmp(locales[i].getName(), "uk") == 0) { // Debug only. 427 // std::cout << " uk " << US(resultKeyword).cstr() << " " << values[j] << std::endl; 428 // } 429 if (*keyword != resultKeyword) { 430 errln("file %s, line %d, Locale %s, sample for keyword \"%s\": %g, select(%g) returns keyword \"%s\"", 431 __FILE__, __LINE__, locales[i].getName(), US(*keyword).cstr(), values[j], values[j], US(resultKeyword).cstr()); 432 } 433 } 434 } 435 } 436 delete keywords; 437 delete rules; 438 } 439 } 440 441 void PluralRulesTest::testWithin() { 442 // goes to show you what lack of testing will do. 443 // of course, this has been broken for two years and no one has noticed... 444 UErrorCode status = U_ZERO_ERROR; 445 PluralRules *rules = PluralRules::createRules("a: n mod 10 in 5..8", status); 446 if (!rules) { 447 errln("couldn't instantiate rules"); 448 return; 449 } 450 451 UnicodeString keyword = rules->select((int32_t)26); 452 if (keyword != "a") { 453 errln("expected 'a' for 26 but didn't get it."); 454 } 455 456 keyword = rules->select(26.5); 457 if (keyword != "other") { 458 errln("expected 'other' for 26.5 but didn't get it."); 459 } 460 461 delete rules; 462 } 463 464 void 465 PluralRulesTest::testGetAllKeywordValues() { 466 const char* data[] = { 467 "a: n in 2..5", "a: 2,3,4,5; other: null; b:", 468 "a: n not in 2..5", "a: null; other: null", 469 "a: n within 2..5", "a: null; other: null", 470 "a: n not within 2..5", "a: null; other: null", 471 "a: n in 2..5 or n within 6..8", "a: null", // ignore 'other' here on out, always null 472 "a: n in 2..5 and n within 6..8", "a:", 473 "a: n in 2..5 and n within 5..8", "a: 5", 474 "a: n within 2..5 and n within 6..8", "a:", // our sampling catches these 475 "a: n within 2..5 and n within 5..8", "a: 5", // '' 476 "a: n within 1..2 and n within 2..3 or n within 3..4 and n within 4..5", "a: 2,4", 477 "a: n within 1..2 and n within 2..3 or n within 3..4 and n within 4..5 " 478 "or n within 5..6 and n within 6..7", "a: null", // but not this... 479 "a: n mod 3 is 0", "a: null", 480 "a: n mod 3 is 0 and n within 1..2", "a:", 481 "a: n mod 3 is 0 and n within 0..5", "a: 0,3", 482 "a: n mod 3 is 0 and n within 0..6", "a: null", // similarly with mod, we don't catch... 483 "a: n mod 3 is 0 and n in 3..12", "a: 3,6,9,12", 484 NULL 485 }; 486 487 for (int i = 0; data[i] != NULL; i += 2) { 488 UErrorCode status = U_ZERO_ERROR; 489 UnicodeString ruleDescription(data[i], -1, US_INV); 490 const char* result = data[i+1]; 491 492 logln("[%d] %s", i >> 1, data[i]); 493 494 PluralRules *p = PluralRules::createRules(ruleDescription, status); 495 if (p == NULL || U_FAILURE(status)) { 496 errln("file %s, line %d: could not create rules from '%s'\n" 497 " ErrorCode: %s\n", 498 __FILE__, __LINE__, data[i], u_errorName(status)); 499 continue; 500 } 501 502 // TODO: fix samples implementation, re-enable test. 503 (void)result; 504 #if 0 505 506 const char* rp = result; 507 while (*rp) { 508 while (*rp == ' ') ++rp; 509 if (!rp) { 510 break; 511 } 512 513 const char* ep = rp; 514 while (*ep && *ep != ':') ++ep; 515 516 status = U_ZERO_ERROR; 517 UnicodeString keyword(rp, ep - rp, US_INV); 518 double samples[4]; // no test above should have more samples than 4 519 int32_t count = p->getAllKeywordValues(keyword, &samples[0], 4, status); 520 if (U_FAILURE(status)) { 521 errln("error getting samples for %s", rp); 522 break; 523 } 524 525 if (count > 4) { 526 errln("count > 4 for keyword %s", rp); 527 count = 4; 528 } 529 530 if (*ep) { 531 ++ep; // skip colon 532 while (*ep && *ep == ' ') ++ep; // and spaces 533 } 534 535 UBool ok = TRUE; 536 if (count == -1) { 537 if (*ep != 'n') { 538 errln("expected values for keyword %s but got -1 (%s)", rp, ep); 539 ok = FALSE; 540 } 541 } else if (*ep == 'n') { 542 errln("expected count of -1, got %d, for keyword %s (%s)", count, rp, ep); 543 ok = FALSE; 544 } 545 546 // We'll cheat a bit here. The samples happend to be in order and so are our 547 // expected values, so we'll just test in order until a failure. If the 548 // implementation changes to return samples in an arbitrary order, this test 549 // must change. There's no actual restriction on the order of the samples. 550 551 for (int j = 0; ok && j < count; ++j ) { // we've verified count < 4 552 double val = samples[j]; 553 if (*ep == 0 || *ep == ';') { 554 errln("got unexpected value[%d]: %g", j, val); 555 ok = FALSE; 556 break; 557 } 558 char* xp; 559 double expectedVal = strtod(ep, &xp); 560 if (xp == ep) { 561 // internal error 562 errln("yikes!"); 563 ok = FALSE; 564 break; 565 } 566 ep = xp; 567 if (expectedVal != val) { 568 errln("expected %g but got %g", expectedVal, val); 569 ok = FALSE; 570 break; 571 } 572 if (*ep == ',') ++ep; 573 } 574 575 if (ok && count != -1) { 576 if (!(*ep == 0 || *ep == ';')) { 577 errln("file: %s, line %d, didn't get expected value: %s", __FILE__, __LINE__, ep); 578 ok = FALSE; 579 } 580 } 581 582 while (*ep && *ep != ';') ++ep; 583 if (*ep == ';') ++ep; 584 rp = ep; 585 } 586 #endif 587 delete p; 588 } 589 } 590 591 void PluralRulesTest::testOrdinal() { 592 IcuTestErrorCode errorCode(*this, "testOrdinal"); 593 LocalPointer<PluralRules> pr(PluralRules::forLocale("en", UPLURAL_TYPE_ORDINAL, errorCode)); 594 if (errorCode.logIfFailureAndReset("PluralRules::forLocale(en, UPLURAL_TYPE_ORDINAL) failed")) { 595 return; 596 } 597 UnicodeString keyword = pr->select(2.); 598 if (keyword != UNICODE_STRING("two", 3)) { 599 dataerrln("PluralRules(en-ordinal).select(2) failed"); 600 } 601 } 602 603 604 static const char * END_MARK = "999.999"; // Mark end of varargs data. 605 606 void PluralRulesTest::checkSelect(const LocalPointer<PluralRules> &rules, UErrorCode &status, 607 int32_t line, const char *keyword, ...) { 608 // The varargs parameters are a const char* strings, each being a decimal number. 609 // The formatting of the numbers as strings is significant, e.g. 610 // the difference between "2" and "2.0" can affect which rule matches (which keyword is selected). 611 // Note: rules parameter is a LocalPointer reference rather than a PluralRules * to avoid having 612 // to write getAlias() at every (numerous) call site. 613 614 if (U_FAILURE(status)) { 615 errln("file %s, line %d, ICU error status: %s.", __FILE__, line, u_errorName(status)); 616 status = U_ZERO_ERROR; 617 return; 618 } 619 620 if (rules == NULL) { 621 errln("file %s, line %d: rules pointer is NULL", __FILE__, line); 622 return; 623 } 624 625 va_list ap; 626 va_start(ap, keyword); 627 for (;;) { 628 const char *num = va_arg(ap, const char *); 629 if (strcmp(num, END_MARK) == 0) { 630 break; 631 } 632 633 // DigitList is a convenient way to parse the decimal number string and get a double. 634 DigitList dl; 635 dl.set(StringPiece(num), status); 636 if (U_FAILURE(status)) { 637 errln("file %s, line %d, ICU error status: %s.", __FILE__, line, u_errorName(status)); 638 status = U_ZERO_ERROR; 639 continue; 640 } 641 double numDbl = dl.getDouble(); 642 const char *decimalPoint = strchr(num, '.'); 643 int fractionDigitCount = decimalPoint == NULL ? 0 : (num + strlen(num) - 1) - decimalPoint; 644 int fractionDigits = fractionDigitCount == 0 ? 0 : atoi(decimalPoint + 1); 645 FixedDecimal ni(numDbl, fractionDigitCount, fractionDigits); 646 647 UnicodeString actualKeyword = rules->select(ni); 648 if (actualKeyword != UnicodeString(keyword)) { 649 errln("file %s, line %d, select(%s) returned incorrect keyword. Expected %s, got %s", 650 __FILE__, line, num, keyword, US(actualKeyword).cstr()); 651 } 652 } 653 va_end(ap); 654 } 655 656 void PluralRulesTest::testSelect() { 657 UErrorCode status = U_ZERO_ERROR; 658 LocalPointer<PluralRules> pr(PluralRules::createRules("s: n in 1,3,4,6", status)); 659 checkSelect(pr, status, __LINE__, "s", "1.0", "3.0", "4.0", "6.0", END_MARK); 660 checkSelect(pr, status, __LINE__, "other", "0.0", "2.0", "3.1", "7.0", END_MARK); 661 662 pr.adoptInstead(PluralRules::createRules("s: n not in 1,3,4,6", status)); 663 checkSelect(pr, status, __LINE__, "other", "1.0", "3.0", "4.0", "6.0", END_MARK); 664 checkSelect(pr, status, __LINE__, "s", "0.0", "2.0", "3.1", "7.0", END_MARK); 665 666 pr.adoptInstead(PluralRules::createRules("r: n in 1..4, 7..10, 14 .. 17;" 667 "s: n is 29;", status)); 668 checkSelect(pr, status, __LINE__, "r", "1.0", "3.0", "7.0", "8.0", "10.0", "14.0", "17.0", END_MARK); 669 checkSelect(pr, status, __LINE__, "s", "29.0", END_MARK); 670 checkSelect(pr, status, __LINE__, "other", "28.0", "29.1", END_MARK); 671 672 pr.adoptInstead(PluralRules::createRules("a: n mod 10 is 1; b: n mod 100 is 0 ", status)); 673 checkSelect(pr, status, __LINE__, "a", "1", "11", "41", "101", "301.00", END_MARK); 674 checkSelect(pr, status, __LINE__, "b", "0", "100", "200.0", "300.", "1000", "1100", "110000", END_MARK); 675 checkSelect(pr, status, __LINE__, "other", "0.01", "1.01", "0.99", "2", "3", "99", "102", END_MARK); 676 677 // Rules that end with or without a ';' and with or without trailing spaces. 678 // (There was a rule parser bug here with these.) 679 pr.adoptInstead(PluralRules::createRules("a: n is 1", status)); 680 checkSelect(pr, status, __LINE__, "a", "1", END_MARK); 681 checkSelect(pr, status, __LINE__, "other", "2", END_MARK); 682 683 pr.adoptInstead(PluralRules::createRules("a: n is 1 ", status)); 684 checkSelect(pr, status, __LINE__, "a", "1", END_MARK); 685 checkSelect(pr, status, __LINE__, "other", "2", END_MARK); 686 687 pr.adoptInstead(PluralRules::createRules("a: n is 1;", status)); 688 checkSelect(pr, status, __LINE__, "a", "1", END_MARK); 689 checkSelect(pr, status, __LINE__, "other", "2", END_MARK); 690 691 pr.adoptInstead(PluralRules::createRules("a: n is 1 ; ", status)); 692 checkSelect(pr, status, __LINE__, "a", "1", END_MARK); 693 checkSelect(pr, status, __LINE__, "other", "2", END_MARK); 694 695 // First match when rules for different keywords are not disjoint. 696 // Also try spacing variations around ':' and '..' 697 pr.adoptInstead(PluralRules::createRules("c: n in 5..15; b : n in 1..10 ;a:n in 10 .. 20", status)); 698 checkSelect(pr, status, __LINE__, "a", "20", END_MARK); 699 checkSelect(pr, status, __LINE__, "b", "1", END_MARK); 700 checkSelect(pr, status, __LINE__, "c", "10", END_MARK); 701 checkSelect(pr, status, __LINE__, "other", "0", "21", "10.1", END_MARK); 702 703 // in vs within 704 pr.adoptInstead(PluralRules::createRules("a: n in 2..10; b: n within 8..15", status)); 705 checkSelect(pr, status, __LINE__, "a", "2", "8", "10", END_MARK); 706 checkSelect(pr, status, __LINE__, "b", "8.01", "9.5", "11", "14.99", "15", END_MARK); 707 checkSelect(pr, status, __LINE__, "other", "1", "7.7", "15.01", "16", END_MARK); 708 709 // OR and AND chains. 710 pr.adoptInstead(PluralRules::createRules("a: n in 2..10 and n in 4..12 and n not in 5..7", status)); 711 checkSelect(pr, status, __LINE__, "a", "4", "8", "9", "10", END_MARK); 712 checkSelect(pr, status, __LINE__, "other", "2", "3", "5", "7", "11", END_MARK); 713 pr.adoptInstead(PluralRules::createRules("a: n is 2 or n is 5 or n in 7..11 and n in 11..13", status)); 714 checkSelect(pr, status, __LINE__, "a", "2", "5", "11", END_MARK); 715 checkSelect(pr, status, __LINE__, "other", "3", "4", "6", "8", "10", "12", "13", END_MARK); 716 717 // Number attributes - 718 // n: the number itself 719 // i: integer digits 720 // f: visible fraction digits 721 // t: f with trailing zeros removed. 722 // v: number of visible fraction digits 723 // j: = n if there are no visible fraction digits 724 // != anything if there are visible fraction digits 725 726 pr.adoptInstead(PluralRules::createRules("a: i is 123", status)); 727 checkSelect(pr, status, __LINE__, "a", "123", "123.0", "123.1", "0123.99", END_MARK); 728 checkSelect(pr, status, __LINE__, "other", "124", "122.0", END_MARK); 729 730 pr.adoptInstead(PluralRules::createRules("a: f is 120", status)); 731 checkSelect(pr, status, __LINE__, "a", "1.120", "0.120", "11123.120", "0123.120", END_MARK); 732 checkSelect(pr, status, __LINE__, "other", "1.121", "122.1200", "1.12", "120", END_MARK); 733 734 pr.adoptInstead(PluralRules::createRules("a: t is 12", status)); 735 checkSelect(pr, status, __LINE__, "a", "1.120", "0.12", "11123.12000", "0123.1200000", END_MARK); 736 checkSelect(pr, status, __LINE__, "other", "1.121", "122.1200001", "1.11", "12", END_MARK); 737 738 pr.adoptInstead(PluralRules::createRules("a: v is 3", status)); 739 checkSelect(pr, status, __LINE__, "a", "1.120", "0.000", "11123.100", "0123.124", ".666", END_MARK); 740 checkSelect(pr, status, __LINE__, "other", "1.1212", "122.12", "1.1", "122", "0.0000", END_MARK); 741 742 pr.adoptInstead(PluralRules::createRules("a: v is 0 and i is 123", status)); 743 checkSelect(pr, status, __LINE__, "a", "123", "123.", END_MARK); 744 checkSelect(pr, status, __LINE__, "other", "123.0", "123.1", "123.123", "0.123", END_MARK); 745 746 // The reserved words from the rule syntax will also function as keywords. 747 pr.adoptInstead(PluralRules::createRules("a: n is 21; n: n is 22; i: n is 23; f: n is 24;" 748 "t: n is 25; v: n is 26; w: n is 27; j: n is 28" 749 , status)); 750 checkSelect(pr, status, __LINE__, "other", "20", "29", END_MARK); 751 checkSelect(pr, status, __LINE__, "a", "21", END_MARK); 752 checkSelect(pr, status, __LINE__, "n", "22", END_MARK); 753 checkSelect(pr, status, __LINE__, "i", "23", END_MARK); 754 checkSelect(pr, status, __LINE__, "f", "24", END_MARK); 755 checkSelect(pr, status, __LINE__, "t", "25", END_MARK); 756 checkSelect(pr, status, __LINE__, "v", "26", END_MARK); 757 checkSelect(pr, status, __LINE__, "w", "27", END_MARK); 758 checkSelect(pr, status, __LINE__, "j", "28", END_MARK); 759 760 761 pr.adoptInstead(PluralRules::createRules("not: n=31; and: n=32; or: n=33; mod: n=34;" 762 "in: n=35; within: n=36;is:n=37" 763 , status)); 764 checkSelect(pr, status, __LINE__, "other", "30", "39", END_MARK); 765 checkSelect(pr, status, __LINE__, "not", "31", END_MARK); 766 checkSelect(pr, status, __LINE__, "and", "32", END_MARK); 767 checkSelect(pr, status, __LINE__, "or", "33", END_MARK); 768 checkSelect(pr, status, __LINE__, "mod", "34", END_MARK); 769 checkSelect(pr, status, __LINE__, "in", "35", END_MARK); 770 checkSelect(pr, status, __LINE__, "within", "36", END_MARK); 771 checkSelect(pr, status, __LINE__, "is", "37", END_MARK); 772 773 // Test cases from ICU4J PluralRulesTest.parseTestData 774 775 pr.adoptInstead(PluralRules::createRules("a: n is 1", status)); 776 checkSelect(pr, status, __LINE__, "a", "1", END_MARK); 777 pr.adoptInstead(PluralRules::createRules("a: n mod 10 is 2", status)); 778 checkSelect(pr, status, __LINE__, "a", "2", "12", "22", END_MARK); 779 pr.adoptInstead(PluralRules::createRules("a: n is not 1", status)); 780 checkSelect(pr, status, __LINE__, "a", "0", "2", "3", "4", "5", END_MARK); 781 pr.adoptInstead(PluralRules::createRules("a: n mod 3 is not 1", status)); 782 checkSelect(pr, status, __LINE__, "a", "0", "2", "3", "5", "6", "8", "9", END_MARK); 783 pr.adoptInstead(PluralRules::createRules("a: n in 2..5", status)); 784 checkSelect(pr, status, __LINE__, "a", "2", "3", "4", "5", END_MARK); 785 pr.adoptInstead(PluralRules::createRules("a: n within 2..5", status)); 786 checkSelect(pr, status, __LINE__, "a", "2", "3", "4", "5", END_MARK); 787 pr.adoptInstead(PluralRules::createRules("a: n not in 2..5", status)); 788 checkSelect(pr, status, __LINE__, "a", "0", "1", "6", "7", "8", END_MARK); 789 pr.adoptInstead(PluralRules::createRules("a: n not within 2..5", status)); 790 checkSelect(pr, status, __LINE__, "a", "0", "1", "6", "7", "8", END_MARK); 791 pr.adoptInstead(PluralRules::createRules("a: n mod 10 in 2..5", status)); 792 checkSelect(pr, status, __LINE__, "a", "2", "3", "4", "5", "12", "13", "14", "15", "22", "23", "24", "25", END_MARK); 793 pr.adoptInstead(PluralRules::createRules("a: n mod 10 within 2..5", status)); 794 checkSelect(pr, status, __LINE__, "a", "2", "3", "4", "5", "12", "13", "14", "15", "22", "23", "24", "25", END_MARK); 795 pr.adoptInstead(PluralRules::createRules("a: n mod 10 is 2 and n is not 12", status)); 796 checkSelect(pr, status, __LINE__, "a", "2", "22", "32", "42", END_MARK); 797 pr.adoptInstead(PluralRules::createRules("a: n mod 10 in 2..3 or n mod 10 is 5", status)); 798 checkSelect(pr, status, __LINE__, "a", "2", "3", "5", "12", "13", "15", "22", "23", "25", END_MARK); 799 pr.adoptInstead(PluralRules::createRules("a: n mod 10 within 2..3 or n mod 10 is 5", status)); 800 checkSelect(pr, status, __LINE__, "a", "2", "3", "5", "12", "13", "15", "22", "23", "25", END_MARK); 801 pr.adoptInstead(PluralRules::createRules("a: n is 1 or n is 4 or n is 23", status)); 802 checkSelect(pr, status, __LINE__, "a", "1", "4", "23", END_MARK); 803 pr.adoptInstead(PluralRules::createRules("a: n mod 2 is 1 and n is not 3 and n in 1..11", status)); 804 checkSelect(pr, status, __LINE__, "a", "1", "5", "7", "9", "11", END_MARK); 805 pr.adoptInstead(PluralRules::createRules("a: n mod 2 is 1 and n is not 3 and n within 1..11", status)); 806 checkSelect(pr, status, __LINE__, "a", "1", "5", "7", "9", "11", END_MARK); 807 pr.adoptInstead(PluralRules::createRules("a: n mod 2 is 1 or n mod 5 is 1 and n is not 6", status)); 808 checkSelect(pr, status, __LINE__, "a", "1", "3", "5", "7", "9", "11", "13", "15", "16", END_MARK); 809 pr.adoptInstead(PluralRules::createRules("a: n in 2..5; b: n in 5..8; c: n mod 2 is 1", status)); 810 checkSelect(pr, status, __LINE__, "a", "2", "3", "4", "5", END_MARK); 811 checkSelect(pr, status, __LINE__, "b", "6", "7", "8", END_MARK); 812 checkSelect(pr, status, __LINE__, "c", "1", "9", "11", END_MARK); 813 pr.adoptInstead(PluralRules::createRules("a: n within 2..5; b: n within 5..8; c: n mod 2 is 1", status)); 814 checkSelect(pr, status, __LINE__, "a", "2", "3", "4", "5", END_MARK); 815 checkSelect(pr, status, __LINE__, "b", "6", "7", "8", END_MARK); 816 checkSelect(pr, status, __LINE__, "c", "1", "9", "11", END_MARK); 817 pr.adoptInstead(PluralRules::createRules("a: n in 2, 4..6; b: n within 7..9,11..12,20", status)); 818 checkSelect(pr, status, __LINE__, "a", "2", "4", "5", "6", END_MARK); 819 checkSelect(pr, status, __LINE__, "b", "7", "8", "9", "11", "12", "20", END_MARK); 820 pr.adoptInstead(PluralRules::createRules("a: n in 2..8, 12 and n not in 4..6", status)); 821 checkSelect(pr, status, __LINE__, "a", "2", "3", "7", "8", "12", END_MARK); 822 pr.adoptInstead(PluralRules::createRules("a: n mod 10 in 2, 3,5..7 and n is not 12", status)); 823 checkSelect(pr, status, __LINE__, "a", "2", "3", "5", "6", "7", "13", "15", "16", "17", END_MARK); 824 pr.adoptInstead(PluralRules::createRules("a: n in 2..6, 3..7", status)); 825 checkSelect(pr, status, __LINE__, "a", "2", "3", "4", "5", "6", "7", END_MARK); 826 827 // Extended Syntax, with '=', '!=' and '%' operators. 828 pr.adoptInstead(PluralRules::createRules("a: n = 1..8 and n!= 2,3,4,5", status)); 829 checkSelect(pr, status, __LINE__, "a", "1", "6", "7", "8", END_MARK); 830 checkSelect(pr, status, __LINE__, "other", "0", "2", "3", "4", "5", "9", END_MARK); 831 pr.adoptInstead(PluralRules::createRules("a:n % 10 != 1", status)); 832 checkSelect(pr, status, __LINE__, "a", "2", "6", "7", "8", END_MARK); 833 checkSelect(pr, status, __LINE__, "other", "1", "21", "211", "91", END_MARK); 834 } 835 836 837 void PluralRulesTest::testAvailbleLocales() { 838 839 // Hash set of (char *) strings. 840 UErrorCode status = U_ZERO_ERROR; 841 UHashtable *localeSet = uhash_open(uhash_hashUnicodeString, uhash_compareUnicodeString, uhash_compareLong, &status); 842 uhash_setKeyDeleter(localeSet, uprv_deleteUObject); 843 if (U_FAILURE(status)) { 844 errln("file %s, line %d: Error status = %s", __FILE__, __LINE__, u_errorName(status)); 845 return; 846 } 847 848 // Check that each locale returned by the iterator is unique. 849 StringEnumeration *localesEnum = PluralRules::getAvailableLocales(status); 850 int localeCount = 0; 851 for (;;) { 852 const char *locale = localesEnum->next(NULL, status); 853 if (U_FAILURE(status)) { 854 dataerrln("file %s, line %d: Error status = %s", __FILE__, __LINE__, u_errorName(status)); 855 return; 856 } 857 if (locale == NULL) { 858 break; 859 } 860 localeCount++; 861 int32_t oldVal = uhash_puti(localeSet, new UnicodeString(locale), 1, &status); 862 if (oldVal != 0) { 863 errln("file %s, line %d: locale %s was seen before.", __FILE__, __LINE__, locale); 864 } 865 } 866 867 // Reset the iterator, verify that we get the same count. 868 localesEnum->reset(status); 869 int32_t localeCount2 = 0; 870 while (localesEnum->next(NULL, status) != NULL) { 871 if (U_FAILURE(status)) { 872 errln("file %s, line %d: Error status = %s", __FILE__, __LINE__, u_errorName(status)); 873 break; 874 } 875 localeCount2++; 876 } 877 if (localeCount != localeCount2) { 878 errln("file %s, line %d: locale counts differ. They are (%d, %d)", 879 __FILE__, __LINE__, localeCount, localeCount2); 880 } 881 882 // Instantiate plural rules for each available locale. 883 localesEnum->reset(status); 884 for (;;) { 885 status = U_ZERO_ERROR; 886 const char *localeName = localesEnum->next(NULL, status); 887 if (U_FAILURE(status)) { 888 errln("file %s, line %d: Error status = %s, locale = %s", 889 __FILE__, __LINE__, u_errorName(status), localeName); 890 return; 891 } 892 if (localeName == NULL) { 893 break; 894 } 895 Locale locale = Locale::createFromName(localeName); 896 PluralRules *pr = PluralRules::forLocale(locale, status); 897 if (U_FAILURE(status)) { 898 errln("file %s, line %d: Error %s creating plural rules for locale %s", 899 __FILE__, __LINE__, u_errorName(status), localeName); 900 continue; 901 } 902 if (pr == NULL) { 903 errln("file %s, line %d: Null plural rules for locale %s", __FILE__, __LINE__, localeName); 904 continue; 905 } 906 907 // Pump some numbers through the plural rules. Can't check for correct results, 908 // mostly this to tickle any asserts or crashes that may be lurking. 909 for (double n=0; n<120.0; n+=0.5) { 910 UnicodeString keyword = pr->select(n); 911 if (keyword.length() == 0) { 912 errln("file %s, line %d, empty keyword for n = %g, locale %s", 913 __FILE__, __LINE__, n, localeName); 914 } 915 } 916 delete pr; 917 } 918 919 uhash_close(localeSet); 920 delete localesEnum; 921 922 } 923 924 925 void PluralRulesTest::testParseErrors() { 926 // Test rules with syntax errors. 927 // Creation of PluralRules from them should fail. 928 929 static const char *testCases[] = { 930 "a: n mod 10, is 1", 931 "a: q is 13", 932 "a n is 13", 933 "a: n is 13,", 934 "a: n is 13, 15, b: n is 4", 935 "a: n is 1, 3, 4.. ", 936 "a: n within 5..4", 937 "A: n is 13", // Uppercase keywords not allowed. 938 "a: n ! = 3", // spaces in != operator 939 "a: n = not 3", // '=' not exact equivalent of 'is' 940 "a: n ! in 3..4" // '!' not exact equivalent of 'not' 941 "a: n % 37 ! in 3..4" 942 943 }; 944 for (int i=0; i<UPRV_LENGTHOF(testCases); i++) { 945 const char *rules = testCases[i]; 946 UErrorCode status = U_ZERO_ERROR; 947 PluralRules *pr = PluralRules::createRules(UnicodeString(rules), status); 948 if (U_SUCCESS(status)) { 949 errln("file %s, line %d, expected failure with \"%s\".", __FILE__, __LINE__, rules); 950 } 951 if (pr != NULL) { 952 errln("file %s, line %d, expected NULL. Rules: \"%s\"", __FILE__, __LINE__, rules); 953 } 954 } 955 return; 956 } 957 958 959 void PluralRulesTest::testFixedDecimal() { 960 struct DoubleTestCase { 961 double n; 962 int32_t fractionDigitCount; 963 int64_t fractionDigits; 964 }; 965 966 // Check that the internal functions for extracting the decimal fraction digits from 967 // a double value are working. 968 static DoubleTestCase testCases[] = { 969 {1.0, 0, 0}, 970 {123456.0, 0, 0}, 971 {1.1, 1, 1}, 972 {1.23, 2, 23}, 973 {1.234, 3, 234}, 974 {1.2345, 4, 2345}, 975 {1.23456, 5, 23456}, 976 {.1234, 4, 1234}, 977 {.01234, 5, 1234}, 978 {.001234, 6, 1234}, 979 {.0001234, 7, 1234}, 980 {100.1234, 4, 1234}, 981 {100.01234, 5, 1234}, 982 {100.001234, 6, 1234}, 983 {100.0001234, 7, 1234} 984 }; 985 986 for (int i=0; i<UPRV_LENGTHOF(testCases); ++i) { 987 DoubleTestCase &tc = testCases[i]; 988 int32_t numFractionDigits = FixedDecimal::decimals(tc.n); 989 if (numFractionDigits != tc.fractionDigitCount) { 990 errln("file %s, line %d: decimals(%g) expected %d, actual %d", 991 __FILE__, __LINE__, tc.n, tc.fractionDigitCount, numFractionDigits); 992 continue; 993 } 994 int64_t actualFractionDigits = FixedDecimal::getFractionalDigits(tc.n, numFractionDigits); 995 if (actualFractionDigits != tc.fractionDigits) { 996 errln("file %s, line %d: getFractionDigits(%g, %d): expected %ld, got %ld", 997 __FILE__, __LINE__, tc.n, numFractionDigits, tc.fractionDigits, actualFractionDigits); 998 } 999 } 1000 } 1001 1002 1003 1004 #endif /* #if !UCONFIG_NO_FORMATTING */ 1005