1 /* 2 ************************************************************************ 3 * Copyright (c) 1997-2010, International Business Machines 4 * Corporation and others. All Rights Reserved. 5 ************************************************************************ 6 */ 7 8 #include "unicode/utypes.h" 9 10 #if !UCONFIG_NO_NORMALIZATION 11 12 #include "unicode/uchar.h" 13 #include "unicode/normlzr.h" 14 #include "unicode/uniset.h" 15 #include "unicode/putil.h" 16 #include "cstring.h" 17 #include "filestrm.h" 18 #include "normconf.h" 19 #include <stdio.h> 20 21 #define ARRAY_LENGTH(array) (sizeof(array) / sizeof(array[0])) 22 23 #define CASE(id,test,exec) case id: \ 24 name = #test; \ 25 if (exec) { \ 26 logln(#test "---"); \ 27 logln((UnicodeString)""); \ 28 test(); \ 29 } \ 30 break 31 32 void NormalizerConformanceTest::runIndexedTest(int32_t index, UBool exec, const char* &name, char* /*par*/) { 33 switch (index) { 34 CASE(0, TestConformance, exec); 35 #if !UCONFIG_NO_FILE_IO && !UCONFIG_NO_LEGACY_CONVERSION 36 CASE(1, TestConformance32, exec); 37 #endif 38 // CASE(2, TestCase6); 39 default: name = ""; break; 40 } 41 } 42 43 #define FIELD_COUNT 5 44 45 NormalizerConformanceTest::NormalizerConformanceTest() : 46 normalizer(UnicodeString(), UNORM_NFC) {} 47 48 NormalizerConformanceTest::~NormalizerConformanceTest() {} 49 50 // more interesting conformance test cases, not in the unicode.org NormalizationTest.txt 51 static const char *moreCases[]={ 52 // Markus 2001aug30 53 "0061 0332 0308;00E4 0332;0061 0332 0308;00E4 0332;0061 0332 0308; # Markus 0", 54 55 // Markus 2001oct26 - test edge case for iteration: U+0f73.cc==0 but decomposition.lead.cc==129 56 "0061 0301 0F73;00E1 0F71 0F72;0061 0F71 0F72 0301;00E1 0F71 0F72;0061 0F71 0F72 0301; # Markus 1" 57 }; 58 59 void NormalizerConformanceTest::compare(const UnicodeString& s1, const UnicodeString& s2){ 60 UErrorCode status=U_ZERO_ERROR; 61 // TODO: Re-enable this tests after UTC fixes UAX 21 62 if(s1.indexOf((UChar32)0x0345)>=0)return; 63 if(Normalizer::compare(s1,s2,U_FOLD_CASE_DEFAULT,status)!=0){ 64 errln("Normalizer::compare() failed for s1: " + prettify(s1) + " s2: " +prettify(s2)); 65 } 66 } 67 68 FileStream * 69 NormalizerConformanceTest::openNormalizationTestFile(const char *filename) { 70 char unidataPath[2000]; 71 const char *folder; 72 FileStream *input; 73 UErrorCode errorCode; 74 75 // look inside ICU_DATA first 76 folder=pathToDataDirectory(); 77 if(folder!=NULL) { 78 strcpy(unidataPath, folder); 79 strcat(unidataPath, "unidata" U_FILE_SEP_STRING); 80 strcat(unidataPath, filename); 81 input=T_FileStream_open(unidataPath, "rb"); 82 if(input!=NULL) { 83 return input; 84 } 85 } 86 87 // find icu/source/data/unidata relative to the test data 88 errorCode=U_ZERO_ERROR; 89 folder=loadTestData(errorCode); 90 if(U_SUCCESS(errorCode)) { 91 strcpy(unidataPath, folder); 92 strcat(unidataPath, U_FILE_SEP_STRING ".." U_FILE_SEP_STRING ".." 93 U_FILE_SEP_STRING ".." U_FILE_SEP_STRING ".." 94 U_FILE_SEP_STRING "data" U_FILE_SEP_STRING "unidata" U_FILE_SEP_STRING); 95 strcat(unidataPath, filename); 96 input=T_FileStream_open(unidataPath, "rb"); 97 if(input!=NULL) { 98 return input; 99 } 100 } 101 102 // look in icu/source/test/testdata/out/build 103 errorCode=U_ZERO_ERROR; 104 folder=loadTestData(errorCode); 105 if(U_SUCCESS(errorCode)) { 106 strcpy(unidataPath, folder); 107 strcat(unidataPath, U_FILE_SEP_STRING); 108 strcat(unidataPath, filename); 109 input=T_FileStream_open(unidataPath, "rb"); 110 if(input!=NULL) { 111 return input; 112 } 113 } 114 115 // look in icu/source/test/testdata 116 errorCode=U_ZERO_ERROR; 117 folder=loadTestData(errorCode); 118 if(U_SUCCESS(errorCode)) { 119 strcpy(unidataPath, folder); 120 strcat(unidataPath, U_FILE_SEP_STRING ".." U_FILE_SEP_STRING ".." U_FILE_SEP_STRING); 121 strcat(unidataPath, filename); 122 input=T_FileStream_open(unidataPath, "rb"); 123 if(input!=NULL) { 124 return input; 125 } 126 } 127 128 // find icu/source/data/unidata relative to U_TOPSRCDIR 129 #if defined(U_TOPSRCDIR) 130 strcpy(unidataPath, U_TOPSRCDIR U_FILE_SEP_STRING "data" U_FILE_SEP_STRING "unidata" U_FILE_SEP_STRING); 131 strcat(unidataPath, filename); 132 input=T_FileStream_open(unidataPath, "rb"); 133 if(input!=NULL) { 134 return input; 135 } 136 137 strcpy(unidataPath, U_TOPSRCDIR U_FILE_SEP_STRING "test" U_FILE_SEP_STRING "testdata" U_FILE_SEP_STRING); 138 strcat(unidataPath, filename); 139 input=T_FileStream_open(unidataPath, "rb"); 140 if(input!=NULL) { 141 return input; 142 } 143 #endif 144 145 dataerrln("Failed to open %s", filename); 146 return NULL; 147 } 148 149 /** 150 * Test the conformance of Normalizer to 151 * http://www.unicode.org/Public/UNIDATA/NormalizationTest.txt 152 */ 153 void NormalizerConformanceTest::TestConformance() { 154 TestConformance(openNormalizationTestFile("NormalizationTest.txt"), 0); 155 } 156 157 void NormalizerConformanceTest::TestConformance32() { 158 TestConformance(openNormalizationTestFile("NormalizationTest-3.2.0.txt"), UNORM_UNICODE_3_2); 159 } 160 161 void NormalizerConformanceTest::TestConformance(FileStream *input, int32_t options) { 162 enum { BUF_SIZE = 1024 }; 163 char lineBuf[BUF_SIZE]; 164 UnicodeString fields[FIELD_COUNT]; 165 UErrorCode status = U_ZERO_ERROR; 166 int32_t passCount = 0; 167 int32_t failCount = 0; 168 UChar32 c; 169 170 if(input==NULL) { 171 return; 172 } 173 174 // UnicodeSet for all code points that are not mentioned in NormalizationTest.txt 175 UnicodeSet other(0, 0x10ffff); 176 177 int32_t count, countMoreCases = sizeof(moreCases)/sizeof(moreCases[0]); 178 for (count = 1;;++count) { 179 if (!T_FileStream_eof(input)) { 180 T_FileStream_readLine(input, lineBuf, (int32_t)sizeof(lineBuf)); 181 } else { 182 // once NormalizationTest.txt is finished, use moreCases[] 183 if(count > countMoreCases) { 184 count = 0; 185 } else if(count == countMoreCases) { 186 // all done 187 break; 188 } 189 uprv_strcpy(lineBuf, moreCases[count]); 190 } 191 if (lineBuf[0] == 0 || lineBuf[0] == '\n' || lineBuf[0] == '\r') continue; 192 193 // Expect 5 columns of this format: 194 // 1E0C;1E0C;0044 0323;1E0C;0044 0323; # <comments> 195 196 // Parse out the comment. 197 if (lineBuf[0] == '#') continue; 198 199 // Read separator lines starting with '@' 200 if (lineBuf[0] == '@') { 201 logln(lineBuf); 202 continue; 203 } 204 205 // Parse out the fields 206 if (!hexsplit(lineBuf, ';', fields, FIELD_COUNT)) { 207 errln((UnicodeString)"Unable to parse line " + count); 208 break; // Syntax error 209 } 210 211 // Remove a single code point from the "other" UnicodeSet 212 if(fields[0].length()==fields[0].moveIndex32(0, 1)) { 213 c=fields[0].char32At(0); 214 if(0xac20<=c && c<=0xd73f && quick) { 215 // not an exhaustive test run: skip most Hangul syllables 216 if(c==0xac20) { 217 other.remove(0xac20, 0xd73f); 218 } 219 continue; 220 } 221 other.remove(c); 222 } 223 224 if (checkConformance(fields, lineBuf, options, status)) { 225 ++passCount; 226 } else { 227 ++failCount; 228 if(status == U_FILE_ACCESS_ERROR) { 229 dataerrln("Something is wrong with the normalizer, skipping the rest of the test."); 230 break; 231 } 232 } 233 if ((count % 1000) == 0) { 234 logln("Line %d", count); 235 } 236 } 237 238 T_FileStream_close(input); 239 240 /* 241 * Test that all characters that are not mentioned 242 * as single code points in column 1 243 * do not change under any normalization. 244 */ 245 246 // remove U+ffff because that is the end-of-iteration sentinel value 247 other.remove(0xffff); 248 249 for(c=0; c<=0x10ffff; quick ? c+=113 : ++c) { 250 if(0x30000<=c && c<0xe0000) { 251 c=0xe0000; 252 } 253 if(!other.contains(c)) { 254 continue; 255 } 256 257 fields[0]=fields[1]=fields[2]=fields[3]=fields[4].setTo(c); 258 sprintf(lineBuf, "not mentioned code point U+%04lx", (long)c); 259 260 if (checkConformance(fields, lineBuf, options, status)) { 261 ++passCount; 262 } else { 263 ++failCount; 264 if(status == U_FILE_ACCESS_ERROR) { 265 dataerrln("Something is wrong with the normalizer, skipping the rest of the test.: %s", u_errorName(status)); 266 break; 267 } 268 } 269 if ((c % 0x1000) == 0) { 270 logln("Code point U+%04lx", c); 271 } 272 } 273 274 if (failCount != 0) { 275 dataerrln((UnicodeString)"Total: " + failCount + " lines/code points failed, " + 276 passCount + " lines/code points passed"); 277 } else { 278 logln((UnicodeString)"Total: " + passCount + " lines/code points passed"); 279 } 280 } 281 282 /** 283 * Verify the conformance of the given line of the Unicode 284 * normalization (UTR 15) test suite file. For each line, 285 * there are five columns, corresponding to field[0]..field[4]. 286 * 287 * The following invariants must be true for all conformant implementations 288 * c2 == NFC(c1) == NFC(c2) == NFC(c3) 289 * c3 == NFD(c1) == NFD(c2) == NFD(c3) 290 * c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5) 291 * c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5) 292 * 293 * @param field the 5 columns 294 * @param line the source line from the test suite file 295 * @return true if the test passes 296 */ 297 UBool NormalizerConformanceTest::checkConformance(const UnicodeString* field, 298 const char *line, 299 int32_t options, 300 UErrorCode &status) { 301 UBool pass = TRUE, result; 302 //UErrorCode status = U_ZERO_ERROR; 303 UnicodeString out, fcd; 304 int32_t fieldNum; 305 306 for (int32_t i=0; i<FIELD_COUNT; ++i) { 307 fieldNum = i+1; 308 if (i<3) { 309 Normalizer::normalize(field[i], UNORM_NFC, options, out, status); 310 if (U_FAILURE(status)) { 311 dataerrln("Error running normalize UNORM_NFC: %s", u_errorName(status)); 312 } else { 313 pass &= assertEqual("C", field[i], out, field[1], "c2!=C(c", fieldNum); 314 iterativeNorm(field[i], UNORM_NFC, options, out, +1); 315 pass &= assertEqual("C(+1)", field[i], out, field[1], "c2!=C(c", fieldNum); 316 iterativeNorm(field[i], UNORM_NFC, options, out, -1); 317 pass &= assertEqual("C(-1)", field[i], out, field[1], "c2!=C(c", fieldNum); 318 } 319 320 Normalizer::normalize(field[i], UNORM_NFD, options, out, status); 321 if (U_FAILURE(status)) { 322 dataerrln("Error running normalize UNORM_NFD: %s", u_errorName(status)); 323 } else { 324 pass &= assertEqual("D", field[i], out, field[2], "c3!=D(c", fieldNum); 325 iterativeNorm(field[i], UNORM_NFD, options, out, +1); 326 pass &= assertEqual("D(+1)", field[i], out, field[2], "c3!=D(c", fieldNum); 327 iterativeNorm(field[i], UNORM_NFD, options, out, -1); 328 pass &= assertEqual("D(-1)", field[i], out, field[2], "c3!=D(c", fieldNum); 329 } 330 } 331 Normalizer::normalize(field[i], UNORM_NFKC, options, out, status); 332 if (U_FAILURE(status)) { 333 dataerrln("Error running normalize UNORM_NFKC: %s", u_errorName(status)); 334 } else { 335 pass &= assertEqual("KC", field[i], out, field[3], "c4!=KC(c", fieldNum); 336 iterativeNorm(field[i], UNORM_NFKC, options, out, +1); 337 pass &= assertEqual("KC(+1)", field[i], out, field[3], "c4!=KC(c", fieldNum); 338 iterativeNorm(field[i], UNORM_NFKC, options, out, -1); 339 pass &= assertEqual("KC(-1)", field[i], out, field[3], "c4!=KC(c", fieldNum); 340 } 341 342 Normalizer::normalize(field[i], UNORM_NFKD, options, out, status); 343 if (U_FAILURE(status)) { 344 dataerrln("Error running normalize UNORM_NFKD: %s", u_errorName(status)); 345 } else { 346 pass &= assertEqual("KD", field[i], out, field[4], "c5!=KD(c", fieldNum); 347 iterativeNorm(field[i], UNORM_NFKD, options, out, +1); 348 pass &= assertEqual("KD(+1)", field[i], out, field[4], "c5!=KD(c", fieldNum); 349 iterativeNorm(field[i], UNORM_NFKD, options, out, -1); 350 pass &= assertEqual("KD(-1)", field[i], out, field[4], "c5!=KD(c", fieldNum); 351 } 352 } 353 compare(field[1],field[2]); 354 compare(field[0],field[1]); 355 // test quick checks 356 if(UNORM_NO == Normalizer::quickCheck(field[1], UNORM_NFC, options, status)) { 357 errln("Normalizer error: quickCheck(NFC(s), UNORM_NFC) is UNORM_NO"); 358 pass = FALSE; 359 } 360 if(UNORM_NO == Normalizer::quickCheck(field[2], UNORM_NFD, options, status)) { 361 errln("Normalizer error: quickCheck(NFD(s), UNORM_NFD) is UNORM_NO"); 362 pass = FALSE; 363 } 364 if(UNORM_NO == Normalizer::quickCheck(field[3], UNORM_NFKC, options, status)) { 365 errln("Normalizer error: quickCheck(NFKC(s), UNORM_NFKC) is UNORM_NO"); 366 pass = FALSE; 367 } 368 if(UNORM_NO == Normalizer::quickCheck(field[4], UNORM_NFKD, options, status)) { 369 errln("Normalizer error: quickCheck(NFKD(s), UNORM_NFKD) is UNORM_NO"); 370 pass = FALSE; 371 } 372 373 // branch on options==0 for better code coverage 374 if(options==0) { 375 result = Normalizer::isNormalized(field[1], UNORM_NFC, status); 376 } else { 377 result = Normalizer::isNormalized(field[1], UNORM_NFC, options, status); 378 } 379 if(!result) { 380 dataerrln("Normalizer error: isNormalized(NFC(s), UNORM_NFC) is FALSE"); 381 pass = FALSE; 382 } 383 if(field[0]!=field[1] && Normalizer::isNormalized(field[0], UNORM_NFC, options, status)) { 384 errln("Normalizer error: isNormalized(s, UNORM_NFC) is TRUE"); 385 pass = FALSE; 386 } 387 if(!Normalizer::isNormalized(field[3], UNORM_NFKC, options, status)) { 388 dataerrln("Normalizer error: isNormalized(NFKC(s), UNORM_NFKC) is FALSE"); 389 pass = FALSE; 390 } 391 if(field[0]!=field[3] && Normalizer::isNormalized(field[0], UNORM_NFKC, options, status)) { 392 errln("Normalizer error: isNormalized(s, UNORM_NFKC) is TRUE"); 393 pass = FALSE; 394 } 395 396 // test FCD quick check and "makeFCD" 397 Normalizer::normalize(field[0], UNORM_FCD, options, fcd, status); 398 if(UNORM_NO == Normalizer::quickCheck(fcd, UNORM_FCD, options, status)) { 399 errln("Normalizer error: quickCheck(FCD(s), UNORM_FCD) is UNORM_NO"); 400 pass = FALSE; 401 } 402 if(UNORM_NO == Normalizer::quickCheck(field[2], UNORM_FCD, options, status)) { 403 errln("Normalizer error: quickCheck(NFD(s), UNORM_FCD) is UNORM_NO"); 404 pass = FALSE; 405 } 406 if(UNORM_NO == Normalizer::quickCheck(field[4], UNORM_FCD, options, status)) { 407 errln("Normalizer error: quickCheck(NFKD(s), UNORM_FCD) is UNORM_NO"); 408 pass = FALSE; 409 } 410 411 Normalizer::normalize(fcd, UNORM_NFD, options, out, status); 412 if(out != field[2]) { 413 dataerrln("Normalizer error: NFD(FCD(s))!=NFD(s)"); 414 pass = FALSE; 415 } 416 417 if (U_FAILURE(status)) { 418 dataerrln("Normalizer::normalize returned error status: %s", u_errorName(status)); 419 pass = FALSE; 420 } 421 422 if(field[0]!=field[2]) { 423 // two strings that are canonically equivalent must test 424 // equal under a canonical caseless match 425 // see UAX #21 Case Mappings and Jitterbug 2021 and 426 // Unicode Technical Committee meeting consensus 92-C31 427 int32_t rc; 428 429 status=U_ZERO_ERROR; 430 rc=Normalizer::compare(field[0], field[2], (options<<UNORM_COMPARE_NORM_OPTIONS_SHIFT)|U_COMPARE_IGNORE_CASE, status); 431 if(U_FAILURE(status)) { 432 dataerrln("Normalizer::compare(case-insensitive) sets %s", u_errorName(status)); 433 pass=FALSE; 434 } else if(rc!=0) { 435 errln("Normalizer::compare(original, NFD, case-insensitive) returned %d instead of 0 for equal", rc); 436 pass=FALSE; 437 } 438 } 439 440 if (!pass) { 441 dataerrln("FAIL: %s", line); 442 } 443 return pass; 444 } 445 446 /** 447 * Do a normalization using the iterative API in the given direction. 448 * @param dir either +1 or -1 449 */ 450 void NormalizerConformanceTest::iterativeNorm(const UnicodeString& str, 451 UNormalizationMode mode, int32_t options, 452 UnicodeString& result, 453 int8_t dir) { 454 UErrorCode status = U_ZERO_ERROR; 455 normalizer.setText(str, status); 456 normalizer.setMode(mode); 457 normalizer.setOption(-1, 0); // reset all options 458 normalizer.setOption(options, 1); // set desired options 459 result.truncate(0); 460 if (U_FAILURE(status)) { 461 return; 462 } 463 UChar32 ch; 464 if (dir > 0) { 465 for (ch = normalizer.first(); ch != Normalizer::DONE; 466 ch = normalizer.next()) { 467 result.append(ch); 468 } 469 } else { 470 for (ch = normalizer.last(); ch != Normalizer::DONE; 471 ch = normalizer.previous()) { 472 result.insert(0, ch); 473 } 474 } 475 } 476 477 /** 478 * @param op name of normalization form, e.g., "KC" 479 * @param s string being normalized 480 * @param got value received 481 * @param exp expected value 482 * @param msg description of this test 483 * @param return true if got == exp 484 */ 485 UBool NormalizerConformanceTest::assertEqual(const char *op, 486 const UnicodeString& s, 487 const UnicodeString& got, 488 const UnicodeString& exp, 489 const char *msg, 490 int32_t field) 491 { 492 if (exp == got) 493 return TRUE; 494 495 char *sChars, *gotChars, *expChars; 496 UnicodeString sPretty(prettify(s)); 497 UnicodeString gotPretty(prettify(got)); 498 UnicodeString expPretty(prettify(exp)); 499 500 sChars = new char[sPretty.length() + 1]; 501 gotChars = new char[gotPretty.length() + 1]; 502 expChars = new char[expPretty.length() + 1]; 503 504 sPretty.extract(0, sPretty.length(), sChars, sPretty.length() + 1); 505 sChars[sPretty.length()] = 0; 506 gotPretty.extract(0, gotPretty.length(), gotChars, gotPretty.length() + 1); 507 gotChars[gotPretty.length()] = 0; 508 expPretty.extract(0, expPretty.length(), expChars, expPretty.length() + 1); 509 expChars[expPretty.length()] = 0; 510 511 errln(" %s%d)%s(%s)=%s, exp. %s", msg, field, op, sChars, gotChars, expChars); 512 513 delete []sChars; 514 delete []gotChars; 515 delete []expChars; 516 return FALSE; 517 } 518 519 /** 520 * Split a string into pieces based on the given delimiter 521 * character. Then, parse the resultant fields from hex into 522 * characters. That is, "0040 0400;0C00;0899" -> new String[] { 523 * "\u0040\u0400", "\u0C00", "\u0899" }. The output is assumed to 524 * be of the proper length already, and exactly output.length 525 * fields are parsed. If there are too few an exception is 526 * thrown. If there are too many the extras are ignored. 527 * 528 * @return FALSE upon failure 529 */ 530 UBool NormalizerConformanceTest::hexsplit(const char *s, char delimiter, 531 UnicodeString output[], int32_t outputLength) { 532 const char *t = s; 533 char *end = NULL; 534 UChar32 c; 535 int32_t i; 536 for (i=0; i<outputLength; ++i) { 537 // skip whitespace 538 while(*t == ' ' || *t == '\t') { 539 ++t; 540 } 541 542 // read a sequence of code points 543 output[i].remove(); 544 for(;;) { 545 c = (UChar32)uprv_strtoul(t, &end, 16); 546 547 if( (char *)t == end || 548 (uint32_t)c > 0x10ffff || 549 (*end != ' ' && *end != '\t' && *end != delimiter) 550 ) { 551 errln(UnicodeString("Bad field ", "") + (i + 1) + " in " + UnicodeString(s, "")); 552 return FALSE; 553 } 554 555 output[i].append(c); 556 557 t = (const char *)end; 558 559 // skip whitespace 560 while(*t == ' ' || *t == '\t') { 561 ++t; 562 } 563 564 if(*t == delimiter) { 565 ++t; 566 break; 567 } 568 if(*t == 0) { 569 if((i + 1) == outputLength) { 570 return TRUE; 571 } else { 572 errln(UnicodeString("Missing field(s) in ", "") + s + " only " + (i + 1) + " out of " + outputLength); 573 return FALSE; 574 } 575 } 576 } 577 } 578 return TRUE; 579 } 580 581 // Specific tests for debugging. These are generally failures taken from 582 // the conformance file, but culled out to make debugging easier. 583 584 void NormalizerConformanceTest::TestCase6(void) { 585 _testOneLine("0385;0385;00A8 0301;0020 0308 0301;0020 0308 0301;"); 586 } 587 588 void NormalizerConformanceTest::_testOneLine(const char *line) { 589 UErrorCode status = U_ZERO_ERROR; 590 UnicodeString fields[FIELD_COUNT]; 591 if (!hexsplit(line, ';', fields, FIELD_COUNT)) { 592 errln((UnicodeString)"Unable to parse line " + line); 593 } else { 594 checkConformance(fields, line, 0, status); 595 } 596 } 597 598 #endif /* #if !UCONFIG_NO_NORMALIZATION */ 599