1/******************************************************************** 2 * COPYRIGHT: 3 * Copyright (c) 2002-2011, 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//--------------------------------------------------------------------------- 40DecimalFormatTest::DecimalFormatTest() 41{ 42} 43 44 45DecimalFormatTest::~DecimalFormatTest() 46{ 47} 48 49 50 51void 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);\ 83if (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// 100class InvariantStringPiece: public StringPiece { 101 public: 102 InvariantStringPiece(const UnicodeString &s); 103 ~InvariantStringPiece() {}; 104 private: 105 MaybeStackArray<char, 20> buf; 106}; 107 108InvariantStringPiece::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// 124class UnicodeStringPiece: public StringPiece { 125 public: 126 UnicodeStringPiece(const UnicodeString &s); 127 ~UnicodeStringPiece() {}; 128 private: 129 MaybeStackArray<char, 20> buf; 130}; 131 132UnicodeStringPiece::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. 154static 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 169const char * 170DecimalFormatTest::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 180void 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|unnecessary)\\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 dataerrln("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 300void 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 dataerrln("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 366void 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 dataerrln("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 if (round=="unnecessary") { 401 fmtr.setRoundingMode(DecimalFormat::kRoundUnnecessary); 402 } else { 403 fmtr.setRoundingMode(DecimalFormat::kRoundFloor); 404 errln("file dcfmtest.txt, line %d: Bad rounding mode \"%s\"", 405 lineNum, UnicodeStringPiece(round).data()); 406 } 407 408 UnicodeString result; 409 UnicodeStringPiece spInput(input); 410 //fmtr.format(spInput, result, NULL, status); 411 412 Formattable fmtbl; 413 fmtbl.setDecimalNumber(spInput, status); 414 //NumberFormat &nfmtr = fmtr; 415 fmtr.format(fmtbl, result, NULL, status); 416 417 if ((status == U_FORMAT_INEXACT_ERROR) && (result == "") && (expected == "Inexact")) { 418 // Test succeeded. 419 status = U_ZERO_ERROR; 420 return; 421 } 422 if (U_FAILURE(status)) { 423 errln("file dcfmtest.txt, line %d: format() returned %s.", 424 lineNum, u_errorName(status)); 425 status = U_ZERO_ERROR; 426 return; 427 } 428 429 if (result != expected) { 430 errln("file dcfmtest.txt, line %d: expected \"%s\", got \"%s\"", 431 lineNum, UnicodeStringPiece(expected).data(), UnicodeStringPiece(result).data()); 432 } 433} 434 435 436//------------------------------------------------------------------------------- 437// 438// Read a text data file, convert it from UTF-8 to UChars, and return the data 439// in one big UChar * buffer, which the caller must delete. 440// 441// (Lightly modified version of a similar function in regextst.cpp) 442// 443//-------------------------------------------------------------------------------- 444UChar *DecimalFormatTest::ReadAndConvertFile(const char *fileName, int32_t &ulen, 445 UErrorCode &status) { 446 UChar *retPtr = NULL; 447 char *fileBuf = NULL; 448 const char *fileBufNoBOM = NULL; 449 FILE *f = NULL; 450 451 ulen = 0; 452 if (U_FAILURE(status)) { 453 return retPtr; 454 } 455 456 // 457 // Open the file. 458 // 459 f = fopen(fileName, "rb"); 460 if (f == 0) { 461 dataerrln("Error opening test data file %s\n", fileName); 462 status = U_FILE_ACCESS_ERROR; 463 return NULL; 464 } 465 // 466 // Read it in 467 // 468 int32_t fileSize; 469 int32_t amtRead; 470 int32_t amtReadNoBOM; 471 472 fseek( f, 0, SEEK_END); 473 fileSize = ftell(f); 474 fileBuf = new char[fileSize]; 475 fseek(f, 0, SEEK_SET); 476 amtRead = fread(fileBuf, 1, fileSize, f); 477 if (amtRead != fileSize || fileSize <= 0) { 478 errln("Error reading test data file."); 479 goto cleanUpAndReturn; 480 } 481 482 // 483 // Look for a UTF-8 BOM on the data just read. 484 // The test data file is UTF-8. 485 // The BOM needs to be there in the source file to keep the Windows & 486 // EBCDIC machines happy, so force an error if it goes missing. 487 // Many Linux editors will silently strip it. 488 // 489 fileBufNoBOM = fileBuf + 3; 490 amtReadNoBOM = amtRead - 3; 491 if (fileSize<3 || uprv_strncmp(fileBuf, "\xEF\xBB\xBF", 3) != 0) { 492 // TODO: restore this check. 493 errln("Test data file %s is missing its BOM", fileName); 494 fileBufNoBOM = fileBuf; 495 amtReadNoBOM = amtRead; 496 } 497 498 // 499 // Find the length of the input in UTF-16 UChars 500 // (by preflighting the conversion) 501 // 502 u_strFromUTF8(NULL, 0, &ulen, fileBufNoBOM, amtReadNoBOM, &status); 503 504 // 505 // Convert file contents from UTF-8 to UTF-16 506 // 507 if (status == U_BUFFER_OVERFLOW_ERROR) { 508 // Buffer Overflow is expected from the preflight operation. 509 status = U_ZERO_ERROR; 510 retPtr = new UChar[ulen+1]; 511 u_strFromUTF8(retPtr, ulen+1, NULL, fileBufNoBOM, amtReadNoBOM, &status); 512 } 513 514cleanUpAndReturn: 515 fclose(f); 516 delete[] fileBuf; 517 if (U_FAILURE(status)) { 518 errln("ICU Error \"%s\"\n", u_errorName(status)); 519 delete retPtr; 520 retPtr = NULL; 521 }; 522 return retPtr; 523} 524 525#endif /* !UCONFIG_NO_REGULAR_EXPRESSIONS */ 526 527