1/******************************************************************** 2 * COPYRIGHT: 3 * Copyright (c) 2002-2013, 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.getAlias(), 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.getAlias(), 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 kFormattable, 284 status); 285 286 execFormatTest(lineNum, 287 formatLineMat.group(1, status), // Pattern 288 formatLineMat.group(2, status), // rounding mode 289 formatLineMat.group(3, status), // input decimal number 290 formatLineMat.group(4, status), // expected formatted result 291 kStringPiece, 292 status); 293 continue; 294 } 295 296 // 297 // Line is not a recognizable test case. 298 // 299 errln("Badly formed test case at line %d.\n%s\n", 300 lineNum, UnicodeStringPiece(testLine).data()); 301 302 } 303 304 delete [] testData; 305} 306 307 308 309void DecimalFormatTest::execParseTest(int32_t lineNum, 310 const UnicodeString &inputText, 311 const UnicodeString &expectedType, 312 const UnicodeString &expectedDecimal, 313 UErrorCode &status) { 314 315 if (U_FAILURE(status)) { 316 return; 317 } 318 319 DecimalFormatSymbols symbols(Locale::getUS(), status); 320 UnicodeString pattern = UNICODE_STRING_SIMPLE("####"); 321 DecimalFormat format(pattern, symbols, status); 322 Formattable result; 323 if (U_FAILURE(status)) { 324 dataerrln("file dcfmtest.txt, line %d: %s error creating the formatter.", 325 lineNum, u_errorName(status)); 326 return; 327 } 328 329 ParsePosition pos; 330 int32_t expectedParseEndPosition = inputText.length(); 331 332 format.parse(inputText, result, pos); 333 334 if (expectedParseEndPosition != pos.getIndex()) { 335 errln("file dcfmtest.txt, line %d: Expected parse position afeter parsing: %d. " 336 "Actual parse position: %d", expectedParseEndPosition, pos.getIndex()); 337 return; 338 } 339 340 char expectedTypeC[2]; 341 expectedType.extract(0, 1, expectedTypeC, 2, US_INV); 342 Formattable::Type expectType = Formattable::kDate; 343 switch (expectedTypeC[0]) { 344 case 'd': expectType = Formattable::kDouble; break; 345 case 'i': expectType = Formattable::kLong; break; 346 case 'l': expectType = Formattable::kInt64; break; 347 default: 348 errln("file dcfmtest.tx, line %d: unrecongized expected type \"%s\"", 349 lineNum, InvariantStringPiece(expectedType).data()); 350 return; 351 } 352 if (result.getType() != expectType) { 353 errln("file dcfmtest.txt, line %d: expectedParseType(%s) != actual parseType(%s)", 354 lineNum, formattableType(expectType), formattableType(result.getType())); 355 return; 356 } 357 358 StringPiece decimalResult = result.getDecimalNumber(status); 359 if (U_FAILURE(status)) { 360 errln("File %s, line %d: error %s. Line in file dcfmtest.txt: %d:", 361 __FILE__, __LINE__, u_errorName(status), lineNum); 362 return; 363 } 364 365 InvariantStringPiece expectedResults(expectedDecimal); 366 if (decimalResult != expectedResults) { 367 errln("file dcfmtest.txt, line %d: expected \"%s\", got \"%s\"", 368 lineNum, expectedResults.data(), decimalResult.data()); 369 } 370 371 return; 372} 373 374 375void DecimalFormatTest::execFormatTest(int32_t lineNum, 376 const UnicodeString &pattern, // Pattern 377 const UnicodeString &round, // rounding mode 378 const UnicodeString &input, // input decimal number 379 const UnicodeString &expected, // expected formatted result 380 EFormatInputType inType, // input number type 381 UErrorCode &status) { 382 if (U_FAILURE(status)) { 383 return; 384 } 385 386 DecimalFormatSymbols symbols(Locale::getUS(), status); 387 // printf("Pattern = %s\n", UnicodeStringPiece(pattern).data()); 388 DecimalFormat fmtr(pattern, symbols, status); 389 if (U_FAILURE(status)) { 390 dataerrln("file dcfmtest.txt, line %d: %s error creating the formatter.", 391 lineNum, u_errorName(status)); 392 return; 393 } 394 if (round=="ceiling") { 395 fmtr.setRoundingMode(DecimalFormat::kRoundCeiling); 396 } else if (round=="floor") { 397 fmtr.setRoundingMode(DecimalFormat::kRoundFloor); 398 } else if (round=="down") { 399 fmtr.setRoundingMode(DecimalFormat::kRoundDown); 400 } else if (round=="up") { 401 fmtr.setRoundingMode(DecimalFormat::kRoundUp); 402 } else if (round=="halfeven") { 403 fmtr.setRoundingMode(DecimalFormat::kRoundHalfEven); 404 } else if (round=="halfdown") { 405 fmtr.setRoundingMode(DecimalFormat::kRoundHalfDown); 406 } else if (round=="halfup") { 407 fmtr.setRoundingMode(DecimalFormat::kRoundHalfUp); 408 } else if (round=="default") { 409 // don't set any value. 410 } else if (round=="unnecessary") { 411 fmtr.setRoundingMode(DecimalFormat::kRoundUnnecessary); 412 } else { 413 fmtr.setRoundingMode(DecimalFormat::kRoundFloor); 414 errln("file dcfmtest.txt, line %d: Bad rounding mode \"%s\"", 415 lineNum, UnicodeStringPiece(round).data()); 416 } 417 418 const char *typeStr = "Unknown"; 419 UnicodeString result; 420 UnicodeStringPiece spInput(input); 421 422 switch (inType) { 423 case kFormattable: 424 { 425 typeStr = "Formattable"; 426 Formattable fmtbl; 427 fmtbl.setDecimalNumber(spInput, status); 428 fmtr.format(fmtbl, result, NULL, status); 429 } 430 break; 431 case kStringPiece: 432 typeStr = "StringPiece"; 433 fmtr.format(spInput, result, NULL, status); 434 break; 435 } 436 437 if ((status == U_FORMAT_INEXACT_ERROR) && (result == "") && (expected == "Inexact")) { 438 // Test succeeded. 439 status = U_ZERO_ERROR; 440 return; 441 } 442 443 if (U_FAILURE(status)) { 444 errln("[%s] file dcfmtest.txt, line %d: format() returned %s.", 445 typeStr, lineNum, u_errorName(status)); 446 status = U_ZERO_ERROR; 447 return; 448 } 449 450 if (result != expected) { 451 errln("[%s] file dcfmtest.txt, line %d: expected \"%s\", got \"%s\"", 452 typeStr, lineNum, UnicodeStringPiece(expected).data(), UnicodeStringPiece(result).data()); 453 } 454} 455 456 457//------------------------------------------------------------------------------- 458// 459// Read a text data file, convert it from UTF-8 to UChars, and return the data 460// in one big UChar * buffer, which the caller must delete. 461// 462// (Lightly modified version of a similar function in regextst.cpp) 463// 464//-------------------------------------------------------------------------------- 465UChar *DecimalFormatTest::ReadAndConvertFile(const char *fileName, int32_t &ulen, 466 UErrorCode &status) { 467 UChar *retPtr = NULL; 468 char *fileBuf = NULL; 469 const char *fileBufNoBOM = NULL; 470 FILE *f = NULL; 471 472 ulen = 0; 473 if (U_FAILURE(status)) { 474 return retPtr; 475 } 476 477 // 478 // Open the file. 479 // 480 f = fopen(fileName, "rb"); 481 if (f == 0) { 482 dataerrln("Error opening test data file %s\n", fileName); 483 status = U_FILE_ACCESS_ERROR; 484 return NULL; 485 } 486 // 487 // Read it in 488 // 489 int32_t fileSize; 490 int32_t amtRead; 491 int32_t amtReadNoBOM; 492 493 fseek( f, 0, SEEK_END); 494 fileSize = ftell(f); 495 fileBuf = new char[fileSize]; 496 fseek(f, 0, SEEK_SET); 497 amtRead = fread(fileBuf, 1, fileSize, f); 498 if (amtRead != fileSize || fileSize <= 0) { 499 errln("Error reading test data file."); 500 goto cleanUpAndReturn; 501 } 502 503 // 504 // Look for a UTF-8 BOM on the data just read. 505 // The test data file is UTF-8. 506 // The BOM needs to be there in the source file to keep the Windows & 507 // EBCDIC machines happy, so force an error if it goes missing. 508 // Many Linux editors will silently strip it. 509 // 510 fileBufNoBOM = fileBuf + 3; 511 amtReadNoBOM = amtRead - 3; 512 if (fileSize<3 || uprv_strncmp(fileBuf, "\xEF\xBB\xBF", 3) != 0) { 513 // TODO: restore this check. 514 errln("Test data file %s is missing its BOM", fileName); 515 fileBufNoBOM = fileBuf; 516 amtReadNoBOM = amtRead; 517 } 518 519 // 520 // Find the length of the input in UTF-16 UChars 521 // (by preflighting the conversion) 522 // 523 u_strFromUTF8(NULL, 0, &ulen, fileBufNoBOM, amtReadNoBOM, &status); 524 525 // 526 // Convert file contents from UTF-8 to UTF-16 527 // 528 if (status == U_BUFFER_OVERFLOW_ERROR) { 529 // Buffer Overflow is expected from the preflight operation. 530 status = U_ZERO_ERROR; 531 retPtr = new UChar[ulen+1]; 532 u_strFromUTF8(retPtr, ulen+1, NULL, fileBufNoBOM, amtReadNoBOM, &status); 533 } 534 535cleanUpAndReturn: 536 fclose(f); 537 delete[] fileBuf; 538 if (U_FAILURE(status)) { 539 errln("ICU Error \"%s\"\n", u_errorName(status)); 540 delete retPtr; 541 retPtr = NULL; 542 }; 543 return retPtr; 544} 545 546#endif /* !UCONFIG_NO_REGULAR_EXPRESSIONS */ 547 548