]>
Commit | Line | Data |
---|---|---|
0f5d89e8 A |
1 | // © 2017 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 "unicode/dcfmtsym.h" | |
9 | ||
10 | #include "cstr.h" | |
11 | #include "numbertest.h" | |
12 | #include "number_utils.h" | |
13 | #include "number_skeletons.h" | |
14 | #include "putilimp.h" | |
15 | ||
16 | using namespace icu::number::impl; | |
17 | ||
18 | ||
19 | void NumberSkeletonTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) { | |
20 | if (exec) { | |
21 | logln("TestSuite AffixUtilsTest: "); | |
22 | } | |
23 | TESTCASE_AUTO_BEGIN; | |
24 | TESTCASE_AUTO(validTokens); | |
25 | TESTCASE_AUTO(invalidTokens); | |
26 | TESTCASE_AUTO(unknownTokens); | |
27 | TESTCASE_AUTO(unexpectedTokens); | |
28 | TESTCASE_AUTO(duplicateValues); | |
29 | TESTCASE_AUTO(stemsRequiringOption); | |
30 | TESTCASE_AUTO(defaultTokens); | |
31 | TESTCASE_AUTO(flexibleSeparators); | |
32 | TESTCASE_AUTO_END; | |
33 | } | |
34 | ||
35 | void NumberSkeletonTest::validTokens() { | |
36 | IcuTestErrorCode status(*this, "validTokens"); | |
37 | ||
38 | // This tests only if the tokens are valid, not their behavior. | |
39 | // Most of these are from the design doc. | |
40 | static const char16_t* cases[] = { | |
41 | u"precision-integer", | |
42 | u"precision-unlimited", | |
43 | u"@@@##", | |
44 | u"@@+", | |
45 | u".000##", | |
46 | u".00+", | |
47 | u".", | |
48 | u".+", | |
49 | u".######", | |
50 | u".00/@@+", | |
51 | u".00/@##", | |
52 | u"precision-increment/3.14", | |
53 | u"precision-currency-standard", | |
54 | u"precision-integer rounding-mode-half-up", | |
55 | u".00# rounding-mode-ceiling", | |
56 | u".00/@@+ rounding-mode-floor", | |
57 | u"scientific", | |
58 | u"scientific/+ee", | |
59 | u"scientific/sign-always", | |
60 | u"scientific/+ee/sign-always", | |
61 | u"scientific/sign-always/+ee", | |
62 | u"scientific/sign-except-zero", | |
63 | u"engineering", | |
64 | u"engineering/+eee", | |
65 | u"compact-short", | |
66 | u"compact-long", | |
67 | u"notation-simple", | |
68 | u"percent", | |
69 | u"permille", | |
70 | u"measure-unit/length-meter", | |
71 | u"measure-unit/area-square-meter", | |
72 | u"measure-unit/energy-joule per-measure-unit/length-meter", | |
73 | u"currency/XXX", | |
74 | u"currency/ZZZ", | |
75 | u"currency/usd", | |
76 | u"group-off", | |
77 | u"group-min2", | |
78 | u"group-auto", | |
79 | u"group-on-aligned", | |
80 | u"group-thousands", | |
81 | u"integer-width/00", | |
82 | u"integer-width/#0", | |
83 | u"integer-width/+00", | |
84 | u"sign-always", | |
85 | u"sign-auto", | |
86 | u"sign-never", | |
87 | u"sign-accounting", | |
88 | u"sign-accounting-always", | |
89 | u"sign-except-zero", | |
90 | u"sign-accounting-except-zero", | |
91 | u"unit-width-narrow", | |
92 | u"unit-width-short", | |
93 | u"unit-width-iso-code", | |
94 | u"unit-width-full-name", | |
95 | u"unit-width-hidden", | |
96 | u"decimal-auto", | |
97 | u"decimal-always", | |
98 | u"scale/5.2", | |
99 | u"scale/-5.2", | |
100 | u"scale/100", | |
101 | u"scale/1E2", | |
102 | u"scale/1", | |
103 | u"latin", | |
104 | u"numbering-system/arab", | |
105 | u"numbering-system/latn", | |
106 | u"precision-integer/@##", | |
107 | u"precision-integer rounding-mode-ceiling", | |
108 | u"precision-currency-cash rounding-mode-ceiling"}; | |
109 | ||
110 | for (auto& cas : cases) { | |
111 | UnicodeString skeletonString(cas); | |
112 | status.setScope(skeletonString); | |
3d1f044b A |
113 | UParseError perror; |
114 | NumberFormatter::forSkeleton(skeletonString, perror, status); | |
0f5d89e8 | 115 | assertSuccess(CStr(skeletonString)(), status, true); |
3d1f044b | 116 | assertEquals(skeletonString, -1, perror.offset); |
0f5d89e8 A |
117 | status.errIfFailureAndReset(); |
118 | } | |
119 | } | |
120 | ||
121 | void NumberSkeletonTest::invalidTokens() { | |
122 | static const char16_t* cases[] = { | |
123 | u".00x", | |
124 | u".00##0", | |
125 | u".##+", | |
126 | u".00##+", | |
127 | u".0#+", | |
128 | u"@@x", | |
129 | u"@@##0", | |
130 | u"@#+", | |
131 | u".00/@", | |
132 | u".00/@@", | |
133 | u".00/@@x", | |
134 | u".00/@@#", | |
135 | u".00/@@#+", | |
136 | u".00/floor/@@+", // wrong order | |
137 | u"precision-increment/français", // non-invariant characters for C++ | |
138 | u"scientific/ee", | |
139 | u"precision-increment/xxx", | |
140 | u"precision-increment/NaN", | |
141 | u"precision-increment/0.1.2", | |
142 | u"scale/xxx", | |
143 | u"scale/NaN", | |
144 | u"scale/0.1.2", | |
145 | u"scale/français", // non-invariant characters for C++ | |
146 | u"currency/dummy", | |
147 | u"currency/ççç", // three characters but not ASCII | |
148 | u"measure-unit/foo", | |
149 | u"integer-width/xxx", | |
150 | u"integer-width/0+", | |
151 | u"integer-width/+0#", | |
340931cb A |
152 | u"integer-width/+#", |
153 | u"integer-width/+#0", | |
0f5d89e8 A |
154 | u"scientific/foo"}; |
155 | ||
156 | expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases)); | |
157 | } | |
158 | ||
159 | void NumberSkeletonTest::unknownTokens() { | |
160 | static const char16_t* cases[] = { | |
161 | u"maesure-unit", | |
162 | u"measure-unit/foo-bar", | |
163 | u"numbering-system/dummy", | |
164 | u"français", | |
165 | u"measure-unit/français-français", // non-invariant characters for C++ | |
166 | u"numbering-system/français", // non-invariant characters for C++ | |
167 | u"currency-USD"}; | |
168 | ||
169 | expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases)); | |
170 | } | |
171 | ||
172 | void NumberSkeletonTest::unexpectedTokens() { | |
173 | static const char16_t* cases[] = { | |
174 | u"group-thousands/foo", | |
175 | u"precision-integer//@## group-off", | |
176 | u"precision-integer//@## group-off", | |
177 | u"precision-integer/ group-off", | |
178 | u"precision-integer// group-off"}; | |
179 | ||
180 | expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases)); | |
181 | } | |
182 | ||
183 | void NumberSkeletonTest::duplicateValues() { | |
184 | static const char16_t* cases[] = { | |
185 | u"precision-integer precision-integer", | |
186 | u"precision-integer .00+", | |
187 | u"precision-integer precision-unlimited", | |
188 | u"precision-integer @@@", | |
189 | u"scientific engineering", | |
190 | u"engineering compact-long", | |
191 | u"sign-auto sign-always"}; | |
192 | ||
193 | expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases)); | |
194 | } | |
195 | ||
196 | void NumberSkeletonTest::stemsRequiringOption() { | |
197 | static const char16_t* stems[] = { | |
198 | u"precision-increment", | |
199 | u"measure-unit", | |
3d1f044b | 200 | u"per-measure-unit", |
0f5d89e8 A |
201 | u"currency", |
202 | u"integer-width", | |
203 | u"numbering-system", | |
204 | u"scale"}; | |
205 | static const char16_t* suffixes[] = {u"", u"/@##", u" scientific", u"/@## scientific"}; | |
206 | ||
207 | for (auto& stem : stems) { | |
208 | for (auto& suffix : suffixes) { | |
209 | UnicodeString skeletonString = UnicodeString(stem) + suffix; | |
210 | UErrorCode status = U_ZERO_ERROR; | |
3d1f044b A |
211 | UParseError perror; |
212 | NumberFormatter::forSkeleton(skeletonString, perror, status); | |
0f5d89e8 | 213 | assertEquals(skeletonString, U_NUMBER_SKELETON_SYNTAX_ERROR, status); |
3d1f044b A |
214 | |
215 | // Check the UParseError for integrity. | |
216 | // If an option is present, the option is wrong; error offset is at the start of the option | |
217 | // If an option is not present, the error offset is at the token separator (end of stem) | |
218 | int32_t expectedOffset = u_strlen(stem) + ((suffix[0] == u'/') ? 1 : 0); | |
219 | assertEquals(skeletonString, expectedOffset, perror.offset); | |
220 | UnicodeString expectedPreContext = skeletonString.tempSubString(0, expectedOffset); | |
221 | if (expectedPreContext.length() >= U_PARSE_CONTEXT_LEN - 1) { | |
222 | expectedPreContext = expectedPreContext.tempSubString(expectedOffset - U_PARSE_CONTEXT_LEN + 1); | |
223 | } | |
224 | assertEquals(skeletonString, expectedPreContext, perror.preContext); | |
225 | UnicodeString expectedPostContext = skeletonString.tempSubString(expectedOffset); | |
226 | // None of the postContext strings in this test exceed U_PARSE_CONTEXT_LEN | |
227 | assertEquals(skeletonString, expectedPostContext, perror.postContext); | |
0f5d89e8 A |
228 | } |
229 | } | |
230 | } | |
231 | ||
232 | void NumberSkeletonTest::defaultTokens() { | |
233 | IcuTestErrorCode status(*this, "defaultTokens"); | |
234 | ||
235 | static const char16_t* cases[] = { | |
236 | u"notation-simple", | |
237 | u"base-unit", | |
238 | u"group-auto", | |
239 | u"integer-width/+0", | |
240 | u"sign-auto", | |
241 | u"unit-width-short", | |
242 | u"decimal-auto"}; | |
243 | ||
244 | for (auto& cas : cases) { | |
245 | UnicodeString skeletonString(cas); | |
246 | status.setScope(skeletonString); | |
247 | UnicodeString normalized = NumberFormatter::forSkeleton( | |
248 | skeletonString, status).toSkeleton(status); | |
249 | // Skeleton should become empty when normalized | |
250 | assertEquals(skeletonString, u"", normalized); | |
251 | status.errIfFailureAndReset(); | |
252 | } | |
253 | } | |
254 | ||
255 | void NumberSkeletonTest::flexibleSeparators() { | |
256 | IcuTestErrorCode status(*this, "flexibleSeparators"); | |
257 | ||
258 | static struct TestCase { | |
259 | const char16_t* skeleton; | |
260 | const char16_t* expected; | |
261 | } cases[] = {{u"precision-integer group-off", u"5142"}, | |
262 | {u"precision-integer group-off", u"5142"}, | |
263 | {u"precision-integer/@## group-off", u"5140"}, | |
264 | {u"precision-integer/@## group-off", u"5140"}}; | |
265 | ||
266 | for (auto& cas : cases) { | |
267 | UnicodeString skeletonString(cas.skeleton); | |
268 | UnicodeString expected(cas.expected); | |
269 | status.setScope(skeletonString); | |
270 | UnicodeString actual = NumberFormatter::forSkeleton(skeletonString, status).locale("en") | |
271 | .formatDouble(5142.3, status) | |
3d1f044b | 272 | .toString(status); |
0f5d89e8 A |
273 | if (!status.errDataIfFailureAndReset()) { |
274 | assertEquals(skeletonString, expected, actual); | |
275 | } | |
276 | status.errIfFailureAndReset(); | |
277 | } | |
278 | } | |
279 | ||
280 | // In C++, there is no distinguishing between "invalid", "unknown", and "unexpected" tokens. | |
281 | void NumberSkeletonTest::expectedErrorSkeleton(const char16_t** cases, int32_t casesLen) { | |
282 | for (int32_t i = 0; i < casesLen; i++) { | |
283 | UnicodeString skeletonString(cases[i]); | |
284 | UErrorCode status = U_ZERO_ERROR; | |
285 | NumberFormatter::forSkeleton(skeletonString, status); | |
286 | assertEquals(skeletonString, U_NUMBER_SKELETON_SYNTAX_ERROR, status); | |
287 | } | |
288 | } | |
289 | ||
290 | ||
291 | #endif /* #if !UCONFIG_NO_FORMATTING */ |