1 /* 2 ******************************************************************************* 3 * Copyright (C) 2007-2012, 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> // for strtod 17 #include "plurults.h" 18 #include "unicode/localpointer.h" 19 #include "unicode/plurrule.h" 20 21 #define LENGTHOF(array) (int32_t)(sizeof(array)/sizeof(array[0])) 22 23 void setupResult(const int32_t testSource[], char result[], int32_t* max); 24 UBool checkEqual(const PluralRules &test, char *result, int32_t max); 25 UBool testEquality(const PluralRules &test); 26 27 // This is an API test, not a unit test. It doesn't test very many cases, and doesn't 28 // try to test the full functionality. It just calls each function in the class and 29 // verifies that it works on a basic level. 30 31 void PluralRulesTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) 32 { 33 if (exec) logln("TestSuite PluralRulesAPI"); 34 TESTCASE_AUTO_BEGIN; 35 TESTCASE_AUTO(testAPI); 36 TESTCASE_AUTO(testGetUniqueKeywordValue); 37 TESTCASE_AUTO(testGetSamples); 38 TESTCASE_AUTO(testWithin); 39 TESTCASE_AUTO(testGetAllKeywordValues); 40 TESTCASE_AUTO(testOrdinal); 41 TESTCASE_AUTO_END; 42 } 43 44 #define PLURAL_TEST_NUM 18 45 /** 46 * Test various generic API methods of PluralRules for API coverage. 47 */ 48 void PluralRulesTest::testAPI(/*char *par*/) 49 { 50 UnicodeString pluralTestData[PLURAL_TEST_NUM] = { 51 UNICODE_STRING_SIMPLE("a: n is 1"), 52 UNICODE_STRING_SIMPLE("a: n mod 10 is 2"), 53 UNICODE_STRING_SIMPLE("a: n is not 1"), 54 UNICODE_STRING_SIMPLE("a: n mod 3 is not 1"), 55 UNICODE_STRING_SIMPLE("a: n in 2..5"), 56 UNICODE_STRING_SIMPLE("a: n within 2..5"), 57 UNICODE_STRING_SIMPLE("a: n not in 2..5"), 58 UNICODE_STRING_SIMPLE("a: n not within 2..5"), 59 UNICODE_STRING_SIMPLE("a: n mod 10 in 2..5"), 60 UNICODE_STRING_SIMPLE("a: n mod 10 within 2..5"), 61 UNICODE_STRING_SIMPLE("a: n mod 10 is 2 and n is not 12"), 62 UNICODE_STRING_SIMPLE("a: n mod 10 in 2..3 or n mod 10 is 5"), 63 UNICODE_STRING_SIMPLE("a: n mod 10 within 2..3 or n mod 10 is 5"), 64 UNICODE_STRING_SIMPLE("a: n is 1 or n is 4 or n is 23"), 65 UNICODE_STRING_SIMPLE("a: n mod 2 is 1 and n is not 3 and n in 1..11"), 66 UNICODE_STRING_SIMPLE("a: n mod 2 is 1 and n is not 3 and n within 1..11"), 67 UNICODE_STRING_SIMPLE("a: n mod 2 is 1 or n mod 5 is 1 and n is not 6"), 68 "", 69 }; 70 static const int32_t pluralTestResult[PLURAL_TEST_NUM][30] = { 71 {1, 0}, 72 {2,12,22, 0}, 73 {0,2,3,4,5,0}, 74 {0,2,3,5,6,8,9,0}, 75 {2,3,4,5,0}, 76 {2,3,4,5,0}, 77 {0,1,6,7,8, 0}, 78 {0,1,6,7,8, 0}, 79 {2,3,4,5,12,13,14,15,22,23,24,25,0}, 80 {2,3,4,5,12,13,14,15,22,23,24,25,0}, 81 {2,22,32,42,0}, 82 {2,3,5,12,13,15,22,23,25,0}, 83 {2,3,5,12,13,15,22,23,25,0}, 84 {1,4,23,0}, 85 {1,5,7,9,11,0}, 86 {1,5,7,9,11,0}, 87 {1,3,5,7,9,11,13,15,16,0}, 88 }; 89 UErrorCode status = U_ZERO_ERROR; 90 91 // ======= Test constructors 92 logln("Testing PluralRules constructors"); 93 94 95 logln("\n start default locale test case ..\n"); 96 97 PluralRules defRule(status); 98 LocalPointer<PluralRules> test(new PluralRules(status)); 99 LocalPointer<PluralRules> newEnPlural(test->forLocale(Locale::getEnglish(), status)); 100 if(U_FAILURE(status)) { 101 dataerrln("ERROR: Could not create PluralRules (default) - exitting"); 102 return; 103 } 104 105 // ======= Test clone, assignment operator && == operator. 106 LocalPointer<PluralRules> dupRule(defRule.clone()); 107 if (dupRule==NULL) { 108 errln("ERROR: clone plural rules test failed!"); 109 return; 110 } else { 111 if ( *dupRule != defRule ) { 112 errln("ERROR: clone plural rules test failed!"); 113 } 114 } 115 *dupRule = *newEnPlural; 116 if (dupRule!=NULL) { 117 if ( *dupRule != *newEnPlural ) { 118 errln("ERROR: clone plural rules test failed!"); 119 } 120 } 121 122 // ======= Test empty plural rules 123 logln("Testing Simple PluralRules"); 124 125 LocalPointer<PluralRules> empRule(test->createRules(UNICODE_STRING_SIMPLE("a:n"), status)); 126 UnicodeString key; 127 for (int32_t i=0; i<10; ++i) { 128 key = empRule->select(i); 129 if ( key.charAt(0)!= 0x61 ) { // 'a' 130 errln("ERROR: empty plural rules test failed! - exitting"); 131 } 132 } 133 134 // ======= Test simple plural rules 135 logln("Testing Simple PluralRules"); 136 137 char result[100]; 138 int32_t max; 139 140 for (int32_t i=0; i<PLURAL_TEST_NUM-1; ++i) { 141 LocalPointer<PluralRules> newRules(test->createRules(pluralTestData[i], status)); 142 setupResult(pluralTestResult[i], result, &max); 143 if ( !checkEqual(*newRules, result, max) ) { 144 errln("ERROR: simple plural rules failed! - exitting"); 145 return; 146 } 147 } 148 149 // ======= Test complex plural rules 150 logln("Testing Complex PluralRules"); 151 // TODO: the complex test data is hard coded. It's better to implement 152 // a parser to parse the test data. 153 UnicodeString complexRule = UNICODE_STRING_SIMPLE("a: n in 2..5; b: n in 5..8; c: n mod 2 is 1"); 154 UnicodeString complexRule2 = UNICODE_STRING_SIMPLE("a: n within 2..5; b: n within 5..8; c: n mod 2 is 1"); 155 char cRuleResult[] = 156 { 157 0x6F, // 'o' 158 0x63, // 'c' 159 0x61, // 'a' 160 0x61, // 'a' 161 0x61, // 'a' 162 0x61, // 'a' 163 0x62, // 'b' 164 0x62, // 'b' 165 0x62, // 'b' 166 0x63, // 'c' 167 0x6F, // 'o' 168 0x63 // 'c' 169 }; 170 LocalPointer<PluralRules> newRules(test->createRules(complexRule, status)); 171 if ( !checkEqual(*newRules, cRuleResult, 12) ) { 172 errln("ERROR: complex plural rules failed! - exitting"); 173 return; 174 } 175 newRules.adoptInstead(test->createRules(complexRule2, status)); 176 if ( !checkEqual(*newRules, cRuleResult, 12) ) { 177 errln("ERROR: complex plural rules failed! - exitting"); 178 return; 179 } 180 181 // ======= Test decimal fractions plural rules 182 UnicodeString decimalRule= UNICODE_STRING_SIMPLE("a: n not in 0..100;"); 183 UnicodeString KEYWORD_A = UNICODE_STRING_SIMPLE("a"); 184 status = U_ZERO_ERROR; 185 newRules.adoptInstead(test->createRules(decimalRule, status)); 186 if (U_FAILURE(status)) { 187 dataerrln("ERROR: Could not create PluralRules for testing fractions - exitting"); 188 return; 189 } 190 double fData[10] = {-100, -1, -0.0, 0, 0.1, 1, 1.999, 2.0, 100, 100.001 }; 191 UBool isKeywordA[10] = { 192 TRUE, TRUE, FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, TRUE }; 193 for (int32_t i=0; i<10; i++) { 194 if ((newRules->select(fData[i])== KEYWORD_A) != isKeywordA[i]) { 195 errln("ERROR: plural rules for decimal fractions test failed!"); 196 } 197 } 198 199 // ======= Test Equality 200 logln("Testing Equality of PluralRules"); 201 202 if ( !testEquality(*test) ) { 203 errln("ERROR: complex plural rules failed! - exitting"); 204 return; 205 } 206 207 208 // ======= Test getStaticClassID() 209 logln("Testing getStaticClassID()"); 210 211 if(test->getDynamicClassID() != PluralRules::getStaticClassID()) { 212 errln("ERROR: getDynamicClassID() didn't return the expected value"); 213 } 214 // ====== Test fallback to parent locale 215 LocalPointer<PluralRules> en_UK(test->forLocale(Locale::getUK(), status)); 216 LocalPointer<PluralRules> en(test->forLocale(Locale::getEnglish(), status)); 217 if (en_UK.isValid() && en.isValid()) { 218 if ( *en_UK != *en ) { 219 errln("ERROR: test locale fallback failed!"); 220 } 221 } 222 223 LocalPointer<PluralRules> zh_Hant(test->forLocale(Locale::getTaiwan(), status)); 224 LocalPointer<PluralRules> zh(test->forLocale(Locale::getChinese(), status)); 225 if (zh_Hant.isValid() && zh.isValid()) { 226 if ( *zh_Hant != *zh ) { 227 errln("ERROR: test locale fallback failed!"); 228 } 229 } 230 } 231 232 void setupResult(const int32_t testSource[], char result[], int32_t* max) { 233 int32_t i=0; 234 int32_t curIndex=0; 235 236 do { 237 while (curIndex < testSource[i]) { 238 result[curIndex++]=0x6F; //'o' other 239 } 240 result[curIndex++]=0x61; // 'a' 241 242 } while(testSource[++i]>0); 243 *max=curIndex; 244 } 245 246 247 UBool checkEqual(const PluralRules &test, char *result, int32_t max) { 248 UnicodeString key; 249 UBool isEqual = TRUE; 250 for (int32_t i=0; i<max; ++i) { 251 key= test.select(i); 252 if ( key.charAt(0)!=result[i] ) { 253 isEqual = FALSE; 254 } 255 } 256 return isEqual; 257 } 258 259 #define MAX_EQ_ROW 2 260 #define MAX_EQ_COL 5 261 UBool testEquality(const PluralRules &test) { 262 UnicodeString testEquRules[MAX_EQ_ROW][MAX_EQ_COL] = { 263 { UNICODE_STRING_SIMPLE("a: n in 2..3"), 264 UNICODE_STRING_SIMPLE("a: n is 2 or n is 3"), 265 UNICODE_STRING_SIMPLE( "a:n is 3 and n in 2..5 or n is 2"), 266 "", 267 }, 268 { UNICODE_STRING_SIMPLE("a: n is 12; b:n mod 10 in 2..3"), 269 UNICODE_STRING_SIMPLE("b: n mod 10 in 2..3 and n is not 12; a: n in 12..12"), 270 UNICODE_STRING_SIMPLE("b: n is 13; a: n in 12..13; b: n mod 10 is 2 or n mod 10 is 3"), 271 "", 272 } 273 }; 274 UErrorCode status = U_ZERO_ERROR; 275 UnicodeString key[MAX_EQ_COL]; 276 UBool ret=TRUE; 277 for (int32_t i=0; i<MAX_EQ_ROW; ++i) { 278 PluralRules* rules[MAX_EQ_COL]; 279 280 for (int32_t j=0; j<MAX_EQ_COL; ++j) { 281 rules[j]=NULL; 282 } 283 int32_t totalRules=0; 284 while((totalRules<MAX_EQ_COL) && (testEquRules[i][totalRules].length()>0) ) { 285 rules[totalRules]=test.createRules(testEquRules[i][totalRules], status); 286 totalRules++; 287 } 288 for (int32_t n=0; n<300 && ret ; ++n) { 289 for(int32_t j=0; j<totalRules;++j) { 290 key[j] = rules[j]->select(n); 291 } 292 for(int32_t j=0; j<totalRules-1;++j) { 293 if (key[j]!=key[j+1]) { 294 ret= FALSE; 295 break; 296 } 297 } 298 299 } 300 for (int32_t j=0; j<MAX_EQ_COL; ++j) { 301 if (rules[j]!=NULL) { 302 delete rules[j]; 303 } 304 } 305 } 306 307 return ret; 308 } 309 310 void 311 PluralRulesTest::assertRuleValue(const UnicodeString& rule, double expected) { 312 assertRuleKeyValue("a:" + rule, "a", expected); 313 } 314 315 void 316 PluralRulesTest::assertRuleKeyValue(const UnicodeString& rule, 317 const UnicodeString& key, double expected) { 318 UErrorCode status = U_ZERO_ERROR; 319 PluralRules *pr = PluralRules::createRules(rule, status); 320 double result = pr->getUniqueKeywordValue(key); 321 delete pr; 322 if (expected != result) { 323 errln("expected %g but got %g", expected, result); 324 } 325 } 326 327 void PluralRulesTest::testGetUniqueKeywordValue() { 328 assertRuleValue("n is 1", 1); 329 assertRuleValue("n in 2..2", 2); 330 assertRuleValue("n within 2..2", 2); 331 assertRuleValue("n in 3..4", UPLRULES_NO_UNIQUE_VALUE); 332 assertRuleValue("n within 3..4", UPLRULES_NO_UNIQUE_VALUE); 333 assertRuleValue("n is 2 or n is 2", 2); 334 assertRuleValue("n is 2 and n is 2", 2); 335 assertRuleValue("n is 2 or n is 3", UPLRULES_NO_UNIQUE_VALUE); 336 assertRuleValue("n is 2 and n is 3", UPLRULES_NO_UNIQUE_VALUE); 337 assertRuleValue("n is 2 or n in 2..3", UPLRULES_NO_UNIQUE_VALUE); 338 assertRuleValue("n is 2 and n in 2..3", 2); 339 assertRuleKeyValue("a: n is 1", "not_defined", UPLRULES_NO_UNIQUE_VALUE); // key not defined 340 assertRuleKeyValue("a: n is 1", "other", UPLRULES_NO_UNIQUE_VALUE); // key matches default rule 341 } 342 343 void PluralRulesTest::testGetSamples() { 344 // no get functional equivalent API in ICU4C, so just 345 // test every locale... 346 UErrorCode status = U_ZERO_ERROR; 347 int32_t numLocales; 348 const Locale* locales = Locale::getAvailableLocales(numLocales); 349 350 double values[4]; 351 for (int32_t i = 0; U_SUCCESS(status) && i < numLocales; ++i) { 352 PluralRules *rules = PluralRules::forLocale(locales[i], status); 353 if (U_FAILURE(status)) { 354 break; 355 } 356 StringEnumeration *keywords = rules->getKeywords(status); 357 if (U_FAILURE(status)) { 358 delete rules; 359 break; 360 } 361 const UnicodeString* keyword; 362 while (NULL != (keyword = keywords->snext(status))) { 363 int32_t count = rules->getSamples(*keyword, values, 4, status); 364 if (U_FAILURE(status)) { 365 errln(UNICODE_STRING_SIMPLE("getSamples() failed for locale ") + 366 locales[i].getName() + 367 UNICODE_STRING_SIMPLE(", keyword ") + *keyword); 368 continue; 369 } 370 if (count == 0) { 371 errln("no samples for keyword"); 372 } 373 if (count > LENGTHOF(values)) { 374 errln(UNICODE_STRING_SIMPLE("getSamples()=") + count + 375 UNICODE_STRING_SIMPLE(", too many values, for locale ") + 376 locales[i].getName() + 377 UNICODE_STRING_SIMPLE(", keyword ") + *keyword); 378 count = LENGTHOF(values); 379 } 380 for (int32_t j = 0; j < count; ++j) { 381 if (values[j] == UPLRULES_NO_UNIQUE_VALUE) { 382 errln("got 'no unique value' among values"); 383 } else { 384 UnicodeString resultKeyword = rules->select(values[j]); 385 if (*keyword != resultKeyword) { 386 errln("keywords don't match"); 387 } 388 } 389 } 390 } 391 delete keywords; 392 delete rules; 393 } 394 } 395 396 void PluralRulesTest::testWithin() { 397 // goes to show you what lack of testing will do. 398 // of course, this has been broken for two years and no one has noticed... 399 UErrorCode status = U_ZERO_ERROR; 400 PluralRules *rules = PluralRules::createRules("a: n mod 10 in 5..8", status); 401 if (!rules) { 402 errln("couldn't instantiate rules"); 403 return; 404 } 405 406 UnicodeString keyword = rules->select((int32_t)26); 407 if (keyword != "a") { 408 errln("expected 'a' for 26 but didn't get it."); 409 } 410 411 keyword = rules->select(26.5); 412 if (keyword != "other") { 413 errln("expected 'other' for 26.5 but didn't get it."); 414 } 415 416 delete rules; 417 } 418 419 void 420 PluralRulesTest::testGetAllKeywordValues() { 421 const char* data[] = { 422 "a: n in 2..5", "a: 2,3,4,5; other: null; b:", 423 "a: n not in 2..5", "a: null; other: null", 424 "a: n within 2..5", "a: null; other: null", 425 "a: n not within 2..5", "a: null; other: null", 426 "a: n in 2..5 or n within 6..8", "a: null", // ignore 'other' here on out, always null 427 "a: n in 2..5 and n within 6..8", "a:", 428 "a: n in 2..5 and n within 5..8", "a: 5", 429 "a: n within 2..5 and n within 6..8", "a:", // our sampling catches these 430 "a: n within 2..5 and n within 5..8", "a: 5", // '' 431 "a: n within 1..2 and n within 2..3 or n within 3..4 and n within 4..5", "a: 2,4", 432 "a: n within 1..2 and n within 2..3 or n within 3..4 and n within 4..5 " 433 "or n within 5..6 and n within 6..7", "a: null", // but not this... 434 "a: n mod 3 is 0", "a: null", 435 "a: n mod 3 is 0 and n within 1..2", "a:", 436 "a: n mod 3 is 0 and n within 0..5", "a: 0,3", 437 "a: n mod 3 is 0 and n within 0..6", "a: null", // similarly with mod, we don't catch... 438 "a: n mod 3 is 0 and n in 3..12", "a: 3,6,9,12", 439 NULL 440 }; 441 442 for (int i = 0; data[i] != NULL; i += 2) { 443 UErrorCode status = U_ZERO_ERROR; 444 UnicodeString ruleDescription(data[i], -1, US_INV); 445 const char* result = data[i+1]; 446 447 logln("[%d] %s", i >> 1, data[i]); 448 449 PluralRules *p = PluralRules::createRules(ruleDescription, status); 450 if (U_FAILURE(status)) { 451 logln("could not create rules from '%s'\n", data[i]); 452 continue; 453 } 454 455 const char* rp = result; 456 while (*rp) { 457 while (*rp == ' ') ++rp; 458 if (!rp) { 459 break; 460 } 461 462 const char* ep = rp; 463 while (*ep && *ep != ':') ++ep; 464 465 status = U_ZERO_ERROR; 466 UnicodeString keyword(rp, ep - rp, US_INV); 467 double samples[4]; // no test above should have more samples than 4 468 int32_t count = p->getAllKeywordValues(keyword, &samples[0], 4, status); 469 if (U_FAILURE(status)) { 470 errln("error getting samples for %s", rp); 471 break; 472 } 473 474 if (count > 4) { 475 errln("count > 4 for keyword %s", rp); 476 count = 4; 477 } 478 479 if (*ep) { 480 ++ep; // skip colon 481 while (*ep && *ep == ' ') ++ep; // and spaces 482 } 483 484 UBool ok = TRUE; 485 if (count == -1) { 486 if (*ep != 'n') { 487 errln("expected values for keyword %s but got -1 (%s)", rp, ep); 488 ok = FALSE; 489 } 490 } else if (*ep == 'n') { 491 errln("expected count of -1, got %d, for keyword %s (%s)", count, rp, ep); 492 ok = FALSE; 493 } 494 495 // We'll cheat a bit here. The samples happend to be in order and so are our 496 // expected values, so we'll just test in order until a failure. If the 497 // implementation changes to return samples in an arbitrary order, this test 498 // must change. There's no actual restriction on the order of the samples. 499 500 for (int j = 0; ok && j < count; ++j ) { // we've verified count < 4 501 double val = samples[j]; 502 if (*ep == 0 || *ep == ';') { 503 errln("got unexpected value[%d]: %g", j, val); 504 ok = FALSE; 505 break; 506 } 507 char* xp; 508 double expectedVal = strtod(ep, &xp); 509 if (xp == ep) { 510 // internal error 511 errln("yikes!"); 512 ok = FALSE; 513 break; 514 } 515 ep = xp; 516 if (expectedVal != val) { 517 errln("expected %g but got %g", expectedVal, val); 518 ok = FALSE; 519 break; 520 } 521 if (*ep == ',') ++ep; 522 } 523 524 if (ok && count != -1) { 525 if (!(*ep == 0 || *ep == ';')) { 526 errln("didn't get expected value: %s", ep); 527 ok = FALSE; 528 } 529 } 530 531 while (*ep && *ep != ';') ++ep; 532 if (*ep == ';') ++ep; 533 rp = ep; 534 } 535 delete p; 536 } 537 } 538 539 void PluralRulesTest::testOrdinal() { 540 IcuTestErrorCode errorCode(*this, "testOrdinal"); 541 LocalPointer<PluralRules> pr(PluralRules::forLocale("en", UPLURAL_TYPE_ORDINAL, errorCode)); 542 if (errorCode.logIfFailureAndReset("PluralRules::forLocale(en, UPLURAL_TYPE_ORDINAL) failed")) { 543 return; 544 } 545 UnicodeString keyword = pr->select(2.); 546 if (keyword != UNICODE_STRING("two", 3)) { 547 dataerrln("PluralRules(en-ordinal).select(2) failed"); 548 } 549 } 550 551 #endif /* #if !UCONFIG_NO_FORMATTING */ 552