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