• 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_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