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