Home | History | Annotate | Download | only in intltest
      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