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