Home | History | Annotate | Download | only in intltest
      1 /*
      2 **********************************************************************
      3 * Copyright (C) 2011-2015, International Business Machines Corporation
      4 * and others.  All Rights Reserved.
      5 **********************************************************************
      6 */
      7 /**
      8  * IntlTestSpoof tests for USpoofDetector
      9  */
     10 
     11 #include "unicode/utypes.h"
     12 
     13 #if !UCONFIG_NO_REGULAR_EXPRESSIONS && !UCONFIG_NO_NORMALIZATION && !UCONFIG_NO_FILE_IO
     14 
     15 #include "itspoof.h"
     16 
     17 #include "unicode/normlzr.h"
     18 #include "unicode/regex.h"
     19 #include "unicode/unistr.h"
     20 #include "unicode/uscript.h"
     21 #include "unicode/uspoof.h"
     22 
     23 #include "cstring.h"
     24 #include "identifier_info.h"
     25 #include "scriptset.h"
     26 #include "uhash.h"
     27 
     28 #include <stdlib.h>
     29 #include <stdio.h>
     30 
     31 #define TEST_ASSERT_SUCCESS(status) {if (U_FAILURE(status)) { \
     32     errcheckln(status, "Failure at file %s, line %d, error = %s", __FILE__, __LINE__, u_errorName(status));}}
     33 
     34 #define TEST_ASSERT(expr) {if ((expr)==FALSE) { \
     35     errln("Test Failure at file %s, line %d: \"%s\" is false.", __FILE__, __LINE__, #expr);};}
     36 
     37 #define TEST_ASSERT_MSG(expr, msg) {if ((expr)==FALSE) { \
     38     dataerrln("Test Failure at file %s, line %d, %s: \"%s\" is false.", __FILE__, __LINE__, msg, #expr);};}
     39 
     40 #define TEST_ASSERT_EQ(a, b) { if ((a) != (b)) { \
     41     errln("Test Failure at file %s, line %d: \"%s\" (%d) != \"%s\" (%d)", \
     42              __FILE__, __LINE__, #a, (a), #b, (b)); }}
     43 
     44 #define TEST_ASSERT_NE(a, b) { if ((a) == (b)) { \
     45     errln("Test Failure at file %s, line %d: \"%s\" (%d) == \"%s\" (%d)", \
     46              __FILE__, __LINE__, #a, (a), #b, (b)); }}
     47 
     48 /*
     49  *   TEST_SETUP and TEST_TEARDOWN
     50  *         macros to handle the boilerplate around setting up test case.
     51  *         Put arbitrary test code between SETUP and TEARDOWN.
     52  *         "sc" is the ready-to-go  SpoofChecker for use in the tests.
     53  */
     54 #define TEST_SETUP {  \
     55     UErrorCode status = U_ZERO_ERROR; \
     56     USpoofChecker *sc;     \
     57     sc = uspoof_open(&status);  \
     58     TEST_ASSERT_SUCCESS(status);   \
     59     if (U_SUCCESS(status)){
     60 
     61 #define TEST_TEARDOWN  \
     62     }  \
     63     TEST_ASSERT_SUCCESS(status);  \
     64     uspoof_close(sc);  \
     65 }
     66 
     67 
     68 
     69 
     70 void IntlTestSpoof::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
     71 {
     72     if (exec) logln("TestSuite spoof: ");
     73     switch (index) {
     74         case 0:
     75             name = "TestSpoofAPI";
     76             if (exec) {
     77                 testSpoofAPI();
     78             }
     79             break;
     80         case 1:
     81             name = "TestSkeleton";
     82             if (exec) {
     83                 testSkeleton();
     84             }
     85             break;
     86         case 2:
     87             name = "TestAreConfusable";
     88             if (exec) {
     89                 testAreConfusable();
     90             }
     91             break;
     92         case 3:
     93             name = "TestInvisible";
     94             if (exec) {
     95                 testInvisible();
     96             }
     97             break;
     98         case 4:
     99             name = "testConfData";
    100             if (exec) {
    101                 testConfData();
    102             }
    103             break;
    104         case 5:
    105             name = "testBug8654";
    106             if (exec) {
    107                 testBug8654();
    108             }
    109             break;
    110         case 6:
    111             name = "testIdentifierInfo";
    112             if (exec) {
    113                 testIdentifierInfo();
    114             }
    115             break;
    116         case 7:
    117             name = "testScriptSet";
    118             if (exec) {
    119                 testScriptSet();
    120             }
    121             break;
    122         case 8:
    123             name = "testRestrictionLevel";
    124             if (exec) {
    125                 testRestrictionLevel();
    126             }
    127             break;
    128        case 9:
    129             name = "testMixedNumbers";
    130             if (exec) {
    131                 testMixedNumbers();
    132             }
    133             break;
    134 
    135 
    136         default: name=""; break;
    137     }
    138 }
    139 
    140 void IntlTestSpoof::testSpoofAPI() {
    141 
    142     TEST_SETUP
    143         UnicodeString s("xyz");  // Many latin ranges are whole-script confusable with other scripts.
    144                                  // If this test starts failing, consult confusablesWholeScript.txt
    145         int32_t position = 666;
    146         int32_t checkResults = uspoof_checkUnicodeString(sc, s, &position, &status);
    147         TEST_ASSERT_SUCCESS(status);
    148         TEST_ASSERT_EQ(0, checkResults);
    149         TEST_ASSERT_EQ(0, position);
    150     TEST_TEARDOWN;
    151 
    152     TEST_SETUP
    153         UnicodeString s1("cxs");
    154         UnicodeString s2 = UnicodeString("\\u0441\\u0445\\u0455").unescape();  // Cyrillic "cxs"
    155         int32_t checkResults = uspoof_areConfusableUnicodeString(sc, s1, s2, &status);
    156         TEST_ASSERT_EQ(USPOOF_MIXED_SCRIPT_CONFUSABLE | USPOOF_WHOLE_SCRIPT_CONFUSABLE, checkResults);
    157 
    158     TEST_TEARDOWN;
    159 
    160     TEST_SETUP
    161         UnicodeString s("I1l0O");
    162         UnicodeString dest;
    163         UnicodeString &retStr = uspoof_getSkeletonUnicodeString(sc, USPOOF_ANY_CASE, s, dest, &status);
    164         TEST_ASSERT_SUCCESS(status);
    165         TEST_ASSERT(UnicodeString("lllOO") == dest);
    166         TEST_ASSERT(&dest == &retStr);
    167     TEST_TEARDOWN;
    168 }
    169 
    170 
    171 #define CHECK_SKELETON(type, input, expected) { \
    172     checkSkeleton(sc, type, input, expected, __LINE__); \
    173     }
    174 
    175 
    176 // testSkeleton.   Spot check a number of confusable skeleton substitutions from the
    177 //                 Unicode data file confusables.txt
    178 //                 Test cases chosen for substitutions of various lengths, and
    179 //                 membership in different mapping tables.
    180 //          Note: for ICU 55, all tables collapsed to the MA table data.
    181 //          TODO: for ICU 56 with Unicode 8, revisit this test.
    182 //
    183 void IntlTestSpoof::testSkeleton() {
    184     const uint32_t ML = 0;
    185     const uint32_t SL = USPOOF_SINGLE_SCRIPT_CONFUSABLE;
    186     const uint32_t MA = USPOOF_ANY_CASE;
    187     const uint32_t SA = USPOOF_SINGLE_SCRIPT_CONFUSABLE | USPOOF_ANY_CASE;
    188 
    189     TEST_SETUP
    190         CHECK_SKELETON(SL, "nochange", "nochange");
    191         CHECK_SKELETON(SA, "nochange", "nochange");
    192         CHECK_SKELETON(ML, "nochange", "nochange");
    193         CHECK_SKELETON(MA, "nochange", "nochange");
    194         CHECK_SKELETON(MA, "love", "love");
    195         CHECK_SKELETON(MA, "1ove", "love");   // Digit 1 to letter l
    196         CHECK_SKELETON(ML, "OOPS", "OOPS");
    197         CHECK_SKELETON(ML, "00PS", "OOPS");
    198         CHECK_SKELETON(MA, "OOPS", "OOPS");
    199         CHECK_SKELETON(MA, "00PS", "OOPS");   // Digit 0 to letter O in any case mode only
    200         CHECK_SKELETON(SL, "\\u059c", "\\u0301");
    201         CHECK_SKELETON(SL, "\\u2A74", "\\u003A\\u003A\\u003D");
    202         CHECK_SKELETON(SL, "\\u247E", "\\u0028\\u006C\\u006C\\u0029");  // "(ll)"
    203         CHECK_SKELETON(SL, "\\uFDFB", "\\u062C\\u0644\\u0020\\u062C\\u0644\\u006c\\u0644\\u006f");
    204 
    205         // This mapping exists in the ML and MA tables, does not exist in SL, SA
    206         // 0C83 ;	0983 ;	ML
    207         // 0C83 ;	0983 ;	MA
    208         //
    209 
    210         CHECK_SKELETON(SL, "\\u0C83", "\\u0983");
    211         CHECK_SKELETON(SA, "\\u0C83", "\\u0983");
    212         CHECK_SKELETON(ML, "\\u0C83", "\\u0983");
    213         CHECK_SKELETON(MA, "\\u0C83", "\\u0983");
    214 
    215         // 0391 mappings exist only in MA and SA tables.
    216         CHECK_SKELETON(MA, "\\u0391", "A");
    217         CHECK_SKELETON(SA, "\\u0391", "A");
    218         CHECK_SKELETON(ML, "\\u0391", "A");
    219         CHECK_SKELETON(SL, "\\u0391", "A");
    220 
    221         // 13CF Mappings in all four tables, different in MA.
    222         CHECK_SKELETON(ML, "\\u13CF", "b");
    223         CHECK_SKELETON(MA, "\\u13CF", "b");
    224         CHECK_SKELETON(SL, "\\u13CF", "b");
    225         CHECK_SKELETON(SA, "\\u13CF", "b");
    226 
    227         // 0022 ;  0027 0027 ;
    228         // all tables.
    229         CHECK_SKELETON(SL, "\\u0022", "\\u0027\\u0027");
    230         CHECK_SKELETON(SA, "\\u0022", "\\u0027\\u0027");
    231         CHECK_SKELETON(ML, "\\u0022", "\\u0027\\u0027");
    232         CHECK_SKELETON(MA, "\\u0022", "\\u0027\\u0027");
    233 
    234         // 017F mappings exist only in MA and SA tables.
    235         CHECK_SKELETON(MA, "\\u017F", "f");
    236         CHECK_SKELETON(SA, "\\u017F", "f");
    237         CHECK_SKELETON(ML, "\\u017F", "f");
    238         CHECK_SKELETON(SL, "\\u017F", "f");
    239 
    240     TEST_TEARDOWN;
    241 }
    242 
    243 
    244 //
    245 //  Run a single confusable skeleton transformation test case.
    246 //
    247 void IntlTestSpoof::checkSkeleton(const USpoofChecker *sc, uint32_t type,
    248                                   const char *input, const char *expected, int32_t lineNum) {
    249     UnicodeString uInput = UnicodeString(input).unescape();
    250     UnicodeString uExpected = UnicodeString(expected).unescape();
    251 
    252     UErrorCode status = U_ZERO_ERROR;
    253     UnicodeString actual;
    254     uspoof_getSkeletonUnicodeString(sc, type, uInput, actual, &status);
    255     if (U_FAILURE(status)) {
    256         errln("File %s, Line %d, Test case from line %d, status is %s", __FILE__, __LINE__, lineNum,
    257               u_errorName(status));
    258         return;
    259     }
    260     if (uExpected != actual) {
    261         errln("File %s, Line %d, Test case from line %d, Actual and Expected skeletons differ.",
    262                __FILE__, __LINE__, lineNum);
    263         errln(UnicodeString(" Actual   Skeleton: \"") + actual + UnicodeString("\"\n") +
    264               UnicodeString(" Expected Skeleton: \"") + uExpected + UnicodeString("\""));
    265     }
    266 }
    267 
    268 void IntlTestSpoof::testAreConfusable() {
    269     TEST_SETUP
    270         UnicodeString s1("A long string that will overflow stack buffers.  A long string that will overflow stack buffers. "
    271                          "A long string that will overflow stack buffers.  A long string that will overflow stack buffers. ");
    272         UnicodeString s2("A long string that wi11 overflow stack buffers.  A long string that will overflow stack buffers. "
    273                          "A long string that wi11 overflow stack buffers.  A long string that will overflow stack buffers. ");
    274         TEST_ASSERT_EQ(USPOOF_SINGLE_SCRIPT_CONFUSABLE, uspoof_areConfusableUnicodeString(sc, s1, s2, &status));
    275         TEST_ASSERT_SUCCESS(status);
    276 
    277     TEST_TEARDOWN;
    278 }
    279 
    280 void IntlTestSpoof::testInvisible() {
    281     TEST_SETUP
    282         UnicodeString  s = UnicodeString("abcd\\u0301ef").unescape();
    283         int32_t position = -42;
    284         TEST_ASSERT_EQ(0, uspoof_checkUnicodeString(sc, s, &position, &status));
    285         TEST_ASSERT_SUCCESS(status);
    286         TEST_ASSERT(0 == position);
    287 
    288         UnicodeString  s2 = UnicodeString("abcd\\u0301\\u0302\\u0301ef").unescape();
    289         TEST_ASSERT_EQ(USPOOF_INVISIBLE, uspoof_checkUnicodeString(sc, s2, &position, &status));
    290         TEST_ASSERT_SUCCESS(status);
    291         TEST_ASSERT_EQ(0, position);
    292 
    293         // Two acute accents, one from the composed a with acute accent, \u00e1,
    294         // and one separate.
    295         position = -42;
    296         UnicodeString  s3 = UnicodeString("abcd\\u00e1\\u0301xyz").unescape();
    297         TEST_ASSERT_EQ(USPOOF_INVISIBLE, uspoof_checkUnicodeString(sc, s3, &position, &status));
    298         TEST_ASSERT_SUCCESS(status);
    299         TEST_ASSERT_EQ(0, position);
    300     TEST_TEARDOWN;
    301 }
    302 
    303 void IntlTestSpoof::testBug8654() {
    304     TEST_SETUP
    305         UnicodeString s = UnicodeString("B\\u00c1\\u0301").unescape();
    306         int32_t position = -42;
    307         TEST_ASSERT_EQ(USPOOF_INVISIBLE, uspoof_checkUnicodeString(sc, s, &position, &status) & USPOOF_INVISIBLE );
    308         TEST_ASSERT_SUCCESS(status);
    309         TEST_ASSERT_EQ(0, position);
    310     TEST_TEARDOWN;
    311 }
    312 
    313 static UnicodeString parseHex(const UnicodeString &in) {
    314     // Convert a series of hex numbers in a Unicode String to a string with the
    315     // corresponding characters.
    316     // The conversion is _really_ annoying.  There must be some function to just do it.
    317     UnicodeString result;
    318     UChar32 cc = 0;
    319     for (int32_t i=0; i<in.length(); i++) {
    320         UChar c = in.charAt(i);
    321         if (c == 0x20) {   // Space
    322             if (cc > 0) {
    323                result.append(cc);
    324                cc = 0;
    325             }
    326         } else if (c>=0x30 && c<=0x39) {
    327             cc = (cc<<4) + (c - 0x30);
    328         } else if ((c>=0x41 && c<=0x46) || (c>=0x61 && c<=0x66)) {
    329             cc = (cc<<4) + (c & 0x0f)+9;
    330         }
    331         // else do something with bad input.
    332     }
    333     if (cc > 0) {
    334         result.append(cc);
    335     }
    336     return result;
    337 }
    338 
    339 
    340 //
    341 // Append the hex form of a UChar32 to a UnicodeString.
    342 // Used in formatting error messages.
    343 // Match the formatting of numbers in confusables.txt
    344 // Minimum of 4 digits, no leading zeroes for positions 5 and up.
    345 //
    346 static void appendHexUChar(UnicodeString &dest, UChar32 c) {
    347     UBool   doZeroes = FALSE;
    348     for (int bitNum=28; bitNum>=0; bitNum-=4) {
    349         if (bitNum <= 12) {
    350             doZeroes = TRUE;
    351         }
    352         int hexDigit = (c>>bitNum) & 0x0f;
    353         if (hexDigit != 0 || doZeroes) {
    354             doZeroes = TRUE;
    355             dest.append((UChar)(hexDigit<=9? hexDigit + 0x30: hexDigit -10 + 0x41));
    356         }
    357     }
    358     dest.append((UChar)0x20);
    359 }
    360 
    361 U_DEFINE_LOCAL_OPEN_POINTER(LocalStdioFilePointer, FILE, fclose);
    362 
    363 //  testConfData - Check each data item from the Unicode confusables.txt file,
    364 //                 verify that it transforms correctly in a skeleton.
    365 //
    366 void IntlTestSpoof::testConfData() {
    367     char buffer[2000];
    368     if (getUnidataPath(buffer) == NULL) {
    369         errln("Skipping test spoof/testConfData. Unable to find path to source/data/unidata/.");
    370         return;
    371     }
    372     uprv_strcat(buffer, "confusables.txt");
    373 
    374     LocalStdioFilePointer f(fopen(buffer, "rb"));
    375     if (f.isNull()) {
    376         errln("Skipping test spoof/testConfData.  File confusables.txt not accessible.");
    377         return;
    378     }
    379     fseek(f.getAlias(), 0, SEEK_END);
    380     int32_t  fileSize = ftell(f.getAlias());
    381     LocalArray<char> fileBuf(new char[fileSize]);
    382     fseek(f.getAlias(), 0, SEEK_SET);
    383     int32_t amt_read = fread(fileBuf.getAlias(), 1, fileSize, f.getAlias());
    384     TEST_ASSERT_EQ(amt_read, fileSize);
    385     TEST_ASSERT(fileSize>0);
    386     if (amt_read != fileSize || fileSize <=0) {
    387         return;
    388     }
    389     UnicodeString confusablesTxt = UnicodeString::fromUTF8(StringPiece(fileBuf.getAlias(), fileSize));
    390 
    391     UErrorCode status = U_ZERO_ERROR;
    392     LocalUSpoofCheckerPointer sc(uspoof_open(&status));
    393     TEST_ASSERT_SUCCESS(status);
    394 
    395     // Parse lines from the confusables.txt file.  Example Line:
    396     // FF44 ;	0064 ;	SL	# ( d -> d ) FULLWIDTH ....
    397     // Three fields.  The hex fields can contain more than one character,
    398     //                and each character may be more than 4 digits (for supplemntals)
    399     // This regular expression matches lines and splits the fields into capture groups.
    400     RegexMatcher parseLine("(?m)^([0-9A-F]{4}[^#;]*?);([^#;]*?);([^#]*)", confusablesTxt, 0, status);
    401     TEST_ASSERT_SUCCESS(status);
    402     while (parseLine.find()) {
    403         UnicodeString from = parseHex(parseLine.group(1, status));
    404         if (!Normalizer::isNormalized(from, UNORM_NFD, status)) {
    405             // The source character was not NFD.
    406             // Skip this case; the first step in obtaining a skeleton is to NFD the input,
    407             //  so the mapping in this line of confusables.txt will never be applied.
    408             continue;
    409         }
    410 
    411         UnicodeString rawExpected = parseHex(parseLine.group(2, status));
    412         UnicodeString expected;
    413         Normalizer::decompose(rawExpected, FALSE /*NFD*/, 0, expected, status);
    414         TEST_ASSERT_SUCCESS(status);
    415 
    416         int32_t skeletonType = 0;
    417         UnicodeString tableType = parseLine.group(3, status);
    418         TEST_ASSERT_SUCCESS(status);
    419         if (tableType.indexOf("SL") >= 0) {
    420             skeletonType = USPOOF_SINGLE_SCRIPT_CONFUSABLE;
    421         } else if (tableType.indexOf("SA") >= 0) {
    422             skeletonType = USPOOF_SINGLE_SCRIPT_CONFUSABLE | USPOOF_ANY_CASE;
    423         } else if (tableType.indexOf("ML") >= 0) {
    424             skeletonType = 0;
    425         } else if (tableType.indexOf("MA") >= 0) {
    426             skeletonType = USPOOF_ANY_CASE;
    427         }
    428 
    429         UnicodeString actual;
    430         uspoof_getSkeletonUnicodeString(sc.getAlias(), skeletonType, from, actual, &status);
    431         TEST_ASSERT_SUCCESS(status);
    432         TEST_ASSERT(actual == expected);
    433         if (actual != expected) {
    434             errln(parseLine.group(0, status));
    435             UnicodeString line = "Actual: ";
    436             int i = 0;
    437             while (i < actual.length()) {
    438                 appendHexUChar(line, actual.char32At(i));
    439                 i = actual.moveIndex32(i, 1);
    440             }
    441             errln(line);
    442         }
    443         if (U_FAILURE(status)) {
    444             break;
    445         }
    446     }
    447 }
    448 
    449 // testIdentifierInfo. Note that IdentifierInfo is not public ICU API at this time
    450 void IntlTestSpoof::testIdentifierInfo() {
    451     UErrorCode status = U_ZERO_ERROR;
    452     ScriptSet bitset12; bitset12.set(USCRIPT_LATIN, status).set(USCRIPT_HANGUL, status);
    453     ScriptSet bitset2;  bitset2.set(USCRIPT_HANGUL, status);
    454     TEST_ASSERT(bitset12.contains(bitset2));
    455     TEST_ASSERT(bitset12.contains(bitset12));
    456     TEST_ASSERT(!bitset2.contains(bitset12));
    457 
    458     ScriptSet arabSet;  arabSet.set(USCRIPT_ARABIC, status);
    459     ScriptSet latinSet; latinSet.set(USCRIPT_LATIN, status);
    460     UElement arabEl;  arabEl.pointer = &arabSet;
    461     UElement latinEl; latinEl.pointer = &latinSet;
    462     TEST_ASSERT(uhash_compareScriptSet(arabEl, latinEl) < 0);
    463     TEST_ASSERT(uhash_compareScriptSet(latinEl, arabEl) > 0);
    464 
    465     UnicodeString scriptString;
    466     bitset12.displayScripts(scriptString);
    467     TEST_ASSERT(UNICODE_STRING_SIMPLE("Hang Latn") == scriptString);
    468 
    469     status = U_ZERO_ERROR;
    470     UHashtable *alternates = uhash_open(uhash_hashScriptSet ,uhash_compareScriptSet, NULL, &status);
    471     uhash_puti(alternates, &bitset12, 1, &status);
    472     uhash_puti(alternates, &bitset2, 1, &status);
    473     UnicodeString alternatesString;
    474     IdentifierInfo::displayAlternates(alternatesString, alternates, status);
    475     TEST_ASSERT(UNICODE_STRING_SIMPLE("Hang; Hang Latn") == alternatesString);
    476     TEST_ASSERT_SUCCESS(status);
    477 
    478     status = U_ZERO_ERROR;
    479     ScriptSet tScriptSet;
    480     tScriptSet.parseScripts(scriptString, status);
    481     TEST_ASSERT_SUCCESS(status);
    482     TEST_ASSERT(bitset12 == tScriptSet);
    483     UnicodeString ss;
    484     ss.remove();
    485     uhash_close(alternates);
    486 
    487     struct Test {
    488         const char         *fTestString;
    489         URestrictionLevel   fRestrictionLevel;
    490         const char         *fNumerics;
    491         const char         *fScripts;
    492         const char         *fAlternates;
    493         const char         *fCommonAlternates;
    494     } tests[] = {
    495             {"\\u0061\\u2665",                USPOOF_UNRESTRICTIVE,      "[]", "Latn", "", ""},
    496             {"\\u0061\\u3006",                USPOOF_HIGHLY_RESTRICTIVE, "[]", "Latn", "Hani Hira Kana", "Hani Hira Kana"},
    497             {"\\u0061\\u30FC\\u3006",         USPOOF_HIGHLY_RESTRICTIVE, "[]", "Latn", "Hira Kana", "Hira Kana"},
    498             {"\\u0061\\u30FC\\u3006\\u30A2",  USPOOF_HIGHLY_RESTRICTIVE, "[]", "Latn Kana", "", ""},
    499             {"\\u30A2\\u0061\\u30FC\\u3006",  USPOOF_HIGHLY_RESTRICTIVE, "[]", "Latn Kana", "", ""},
    500             {"\\u0061\\u0031\\u0661",         USPOOF_UNRESTRICTIVE,      "[\\u0030\\u0660]", "Latn", "Arab Thaa", "Arab Thaa"},
    501             {"\\u0061\\u0031\\u0661\\u06F1",  USPOOF_UNRESTRICTIVE,      "[\\u0030\\u0660\\u06F0]", "Latn Arab", "", ""},
    502             {"\\u0661\\u30FC\\u3006\\u0061\\u30A2\\u0031\\u0967\\u06F1",  USPOOF_UNRESTRICTIVE,
    503                   "[\\u0030\\u0660\\u06F0\\u0966]", "Latn Kana Arab", "Deva Kthi Mahj", "Deva Kthi Mahj"},
    504             {"\\u0061\\u30A2\\u30FC\\u3006\\u0031\\u0967\\u0661\\u06F1",  USPOOF_UNRESTRICTIVE,
    505                   "[\\u0030\\u0660\\u06F0\\u0966]", "Latn Kana Arab", "Deva Kthi Mahj", "Deva Kthi Mahj"}
    506     };
    507 
    508     int testNum;
    509     for (testNum = 0; testNum < UPRV_LENGTHOF(tests); testNum++) {
    510         char testNumStr[40];
    511         sprintf(testNumStr, "testNum = %d", testNum);
    512         Test &test = tests[testNum];
    513         status = U_ZERO_ERROR;
    514         UnicodeString testString(test.fTestString);  // Note: may do charset conversion.
    515         testString = testString.unescape();
    516         IdentifierInfo idInfo(status);
    517         TEST_ASSERT_SUCCESS(status);
    518         idInfo.setIdentifierProfile(*uspoof_getRecommendedUnicodeSet(&status));
    519         idInfo.setIdentifier(testString, status);
    520         TEST_ASSERT_MSG(*idInfo.getIdentifier() == testString, testNumStr);
    521 
    522         URestrictionLevel restrictionLevel = test.fRestrictionLevel;
    523         TEST_ASSERT_MSG(restrictionLevel == idInfo.getRestrictionLevel(status), testNumStr);
    524 
    525         status = U_ZERO_ERROR;
    526         UnicodeSet numerics(UnicodeString(test.fNumerics).unescape(), status);
    527         TEST_ASSERT_SUCCESS(status);
    528         TEST_ASSERT_MSG(numerics == *idInfo.getNumerics(), testNumStr);
    529 
    530         ScriptSet scripts;
    531         scripts.parseScripts(UnicodeString(test.fScripts), status);
    532         TEST_ASSERT_MSG(scripts == *idInfo.getScripts(), testNumStr);
    533 
    534         UnicodeString alternatesStr;
    535         IdentifierInfo::displayAlternates(alternatesStr, idInfo.getAlternates(), status);
    536         TEST_ASSERT_MSG(UnicodeString(test.fAlternates) == alternatesStr, testNumStr);
    537 
    538         ScriptSet commonAlternates;
    539         commonAlternates.parseScripts(UnicodeString(test.fCommonAlternates), status);
    540         TEST_ASSERT_MSG(commonAlternates == *idInfo.getCommonAmongAlternates(), testNumStr);
    541     }
    542 
    543     // Test of getScriptCount()
    544     //   Script and or Script Extension for chars used in the tests
    545     //     \\u3013  ; Bopo Hang Hani Hira Kana # So       GETA MARK
    546     //     \\uA838  ; Deva Gujr Guru Kthi Takr # Sc       NORTH INDIC RUPEE MARK
    547     //     \\u0951  ; Deva Latn                # Mn       DEVANAGARI STRESS SIGN UDATTA
    548     //
    549     //     \\u0370  ; Greek                    # L        GREEK CAPITAL LETTER HETA
    550     //     \\u0481  ; Cyrillic                 # L&       CYRILLIC SMALL LETTER KOPPA
    551     //     \\u0904  ; Devanagari               # Lo       DEVANAGARI LETTER SHORT A
    552     //     \\u3041  ; Hiragana                 # Lo       HIRAGANA LETTER SMALL A
    553     //     1234     ; Common                   #          ascii digits
    554     //     \\u0300  ; Inherited                # Mn       COMBINING GRAVE ACCENT
    555 
    556     struct ScriptTest {
    557         const char *fTestString;
    558         int32_t     fScriptCount;
    559     } scriptTests[] = {
    560         {"Hello", 1},
    561         {"Hello\\u0370", 2},
    562         {"1234", 0},
    563         {"Hello1234\\u0300", 1},   // Common and Inherited are ignored.
    564         {"\\u0030", 0},
    565         {"abc\\u0951", 1},
    566         {"abc\\u3013", 2},
    567         {"\\uA838\\u0951", 1},     // Triggers commonAmongAlternates path.
    568         {"\\u3013\\uA838", 2}
    569     };
    570 
    571     status = U_ZERO_ERROR;
    572     IdentifierInfo identifierInfo(status);
    573     for (testNum=0; testNum<UPRV_LENGTHOF(scriptTests); testNum++) {
    574         ScriptTest &test = scriptTests[testNum];
    575         char msgBuf[100];
    576         sprintf(msgBuf, "testNum = %d ", testNum);
    577         UnicodeString testString = UnicodeString(test.fTestString).unescape();
    578 
    579         status = U_ZERO_ERROR;
    580         identifierInfo.setIdentifier(testString, status);
    581         int32_t scriptCount = identifierInfo.getScriptCount();
    582         TEST_ASSERT_MSG(test.fScriptCount == scriptCount, msgBuf);
    583     }
    584 }
    585 
    586 void IntlTestSpoof::testScriptSet() {
    587     ScriptSet s1;
    588     ScriptSet s2;
    589     UErrorCode status = U_ZERO_ERROR;
    590 
    591     TEST_ASSERT(s1 == s2);
    592     s1.set(USCRIPT_ARABIC,status);
    593     TEST_ASSERT_SUCCESS(status);
    594     TEST_ASSERT(!(s1 == s2));
    595     TEST_ASSERT(s1.test(USCRIPT_ARABIC, status));
    596     TEST_ASSERT(s1.test(USCRIPT_GREEK, status) == FALSE);
    597 
    598     status = U_ZERO_ERROR;
    599     s1.reset(USCRIPT_ARABIC, status);
    600     TEST_ASSERT(s1 == s2);
    601 
    602     status = U_ZERO_ERROR;
    603     s1.setAll();
    604     TEST_ASSERT(s1.test(USCRIPT_COMMON, status));
    605     TEST_ASSERT(s1.test(USCRIPT_ETHIOPIC, status));
    606     TEST_ASSERT(s1.test(USCRIPT_CODE_LIMIT, status));
    607     s1.resetAll();
    608     TEST_ASSERT(!s1.test(USCRIPT_COMMON, status));
    609     TEST_ASSERT(!s1.test(USCRIPT_ETHIOPIC, status));
    610     TEST_ASSERT(!s1.test(USCRIPT_CODE_LIMIT, status));
    611 
    612     status = U_ZERO_ERROR;
    613     s1.set(USCRIPT_TAKRI, status);
    614     s1.set(USCRIPT_BLISSYMBOLS, status);
    615     s2.setAll();
    616     TEST_ASSERT(s2.contains(s1));
    617     TEST_ASSERT(!s1.contains(s2));
    618     TEST_ASSERT(s2.intersects(s1));
    619     TEST_ASSERT(s1.intersects(s2));
    620     s2.reset(USCRIPT_TAKRI, status);
    621     TEST_ASSERT(!s2.contains(s1));
    622     TEST_ASSERT(!s1.contains(s2));
    623     TEST_ASSERT(s1.intersects(s2));
    624     TEST_ASSERT(s2.intersects(s1));
    625     TEST_ASSERT_SUCCESS(status);
    626 
    627     status = U_ZERO_ERROR;
    628     s1.resetAll();
    629     s1.set(USCRIPT_NKO, status);
    630     s1.set(USCRIPT_COMMON, status);
    631     s2 = s1;
    632     TEST_ASSERT(s2 == s1);
    633     TEST_ASSERT_EQ(2, s2.countMembers());
    634     s2.intersect(s1);
    635     TEST_ASSERT(s2 == s1);
    636     s2.setAll();
    637     TEST_ASSERT(!(s2 == s1));
    638     TEST_ASSERT(s2.countMembers() >= USCRIPT_CODE_LIMIT);
    639     s2.intersect(s1);
    640     TEST_ASSERT(s2 == s1);
    641 
    642     s2.setAll();
    643     s2.reset(USCRIPT_COMMON, status);
    644     s2.intersect(s1);
    645     TEST_ASSERT(s2.countMembers() == 1);
    646 
    647     s1.resetAll();
    648     s1.set(USCRIPT_AFAKA, status);
    649     s1.set(USCRIPT_VAI, status);
    650     s1.set(USCRIPT_INHERITED, status);
    651     int32_t n = -1;
    652     for (int32_t i=0; i<4; i++) {
    653         n = s1.nextSetBit(n+1);
    654         switch (i) {
    655           case 0: TEST_ASSERT_EQ(USCRIPT_INHERITED, n); break;
    656           case 1: TEST_ASSERT_EQ(USCRIPT_VAI, n); break;
    657           case 2: TEST_ASSERT_EQ(USCRIPT_AFAKA, n); break;
    658           case 3: TEST_ASSERT_EQ(-1, (int32_t)n); break;
    659           default: TEST_ASSERT(FALSE);
    660         }
    661     }
    662     TEST_ASSERT_SUCCESS(status);
    663 }
    664 
    665 
    666 void IntlTestSpoof::testRestrictionLevel() {
    667     struct Test {
    668         const char         *fId;
    669         URestrictionLevel   fExpectedRestrictionLevel;
    670     } tests[] = {
    671         {"\\u0061\\u03B3\\u2665", USPOOF_UNRESTRICTIVE},
    672         {"a",                     USPOOF_ASCII},
    673         {"\\u03B3",               USPOOF_SINGLE_SCRIPT_RESTRICTIVE},
    674         {"\\u0061\\u30A2\\u30FC", USPOOF_HIGHLY_RESTRICTIVE},
    675         {"\\u0061\\u0904",        USPOOF_MODERATELY_RESTRICTIVE},
    676         {"\\u0061\\u03B3",        USPOOF_MINIMALLY_RESTRICTIVE}
    677     };
    678     char msgBuffer[100];
    679 
    680     URestrictionLevel restrictionLevels[] = { USPOOF_ASCII, USPOOF_SINGLE_SCRIPT_RESTRICTIVE,
    681          USPOOF_HIGHLY_RESTRICTIVE, USPOOF_MODERATELY_RESTRICTIVE, USPOOF_MINIMALLY_RESTRICTIVE,
    682          USPOOF_UNRESTRICTIVE};
    683 
    684     UErrorCode status = U_ZERO_ERROR;
    685     IdentifierInfo idInfo(status);
    686     TEST_ASSERT_SUCCESS(status);
    687     idInfo.setIdentifierProfile(*uspoof_getRecommendedUnicodeSet(&status));
    688     TEST_ASSERT_SUCCESS(status);
    689     for (int32_t testNum=0; testNum < UPRV_LENGTHOF(tests); testNum++) {
    690         status = U_ZERO_ERROR;
    691         const Test &test = tests[testNum];
    692         UnicodeString testString = UnicodeString(test.fId).unescape();
    693         URestrictionLevel expectedLevel = test.fExpectedRestrictionLevel;
    694         idInfo.setIdentifier(testString, status);
    695         sprintf(msgBuffer, "testNum = %d ", testNum);
    696         TEST_ASSERT_SUCCESS(status);
    697         TEST_ASSERT_MSG(expectedLevel == idInfo.getRestrictionLevel(status), msgBuffer);
    698         for (int levelIndex=0; levelIndex<UPRV_LENGTHOF(restrictionLevels); levelIndex++) {
    699             status = U_ZERO_ERROR;
    700             URestrictionLevel levelSetInSpoofChecker = restrictionLevels[levelIndex];
    701             USpoofChecker *sc = uspoof_open(&status);
    702             uspoof_setChecks(sc, USPOOF_RESTRICTION_LEVEL, &status);
    703             uspoof_setAllowedChars(sc, uspoof_getRecommendedSet(&status), &status);
    704             uspoof_setRestrictionLevel(sc, levelSetInSpoofChecker);
    705             int32_t actualValue = uspoof_checkUnicodeString(sc, testString, NULL, &status);
    706 
    707             // we want to fail if the text is (say) MODERATE and the testLevel is ASCII
    708             int32_t expectedValue = 0;
    709             if (expectedLevel > levelSetInSpoofChecker) {
    710                 expectedValue |= USPOOF_RESTRICTION_LEVEL;
    711             }
    712             if (!uspoof_getRecommendedUnicodeSet(&status)->containsAll(testString)) {
    713                 expectedValue |= USPOOF_CHAR_LIMIT;
    714             }
    715             sprintf(msgBuffer, "testNum = %d, levelIndex = %d, expected = %#x, actual = %#x",
    716                     testNum, levelIndex, expectedValue, actualValue);
    717             TEST_ASSERT_MSG(expectedValue == actualValue, msgBuffer);
    718             TEST_ASSERT_SUCCESS(status);
    719 
    720             // Run the same check again, with the Spoof Checker configured to return
    721             // the actual restriction level.
    722             uspoof_setChecks(sc, USPOOF_AUX_INFO | USPOOF_RESTRICTION_LEVEL, &status);
    723             uspoof_setAllowedChars(sc, uspoof_getRecommendedSet(&status), &status);
    724             uspoof_setRestrictionLevel(sc, levelSetInSpoofChecker);
    725             int32_t result = uspoof_checkUnicodeString(sc, testString, NULL, &status);
    726             TEST_ASSERT_SUCCESS(status);
    727             if (U_SUCCESS(status)) {
    728                 TEST_ASSERT_EQ(expectedLevel, result & USPOOF_RESTRICTION_LEVEL_MASK);
    729                 TEST_ASSERT_EQ(expectedValue, result & USPOOF_ALL_CHECKS);
    730             }
    731             uspoof_close(sc);
    732         }
    733     }
    734 }
    735 
    736 
    737 void IntlTestSpoof::testMixedNumbers() {
    738     struct Test {
    739         const char *fTestString;
    740         const char *fExpectedSet;
    741     } tests[] = {
    742         {"1",              "[0]"},
    743         {"\\u0967",        "[\\u0966]"},
    744         {"1\\u0967",       "[0\\u0966]"},
    745         {"\\u0661\\u06F1", "[\\u0660\\u06F0]"}
    746     };
    747     UErrorCode status = U_ZERO_ERROR;
    748     IdentifierInfo idInfo(status);
    749     for (int32_t testNum=0; testNum < UPRV_LENGTHOF(tests); testNum++) {
    750         char msgBuf[100];
    751         sprintf(msgBuf, "testNum = %d ", testNum);
    752         Test &test = tests[testNum];
    753 
    754         status = U_ZERO_ERROR;
    755         UnicodeString testString = UnicodeString(test.fTestString).unescape();
    756         UnicodeSet expectedSet(UnicodeString(test.fExpectedSet).unescape(), status);
    757         idInfo.setIdentifier(testString, status);
    758         TEST_ASSERT_SUCCESS(status);
    759         TEST_ASSERT_MSG(expectedSet == *idInfo.getNumerics(), msgBuf);
    760 
    761         status = U_ZERO_ERROR;
    762         USpoofChecker *sc = uspoof_open(&status);
    763         uspoof_setChecks(sc, USPOOF_MIXED_NUMBERS, &status); // only check this
    764         int32_t result = uspoof_checkUnicodeString(sc, testString, NULL, &status);
    765         UBool mixedNumberFailure = ((result & USPOOF_MIXED_NUMBERS) != 0);
    766         TEST_ASSERT_MSG((expectedSet.size() > 1) == mixedNumberFailure, msgBuf);
    767         uspoof_close(sc);
    768     }
    769 }
    770 
    771 #endif /* !UCONFIG_NO_REGULAR_EXPRESSIONS && !UCONFIG_NO_NORMALIZATION && !UCONFIG_NO_FILE_IO */
    772