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