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