1 // © 2019 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 
4 #include "unicode/utypes.h"
5 
6 #if !UCONFIG_NO_FORMATTING
7 
8 #include <fstream>
9 #include <iostream>
10 #include <vector>
11 
12 #include "numbertest.h"
13 #include "ucbuf.h"
14 #include "unicode/numberformatter.h"
15 
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)16 void NumberPermutationTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) {
17     if (exec) {
18         logln("TestSuite NumberPermutationTest: ");
19     }
20     TESTCASE_AUTO_BEGIN;
21     TESTCASE_AUTO(testPermutations);
22     TESTCASE_AUTO_END;
23 }
24 
25 static const char16_t* kSkeletonParts[] = {
26     // Notation
27     u"compact-short",
28     u"scientific/+ee/sign-always",
29     nullptr,
30     // Unit
31     u"percent",
32     u"currency/EUR",
33     u"measure-unit/length-furlong",
34     nullptr,
35     // Unit Width
36     u"unit-width-narrow",
37     u"unit-width-full-name",
38     nullptr,
39     // Precision
40     u"precision-integer",
41     u".000",
42     u".##/@@@+",
43     u"@@",
44     nullptr,
45     // Rounding Mode
46     u"rounding-mode-floor",
47     nullptr,
48     // Integer Width
49     u"integer-width/##00",
50     nullptr,
51     // Scale
52     u"scale/0.5",
53     nullptr,
54     // Grouping
55     u"group-on-aligned",
56     nullptr,
57     // Symbols
58     u"latin",
59     nullptr,
60     // Sign Display
61     u"sign-accounting-except-zero",
62     nullptr,
63     // Decimal Separator Display
64     u"decimal-always",
65     nullptr,
66 };
67 
68 static const double kNumbersToTest[]{0, 91827.3645, -0.22222};
69 
70 /**
71  * Test permutations of 3 orthogonal skeleton parts from the list above.
72  * Compare the results against the golden data file:
73  *     numberpermutationtest.txt
74  * To regenerate that file, run intltest with the -e and -G options.
75  * On Linux, from icu4c/source:
76  *     make -j8 tests && (cd test/intltest && LD_LIBRARY_PATH=../../lib:../../tools/ctestfw ./intltest -e -G format/NumberTest/NumberPermutationTest)
77  * After re-generating the file, copy it into icu4j:
78  *     cp test/testdata/numberpermutationtest.txt ../../icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberpermutationtest.txt
79  */
testPermutations()80 void NumberPermutationTest::testPermutations() {
81     IcuTestErrorCode status(*this, "testPermutations");
82 
83     const struct LocaleData {
84         Locale locale;
85         const char16_t* ustring;
86     } localesToTest[] = {
87         {"es-MX", u"es-MX"},
88         {"zh-TW", u"zh-TW"},
89         {"bn-BD", u"bn-BD"},
90     };
91 
92     // Convert kSkeletonParts to a more convenient data structure
93     auto skeletonParts = std::vector<std::vector<const char16_t*>>();
94     auto currentSection = std::vector<const char16_t*>();
95     for (int32_t i = 0; i < UPRV_LENGTHOF(kSkeletonParts); i++) {
96         const char16_t* skeletonPart = kSkeletonParts[i];
97         if (skeletonPart == nullptr) {
98             skeletonParts.push_back(currentSection);
99             currentSection.clear();
100         } else {
101             currentSection.push_back(skeletonPart);
102         }
103     }
104 
105     // Build up the golden data string as we evaluate all permutations
106     std::vector<UnicodeString> resultLines;
107     resultLines.push_back(u"# © 2019 and later: Unicode, Inc. and others.");
108     resultLines.push_back(u"# License & terms of use: http://www.unicode.org/copyright.html");
109     resultLines.push_back(UnicodeString());
110 
111     // Take combinations of 3 orthogonal options
112     for (size_t i = 0; i < skeletonParts.size() - 2; i++) {
113         const auto& skeletons1 = skeletonParts[i];
114         for (size_t j = i + 1; j < skeletonParts.size() - 1; j++) {
115             const auto& skeletons2 = skeletonParts[j];
116             for (size_t k = j + 1; k < skeletonParts.size(); k++) {
117                 const auto& skeletons3 = skeletonParts[k];
118 
119                 // Evaluate all combinations of skeletons for these options
120                 for (const auto& skel1 : skeletons1) {
121                     for (const auto& skel2 : skeletons2) {
122                         for (const auto& skel3 : skeletons3) {
123                             // Compute the skeleton
124                             UnicodeString skeleton;
125                             skeleton
126                                 .append(skel1)  //
127                                 .append(u' ')   //
128                                 .append(skel2)  //
129                                 .append(u' ')   //
130                                 .append(skel3);
131                             resultLines.push_back(skeleton);
132 
133                             // Check several locales and several numbers in each locale
134                             for (const auto& locData : localesToTest) {
135                                 auto lnf = NumberFormatter::forSkeleton(skeleton, status)
136                                                .locale(locData.locale);
137                                 resultLines.push_back(UnicodeString(u"  ").append(locData.ustring));
138                                 for (const auto& input : kNumbersToTest) {
139                                     resultLines.push_back(UnicodeString(u"    ").append(
140                                         lnf.formatDouble(input, status).toTempString(status)));
141                                 }
142                             }
143 
144                             resultLines.push_back(UnicodeString());
145                         }
146                     }
147                 }
148             }
149 
150             // Quick mode: test all fields at least once but stop early.
151             if (quick) {
152                 infoln(u"Quick mode: stopped after " + Int64ToUnicodeString(resultLines.size()) +
153                        u" lines");
154                 goto outerEnd;
155             }
156         }
157     }
158 outerEnd:
159     void();
160 
161     CharString goldenFilePath(getSourceTestData(status), status);
162     goldenFilePath.appendPathPart("numberpermutationtest.txt", status);
163 
164     // Compare it to the golden file
165     const char* codePage = "UTF-8";
166     LocalUCHARBUFPointer f(ucbuf_open(goldenFilePath.data(), &codePage, TRUE, FALSE, status));
167     if (!assertSuccess("Can't open data file", status)) {
168         return;
169     }
170 
171     int32_t lineNumber = 1;
172     int32_t lineLength;
173     for (const auto& actualLine : resultLines) {
174         const UChar* lineBuf = ucbuf_readline(f.getAlias(), &lineLength, status);
175         if (lineBuf == nullptr) {
176             errln("More lines generated than are in the data file!");
177             break;
178         }
179         UnicodeString expectedLine(lineBuf, lineLength - 1);
180         assertEquals(u"Line #" + Int64ToUnicodeString(lineNumber) + u" differs",  //
181             expectedLine, actualLine);
182         lineNumber++;
183     }
184     // Quick mode: test all fields at least once but stop early.
185     if (!quick && ucbuf_readline(f.getAlias(), &lineLength, status) != nullptr) {
186         errln("Fewer lines generated than are in the data file!");
187     }
188 
189     // Overwrite the golden data if requested
190     if (write_golden_data) {
191         std::ofstream outFile;
192         outFile.open(goldenFilePath.data());
193         for (const auto& uniLine : resultLines) {
194             std::string byteLine;
195             uniLine.toUTF8String(byteLine);
196             outFile << byteLine << std::endl;
197         }
198         outFile.close();
199     }
200 }
201 
202 #endif /* #if !UCONFIG_NO_FORMATTING */
203