1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /********************************************************************
4  * Copyright (c) 2011-2014, International Business Machines Corporation
5  * and others. All Rights Reserved.
6  ********************************************************************/
7 /* C API TEST FOR PLURAL RULES */
8 
9 #include "unicode/utypes.h"
10 
11 #if !UCONFIG_NO_FORMATTING
12 
13 #include "unicode/upluralrules.h"
14 #include "unicode/ustring.h"
15 #include "unicode/uenum.h"
16 #include "unicode/unumberformatter.h"
17 #include "unicode/unumberrangeformatter.h"
18 #include "cintltst.h"
19 #include "cmemory.h"
20 #include "cstring.h"
21 
22 static void TestPluralRules(void);
23 static void TestOrdinalRules(void);
24 static void TestGetKeywords(void);
25 static void TestFormatted(void);
26 static void TestSelectRange(void);
27 
28 void addPluralRulesTest(TestNode** root);
29 
30 #define TESTCASE(x) addTest(root, &x, "tsformat/cpluralrulestest/" #x)
31 
addPluralRulesTest(TestNode ** root)32 void addPluralRulesTest(TestNode** root)
33 {
34     TESTCASE(TestPluralRules);
35     TESTCASE(TestOrdinalRules);
36     TESTCASE(TestGetKeywords);
37     TESTCASE(TestFormatted);
38     TESTCASE(TestSelectRange);
39 }
40 
41 typedef struct {
42     const char * locale;
43     double       number;
44     const char * keywordExpected;
45     const char * keywordExpectedForDecimals;
46 } PluralRulesTestItem;
47 
48 /* Just a small set of tests for now, other functionality is tested in the C++ tests */
49 static const PluralRulesTestItem testItems[] = {
50     { "en",   0, "other", "other" },
51     { "en", 0.5, "other", "other" },
52     { "en",   1, "one",   "other" },
53     { "en", 1.5, "other", "other" },
54     { "en",   2, "other", "other" },
55     { "fr",   0, "one",   "one" },
56     { "fr", 0.5, "one",   "one" },
57     { "fr",   1, "one",   "one" },
58     { "fr", 1.5, "one",   "one" },
59     { "fr",   2, "other", "other" },
60     { "ru",   0, "many",  "other" },
61     { "ru", 0.5, "other", "other" },
62     { "ru",   1, "one",   "other" },
63     { "ru", 1.5, "other", "other" },
64     { "ru",   2, "few",   "other" },
65     { "ru",   5, "many",  "other" },
66     { "ru",  10, "many",  "other" },
67     { "ru",  11, "many",  "other" },
68     { NULL,   0, NULL,    NULL }
69 };
70 
71 static const UChar twoDecimalPat[] = { 0x23,0x30,0x2E,0x30,0x30,0 }; /* "#0.00" */
72 
73 enum {
74     kKeywordBufLen = 32
75 };
76 
TestPluralRules()77 static void TestPluralRules()
78 {
79     const PluralRulesTestItem * testItemPtr;
80     log_verbose("\nTesting uplrules_open() and uplrules_select() with various parameters\n");
81     for ( testItemPtr = testItems; testItemPtr->locale != NULL; ++testItemPtr ) {
82         UErrorCode status = U_ZERO_ERROR;
83         UPluralRules* uplrules = uplrules_open(testItemPtr->locale, &status);
84         if ( U_SUCCESS(status) ) {
85             UNumberFormat* unumfmt;
86             UChar keyword[kKeywordBufLen];
87             UChar keywordExpected[kKeywordBufLen];
88             int32_t keywdLen = uplrules_select(uplrules, testItemPtr->number, keyword, kKeywordBufLen, &status);
89             if (keywdLen >= kKeywordBufLen) {
90                 keyword[kKeywordBufLen-1] = 0;
91             }
92             if ( U_SUCCESS(status) ) {
93                 u_unescape(testItemPtr->keywordExpected, keywordExpected, kKeywordBufLen);
94                 if ( u_strcmp(keyword, keywordExpected) != 0 ) {
95                     char bcharBuf[kKeywordBufLen];
96                     log_data_err("ERROR: uplrules_select for locale %s, number %.1f: expect %s, get %s\n",
97                              testItemPtr->locale, testItemPtr->number, testItemPtr->keywordExpected, u_austrcpy(bcharBuf,keyword) );
98                 }
99             } else {
100                 log_err("FAIL: uplrules_select for locale %s, number %.1f: %s\n",
101                         testItemPtr->locale, testItemPtr->number, myErrorName(status) );
102             }
103 
104             status = U_ZERO_ERROR;
105             unumfmt = unum_open(UNUM_PATTERN_DECIMAL, twoDecimalPat, -1, testItemPtr->locale, NULL, &status);
106             if ( U_SUCCESS(status) ) {
107                 keywdLen = uplrules_selectWithFormat(uplrules, testItemPtr->number, unumfmt, keyword, kKeywordBufLen, &status);
108                 if (keywdLen >= kKeywordBufLen) {
109                     keyword[kKeywordBufLen-1] = 0;
110                 }
111                 if ( U_SUCCESS(status) ) {
112                     u_unescape(testItemPtr->keywordExpectedForDecimals, keywordExpected, kKeywordBufLen);
113                     if ( u_strcmp(keyword, keywordExpected) != 0 ) {
114                         char bcharBuf[kKeywordBufLen];
115                         log_data_err("ERROR: uplrules_selectWithFormat for locale %s, number %.1f: expect %s, get %s\n",
116                                  testItemPtr->locale, testItemPtr->number, testItemPtr->keywordExpectedForDecimals, u_austrcpy(bcharBuf,keyword) );
117                     }
118                 } else {
119                     log_err("FAIL: uplrules_selectWithFormat for locale %s, number %.1f: %s\n",
120                             testItemPtr->locale, testItemPtr->number, myErrorName(status) );
121                 }
122                 unum_close(unumfmt);
123             } else {
124                 log_err("FAIL: unum_open for locale %s: %s\n", testItemPtr->locale, myErrorName(status) );
125             }
126 
127             uplrules_close(uplrules);
128         } else {
129             log_err("FAIL: uplrules_open for locale %s: %s\n", testItemPtr->locale, myErrorName(status) );
130         }
131     }
132 }
133 
TestOrdinalRules()134 static void TestOrdinalRules() {
135     U_STRING_DECL(two, "two", 3);
136     UChar keyword[8];
137     int32_t length;
138     UErrorCode errorCode = U_ZERO_ERROR;
139     UPluralRules* upr = uplrules_openForType("en", UPLURAL_TYPE_ORDINAL, &errorCode);
140     if (U_FAILURE(errorCode)) {
141         log_err("uplrules_openForType(en, ordinal) failed - %s\n", u_errorName(errorCode));
142         return;
143     }
144     U_STRING_INIT(two, "two", 3);
145     length = uplrules_select(upr, 2., keyword, 8, &errorCode);
146     if (U_FAILURE(errorCode) || u_strCompare(keyword, length, two, 3, FALSE) != 0) {
147         log_data_err("uplrules_select(en-ordinal, 2) failed - %s\n", u_errorName(errorCode));
148     }
149     uplrules_close(upr);
150 }
151 
152 /* items for TestGetKeywords */
153 
154 /* all possible plural keywords, in alphabetical order */
155 static const char* knownKeywords[] = {
156     "few",
157     "many",
158     "one",
159     "other",
160     "two",
161     "zero"
162 };
163 enum {
164     kNumKeywords = UPRV_LENGTHOF(knownKeywords)
165 };
166 
167 /* Return the index of keyword in knownKeywords[], or -1 if not found */
getKeywordIndex(const char * keyword)168 static int32_t getKeywordIndex(const char* keyword) {
169     int32_t i, compare;
170     for (i = 0; i < kNumKeywords && (compare = uprv_strcmp(keyword,knownKeywords[i])) >= 0; i++) {
171         if (compare == 0) {
172         	return i;
173         }
174     }
175     return -1;
176 }
177 
178 typedef struct {
179     const char* locale;
180     const char* keywords[kNumKeywords + 1];
181 } KeywordsForLang;
182 
183 static const KeywordsForLang getKeywordsItems[] = {
184     { "zh", { "other" } },
185     { "en", { "one", "other" } },
186     { "fr", { "one", "many", "other" } },
187     { "lv", { "zero", "one", "other" } },
188     { "hr", { "one", "few", "other" } },
189     { "sl", { "one", "two", "few", "other" } },
190     { "he", { "one", "two", "many", "other" } },
191     { "cs", { "one", "few", "many", "other" } },
192     { "ar", { "zero", "one", "two", "few", "many" , "other" } },
193     { NULL, { NULL } }
194 };
195 
TestGetKeywords()196 static void TestGetKeywords() {
197     /*
198      * We don't know the order in which the enumeration will return keywords,
199      * so we have an array with known keywords in a fixed order and then
200      * parallel arrays of flags for expected and actual results that indicate
201      * which keywords are expected to be or actually are found.
202      */
203     const KeywordsForLang* itemPtr = getKeywordsItems;
204     for (; itemPtr->locale != NULL; itemPtr++) {
205         UPluralRules* uplrules;
206         UEnumeration* uenum;
207         UBool expectKeywords[kNumKeywords];
208         UBool getKeywords[kNumKeywords];
209         int32_t i, iKnown;
210         UErrorCode status = U_ZERO_ERROR;
211 
212         /* initialize arrays for expected and get results */
213         for (i = 0; i < kNumKeywords; i++) {
214             expectKeywords[i] = FALSE;
215             getKeywords[i] = FALSE;
216         }
217         for (i = 0; i < kNumKeywords && itemPtr->keywords[i] != NULL; i++) {
218             iKnown = getKeywordIndex(itemPtr->keywords[i]);
219             if (iKnown >= 0) {
220                 expectKeywords[iKnown] = TRUE;
221             }
222         }
223 
224         uplrules = uplrules_openForType(itemPtr->locale, UPLURAL_TYPE_CARDINAL, &status);
225         if (U_FAILURE(status)) {
226             log_err("FAIL: uplrules_openForType for locale %s, UPLURAL_TYPE_CARDINAL: %s\n", itemPtr->locale, myErrorName(status) );
227             continue;
228         }
229         uenum = uplrules_getKeywords(uplrules, &status);
230         if (U_FAILURE(status)) {
231             log_err("FAIL: uplrules_getKeywords for locale %s: %s\n", itemPtr->locale, myErrorName(status) );
232         } else {
233             const char* keyword;
234             int32_t keywordLen, keywordCount = 0;
235             while ((keyword = uenum_next(uenum, &keywordLen, &status)) != NULL && U_SUCCESS(status)) {
236                 iKnown = getKeywordIndex(keyword);
237                 if (iKnown < 0) {
238                     log_err("FAIL: uplrules_getKeywords for locale %s, unknown keyword %s\n", itemPtr->locale, keyword );
239                 } else {
240                     getKeywords[iKnown] = TRUE;
241                 }
242                 keywordCount++;
243             }
244             if (keywordCount > kNumKeywords) {
245                 log_err("FAIL: uplrules_getKeywords for locale %s, got too many keywords %d\n", itemPtr->locale, keywordCount );
246             }
247             if (uprv_memcmp(expectKeywords, getKeywords, kNumKeywords) != 0) {
248                 log_err("FAIL: uplrules_getKeywords for locale %s, got wrong keyword set; with reference to knownKeywords:\n"
249                         "        expected { %d %d %d %d %d %d },\n"
250                         "        got      { %d %d %d %d %d %d }\n", itemPtr->locale,
251                         expectKeywords[0], expectKeywords[1], expectKeywords[2], expectKeywords[3], expectKeywords[4], expectKeywords[5],
252                         getKeywords[0], getKeywords[1], getKeywords[2], getKeywords[3], getKeywords[4], getKeywords[5] );
253             }
254             uenum_close(uenum);
255         }
256 
257         uplrules_close(uplrules);
258     }
259 }
260 
TestFormatted()261 static void TestFormatted() {
262     UErrorCode ec = U_ZERO_ERROR;
263     UNumberFormatter* unumf = NULL;
264     UFormattedNumber* uresult = NULL;
265     UPluralRules* uplrules = NULL;
266 
267     uplrules = uplrules_open("hr", &ec);
268     if (!assertSuccess("open plural rules", &ec)) {
269         goto cleanup;
270     }
271 
272     unumf = unumf_openForSkeletonAndLocale(u".00", -1, "hr", &ec);
273     if (!assertSuccess("open unumf", &ec)) {
274         goto cleanup;
275     }
276 
277     uresult = unumf_openResult(&ec);
278     if (!assertSuccess("open result", &ec)) {
279         goto cleanup;
280     }
281 
282     unumf_formatDouble(unumf, 100.2, uresult, &ec);
283     if (!assertSuccess("format", &ec)) {
284         goto cleanup;
285     }
286 
287     UChar buffer[40];
288     uplrules_selectFormatted(uplrules, uresult, buffer, 40, &ec);
289     if (!assertSuccess("select", &ec)) {
290         goto cleanup;
291     }
292 
293     assertUEquals("0.20 is plural category 'other' in hr", u"other", buffer);
294 
295 cleanup:
296     uplrules_close(uplrules);
297     unumf_close(unumf);
298     unumf_closeResult(uresult);
299 }
300 
TestSelectRange()301 static void TestSelectRange() {
302     UErrorCode ec = U_ZERO_ERROR;
303     UNumberRangeFormatter* unumrf = NULL;
304     UFormattedNumberRange* uresult = NULL;
305     UPluralRules* uplrules = NULL;
306 
307     int32_t d1 = 102;
308     int32_t d2 = 201;
309 
310     // Locale sl has interesting data: one + two => few
311     uplrules = uplrules_open("sl", &ec);
312     if (!assertSuccess("open plural rules", &ec)) {
313         goto cleanup;
314     }
315 
316     unumrf = unumrf_openForSkeletonWithCollapseAndIdentityFallback(
317         u"",
318         0,
319         UNUM_RANGE_COLLAPSE_AUTO,
320         UNUM_IDENTITY_FALLBACK_APPROXIMATELY,
321         "sl",
322         NULL,
323         &ec);
324     if (!assertSuccess("open unumrf", &ec)) {
325         goto cleanup;
326     }
327 
328     uresult = unumrf_openResult(&ec);
329     if (!assertSuccess("open result", &ec)) {
330         goto cleanup;
331     }
332 
333     unumrf_formatDoubleRange(unumrf, d1, d2, uresult, &ec);
334     if (!assertSuccess("format", &ec)) {
335         goto cleanup;
336     }
337 
338     UChar buffer[40];
339     int32_t len = uplrules_selectForRange(uplrules, uresult, buffer, 40, &ec);
340     if (!assertSuccess("select", &ec)) {
341         goto cleanup;
342     }
343 
344     assertUEquals("102-201 is plural category 'few' in sl", u"few", buffer);
345     assertIntEquals("Length should be as expected", u_strlen(buffer), len);
346 
347 cleanup:
348     uplrules_close(uplrules);
349     unumrf_close(unumrf);
350     unumrf_closeResult(uresult);
351 }
352 
353 #endif /* #if !UCONFIG_NO_FORMATTING */
354