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