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
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)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(wildcardCharacters);
33 TESTCASE_AUTO(perUnitInArabic);
34 TESTCASE_AUTO_END;
35 }
36
validTokens()37 void NumberSkeletonTest::validTokens() {
38 IcuTestErrorCode status(*this, "validTokens");
39
40 // This tests only if the tokens are valid, not their behavior.
41 // Most of these are from the design doc.
42 static const char16_t* cases[] = {
43 u"precision-integer",
44 u"precision-unlimited",
45 u"@@@##",
46 u"@@*",
47 u"@@+",
48 u".000##",
49 u".00*",
50 u".00+",
51 u".",
52 u".*",
53 u".+",
54 u".######",
55 u".00/@@*",
56 u".00/@@+",
57 u".00/@##",
58 u"precision-increment/3.14",
59 u"precision-currency-standard",
60 u"precision-integer rounding-mode-half-up",
61 u".00# rounding-mode-ceiling",
62 u".00/@@* rounding-mode-floor",
63 u".00/@@+ rounding-mode-floor",
64 u"scientific",
65 u"scientific/*ee",
66 u"scientific/+ee",
67 u"scientific/sign-always",
68 u"scientific/*ee/sign-always",
69 u"scientific/+ee/sign-always",
70 u"scientific/sign-always/*ee",
71 u"scientific/sign-always/+ee",
72 u"scientific/sign-except-zero",
73 u"engineering",
74 u"engineering/*eee",
75 u"engineering/+eee",
76 u"compact-short",
77 u"compact-long",
78 u"notation-simple",
79 u"percent",
80 u"permille",
81 u"measure-unit/length-meter",
82 u"measure-unit/area-square-meter",
83 u"measure-unit/energy-joule per-measure-unit/length-meter",
84 u"unit/square-meter-per-square-meter",
85 u"currency/XXX",
86 u"currency/ZZZ",
87 u"currency/usd",
88 u"group-off",
89 u"group-min2",
90 u"group-auto",
91 u"group-on-aligned",
92 u"group-thousands",
93 u"integer-width/00",
94 u"integer-width/#0",
95 u"integer-width/*00",
96 u"integer-width/+00",
97 u"sign-always",
98 u"sign-auto",
99 u"sign-never",
100 u"sign-accounting",
101 u"sign-accounting-always",
102 u"sign-except-zero",
103 u"sign-accounting-except-zero",
104 u"unit-width-narrow",
105 u"unit-width-short",
106 u"unit-width-iso-code",
107 u"unit-width-full-name",
108 u"unit-width-hidden",
109 u"decimal-auto",
110 u"decimal-always",
111 u"scale/5.2",
112 u"scale/-5.2",
113 u"scale/100",
114 u"scale/1E2",
115 u"scale/1",
116 u"latin",
117 u"numbering-system/arab",
118 u"numbering-system/latn",
119 u"precision-integer/@##",
120 u"precision-integer rounding-mode-ceiling",
121 u"precision-currency-cash rounding-mode-ceiling",
122 u"0",
123 u"00",
124 u"000",
125 u"E0",
126 u"E00",
127 u"E000",
128 u"EE0",
129 u"EE00",
130 u"EE+?0",
131 u"EE+?00",
132 u"EE+!0",
133 u"EE+!00",
134 };
135
136 for (auto& cas : cases) {
137 UnicodeString skeletonString(cas);
138 status.setScope(skeletonString);
139 UParseError perror;
140 NumberFormatter::forSkeleton(skeletonString, perror, status);
141 assertSuccess(CStr(skeletonString)(), status, true);
142 assertEquals(skeletonString, -1, perror.offset);
143 status.errIfFailureAndReset();
144 }
145 }
146
invalidTokens()147 void NumberSkeletonTest::invalidTokens() {
148 static const char16_t* cases[] = {
149 u".00x",
150 u".00##0",
151 u".##*",
152 u".00##*",
153 u".0#*",
154 u"@#*",
155 u".##+",
156 u".00##+",
157 u".0#+",
158 u"@#+",
159 u"@@x",
160 u"@@##0",
161 u".00/@",
162 u".00/@@",
163 u".00/@@x",
164 u".00/@@#",
165 u".00/@@#*",
166 u".00/floor/@@*", // wrong order
167 u".00/@@#+",
168 u".00/floor/@@+", // wrong order
169 u"precision-increment/français", // non-invariant characters for C++
170 u"scientific/ee",
171 u"precision-increment/xxx",
172 u"precision-increment/NaN",
173 u"precision-increment/0.1.2",
174 u"scale/xxx",
175 u"scale/NaN",
176 u"scale/0.1.2",
177 u"scale/français", // non-invariant characters for C++
178 u"currency/dummy",
179 u"currency/ççç", // three characters but not ASCII
180 u"measure-unit/foo",
181 u"integer-width/xxx",
182 u"integer-width/0*",
183 u"integer-width/*0#",
184 u"integer-width/*#",
185 u"integer-width/*#0",
186 u"integer-width/0+",
187 u"integer-width/+0#",
188 u"integer-width/+#",
189 u"integer-width/+#0",
190 u"scientific/foo",
191 u"E",
192 u"E1",
193 u"E+",
194 u"E+?",
195 u"E+!",
196 u"E+0",
197 u"EE",
198 u"EE+",
199 u"EEE",
200 u"EEE0",
201 u"001",
202 u"00*",
203 u"00+",
204 };
205
206 expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases));
207 }
208
unknownTokens()209 void NumberSkeletonTest::unknownTokens() {
210 static const char16_t* cases[] = {
211 u"maesure-unit",
212 u"measure-unit/foo-bar",
213 u"numbering-system/dummy",
214 u"français",
215 u"measure-unit/français-français", // non-invariant characters for C++
216 u"numbering-system/français", // non-invariant characters for C++
217 u"currency-USD"};
218
219 expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases));
220 }
221
unexpectedTokens()222 void NumberSkeletonTest::unexpectedTokens() {
223 static const char16_t* cases[] = {
224 u"group-thousands/foo",
225 u"precision-integer//@## group-off",
226 u"precision-integer//@## group-off",
227 u"precision-integer/ group-off",
228 u"precision-integer// group-off"};
229
230 expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases));
231 }
232
duplicateValues()233 void NumberSkeletonTest::duplicateValues() {
234 static const char16_t* cases[] = {
235 u"precision-integer precision-integer",
236 u"precision-integer .00+",
237 u"precision-integer precision-unlimited",
238 u"precision-integer @@@",
239 u"scientific engineering",
240 u"engineering compact-long",
241 u"sign-auto sign-always"};
242
243 expectedErrorSkeleton(cases, UPRV_LENGTHOF(cases));
244 }
245
stemsRequiringOption()246 void NumberSkeletonTest::stemsRequiringOption() {
247 static const char16_t* stems[] = {
248 u"precision-increment",
249 u"measure-unit",
250 u"per-measure-unit",
251 u"currency",
252 u"integer-width",
253 u"numbering-system",
254 u"scale"};
255 static const char16_t* suffixes[] = {u"", u"/@##", u" scientific", u"/@## scientific"};
256
257 for (auto& stem : stems) {
258 for (auto& suffix : suffixes) {
259 UnicodeString skeletonString = UnicodeString(stem) + suffix;
260 UErrorCode status = U_ZERO_ERROR;
261 UParseError perror;
262 NumberFormatter::forSkeleton(skeletonString, perror, status);
263 assertEquals(skeletonString, U_NUMBER_SKELETON_SYNTAX_ERROR, status);
264
265 // Check the UParseError for integrity.
266 // If an option is present, the option is wrong; error offset is at the start of the option
267 // If an option is not present, the error offset is at the token separator (end of stem)
268 int32_t expectedOffset = u_strlen(stem) + ((suffix[0] == u'/') ? 1 : 0);
269 assertEquals(skeletonString, expectedOffset, perror.offset);
270 UnicodeString expectedPreContext = skeletonString.tempSubString(0, expectedOffset);
271 if (expectedPreContext.length() >= U_PARSE_CONTEXT_LEN - 1) {
272 expectedPreContext = expectedPreContext.tempSubString(expectedOffset - U_PARSE_CONTEXT_LEN + 1);
273 }
274 assertEquals(skeletonString, expectedPreContext, perror.preContext);
275 UnicodeString expectedPostContext = skeletonString.tempSubString(expectedOffset);
276 // None of the postContext strings in this test exceed U_PARSE_CONTEXT_LEN
277 assertEquals(skeletonString, expectedPostContext, perror.postContext);
278 }
279 }
280 }
281
defaultTokens()282 void NumberSkeletonTest::defaultTokens() {
283 IcuTestErrorCode status(*this, "defaultTokens");
284
285 static const char16_t* cases[] = {
286 u"notation-simple",
287 u"base-unit",
288 u"group-auto",
289 u"integer-width/+0",
290 u"sign-auto",
291 u"unit-width-short",
292 u"decimal-auto"};
293
294 for (auto& cas : cases) {
295 UnicodeString skeletonString(cas);
296 status.setScope(skeletonString);
297 UnicodeString normalized = NumberFormatter::forSkeleton(
298 skeletonString, status).toSkeleton(status);
299 // Skeleton should become empty when normalized
300 assertEquals(skeletonString, u"", normalized);
301 status.errIfFailureAndReset();
302 }
303 }
304
flexibleSeparators()305 void NumberSkeletonTest::flexibleSeparators() {
306 IcuTestErrorCode status(*this, "flexibleSeparators");
307
308 static struct TestCase {
309 const char16_t* skeleton;
310 const char16_t* expected;
311 } cases[] = {{u"precision-integer group-off", u"5142"},
312 {u"precision-integer group-off", u"5142"},
313 {u"precision-integer/@## group-off", u"5140"},
314 {u"precision-integer/@## group-off", u"5140"}};
315
316 for (auto& cas : cases) {
317 UnicodeString skeletonString(cas.skeleton);
318 UnicodeString expected(cas.expected);
319 status.setScope(skeletonString);
320 UnicodeString actual = NumberFormatter::forSkeleton(skeletonString, status).locale("en")
321 .formatDouble(5142.3, status)
322 .toString(status);
323 if (!status.errDataIfFailureAndReset()) {
324 assertEquals(skeletonString, expected, actual);
325 }
326 status.errIfFailureAndReset();
327 }
328 }
329
wildcardCharacters()330 void NumberSkeletonTest::wildcardCharacters() {
331 IcuTestErrorCode status(*this, "wildcardCharacters");
332
333 struct TestCase {
334 const char16_t* star;
335 const char16_t* plus;
336 } cases[] = {
337 { u".00*", u".00+" },
338 { u"@@*", u"@@+" },
339 { u".00/@@*", u".00/@@+" },
340 { u"scientific/*ee", u"scientific/+ee" },
341 { u"integer-width/*00", u"integer-width/+00" },
342 };
343
344 for (const auto& cas : cases) {
345 UnicodeString star(cas.star);
346 UnicodeString plus(cas.plus);
347 status.setScope(star);
348
349 UnicodeString normalized = NumberFormatter::forSkeleton(plus, status)
350 .toSkeleton(status);
351 assertEquals("Plus should normalize to star", star, normalized);
352 status.errIfFailureAndReset();
353 }
354 }
355
356 // In C++, there is no distinguishing between "invalid", "unknown", and "unexpected" tokens.
expectedErrorSkeleton(const char16_t ** cases,int32_t casesLen)357 void NumberSkeletonTest::expectedErrorSkeleton(const char16_t** cases, int32_t casesLen) {
358 for (int32_t i = 0; i < casesLen; i++) {
359 UnicodeString skeletonString(cases[i]);
360 UErrorCode status = U_ZERO_ERROR;
361 NumberFormatter::forSkeleton(skeletonString, status);
362 assertEquals(skeletonString, U_NUMBER_SKELETON_SYNTAX_ERROR, status);
363 }
364 }
365
perUnitInArabic()366 void NumberSkeletonTest::perUnitInArabic() {
367 IcuTestErrorCode status(*this, "perUnitInArabic");
368
369 struct TestCase {
370 const char16_t* type;
371 const char16_t* subtype;
372 } cases[] = {
373 {u"area", u"acre"},
374 {u"digital", u"bit"},
375 {u"digital", u"byte"},
376 {u"temperature", u"celsius"},
377 {u"length", u"centimeter"},
378 {u"duration", u"day"},
379 {u"angle", u"degree"},
380 {u"temperature", u"fahrenheit"},
381 {u"volume", u"fluid-ounce"},
382 {u"length", u"foot"},
383 {u"volume", u"gallon"},
384 {u"digital", u"gigabit"},
385 {u"digital", u"gigabyte"},
386 {u"mass", u"gram"},
387 {u"area", u"hectare"},
388 {u"duration", u"hour"},
389 {u"length", u"inch"},
390 {u"digital", u"kilobit"},
391 {u"digital", u"kilobyte"},
392 {u"mass", u"kilogram"},
393 {u"length", u"kilometer"},
394 {u"volume", u"liter"},
395 {u"digital", u"megabit"},
396 {u"digital", u"megabyte"},
397 {u"length", u"meter"},
398 {u"length", u"mile"},
399 {u"length", u"mile-scandinavian"},
400 {u"volume", u"milliliter"},
401 {u"length", u"millimeter"},
402 {u"duration", u"millisecond"},
403 {u"duration", u"minute"},
404 {u"duration", u"month"},
405 {u"mass", u"ounce"},
406 {u"concentr", u"percent"},
407 {u"digital", u"petabyte"},
408 {u"mass", u"pound"},
409 {u"duration", u"second"},
410 {u"mass", u"stone"},
411 {u"digital", u"terabit"},
412 {u"digital", u"terabyte"},
413 {u"duration", u"week"},
414 {u"length", u"yard"},
415 {u"duration", u"year"},
416 };
417
418 for (const auto& cas1 : cases) {
419 for (const auto& cas2 : cases) {
420 UnicodeString skeleton(u"measure-unit/");
421 skeleton += cas1.type;
422 skeleton += u"-";
423 skeleton += cas1.subtype;
424 skeleton += u" ";
425 skeleton += u"per-measure-unit/";
426 skeleton += cas2.type;
427 skeleton += u"-";
428 skeleton += cas2.subtype;
429
430 status.setScope(skeleton);
431 UnicodeString actual = NumberFormatter::forSkeleton(skeleton, status).locale("ar")
432 .formatDouble(5142.3, status)
433 .toString(status);
434 status.errIfFailureAndReset();
435 }
436 }
437 }
438
439 #endif /* #if !UCONFIG_NO_FORMATTING */
440