]>
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#", | |
152 | u"scientific/foo"}; | |
153 | ||
154 | expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases)); | |
155 | } | |
156 | ||
157 | void NumberSkeletonTest::unknownTokens() { | |
158 | static const char16_t* cases[] = { | |
159 | u"maesure-unit", | |
160 | u"measure-unit/foo-bar", | |
161 | u"numbering-system/dummy", | |
162 | u"français", | |
163 | u"measure-unit/français-français", // non-invariant characters for C++ | |
164 | u"numbering-system/français", // non-invariant characters for C++ | |
165 | u"currency-USD"}; | |
166 | ||
167 | expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases)); | |
168 | } | |
169 | ||
170 | void NumberSkeletonTest::unexpectedTokens() { | |
171 | static const char16_t* cases[] = { | |
172 | u"group-thousands/foo", | |
173 | u"precision-integer//@## group-off", | |
174 | u"precision-integer//@## group-off", | |
175 | u"precision-integer/ group-off", | |
176 | u"precision-integer// group-off"}; | |
177 | ||
178 | expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases)); | |
179 | } | |
180 | ||
181 | void NumberSkeletonTest::duplicateValues() { | |
182 | static const char16_t* cases[] = { | |
183 | u"precision-integer precision-integer", | |
184 | u"precision-integer .00+", | |
185 | u"precision-integer precision-unlimited", | |
186 | u"precision-integer @@@", | |
187 | u"scientific engineering", | |
188 | u"engineering compact-long", | |
189 | u"sign-auto sign-always"}; | |
190 | ||
191 | expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases)); | |
192 | } | |
193 | ||
194 | void NumberSkeletonTest::stemsRequiringOption() { | |
195 | static const char16_t* stems[] = { | |
196 | u"precision-increment", | |
197 | u"measure-unit", | |
3d1f044b | 198 | u"per-measure-unit", |
0f5d89e8 A |
199 | u"currency", |
200 | u"integer-width", | |
201 | u"numbering-system", | |
202 | u"scale"}; | |
203 | static const char16_t* suffixes[] = {u"", u"/@##", u" scientific", u"/@## scientific"}; | |
204 | ||
205 | for (auto& stem : stems) { | |
206 | for (auto& suffix : suffixes) { | |
207 | UnicodeString skeletonString = UnicodeString(stem) + suffix; | |
208 | UErrorCode status = U_ZERO_ERROR; | |
3d1f044b A |
209 | UParseError perror; |
210 | NumberFormatter::forSkeleton(skeletonString, perror, status); | |
0f5d89e8 | 211 | assertEquals(skeletonString, U_NUMBER_SKELETON_SYNTAX_ERROR, status); |
3d1f044b A |
212 | |
213 | // Check the UParseError for integrity. | |
214 | // If an option is present, the option is wrong; error offset is at the start of the option | |
215 | // If an option is not present, the error offset is at the token separator (end of stem) | |
216 | int32_t expectedOffset = u_strlen(stem) + ((suffix[0] == u'/') ? 1 : 0); | |
217 | assertEquals(skeletonString, expectedOffset, perror.offset); | |
218 | UnicodeString expectedPreContext = skeletonString.tempSubString(0, expectedOffset); | |
219 | if (expectedPreContext.length() >= U_PARSE_CONTEXT_LEN - 1) { | |
220 | expectedPreContext = expectedPreContext.tempSubString(expectedOffset - U_PARSE_CONTEXT_LEN + 1); | |
221 | } | |
222 | assertEquals(skeletonString, expectedPreContext, perror.preContext); | |
223 | UnicodeString expectedPostContext = skeletonString.tempSubString(expectedOffset); | |
224 | // None of the postContext strings in this test exceed U_PARSE_CONTEXT_LEN | |
225 | assertEquals(skeletonString, expectedPostContext, perror.postContext); | |
0f5d89e8 A |
226 | } |
227 | } | |
228 | } | |
229 | ||
230 | void NumberSkeletonTest::defaultTokens() { | |
231 | IcuTestErrorCode status(*this, "defaultTokens"); | |
232 | ||
233 | static const char16_t* cases[] = { | |
234 | u"notation-simple", | |
235 | u"base-unit", | |
236 | u"group-auto", | |
237 | u"integer-width/+0", | |
238 | u"sign-auto", | |
239 | u"unit-width-short", | |
240 | u"decimal-auto"}; | |
241 | ||
242 | for (auto& cas : cases) { | |
243 | UnicodeString skeletonString(cas); | |
244 | status.setScope(skeletonString); | |
245 | UnicodeString normalized = NumberFormatter::forSkeleton( | |
246 | skeletonString, status).toSkeleton(status); | |
247 | // Skeleton should become empty when normalized | |
248 | assertEquals(skeletonString, u"", normalized); | |
249 | status.errIfFailureAndReset(); | |
250 | } | |
251 | } | |
252 | ||
253 | void NumberSkeletonTest::flexibleSeparators() { | |
254 | IcuTestErrorCode status(*this, "flexibleSeparators"); | |
255 | ||
256 | static struct TestCase { | |
257 | const char16_t* skeleton; | |
258 | const char16_t* expected; | |
259 | } cases[] = {{u"precision-integer group-off", u"5142"}, | |
260 | {u"precision-integer group-off", u"5142"}, | |
261 | {u"precision-integer/@## group-off", u"5140"}, | |
262 | {u"precision-integer/@## group-off", u"5140"}}; | |
263 | ||
264 | for (auto& cas : cases) { | |
265 | UnicodeString skeletonString(cas.skeleton); | |
266 | UnicodeString expected(cas.expected); | |
267 | status.setScope(skeletonString); | |
268 | UnicodeString actual = NumberFormatter::forSkeleton(skeletonString, status).locale("en") | |
269 | .formatDouble(5142.3, status) | |
3d1f044b | 270 | .toString(status); |
0f5d89e8 A |
271 | if (!status.errDataIfFailureAndReset()) { |
272 | assertEquals(skeletonString, expected, actual); | |
273 | } | |
274 | status.errIfFailureAndReset(); | |
275 | } | |
276 | } | |
277 | ||
278 | // In C++, there is no distinguishing between "invalid", "unknown", and "unexpected" tokens. | |
279 | void NumberSkeletonTest::expectedErrorSkeleton(const char16_t** cases, int32_t casesLen) { | |
280 | for (int32_t i = 0; i < casesLen; i++) { | |
281 | UnicodeString skeletonString(cases[i]); | |
282 | UErrorCode status = U_ZERO_ERROR; | |
283 | NumberFormatter::forSkeleton(skeletonString, status); | |
284 | assertEquals(skeletonString, U_NUMBER_SKELETON_SYNTAX_ERROR, status); | |
285 | } | |
286 | } | |
287 | ||
288 | ||
289 | #endif /* #if !UCONFIG_NO_FORMATTING */ |