1/*
2*******************************************************************************
3* Copyright (C) 2007-2012, International Business Machines Corporation and
4* others. All Rights Reserved.
5********************************************************************************
6
7* File PLURRULTS.cpp
8*
9********************************************************************************
10*/
11
12#include "unicode/utypes.h"
13
14#if !UCONFIG_NO_FORMATTING
15
16#include <stdlib.h> // for strtod
17#include "plurults.h"
18#include "unicode/localpointer.h"
19#include "unicode/plurrule.h"
20
21#define LENGTHOF(array) (int32_t)(sizeof(array)/sizeof(array[0]))
22
23void setupResult(const int32_t testSource[], char result[], int32_t* max);
24UBool checkEqual(const PluralRules &test, char *result, int32_t max);
25UBool testEquality(const PluralRules &test);
26
27// This is an API test, not a unit test.  It doesn't test very many cases, and doesn't
28// try to test the full functionality.  It just calls each function in the class and
29// verifies that it works on a basic level.
30
31void PluralRulesTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
32{
33    if (exec) logln("TestSuite PluralRulesAPI");
34    TESTCASE_AUTO_BEGIN;
35    TESTCASE_AUTO(testAPI);
36    TESTCASE_AUTO(testGetUniqueKeywordValue);
37    TESTCASE_AUTO(testGetSamples);
38    TESTCASE_AUTO(testWithin);
39    TESTCASE_AUTO(testGetAllKeywordValues);
40    TESTCASE_AUTO(testOrdinal);
41    TESTCASE_AUTO_END;
42}
43
44#define PLURAL_TEST_NUM    18
45/**
46 * Test various generic API methods of PluralRules for API coverage.
47 */
48void PluralRulesTest::testAPI(/*char *par*/)
49{
50    UnicodeString pluralTestData[PLURAL_TEST_NUM] = {
51            UNICODE_STRING_SIMPLE("a: n is 1"),
52            UNICODE_STRING_SIMPLE("a: n mod 10 is 2"),
53            UNICODE_STRING_SIMPLE("a: n is not 1"),
54            UNICODE_STRING_SIMPLE("a: n mod 3 is not 1"),
55            UNICODE_STRING_SIMPLE("a: n in 2..5"),
56            UNICODE_STRING_SIMPLE("a: n within 2..5"),
57            UNICODE_STRING_SIMPLE("a: n not in 2..5"),
58            UNICODE_STRING_SIMPLE("a: n not within 2..5"),
59            UNICODE_STRING_SIMPLE("a: n mod 10 in 2..5"),
60            UNICODE_STRING_SIMPLE("a: n mod 10 within 2..5"),
61            UNICODE_STRING_SIMPLE("a: n mod 10 is 2 and n is not 12"),
62            UNICODE_STRING_SIMPLE("a: n mod 10 in 2..3 or n mod 10 is 5"),
63            UNICODE_STRING_SIMPLE("a: n mod 10 within 2..3 or n mod 10 is 5"),
64            UNICODE_STRING_SIMPLE("a: n is 1 or n is 4 or n is 23"),
65            UNICODE_STRING_SIMPLE("a: n mod 2 is 1 and n is not 3 and n in 1..11"),
66            UNICODE_STRING_SIMPLE("a: n mod 2 is 1 and n is not 3 and n within 1..11"),
67            UNICODE_STRING_SIMPLE("a: n mod 2 is 1 or n mod 5 is 1 and n is not 6"),
68            "",
69    };
70    static const int32_t pluralTestResult[PLURAL_TEST_NUM][30] = {
71        {1, 0},
72        {2,12,22, 0},
73        {0,2,3,4,5,0},
74        {0,2,3,5,6,8,9,0},
75        {2,3,4,5,0},
76        {2,3,4,5,0},
77        {0,1,6,7,8, 0},
78        {0,1,6,7,8, 0},
79        {2,3,4,5,12,13,14,15,22,23,24,25,0},
80        {2,3,4,5,12,13,14,15,22,23,24,25,0},
81        {2,22,32,42,0},
82        {2,3,5,12,13,15,22,23,25,0},
83        {2,3,5,12,13,15,22,23,25,0},
84        {1,4,23,0},
85        {1,5,7,9,11,0},
86        {1,5,7,9,11,0},
87        {1,3,5,7,9,11,13,15,16,0},
88    };
89    UErrorCode status = U_ZERO_ERROR;
90
91    // ======= Test constructors
92    logln("Testing PluralRules constructors");
93
94
95    logln("\n start default locale test case ..\n");
96
97    PluralRules defRule(status);
98    LocalPointer<PluralRules> test(new PluralRules(status));
99    LocalPointer<PluralRules> newEnPlural(test->forLocale(Locale::getEnglish(), status));
100    if(U_FAILURE(status)) {
101        dataerrln("ERROR: Could not create PluralRules (default) - exitting");
102        return;
103    }
104
105    // ======= Test clone, assignment operator && == operator.
106    LocalPointer<PluralRules> dupRule(defRule.clone());
107    if (dupRule==NULL) {
108        errln("ERROR: clone plural rules test failed!");
109        return;
110    } else {
111        if ( *dupRule != defRule ) {
112            errln("ERROR:  clone plural rules test failed!");
113        }
114    }
115    *dupRule = *newEnPlural;
116    if (dupRule!=NULL) {
117        if ( *dupRule != *newEnPlural ) {
118            errln("ERROR:  clone plural rules test failed!");
119        }
120    }
121
122    // ======= Test empty plural rules
123    logln("Testing Simple PluralRules");
124
125    LocalPointer<PluralRules> empRule(test->createRules(UNICODE_STRING_SIMPLE("a:n"), status));
126    UnicodeString key;
127    for (int32_t i=0; i<10; ++i) {
128        key = empRule->select(i);
129        if ( key.charAt(0)!= 0x61 ) { // 'a'
130            errln("ERROR:  empty plural rules test failed! - exitting");
131        }
132    }
133
134    // ======= Test simple plural rules
135    logln("Testing Simple PluralRules");
136
137    char result[100];
138    int32_t max;
139
140    for (int32_t i=0; i<PLURAL_TEST_NUM-1; ++i) {
141       LocalPointer<PluralRules> newRules(test->createRules(pluralTestData[i], status));
142       setupResult(pluralTestResult[i], result, &max);
143       if ( !checkEqual(*newRules, result, max) ) {
144            errln("ERROR:  simple plural rules failed! - exitting");
145            return;
146        }
147    }
148
149    // ======= Test complex plural rules
150    logln("Testing Complex PluralRules");
151    // TODO: the complex test data is hard coded. It's better to implement
152    // a parser to parse the test data.
153    UnicodeString complexRule = UNICODE_STRING_SIMPLE("a: n in 2..5; b: n in 5..8; c: n mod 2 is 1");
154    UnicodeString complexRule2 = UNICODE_STRING_SIMPLE("a: n within 2..5; b: n within 5..8; c: n mod 2 is 1");
155    char cRuleResult[] =
156    {
157       0x6F, // 'o'
158       0x63, // 'c'
159       0x61, // 'a'
160       0x61, // 'a'
161       0x61, // 'a'
162       0x61, // 'a'
163       0x62, // 'b'
164       0x62, // 'b'
165       0x62, // 'b'
166       0x63, // 'c'
167       0x6F, // 'o'
168       0x63  // 'c'
169    };
170    LocalPointer<PluralRules> newRules(test->createRules(complexRule, status));
171    if ( !checkEqual(*newRules, cRuleResult, 12) ) {
172         errln("ERROR:  complex plural rules failed! - exitting");
173         return;
174    }
175    newRules.adoptInstead(test->createRules(complexRule2, status));
176    if ( !checkEqual(*newRules, cRuleResult, 12) ) {
177         errln("ERROR:  complex plural rules failed! - exitting");
178         return;
179    }
180
181    // ======= Test decimal fractions plural rules
182    UnicodeString decimalRule= UNICODE_STRING_SIMPLE("a: n not in 0..100;");
183    UnicodeString KEYWORD_A = UNICODE_STRING_SIMPLE("a");
184    status = U_ZERO_ERROR;
185    newRules.adoptInstead(test->createRules(decimalRule, status));
186    if (U_FAILURE(status)) {
187        dataerrln("ERROR: Could not create PluralRules for testing fractions - exitting");
188        return;
189    }
190    double fData[10] = {-100, -1, -0.0, 0, 0.1, 1, 1.999, 2.0, 100, 100.001 };
191    UBool isKeywordA[10] = {
192           TRUE, TRUE, FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, TRUE };
193    for (int32_t i=0; i<10; i++) {
194        if ((newRules->select(fData[i])== KEYWORD_A) != isKeywordA[i]) {
195             errln("ERROR: plural rules for decimal fractions test failed!");
196        }
197    }
198
199    // ======= Test Equality
200    logln("Testing Equality of PluralRules");
201
202    if ( !testEquality(*test) ) {
203         errln("ERROR:  complex plural rules failed! - exitting");
204         return;
205     }
206
207
208    // ======= Test getStaticClassID()
209    logln("Testing getStaticClassID()");
210
211    if(test->getDynamicClassID() != PluralRules::getStaticClassID()) {
212        errln("ERROR: getDynamicClassID() didn't return the expected value");
213    }
214    // ====== Test fallback to parent locale
215    LocalPointer<PluralRules> en_UK(test->forLocale(Locale::getUK(), status));
216    LocalPointer<PluralRules> en(test->forLocale(Locale::getEnglish(), status));
217    if (en_UK.isValid() && en.isValid()) {
218        if ( *en_UK != *en ) {
219            errln("ERROR:  test locale fallback failed!");
220        }
221    }
222
223    LocalPointer<PluralRules> zh_Hant(test->forLocale(Locale::getTaiwan(), status));
224    LocalPointer<PluralRules> zh(test->forLocale(Locale::getChinese(), status));
225    if (zh_Hant.isValid() && zh.isValid()) {
226        if ( *zh_Hant != *zh ) {
227            errln("ERROR:  test locale fallback failed!");
228        }
229    }
230}
231
232void setupResult(const int32_t testSource[], char result[], int32_t* max) {
233    int32_t i=0;
234    int32_t curIndex=0;
235
236    do {
237        while (curIndex < testSource[i]) {
238            result[curIndex++]=0x6F; //'o' other
239        }
240        result[curIndex++]=0x61; // 'a'
241
242    } while(testSource[++i]>0);
243    *max=curIndex;
244}
245
246
247UBool checkEqual(const PluralRules &test, char *result, int32_t max) {
248    UnicodeString key;
249    UBool isEqual = TRUE;
250    for (int32_t i=0; i<max; ++i) {
251        key= test.select(i);
252        if ( key.charAt(0)!=result[i] ) {
253            isEqual = FALSE;
254        }
255    }
256    return isEqual;
257}
258
259#define MAX_EQ_ROW  2
260#define MAX_EQ_COL  5
261UBool testEquality(const PluralRules &test) {
262    UnicodeString testEquRules[MAX_EQ_ROW][MAX_EQ_COL] = {
263        {   UNICODE_STRING_SIMPLE("a: n in 2..3"),
264            UNICODE_STRING_SIMPLE("a: n is 2 or n is 3"),
265            UNICODE_STRING_SIMPLE( "a:n is 3 and n in 2..5 or n is 2"),
266            "",
267        },
268        {   UNICODE_STRING_SIMPLE("a: n is 12; b:n mod 10 in 2..3"),
269            UNICODE_STRING_SIMPLE("b: n mod 10 in 2..3 and n is not 12; a: n in 12..12"),
270            UNICODE_STRING_SIMPLE("b: n is 13; a: n in 12..13; b: n mod 10 is 2 or n mod 10 is 3"),
271            "",
272        }
273    };
274    UErrorCode status = U_ZERO_ERROR;
275    UnicodeString key[MAX_EQ_COL];
276    UBool ret=TRUE;
277    for (int32_t i=0; i<MAX_EQ_ROW; ++i) {
278        PluralRules* rules[MAX_EQ_COL];
279
280        for (int32_t j=0; j<MAX_EQ_COL; ++j) {
281            rules[j]=NULL;
282        }
283        int32_t totalRules=0;
284        while((totalRules<MAX_EQ_COL) && (testEquRules[i][totalRules].length()>0) ) {
285            rules[totalRules]=test.createRules(testEquRules[i][totalRules], status);
286            totalRules++;
287        }
288        for (int32_t n=0; n<300 && ret ; ++n) {
289            for(int32_t j=0; j<totalRules;++j) {
290                key[j] = rules[j]->select(n);
291            }
292            for(int32_t j=0; j<totalRules-1;++j) {
293                if (key[j]!=key[j+1]) {
294                    ret= FALSE;
295                    break;
296                }
297            }
298
299        }
300        for (int32_t j=0; j<MAX_EQ_COL; ++j) {
301            if (rules[j]!=NULL) {
302                delete rules[j];
303            }
304        }
305    }
306
307    return ret;
308}
309
310void
311PluralRulesTest::assertRuleValue(const UnicodeString& rule, double expected) {
312  assertRuleKeyValue("a:" + rule, "a", expected);
313}
314
315void
316PluralRulesTest::assertRuleKeyValue(const UnicodeString& rule,
317                                    const UnicodeString& key, double expected) {
318  UErrorCode status = U_ZERO_ERROR;
319  PluralRules *pr = PluralRules::createRules(rule, status);
320  double result = pr->getUniqueKeywordValue(key);
321  delete pr;
322  if (expected != result) {
323    errln("expected %g but got %g", expected, result);
324  }
325}
326
327void PluralRulesTest::testGetUniqueKeywordValue() {
328  assertRuleValue("n is 1", 1);
329  assertRuleValue("n in 2..2", 2);
330  assertRuleValue("n within 2..2", 2);
331  assertRuleValue("n in 3..4", UPLRULES_NO_UNIQUE_VALUE);
332  assertRuleValue("n within 3..4", UPLRULES_NO_UNIQUE_VALUE);
333  assertRuleValue("n is 2 or n is 2", 2);
334  assertRuleValue("n is 2 and n is 2", 2);
335  assertRuleValue("n is 2 or n is 3", UPLRULES_NO_UNIQUE_VALUE);
336  assertRuleValue("n is 2 and n is 3", UPLRULES_NO_UNIQUE_VALUE);
337  assertRuleValue("n is 2 or n in 2..3", UPLRULES_NO_UNIQUE_VALUE);
338  assertRuleValue("n is 2 and n in 2..3", 2);
339  assertRuleKeyValue("a: n is 1", "not_defined", UPLRULES_NO_UNIQUE_VALUE); // key not defined
340  assertRuleKeyValue("a: n is 1", "other", UPLRULES_NO_UNIQUE_VALUE); // key matches default rule
341}
342
343void PluralRulesTest::testGetSamples() {
344  // no get functional equivalent API in ICU4C, so just
345  // test every locale...
346  UErrorCode status = U_ZERO_ERROR;
347  int32_t numLocales;
348  const Locale* locales = Locale::getAvailableLocales(numLocales);
349
350  double values[4];
351  for (int32_t i = 0; U_SUCCESS(status) && i < numLocales; ++i) {
352    PluralRules *rules = PluralRules::forLocale(locales[i], status);
353    if (U_FAILURE(status)) {
354      break;
355    }
356    StringEnumeration *keywords = rules->getKeywords(status);
357    if (U_FAILURE(status)) {
358      delete rules;
359      break;
360    }
361    const UnicodeString* keyword;
362    while (NULL != (keyword = keywords->snext(status))) {
363      int32_t count = rules->getSamples(*keyword, values, 4, status);
364      if (U_FAILURE(status)) {
365        errln(UNICODE_STRING_SIMPLE("getSamples() failed for locale ") +
366              locales[i].getName() +
367              UNICODE_STRING_SIMPLE(", keyword ") + *keyword);
368        continue;
369      }
370      if (count == 0) {
371        errln("no samples for keyword");
372      }
373      if (count > LENGTHOF(values)) {
374        errln(UNICODE_STRING_SIMPLE("getSamples()=") + count +
375              UNICODE_STRING_SIMPLE(", too many values, for locale ") +
376              locales[i].getName() +
377              UNICODE_STRING_SIMPLE(", keyword ") + *keyword);
378        count = LENGTHOF(values);
379      }
380      for (int32_t j = 0; j < count; ++j) {
381        if (values[j] == UPLRULES_NO_UNIQUE_VALUE) {
382          errln("got 'no unique value' among values");
383        } else {
384          UnicodeString resultKeyword = rules->select(values[j]);
385          if (*keyword != resultKeyword) {
386            errln("keywords don't match");
387          }
388        }
389      }
390    }
391    delete keywords;
392    delete rules;
393  }
394}
395
396void PluralRulesTest::testWithin() {
397  // goes to show you what lack of testing will do.
398  // of course, this has been broken for two years and no one has noticed...
399  UErrorCode status = U_ZERO_ERROR;
400  PluralRules *rules = PluralRules::createRules("a: n mod 10 in 5..8", status);
401  if (!rules) {
402    errln("couldn't instantiate rules");
403    return;
404  }
405
406  UnicodeString keyword = rules->select((int32_t)26);
407  if (keyword != "a") {
408    errln("expected 'a' for 26 but didn't get it.");
409  }
410
411  keyword = rules->select(26.5);
412  if (keyword != "other") {
413    errln("expected 'other' for 26.5 but didn't get it.");
414  }
415
416  delete rules;
417}
418
419void
420PluralRulesTest::testGetAllKeywordValues() {
421    const char* data[] = {
422        "a: n in 2..5", "a: 2,3,4,5; other: null; b:",
423        "a: n not in 2..5", "a: null; other: null",
424        "a: n within 2..5", "a: null; other: null",
425        "a: n not within 2..5", "a: null; other: null",
426        "a: n in 2..5 or n within 6..8", "a: null", // ignore 'other' here on out, always null
427        "a: n in 2..5 and n within 6..8", "a:",
428        "a: n in 2..5 and n within 5..8", "a: 5",
429        "a: n within 2..5 and n within 6..8", "a:", // our sampling catches these
430        "a: n within 2..5 and n within 5..8", "a: 5", // ''
431        "a: n within 1..2 and n within 2..3 or n within 3..4 and n within 4..5", "a: 2,4",
432        "a: n within 1..2 and n within 2..3 or n within 3..4 and n within 4..5 "
433          "or n within 5..6 and n within 6..7", "a: null", // but not this...
434        "a: n mod 3 is 0", "a: null",
435        "a: n mod 3 is 0 and n within 1..2", "a:",
436        "a: n mod 3 is 0 and n within 0..5", "a: 0,3",
437        "a: n mod 3 is 0 and n within 0..6", "a: null", // similarly with mod, we don't catch...
438        "a: n mod 3 is 0 and n in 3..12", "a: 3,6,9,12",
439        NULL
440    };
441
442    for (int i = 0; data[i] != NULL; i += 2) {
443        UErrorCode status = U_ZERO_ERROR;
444        UnicodeString ruleDescription(data[i], -1, US_INV);
445        const char* result = data[i+1];
446
447        logln("[%d] %s", i >> 1, data[i]);
448
449        PluralRules *p = PluralRules::createRules(ruleDescription, status);
450        if (U_FAILURE(status)) {
451            logln("could not create rules from '%s'\n", data[i]);
452            continue;
453        }
454
455        const char* rp = result;
456        while (*rp) {
457            while (*rp == ' ') ++rp;
458            if (!rp) {
459                break;
460            }
461
462            const char* ep = rp;
463            while (*ep && *ep != ':') ++ep;
464
465            status = U_ZERO_ERROR;
466            UnicodeString keyword(rp, ep - rp, US_INV);
467            double samples[4]; // no test above should have more samples than 4
468            int32_t count = p->getAllKeywordValues(keyword, &samples[0], 4, status);
469            if (U_FAILURE(status)) {
470                errln("error getting samples for %s", rp);
471                break;
472            }
473
474            if (count > 4) {
475              errln("count > 4 for keyword %s", rp);
476              count = 4;
477            }
478
479            if (*ep) {
480                ++ep; // skip colon
481                while (*ep && *ep == ' ') ++ep; // and spaces
482            }
483
484            UBool ok = TRUE;
485            if (count == -1) {
486                if (*ep != 'n') {
487                    errln("expected values for keyword %s but got -1 (%s)", rp, ep);
488                    ok = FALSE;
489                }
490            } else if (*ep == 'n') {
491                errln("expected count of -1, got %d, for keyword %s (%s)", count, rp, ep);
492                ok = FALSE;
493            }
494
495            // We'll cheat a bit here.  The samples happend to be in order and so are our
496            // expected values, so we'll just test in order until a failure.  If the
497            // implementation changes to return samples in an arbitrary order, this test
498            // must change.  There's no actual restriction on the order of the samples.
499
500            for (int j = 0; ok && j < count; ++j ) { // we've verified count < 4
501                double val = samples[j];
502                if (*ep == 0 || *ep == ';') {
503                    errln("got unexpected value[%d]: %g", j, val);
504                    ok = FALSE;
505                    break;
506                }
507                char* xp;
508                double expectedVal = strtod(ep, &xp);
509                if (xp == ep) {
510                    // internal error
511                    errln("yikes!");
512                    ok = FALSE;
513                    break;
514                }
515                ep = xp;
516                if (expectedVal != val) {
517                    errln("expected %g but got %g", expectedVal, val);
518                    ok = FALSE;
519                    break;
520                }
521                if (*ep == ',') ++ep;
522            }
523
524            if (ok && count != -1) {
525                if (!(*ep == 0 || *ep == ';')) {
526                    errln("didn't get expected value: %s", ep);
527                    ok = FALSE;
528                }
529            }
530
531            while (*ep && *ep != ';') ++ep;
532            if (*ep == ';') ++ep;
533            rp = ep;
534        }
535        delete p;
536    }
537}
538
539void PluralRulesTest::testOrdinal() {
540    IcuTestErrorCode errorCode(*this, "testOrdinal");
541    LocalPointer<PluralRules> pr(PluralRules::forLocale("en", UPLURAL_TYPE_ORDINAL, errorCode));
542    if (errorCode.logIfFailureAndReset("PluralRules::forLocale(en, UPLURAL_TYPE_ORDINAL) failed")) {
543        return;
544    }
545    UnicodeString keyword = pr->select(2.);
546    if (keyword != UNICODE_STRING("two", 3)) {
547        dataerrln("PluralRules(en-ordinal).select(2) failed");
548    }
549}
550
551#endif /* #if !UCONFIG_NO_FORMATTING */
552