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