]> git.saurik.com Git - apple/icu.git/blobdiff - icuSources/test/intltest/itspoof.cpp
ICU-62107.0.1.tar.gz
[apple/icu.git] / icuSources / test / intltest / itspoof.cpp
index d33119c34c6b8576df8b58d226d14e003908fc2f..4a441ddce1c63f9d70235c24c3ddb7f84967e61b 100644 (file)
@@ -1,6 +1,8 @@
+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
 /*
 **********************************************************************
-* Copyright (C) 2011, International Business Machines Corporation 
+* Copyright (C) 2011-2015, International Business Machines Corporation 
 * and others.  All Rights Reserved.
 **********************************************************************
 */
 #if !UCONFIG_NO_REGULAR_EXPRESSIONS && !UCONFIG_NO_NORMALIZATION && !UCONFIG_NO_FILE_IO
 
 #include "itspoof.h"
-#include "unicode/uspoof.h"
-#include "unicode/unistr.h"
-#include "unicode/regex.h"
+
 #include "unicode/normlzr.h"
+#include "unicode/regex.h"
+#include "unicode/unistr.h"
+#include "unicode/uscript.h"
+#include "unicode/uspoof.h"
+
 #include "cstring.h"
+#include "scriptset.h"
+#include "uhash.h"
+
 #include <stdlib.h>
 #include <stdio.h>
 
     errcheckln(status, "Failure at file %s, line %d, error = %s", __FILE__, __LINE__, u_errorName(status));}}
 
 #define TEST_ASSERT(expr) {if ((expr)==FALSE) { \
-    errln("Test Failure at file %s, line %d: \"%s\" is false.\n", __FILE__, __LINE__, #expr);};}
+    errln("Test Failure at file %s, line %d: \"%s\" is false.", __FILE__, __LINE__, #expr);};}
+
+#define TEST_ASSERT_MSG(expr, msg) {if ((expr)==FALSE) { \
+    dataerrln("Test Failure at file %s, line %d, %s: \"%s\" is false.", __FILE__, __LINE__, msg, #expr);};}
 
 #define TEST_ASSERT_EQ(a, b) { if ((a) != (b)) { \
-    errln("Test Failure at file %s, line %d: \"%s\" (%d) != \"%s\" (%d) \n", \
+    errln("Test Failure at file %s, line %d: \"%s\" (%d) != \"%s\" (%d)", \
              __FILE__, __LINE__, #a, (a), #b, (b)); }}
 
 #define TEST_ASSERT_NE(a, b) { if ((a) == (b)) { \
-    errln("Test Failure at file %s, line %d: \"%s\" (%d) == \"%s\" (%d) \n", \
+    errln("Test Failure at file %s, line %d: \"%s\" (%d) == \"%s\" (%d)", \
              __FILE__, __LINE__, #a, (a), #b, (b)); }}
 
 /*
     USpoofChecker *sc;     \
     sc = uspoof_open(&status);  \
     TEST_ASSERT_SUCCESS(status);   \
+    USpoofCheckResult *checkResult; \
+    checkResult = uspoof_openCheckResult(&status); \
+    TEST_ASSERT_SUCCESS(status);   \
     if (U_SUCCESS(status)){
 
 #define TEST_TEARDOWN  \
     }  \
     TEST_ASSERT_SUCCESS(status);  \
+    uspoof_closeCheckResult(checkResult); \
     uspoof_close(sc);  \
 }
 
 
 void IntlTestSpoof::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
 {
-    if (exec) logln("TestSuite spoof: ");
-    switch (index) {
-        case 0:
-            name = "TestSpoofAPI"; 
-            if (exec) {
-                testSpoofAPI();
-            }
-            break;
-         case 1:
-            name = "TestSkeleton"; 
-            if (exec) {
-                testSkeleton();
-            }
-            break;
-         case 2:
-            name = "TestAreConfusable";
-            if (exec) {
-                testAreConfusable();
-            }
-            break;
-          case 3:
-            name = "TestInvisible";
-            if (exec) {
-                testInvisible();
-            }
-            break;
-          case 4:
-            name = "testConfData";
-            if (exec) {
-                testConfData();
-            }
-            break;
-          case 5:
-            name = "testBug8654";
-            if (exec) {
-                testBug8654();
-            }
-            break;
-         default: name=""; break;
+    if (exec) {
+        logln("TestSuite spoof: ");
     }
+    TESTCASE_AUTO_BEGIN;
+    TESTCASE_AUTO(testSpoofAPI);
+    TESTCASE_AUTO(testSkeleton);
+    TESTCASE_AUTO(testAreConfusable);
+    TESTCASE_AUTO(testInvisible);
+    TESTCASE_AUTO(testConfData);
+    TESTCASE_AUTO(testBug8654);
+    TESTCASE_AUTO(testScriptSet);
+    TESTCASE_AUTO(testRestrictionLevel);
+    TESTCASE_AUTO(testMixedNumbers);
+    TESTCASE_AUTO(testBug12153);
+    TESTCASE_AUTO(testBug12825);
+    TESTCASE_AUTO(testBug12815);
+    TESTCASE_AUTO(testBug13314_MixedNumbers);
+    TESTCASE_AUTO(testBug13328_MixedCombiningMarks);
+    TESTCASE_AUTO(testCombiningDot);
+    TESTCASE_AUTO_END;
 }
 
 void IntlTestSpoof::testSpoofAPI() {
@@ -110,13 +105,14 @@ void IntlTestSpoof::testSpoofAPI() {
         int32_t checkResults = uspoof_checkUnicodeString(sc, s, &position, &status);
         TEST_ASSERT_SUCCESS(status);
         TEST_ASSERT_EQ(0, checkResults);
-        TEST_ASSERT_EQ(666, position);
+        TEST_ASSERT_EQ(0, position);
     TEST_TEARDOWN;
     
     TEST_SETUP
         UnicodeString s1("cxs");
         UnicodeString s2 = UnicodeString("\\u0441\\u0445\\u0455").unescape();  // Cyrillic "cxs"
         int32_t checkResults = uspoof_areConfusableUnicodeString(sc, s1, s2, &status);
+        TEST_ASSERT_SUCCESS(status);
         TEST_ASSERT_EQ(USPOOF_MIXED_SCRIPT_CONFUSABLE | USPOOF_WHOLE_SCRIPT_CONFUSABLE, checkResults);
 
     TEST_TEARDOWN;
@@ -141,6 +137,9 @@ void IntlTestSpoof::testSpoofAPI() {
 //                 Unicode data file confusables.txt
 //                 Test cases chosen for substitutions of various lengths, and 
 //                 membership in different mapping tables.
+//          Note: for ICU 55, all tables collapsed to the MA table data.
+//          TODO: for ICU 56 with Unicode 8, revisit this test.
+//
 void IntlTestSpoof::testSkeleton() {
     const uint32_t ML = 0;
     const uint32_t SL = USPOOF_SINGLE_SCRIPT_CONFUSABLE;
@@ -148,49 +147,42 @@ void IntlTestSpoof::testSkeleton() {
     const uint32_t SA = USPOOF_SINGLE_SCRIPT_CONFUSABLE | USPOOF_ANY_CASE;
 
     TEST_SETUP
-        // A long "identifier" that will overflow implementation stack buffers, forcing heap allocations.
-        CHECK_SKELETON(SL, " A 1ong \\u02b9identifier' that will overflow implementation stack buffers, forcing heap allocations."
-                           " A 1ong 'identifier' that will overflow implementation stack buffers, forcing heap allocations."
-                           " A 1ong 'identifier' that will overflow implementation stack buffers, forcing heap allocations."
-                           " A 1ong 'identifier' that will overflow implementation stack buffers, forcing heap allocations.",
-
-               " A long 'identifier' that vvill overflovv irnplernentation stack buffers, forcing heap allocations."
-               " A long 'identifier' that vvill overflovv irnplernentation stack buffers, forcing heap allocations."
-               " A long 'identifier' that vvill overflovv irnplernentation stack buffers, forcing heap allocations."
-               " A long 'identifier' that vvill overflovv irnplernentation stack buffers, forcing heap allocations.")
-
         CHECK_SKELETON(SL, "nochange", "nochange");
+        CHECK_SKELETON(SA, "nochange", "nochange");
+        CHECK_SKELETON(ML, "nochange", "nochange");
+        CHECK_SKELETON(MA, "nochange", "nochange");
         CHECK_SKELETON(MA, "love", "love"); 
         CHECK_SKELETON(MA, "1ove", "love");   // Digit 1 to letter l
         CHECK_SKELETON(ML, "OOPS", "OOPS");
-        CHECK_SKELETON(ML, "00PS", "00PS");   // Digit 0 unchanged in lower case mode.
+        CHECK_SKELETON(ML, "00PS", "OOPS");
         CHECK_SKELETON(MA, "OOPS", "OOPS");
         CHECK_SKELETON(MA, "00PS", "OOPS");   // Digit 0 to letter O in any case mode only
         CHECK_SKELETON(SL, "\\u059c", "\\u0301");
         CHECK_SKELETON(SL, "\\u2A74", "\\u003A\\u003A\\u003D");
         CHECK_SKELETON(SL, "\\u247E", "\\u0028\\u006C\\u006C\\u0029");  // "(ll)"
-        CHECK_SKELETON(SL, "\\uFDFB", "\\u062C\\u0644\\u0020\\u062C\\u0644\\u0627\\u0644\\u0647");
+        CHECK_SKELETON(SL, "\\uFDFB", "\\u062C\\u0644\\u0020\\u062C\\u0644\\u006c\\u0644\\u006f");
 
         // This mapping exists in the ML and MA tables, does not exist in SL, SA
-        //0C83 ;       0C03 ;  
-        CHECK_SKELETON(SL, "\\u0C83", "\\u0C83");
-        CHECK_SKELETON(SA, "\\u0C83", "\\u0C83");
+        // 0C83 ;      0983 ;  ML
+        // 0C83 ;      0983 ;  MA
+        // 
+
+        CHECK_SKELETON(SL, "\\u0C83", "\\u0983");      
+        CHECK_SKELETON(SA, "\\u0C83", "\\u0983");          
         CHECK_SKELETON(ML, "\\u0C83", "\\u0983");
         CHECK_SKELETON(MA, "\\u0C83", "\\u0983");
         
-        // 0391 ; 0041 ;
-        // This mapping exists only in the MA table.
+        // 0391 mappings exist only in MA and SA tables.
         CHECK_SKELETON(MA, "\\u0391", "A");
-        CHECK_SKELETON(SA, "\\u0391", "\\u0391");
-        CHECK_SKELETON(ML, "\\u0391", "\\u0391");
-        CHECK_SKELETON(SL, "\\u0391", "\\u0391");
+        CHECK_SKELETON(SA, "\\u0391", "A");
+        CHECK_SKELETON(ML, "\\u0391", "A");
+        CHECK_SKELETON(SL, "\\u0391", "A");
 
-        // 13CF ;  0062 ; 
-        // This mapping exists in the ML and MA tables
+        // 13CF Mappings in all four tables, different in MA.
         CHECK_SKELETON(ML, "\\u13CF", "b");
         CHECK_SKELETON(MA, "\\u13CF", "b");
-        CHECK_SKELETON(SL, "\\u13CF", "\\u13CF");
-        CHECK_SKELETON(SA, "\\u13CF", "\\u13CF");
+        CHECK_SKELETON(SL, "\\u13CF", "b");
+        CHECK_SKELETON(SA, "\\u13CF", "b");
 
         // 0022 ;  0027 0027 ; 
         // all tables.
@@ -199,10 +191,11 @@ void IntlTestSpoof::testSkeleton() {
         CHECK_SKELETON(ML, "\\u0022", "\\u0027\\u0027");
         CHECK_SKELETON(MA, "\\u0022", "\\u0027\\u0027");
 
-        // 017F ;  0066 ;
-        // This mapping exists in the SA and MA tables
+        // 017F mappings exist only in MA and SA tables.
         CHECK_SKELETON(MA, "\\u017F", "f");
         CHECK_SKELETON(SA, "\\u017F", "f");
+        CHECK_SKELETON(ML, "\\u017F", "f");
+        CHECK_SKELETON(SL, "\\u017F", "f");
 
     TEST_TEARDOWN;
 }
@@ -238,8 +231,9 @@ void IntlTestSpoof::testAreConfusable() {
                          "A long string that will overflow stack buffers.  A long string that will overflow stack buffers. ");
         UnicodeString s2("A long string that wi11 overflow stack buffers.  A long string that will overflow stack buffers. "
                          "A long string that wi11 overflow stack buffers.  A long string that will overflow stack buffers. ");
-        TEST_ASSERT_EQ(USPOOF_SINGLE_SCRIPT_CONFUSABLE, uspoof_areConfusableUnicodeString(sc, s1, s2, &status));
+        int32_t result = uspoof_areConfusableUnicodeString(sc, s1, s2, &status);
         TEST_ASSERT_SUCCESS(status);
+        TEST_ASSERT_EQ(USPOOF_SINGLE_SCRIPT_CONFUSABLE, result);
 
     TEST_TEARDOWN;
 }
@@ -250,12 +244,12 @@ void IntlTestSpoof::testInvisible() {
         int32_t position = -42;
         TEST_ASSERT_EQ(0, uspoof_checkUnicodeString(sc, s, &position, &status));
         TEST_ASSERT_SUCCESS(status);
-        TEST_ASSERT(position == -42);
+        TEST_ASSERT(0 == position);
 
         UnicodeString  s2 = UnicodeString("abcd\\u0301\\u0302\\u0301ef").unescape();
         TEST_ASSERT_EQ(USPOOF_INVISIBLE, uspoof_checkUnicodeString(sc, s2, &position, &status));
         TEST_ASSERT_SUCCESS(status);
-        TEST_ASSERT_EQ(7, position);
+        TEST_ASSERT_EQ(0, position);
 
         // Two acute accents, one from the composed a with acute accent, \u00e1,
         // and one separate.
@@ -263,7 +257,7 @@ void IntlTestSpoof::testInvisible() {
         UnicodeString  s3 = UnicodeString("abcd\\u00e1\\u0301xyz").unescape();
         TEST_ASSERT_EQ(USPOOF_INVISIBLE, uspoof_checkUnicodeString(sc, s3, &position, &status));
         TEST_ASSERT_SUCCESS(status);
-        TEST_ASSERT_EQ(7, position);
+        TEST_ASSERT_EQ(0, position);
     TEST_TEARDOWN;
 }
 
@@ -273,7 +267,7 @@ void IntlTestSpoof::testBug8654() {
         int32_t position = -42;
         TEST_ASSERT_EQ(USPOOF_INVISIBLE, uspoof_checkUnicodeString(sc, s, &position, &status) & USPOOF_INVISIBLE );
         TEST_ASSERT_SUCCESS(status);
-        TEST_ASSERT_EQ(3, position);
+        TEST_ASSERT_EQ(0, position);
     TEST_TEARDOWN;
 }
 
@@ -331,12 +325,11 @@ U_DEFINE_LOCAL_OPEN_POINTER(LocalStdioFilePointer, FILE, fclose);
 //                 verify that it transforms correctly in a skeleton.
 //
 void IntlTestSpoof::testConfData() {
-    UErrorCode status = U_ZERO_ERROR;
-
-    const char *testDataDir = IntlTest::getSourceTestData(status);
-    TEST_ASSERT_SUCCESS(status);
     char buffer[2000];
-    uprv_strcpy(buffer, testDataDir);
+    if (getUnidataPath(buffer) == NULL) {
+        errln("Skipping test spoof/testConfData. Unable to find path to source/data/unidata/.");
+        return;
+    }
     uprv_strcat(buffer, "confusables.txt");
 
     LocalStdioFilePointer f(fopen(buffer, "rb"));
@@ -356,6 +349,7 @@ void IntlTestSpoof::testConfData() {
     }
     UnicodeString confusablesTxt = UnicodeString::fromUTF8(StringPiece(fileBuf.getAlias(), fileSize));
 
+    UErrorCode status = U_ZERO_ERROR;
     LocalUSpoofCheckerPointer sc(uspoof_open(&status));
     TEST_ASSERT_SUCCESS(status);
 
@@ -412,5 +406,350 @@ void IntlTestSpoof::testConfData() {
         }
     }
 }
-#endif // UCONFIG_NO_REGULAR_EXPRESSIONS
 
+
+void IntlTestSpoof::testScriptSet() {
+    ScriptSet s1;
+    ScriptSet s2;
+    UErrorCode status = U_ZERO_ERROR;
+
+    TEST_ASSERT(s1 == s2);
+    s1.set(USCRIPT_ARABIC,status);
+    TEST_ASSERT_SUCCESS(status);
+    TEST_ASSERT(!(s1 == s2));
+    TEST_ASSERT(s1.test(USCRIPT_ARABIC, status));
+    TEST_ASSERT(s1.test(USCRIPT_GREEK, status) == FALSE);
+
+    status = U_ZERO_ERROR;
+    s1.reset(USCRIPT_ARABIC, status);
+    TEST_ASSERT(s1 == s2);
+
+    status = U_ZERO_ERROR;
+    s1.setAll();
+    TEST_ASSERT(s1.test(USCRIPT_COMMON, status));
+    TEST_ASSERT(s1.test(USCRIPT_ETHIOPIC, status));
+    TEST_ASSERT(s1.test(USCRIPT_CODE_LIMIT, status));
+    s1.resetAll();
+    TEST_ASSERT(!s1.test(USCRIPT_COMMON, status));
+    TEST_ASSERT(!s1.test(USCRIPT_ETHIOPIC, status));
+    TEST_ASSERT(!s1.test(USCRIPT_CODE_LIMIT, status));
+
+    status = U_ZERO_ERROR;
+    s1.set(USCRIPT_TAKRI, status);
+    s1.set(USCRIPT_BLISSYMBOLS, status);
+    s2.setAll();
+    TEST_ASSERT(s2.contains(s1));
+    TEST_ASSERT(!s1.contains(s2));
+    TEST_ASSERT(s2.intersects(s1));
+    TEST_ASSERT(s1.intersects(s2));
+    s2.reset(USCRIPT_TAKRI, status);
+    TEST_ASSERT(!s2.contains(s1));
+    TEST_ASSERT(!s1.contains(s2));
+    TEST_ASSERT(s1.intersects(s2));
+    TEST_ASSERT(s2.intersects(s1));
+    TEST_ASSERT_SUCCESS(status);
+
+    status = U_ZERO_ERROR;
+    s1.resetAll();
+    s1.set(USCRIPT_NKO, status);
+    s1.set(USCRIPT_COMMON, status);
+    s2 = s1;
+    TEST_ASSERT(s2 == s1);
+    TEST_ASSERT_EQ(2, s2.countMembers());
+    s2.intersect(s1);
+    TEST_ASSERT(s2 == s1);
+    s2.setAll();
+    TEST_ASSERT(!(s2 == s1));
+    TEST_ASSERT(s2.countMembers() >= USCRIPT_CODE_LIMIT);
+    s2.intersect(s1);
+    TEST_ASSERT(s2 == s1);
+    
+    s2.setAll();
+    s2.reset(USCRIPT_COMMON, status);
+    s2.intersect(s1);
+    TEST_ASSERT(s2.countMembers() == 1);
+
+    s1.resetAll();
+    TEST_ASSERT(s1.isEmpty());
+    s1.set(USCRIPT_LATIN, status);
+    TEST_ASSERT(!s1.isEmpty());
+    s1.setAll();
+    TEST_ASSERT(!s1.isEmpty());
+    TEST_ASSERT_SUCCESS(status);
+
+    s1.resetAll();
+    s1.set(USCRIPT_AFAKA, status);
+    s1.set(USCRIPT_VAI, status);
+    s1.set(USCRIPT_INHERITED, status);
+    int32_t n = -1;
+    for (int32_t i=0; i<4; i++) {
+        n = s1.nextSetBit(n+1);
+        switch (i) {
+          case 0: TEST_ASSERT_EQ(USCRIPT_INHERITED, n); break;
+          case 1: TEST_ASSERT_EQ(USCRIPT_VAI, n); break;
+          case 2: TEST_ASSERT_EQ(USCRIPT_AFAKA, n); break;
+          case 3: TEST_ASSERT_EQ(-1, (int32_t)n); break;
+          default: TEST_ASSERT(FALSE);
+        }
+    }
+    TEST_ASSERT_SUCCESS(status);
+
+    // Script extensions.  Depends on data.
+    s1.resetAll();
+    s1.setScriptExtensions(0x67, status);
+    TEST_ASSERT(s1.countMembers() == 1);
+    TEST_ASSERT(s1.test(USCRIPT_LATIN, status));
+    TEST_ASSERT_SUCCESS(status);
+
+    s1.resetAll();
+    s1.setScriptExtensions(0x303C, status);
+    TEST_ASSERT(s1.countMembers() == 3);
+    TEST_ASSERT(s1.test(USCRIPT_HAN, status));
+    TEST_ASSERT(s1.test(USCRIPT_HIRAGANA, status));
+    TEST_ASSERT(s1.test(USCRIPT_KATAKANA, status));
+    TEST_ASSERT_SUCCESS(status);
+
+    // Additional tests
+    ScriptSet bitset12; bitset12.set(USCRIPT_LATIN, status).set(USCRIPT_HANGUL, status);
+    ScriptSet bitset2;  bitset2.set(USCRIPT_HANGUL, status);
+    TEST_ASSERT(bitset12.contains(bitset2));
+    TEST_ASSERT(bitset12.contains(bitset12));
+    TEST_ASSERT(!bitset2.contains(bitset12));
+
+    ScriptSet arabSet;  arabSet.set(USCRIPT_ARABIC, status);
+    ScriptSet latinSet; latinSet.set(USCRIPT_LATIN, status);
+    UElement arabEl;  arabEl.pointer = &arabSet;
+    UElement latinEl; latinEl.pointer = &latinSet;
+    TEST_ASSERT(uhash_compareScriptSet(arabEl, latinEl) < 0);
+    TEST_ASSERT(uhash_compareScriptSet(latinEl, arabEl) > 0);
+
+    UnicodeString scriptString;
+    bitset12.displayScripts(scriptString);
+    TEST_ASSERT(UNICODE_STRING_SIMPLE("Hang Latn") == scriptString);
+}
+
+
+void IntlTestSpoof::testRestrictionLevel() {
+    struct Test {
+        const char         *fId;
+        URestrictionLevel   fExpectedRestrictionLevel;
+    } tests[] = {
+        {"\\u0061\\u03B3\\u2665", USPOOF_UNRESTRICTIVE},
+        {"a",                     USPOOF_ASCII},
+        {"\\u03B3",               USPOOF_SINGLE_SCRIPT_RESTRICTIVE},
+        {"\\u0061\\u30A2\\u30FC", USPOOF_HIGHLY_RESTRICTIVE},
+        {"\\u0061\\u0904",        USPOOF_MODERATELY_RESTRICTIVE},
+        {"\\u0061\\u03B3",        USPOOF_MINIMALLY_RESTRICTIVE},
+        {"\\u0061\\u2665",                USPOOF_UNRESTRICTIVE},
+        {"\\u0061\\u303C",                USPOOF_HIGHLY_RESTRICTIVE},
+        {"\\u0061\\u30FC\\u303C",         USPOOF_HIGHLY_RESTRICTIVE},
+        {"\\u0061\\u30FC\\u303C\\u30A2",  USPOOF_HIGHLY_RESTRICTIVE},
+        {"\\u30A2\\u0061\\u30FC\\u303C",  USPOOF_HIGHLY_RESTRICTIVE},
+        {"\\u0061\\u0031\\u0661",         USPOOF_MODERATELY_RESTRICTIVE},
+        {"\\u0061\\u0031\\u0661\\u06F1",  USPOOF_MODERATELY_RESTRICTIVE},
+        {"\\u0661\\u30FC\\u303C\\u0061\\u30A2\\u0031\\u0967\\u06F1",  USPOOF_MINIMALLY_RESTRICTIVE},
+        {"\\u0061\\u30A2\\u30FC\\u303C\\u0031\\u0967\\u0661\\u06F1",  USPOOF_MINIMALLY_RESTRICTIVE}
+    };
+    char msgBuffer[100];
+    URestrictionLevel restrictionLevels[] = { USPOOF_ASCII, USPOOF_SINGLE_SCRIPT_RESTRICTIVE, 
+        USPOOF_HIGHLY_RESTRICTIVE, USPOOF_MODERATELY_RESTRICTIVE, USPOOF_MINIMALLY_RESTRICTIVE, 
+        USPOOF_UNRESTRICTIVE};
+
+    UErrorCode status = U_ZERO_ERROR;
+    UnicodeSet allowedChars;
+    // Allowed Identifier Characters. In addition to the Recommended Set,
+    //    allow u303c, which has an interesting script extension of Hani Hira Kana.
+    allowedChars.addAll(*uspoof_getRecommendedUnicodeSet(&status)).add(0x303C);
+
+    for (int32_t testNum=0; testNum < UPRV_LENGTHOF(tests); testNum++) {
+        status = U_ZERO_ERROR;
+        const Test &test = tests[testNum];
+        UnicodeString testString = UnicodeString(test.fId).unescape();
+        URestrictionLevel expectedLevel = test.fExpectedRestrictionLevel;
+        for (int levelIndex=0; levelIndex<UPRV_LENGTHOF(restrictionLevels); levelIndex++) {
+            status = U_ZERO_ERROR;
+            URestrictionLevel levelSetInSpoofChecker = restrictionLevels[levelIndex];
+            USpoofChecker *sc = uspoof_open(&status);
+            uspoof_setAllowedChars(sc, allowedChars.toUSet(), &status);
+            uspoof_setRestrictionLevel(sc, levelSetInSpoofChecker);
+            uspoof_setChecks(sc, USPOOF_RESTRICTION_LEVEL, &status);
+            int32_t actualValue = uspoof_checkUnicodeString(sc, testString, NULL, &status);
+            
+            // we want to fail if the text is (say) MODERATE and the testLevel is ASCII
+            int32_t expectedValue = 0;
+            if (expectedLevel > levelSetInSpoofChecker) {
+                expectedValue |= USPOOF_RESTRICTION_LEVEL;
+            }
+            sprintf(msgBuffer, "testNum = %d, levelIndex = %d, expected = %#x, actual = %#x",
+                    testNum, levelIndex, expectedValue, actualValue);
+            TEST_ASSERT_MSG(expectedValue == actualValue, msgBuffer);
+            TEST_ASSERT_SUCCESS(status);
+
+            // Run the same check again, with the Spoof Checker configured to return
+            // the actual restriction level.
+            uspoof_setAllowedChars(sc, allowedChars.toUSet(), &status);
+            uspoof_setRestrictionLevel(sc, levelSetInSpoofChecker);
+            uspoof_setChecks(sc, USPOOF_AUX_INFO | USPOOF_RESTRICTION_LEVEL, &status);
+            int32_t result = uspoof_checkUnicodeString(sc, testString, NULL, &status);
+            TEST_ASSERT_SUCCESS(status);
+            if (U_SUCCESS(status)) {
+                TEST_ASSERT_EQ(expectedLevel, result & USPOOF_RESTRICTION_LEVEL_MASK);
+                TEST_ASSERT_EQ(expectedValue, result & USPOOF_ALL_CHECKS);
+            }
+            uspoof_close(sc);
+        }
+    }
+
+}
+
+void IntlTestSpoof::testMixedNumbers() {
+    struct Test {
+        const char *fTestString;
+        const char *fExpectedSet;
+    } tests[] = {
+        {"1",              "[0]"},
+        {"\\u0967",        "[\\u0966]"},
+        {"1\\u0967",       "[0\\u0966]"},
+        {"\\u0661\\u06F1", "[\\u0660\\u06F0]"},
+        {"\\u0061\\u2665", "[]"},
+        {"\\u0061\\u303C", "[]"},
+        {"\\u0061\\u30FC\\u303C", "[]"},
+        {"\\u0061\\u30FC\\u303C\\u30A2", "[]"},
+        {"\\u30A2\\u0061\\u30FC\\u303C", "[]"},
+        {"\\u0061\\u0031\\u0661", "[\\u0030\\u0660]"},
+        {"\\u0061\\u0031\\u0661\\u06F1", "[\\u0030\\u0660\\u06F0]"},
+        {"\\u0661\\u30FC\\u303C\\u0061\\u30A2\\u0031\\u0967\\u06F1", "[\\u0030\\u0660\\u06F0\\u0966]"},
+        {"\\u0061\\u30A2\\u30FC\\u303C\\u0031\\u0967\\u0661\\u06F1", "[\\u0030\\u0660\\u06F0\\u0966]"}
+    };
+    UErrorCode status = U_ZERO_ERROR;
+    for (int32_t testNum=0; testNum < UPRV_LENGTHOF(tests); testNum++) {
+        char msgBuf[100];
+        sprintf(msgBuf, "testNum = %d ", testNum);
+        Test &test = tests[testNum];
+
+        status = U_ZERO_ERROR;
+        UnicodeString testString = UnicodeString(test.fTestString).unescape();
+        UnicodeSet expectedSet(UnicodeString(test.fExpectedSet).unescape(), status);
+
+        status = U_ZERO_ERROR;
+        TEST_SETUP
+            uspoof_setChecks(sc, USPOOF_MIXED_NUMBERS, &status); // only check this
+            uspoof_check2UnicodeString(sc, testString, checkResult, &status);
+            UBool mixedNumberFailure = ((uspoof_getCheckResultChecks(checkResult, &status) & USPOOF_MIXED_NUMBERS) != 0);
+            TEST_ASSERT_MSG((expectedSet.size() > 1) == mixedNumberFailure, msgBuf);
+            const UnicodeSet* actualSet = UnicodeSet::fromUSet(uspoof_getCheckResultNumerics(checkResult, &status));
+            TEST_ASSERT_MSG(expectedSet == *actualSet, msgBuf);
+        TEST_TEARDOWN
+    }
+}
+
+// Bug #12153 - uspoof_setRestrictionLevel() should enable restriction level testing.
+// 
+void IntlTestSpoof::testBug12153() {
+    UErrorCode status = U_ZERO_ERROR;
+    LocalUSpoofCheckerPointer sc(uspoof_open(&status));
+    if (!assertSuccess("", status, true, __FILE__, __LINE__)) { return; }
+    int32_t checks = uspoof_getChecks(sc.getAlias(), &status);
+    TEST_ASSERT((checks & USPOOF_RESTRICTION_LEVEL) != 0);
+    checks &= ~USPOOF_RESTRICTION_LEVEL;
+    uspoof_setChecks(sc.getAlias(), checks, &status);
+    checks = uspoof_getChecks(sc.getAlias(), &status);
+    TEST_ASSERT((checks & USPOOF_RESTRICTION_LEVEL) == 0);
+
+    uspoof_setRestrictionLevel(sc.getAlias(), USPOOF_MODERATELY_RESTRICTIVE);
+    checks = uspoof_getChecks(sc.getAlias(), &status);
+    TEST_ASSERT((checks & USPOOF_RESTRICTION_LEVEL) != 0);
+    TEST_ASSERT_SUCCESS(status);
+}
+
+// uspoof_checkUnicodeString should NOT have an infinite loop.
+void IntlTestSpoof::testBug12825() {
+    UErrorCode status = U_ZERO_ERROR;
+    LocalUSpoofCheckerPointer sc(uspoof_open(&status));
+    TEST_ASSERT_SUCCESS(status);
+    uspoof_setChecks(sc.getAlias(), USPOOF_ALL_CHECKS | USPOOF_AUX_INFO, &status);
+    TEST_ASSERT_SUCCESS(status);
+    uspoof_checkUnicodeString(sc.getAlias(), UnicodeString("\\u30FB").unescape(), NULL, &status);
+    TEST_ASSERT_SUCCESS(status);
+}
+
+// uspoof_getSkeleton should NOT set an ILLEGAL_ARGUMENT_EXCEPTION.
+void IntlTestSpoof::testBug12815() {
+    UErrorCode status = U_ZERO_ERROR;
+    LocalUSpoofCheckerPointer sc(uspoof_open(&status));
+    TEST_ASSERT_SUCCESS(status);
+    uspoof_setChecks(sc.getAlias(), USPOOF_RESTRICTION_LEVEL, &status);
+    TEST_ASSERT_SUCCESS(status);
+    UnicodeString result;
+    uspoof_getSkeletonUnicodeString(sc.getAlias(), 0, UnicodeString("hello world"), result, &status);
+    TEST_ASSERT_SUCCESS(status);
+}
+
+void IntlTestSpoof::testBug13314_MixedNumbers() {
+    UErrorCode status = U_ZERO_ERROR;
+    LocalUSpoofCheckerPointer sc(uspoof_open(&status));
+    if (!assertSuccess("", status, true, __FILE__, __LINE__)) { return; }
+    uspoof_setChecks(sc.getAlias(), USPOOF_ALL_CHECKS, &status);
+    TEST_ASSERT_SUCCESS(status);
+    int32_t failedChecks = uspoof_areConfusableUnicodeString(sc.getAlias(), u"列", u"列", &status);
+    TEST_ASSERT_SUCCESS(status);
+    assertEquals("The CJK strings should be confusable", USPOOF_SINGLE_SCRIPT_CONFUSABLE, failedChecks);
+    failedChecks = uspoof_check2UnicodeString(sc.getAlias(), u"3Ȝ", nullptr, &status);
+    TEST_ASSERT_SUCCESS(status);
+    assertEquals("The '33' string does not fail spoof", 0, failedChecks);
+}
+
+void IntlTestSpoof::testBug13328_MixedCombiningMarks() {
+    UErrorCode status = U_ZERO_ERROR;
+    LocalUSpoofCheckerPointer sc(uspoof_open(&status));
+    if (!assertSuccess("", status, true, __FILE__, __LINE__)) { return; }
+    int32_t failedChecks = uspoof_check2UnicodeString(sc.getAlias(), u"\u0061\u0F84", nullptr, &status);
+    TEST_ASSERT_SUCCESS(status);
+    assertEquals(
+            "The mismatched combining marks string fails spoof",
+            USPOOF_RESTRICTION_LEVEL,
+            failedChecks);
+}
+
+void IntlTestSpoof::testCombiningDot() {
+    UErrorCode status = U_ZERO_ERROR;
+    LocalUSpoofCheckerPointer sc(uspoof_open(&status));
+    if (!assertSuccess("", status, true, __FILE__, __LINE__)) { return; }
+    uspoof_setChecks(sc.getAlias(), USPOOF_HIDDEN_OVERLAY, &status);
+    TEST_ASSERT_SUCCESS(status);
+
+    static const struct TestCase {
+        bool shouldFail;
+        const char16_t* input;
+    } cases[] = {
+            {false, u"i"},
+            {false, u"j"},
+            {false, u"l"},
+            {true, u"i\u0307"},
+            {true, u"j\u0307"},
+            {true, u"l\u0307"},
+            {true, u"ı\u0307"},
+            {true, u"ȷ\u0307"},
+            {true, u"𝚤\u0307"},
+            {true, u"𝑗\u0307"},
+            {false, u"m\u0307"},
+            {true, u"1\u0307"},
+            {true, u"ij\u0307"},
+            {true, u"i\u0307\u0307"},
+            {true, u"abci\u0307def"},
+            {false, u"i\u0301\u0307"}, // U+0301 has combining class ABOVE (230)
+            {true, u"i\u0320\u0307"}, // U+0320 has combining class BELOW
+            {true, u"i\u0320\u0321\u0307"}, // U+0321 also has combining class BELOW
+            {false, u"i\u0320\u0301\u0307"},
+            {false, u"iz\u0307"},
+    };
+
+    for (auto& cas : cases) {
+        int32_t failedChecks = uspoof_check2(sc.getAlias(), cas.input, -1, nullptr, &status);
+        TEST_ASSERT_SUCCESS(status);
+        int32_t expected = cas.shouldFail ? USPOOF_HIDDEN_OVERLAY : 0;
+        assertEquals(cas.input, expected, failedChecks);
+    }
+}
+
+#endif /* !UCONFIG_NO_REGULAR_EXPRESSIONS && !UCONFIG_NO_NORMALIZATION && !UCONFIG_NO_FILE_IO */