1 // 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 /******************************************************************** 4 * COPYRIGHT: 5 * Copyright (c) 2002-2014, International Business Machines Corporation and 6 * others. All Rights Reserved. 7 ********************************************************************/ 8 9 // 10 // dcfmtest.cpp 11 // 12 // Decimal Formatter tests, data driven. 13 // 14 15 #include "intltest.h" 16 17 #if !UCONFIG_NO_FORMATTING && !UCONFIG_NO_REGULAR_EXPRESSIONS 18 19 #include "unicode/regex.h" 20 #include "unicode/uchar.h" 21 #include "unicode/ustring.h" 22 #include "unicode/unistr.h" 23 #include "unicode/dcfmtsym.h" 24 #include "unicode/decimfmt.h" 25 #include "unicode/locid.h" 26 #include "cmemory.h" 27 #include "dcfmtest.h" 28 #include "util.h" 29 #include "cstring.h" 30 #include <stdlib.h> 31 #include <string.h> 32 #include <stdio.h> 33 34 #if defined(__GLIBCXX__) 35 namespace std { class type_info; } // WORKAROUND: http://llvm.org/bugs/show_bug.cgi?id=13364 36 #endif 37 38 #include <string> 39 #include <iostream> 40 41 //--------------------------------------------------------------------------- 42 // 43 // Test class boilerplate 44 // 45 //--------------------------------------------------------------------------- 46 DecimalFormatTest::DecimalFormatTest() 47 { 48 } 49 50 51 DecimalFormatTest::~DecimalFormatTest() 52 { 53 } 54 55 56 57 void DecimalFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) 58 { 59 if (exec) logln("TestSuite DecimalFormatTest: "); 60 switch (index) { 61 62 #if !UCONFIG_NO_FILE_IO 63 case 0: name = "DataDrivenTests"; 64 if (exec) DataDrivenTests(); 65 break; 66 #else 67 case 0: name = "skip"; 68 break; 69 #endif 70 71 default: name = ""; 72 break; //needed to end loop 73 } 74 } 75 76 77 //--------------------------------------------------------------------------- 78 // 79 // Error Checking / Reporting macros used in all of the tests. 80 // 81 //--------------------------------------------------------------------------- 82 #define DF_CHECK_STATUS {if (U_FAILURE(status)) \ 83 {dataerrln("DecimalFormatTest failure at line %d. status=%s", \ 84 __LINE__, u_errorName(status)); return 0;}} 85 86 #define DF_ASSERT(expr) {if ((expr)==FALSE) {errln("DecimalFormatTest failure at line %d.\n", __LINE__);};} 87 88 #define DF_ASSERT_FAIL(expr, errcode) {UErrorCode status=U_ZERO_ERROR; (expr);\ 89 if (status!=errcode) {dataerrln("DecimalFormatTest failure at line %d. Expected status=%s, got %s", \ 90 __LINE__, u_errorName(errcode), u_errorName(status));};} 91 92 #define DF_CHECK_STATUS_L(line) {if (U_FAILURE(status)) {errln( \ 93 "DecimalFormatTest failure at line %d, from %d. status=%d\n",__LINE__, (line), status); }} 94 95 #define DF_ASSERT_L(expr, line) {if ((expr)==FALSE) { \ 96 errln("DecimalFormatTest failure at line %d, from %d.", __LINE__, (line)); return;}} 97 98 99 100 // 101 // InvariantStringPiece 102 // Wrap a StringPiece around the extracted invariant data of a UnicodeString. 103 // The data is guaranteed to be nul terminated. (This is not true of StringPiece 104 // in general, but is true of InvariantStringPiece) 105 // 106 class InvariantStringPiece: public StringPiece { 107 public: 108 InvariantStringPiece(const UnicodeString &s); 109 ~InvariantStringPiece() {}; 110 private: 111 MaybeStackArray<char, 20> buf; 112 }; 113 114 InvariantStringPiece::InvariantStringPiece(const UnicodeString &s) { 115 int32_t len = s.length(); 116 if (len+1 > buf.getCapacity()) { 117 buf.resize(len+1); 118 } 119 // Buffer size is len+1 so that s.extract() will nul-terminate the string. 120 s.extract(0, len, buf.getAlias(), len+1, US_INV); 121 this->set(buf.getAlias(), len); 122 } 123 124 125 // UnicodeStringPiece 126 // Wrap a StringPiece around the extracted (to the default charset) data of 127 // a UnicodeString. The extracted data is guaranteed to be nul terminated. 128 // (This is not true of StringPiece in general, but is true of UnicodeStringPiece) 129 // 130 class UnicodeStringPiece: public StringPiece { 131 public: 132 UnicodeStringPiece(const UnicodeString &s); 133 ~UnicodeStringPiece() {}; 134 private: 135 MaybeStackArray<char, 20> buf; 136 }; 137 138 UnicodeStringPiece::UnicodeStringPiece(const UnicodeString &s) { 139 int32_t len = s.length(); 140 int32_t capacity = buf.getCapacity(); 141 int32_t requiredCapacity = s.extract(0, len, buf.getAlias(), capacity) + 1; 142 if (capacity < requiredCapacity) { 143 buf.resize(requiredCapacity); 144 capacity = requiredCapacity; 145 s.extract(0, len, buf.getAlias(), capacity); 146 } 147 this->set(buf.getAlias(), requiredCapacity - 1); 148 } 149 150 151 152 //--------------------------------------------------------------------------- 153 // 154 // DataDrivenTests 155 // The test cases are in a separate data file, 156 // 157 //--------------------------------------------------------------------------- 158 159 // Translate a Formattable::type enum value to a string, for error message formatting. 160 static const char *formattableType(Formattable::Type typ) { 161 static const char *types[] = {"kDate", 162 "kDouble", 163 "kLong", 164 "kString", 165 "kArray", 166 "kInt64", 167 "kObject" 168 }; 169 if (typ<0 || typ>Formattable::kObject) { 170 return "Unknown"; 171 } 172 return types[typ]; 173 } 174 175 const char * 176 DecimalFormatTest::getPath(char *buffer, const char *filename) { 177 UErrorCode status=U_ZERO_ERROR; 178 const char *testDataDirectory = IntlTest::getSourceTestData(status); 179 DF_CHECK_STATUS; 180 181 strcpy(buffer, testDataDirectory); 182 strcat(buffer, filename); 183 return buffer; 184 } 185 186 void DecimalFormatTest::DataDrivenTests() { 187 char tdd[2048]; 188 const char *srcPath; 189 UErrorCode status = U_ZERO_ERROR; 190 int32_t lineNum = 0; 191 192 // 193 // Open and read the test data file. 194 // 195 srcPath=getPath(tdd, "dcfmtest.txt"); 196 if(srcPath==NULL) { 197 return; /* something went wrong, error already output */ 198 } 199 200 int32_t len; 201 UChar *testData = ReadAndConvertFile(srcPath, len, status); 202 if (U_FAILURE(status)) { 203 return; /* something went wrong, error already output */ 204 } 205 206 // 207 // Put the test data into a UnicodeString 208 // 209 UnicodeString testString(FALSE, testData, len); 210 211 RegexMatcher parseLineMat(UnicodeString( 212 "(?i)\\s*parse\\s+" 213 "\"([^\"]*)\"\\s+" // Capture group 1: input text 214 "([ild])\\s+" // Capture group 2: expected parsed type 215 "\"([^\"]*)\"\\s+" // Capture group 3: expected parsed decimal 216 "\\s*(?:#.*)?"), // Trailing comment 217 0, status); 218 219 RegexMatcher formatLineMat(UnicodeString( 220 "(?i)\\s*format\\s+" 221 "(\\S+)\\s+" // Capture group 1: pattern 222 "(ceiling|floor|down|up|halfeven|halfdown|halfup|default|unnecessary)\\s+" // Capture group 2: Rounding Mode 223 "\"([^\"]*)\"\\s+" // Capture group 3: input 224 "\"([^\"]*)\"" // Capture group 4: expected output 225 "\\s*(?:#.*)?"), // Trailing comment 226 0, status); 227 228 RegexMatcher commentMat (UNICODE_STRING_SIMPLE("\\s*(#.*)?$"), 0, status); 229 RegexMatcher lineMat(UNICODE_STRING_SIMPLE("(?m)^(.*?)$"), testString, 0, status); 230 231 if (U_FAILURE(status)){ 232 dataerrln("Construct RegexMatcher() error."); 233 delete [] testData; 234 return; 235 } 236 237 // 238 // Loop over the test data file, once per line. 239 // 240 while (lineMat.find()) { 241 lineNum++; 242 if (U_FAILURE(status)) { 243 dataerrln("File dcfmtest.txt, line %d: ICU Error \"%s\"", lineNum, u_errorName(status)); 244 } 245 246 status = U_ZERO_ERROR; 247 UnicodeString testLine = lineMat.group(1, status); 248 // printf("%s\n", UnicodeStringPiece(testLine).data()); 249 if (testLine.length() == 0) { 250 continue; 251 } 252 253 // 254 // Parse the test line. Skip blank and comment only lines. 255 // Separate out the three main fields - pattern, flags, target. 256 // 257 258 commentMat.reset(testLine); 259 if (commentMat.lookingAt(status)) { 260 // This line is a comment, or blank. 261 continue; 262 } 263 264 265 // 266 // Handle "parse" test case line from file 267 // 268 parseLineMat.reset(testLine); 269 if (parseLineMat.lookingAt(status)) { 270 execParseTest(lineNum, 271 parseLineMat.group(1, status), // input 272 parseLineMat.group(2, status), // Expected Type 273 parseLineMat.group(3, status), // Expected Decimal String 274 status 275 ); 276 continue; 277 } 278 279 // 280 // Handle "format" test case line 281 // 282 formatLineMat.reset(testLine); 283 if (formatLineMat.lookingAt(status)) { 284 execFormatTest(lineNum, 285 formatLineMat.group(1, status), // Pattern 286 formatLineMat.group(2, status), // rounding mode 287 formatLineMat.group(3, status), // input decimal number 288 formatLineMat.group(4, status), // expected formatted result 289 kFormattable, 290 status); 291 292 execFormatTest(lineNum, 293 formatLineMat.group(1, status), // Pattern 294 formatLineMat.group(2, status), // rounding mode 295 formatLineMat.group(3, status), // input decimal number 296 formatLineMat.group(4, status), // expected formatted result 297 kStringPiece, 298 status); 299 continue; 300 } 301 302 // 303 // Line is not a recognizable test case. 304 // 305 errln("Badly formed test case at line %d.\n%s\n", 306 lineNum, UnicodeStringPiece(testLine).data()); 307 308 } 309 310 delete [] testData; 311 } 312 313 314 315 void DecimalFormatTest::execParseTest(int32_t lineNum, 316 const UnicodeString &inputText, 317 const UnicodeString &expectedType, 318 const UnicodeString &expectedDecimal, 319 UErrorCode &status) { 320 321 if (U_FAILURE(status)) { 322 return; 323 } 324 325 DecimalFormatSymbols symbols(Locale::getUS(), status); 326 UnicodeString pattern = UNICODE_STRING_SIMPLE("####"); 327 DecimalFormat format(pattern, symbols, status); 328 Formattable result; 329 if (U_FAILURE(status)) { 330 dataerrln("file dcfmtest.txt, line %d: %s error creating the formatter.", 331 lineNum, u_errorName(status)); 332 return; 333 } 334 335 ParsePosition pos; 336 int32_t expectedParseEndPosition = inputText.length(); 337 338 format.parse(inputText, result, pos); 339 340 if (expectedParseEndPosition != pos.getIndex()) { 341 errln("file dcfmtest.txt, line %d: Expected parse position afeter parsing: %d. " 342 "Actual parse position: %d", expectedParseEndPosition, pos.getIndex()); 343 return; 344 } 345 346 char expectedTypeC[2]; 347 expectedType.extract(0, 1, expectedTypeC, 2, US_INV); 348 Formattable::Type expectType = Formattable::kDate; 349 switch (expectedTypeC[0]) { 350 case 'd': expectType = Formattable::kDouble; break; 351 case 'i': expectType = Formattable::kLong; break; 352 case 'l': expectType = Formattable::kInt64; break; 353 default: 354 errln("file dcfmtest.tx, line %d: unrecongized expected type \"%s\"", 355 lineNum, InvariantStringPiece(expectedType).data()); 356 return; 357 } 358 if (result.getType() != expectType) { 359 errln("file dcfmtest.txt, line %d: expectedParseType(%s) != actual parseType(%s)", 360 lineNum, formattableType(expectType), formattableType(result.getType())); 361 return; 362 } 363 364 StringPiece decimalResult = result.getDecimalNumber(status); 365 if (U_FAILURE(status)) { 366 errln("File %s, line %d: error %s. Line in file dcfmtest.txt: %d:", 367 __FILE__, __LINE__, u_errorName(status), lineNum); 368 return; 369 } 370 371 InvariantStringPiece expectedResults(expectedDecimal); 372 if (decimalResult != expectedResults) { 373 errln("file dcfmtest.txt, line %d: expected \"%s\", got \"%s\"", 374 lineNum, expectedResults.data(), decimalResult.data()); 375 } 376 377 return; 378 } 379 380 381 void DecimalFormatTest::execFormatTest(int32_t lineNum, 382 const UnicodeString &pattern, // Pattern 383 const UnicodeString &round, // rounding mode 384 const UnicodeString &input, // input decimal number 385 const UnicodeString &expected, // expected formatted result 386 EFormatInputType inType, // input number type 387 UErrorCode &status) { 388 if (U_FAILURE(status)) { 389 return; 390 } 391 392 DecimalFormatSymbols symbols(Locale::getUS(), status); 393 // printf("Pattern = %s\n", UnicodeStringPiece(pattern).data()); 394 DecimalFormat fmtr(pattern, symbols, status); 395 if (U_FAILURE(status)) { 396 dataerrln("file dcfmtest.txt, line %d: %s error creating the formatter.", 397 lineNum, u_errorName(status)); 398 return; 399 } 400 if (round=="ceiling") { 401 fmtr.setRoundingMode(DecimalFormat::kRoundCeiling); 402 } else if (round=="floor") { 403 fmtr.setRoundingMode(DecimalFormat::kRoundFloor); 404 } else if (round=="down") { 405 fmtr.setRoundingMode(DecimalFormat::kRoundDown); 406 } else if (round=="up") { 407 fmtr.setRoundingMode(DecimalFormat::kRoundUp); 408 } else if (round=="halfeven") { 409 fmtr.setRoundingMode(DecimalFormat::kRoundHalfEven); 410 } else if (round=="halfdown") { 411 fmtr.setRoundingMode(DecimalFormat::kRoundHalfDown); 412 } else if (round=="halfup") { 413 fmtr.setRoundingMode(DecimalFormat::kRoundHalfUp); 414 } else if (round=="default") { 415 // don't set any value. 416 } else if (round=="unnecessary") { 417 fmtr.setRoundingMode(DecimalFormat::kRoundUnnecessary); 418 } else { 419 fmtr.setRoundingMode(DecimalFormat::kRoundFloor); 420 errln("file dcfmtest.txt, line %d: Bad rounding mode \"%s\"", 421 lineNum, UnicodeStringPiece(round).data()); 422 } 423 424 const char *typeStr = "Unknown"; 425 UnicodeString result; 426 UnicodeStringPiece spInput(input); 427 428 switch (inType) { 429 case kFormattable: 430 { 431 typeStr = "Formattable"; 432 Formattable fmtbl; 433 fmtbl.setDecimalNumber(spInput, status); 434 fmtr.format(fmtbl, result, NULL, status); 435 } 436 break; 437 case kStringPiece: 438 typeStr = "StringPiece"; 439 fmtr.format(spInput, result, NULL, status); 440 break; 441 } 442 443 if ((status == U_FORMAT_INEXACT_ERROR) && (result == "") && (expected == "Inexact")) { 444 // Test succeeded. 445 status = U_ZERO_ERROR; 446 return; 447 } 448 449 if (U_FAILURE(status)) { 450 errln("[%s] file dcfmtest.txt, line %d: format() returned %s.", 451 typeStr, lineNum, u_errorName(status)); 452 status = U_ZERO_ERROR; 453 return; 454 } 455 456 if (result != expected) { 457 errln("[%s] file dcfmtest.txt, line %d: expected \"%s\", got \"%s\"", 458 typeStr, lineNum, UnicodeStringPiece(expected).data(), UnicodeStringPiece(result).data()); 459 } 460 } 461 462 463 //------------------------------------------------------------------------------- 464 // 465 // Read a text data file, convert it from UTF-8 to UChars, and return the data 466 // in one big UChar * buffer, which the caller must delete. 467 // 468 // (Lightly modified version of a similar function in regextst.cpp) 469 // 470 //-------------------------------------------------------------------------------- 471 UChar *DecimalFormatTest::ReadAndConvertFile(const char *fileName, int32_t &ulen, 472 UErrorCode &status) { 473 UChar *retPtr = NULL; 474 char *fileBuf = NULL; 475 const char *fileBufNoBOM = NULL; 476 FILE *f = NULL; 477 478 ulen = 0; 479 if (U_FAILURE(status)) { 480 return retPtr; 481 } 482 483 // 484 // Open the file. 485 // 486 f = fopen(fileName, "rb"); 487 if (f == 0) { 488 dataerrln("Error opening test data file %s\n", fileName); 489 status = U_FILE_ACCESS_ERROR; 490 return NULL; 491 } 492 // 493 // Read it in 494 // 495 int32_t fileSize; 496 int32_t amtRead; 497 int32_t amtReadNoBOM; 498 499 fseek( f, 0, SEEK_END); 500 fileSize = ftell(f); 501 fileBuf = new char[fileSize]; 502 fseek(f, 0, SEEK_SET); 503 amtRead = fread(fileBuf, 1, fileSize, f); 504 if (amtRead != fileSize || fileSize <= 0) { 505 errln("Error reading test data file."); 506 goto cleanUpAndReturn; 507 } 508 509 // 510 // Look for a UTF-8 BOM on the data just read. 511 // The test data file is UTF-8. 512 // The BOM needs to be there in the source file to keep the Windows & 513 // EBCDIC machines happy, so force an error if it goes missing. 514 // Many Linux editors will silently strip it. 515 // 516 fileBufNoBOM = fileBuf + 3; 517 amtReadNoBOM = amtRead - 3; 518 if (fileSize<3 || uprv_strncmp(fileBuf, "\xEF\xBB\xBF", 3) != 0) { 519 // TODO: restore this check. 520 errln("Test data file %s is missing its BOM", fileName); 521 fileBufNoBOM = fileBuf; 522 amtReadNoBOM = amtRead; 523 } 524 525 // 526 // Find the length of the input in UTF-16 UChars 527 // (by preflighting the conversion) 528 // 529 u_strFromUTF8(NULL, 0, &ulen, fileBufNoBOM, amtReadNoBOM, &status); 530 531 // 532 // Convert file contents from UTF-8 to UTF-16 533 // 534 if (status == U_BUFFER_OVERFLOW_ERROR) { 535 // Buffer Overflow is expected from the preflight operation. 536 status = U_ZERO_ERROR; 537 retPtr = new UChar[ulen+1]; 538 u_strFromUTF8(retPtr, ulen+1, NULL, fileBufNoBOM, amtReadNoBOM, &status); 539 } 540 541 cleanUpAndReturn: 542 fclose(f); 543 delete[] fileBuf; 544 if (U_FAILURE(status)) { 545 errln("ICU Error \"%s\"\n", u_errorName(status)); 546 delete retPtr; 547 retPtr = NULL; 548 }; 549 return retPtr; 550 } 551 552 #endif /* !UCONFIG_NO_REGULAR_EXPRESSIONS */ 553 554