]> git.saurik.com Git - apple/icu.git/blobdiff - icuSources/test/intltest/bidiconf.cpp
ICU-62107.0.1.tar.gz
[apple/icu.git] / icuSources / test / intltest / bidiconf.cpp
index c30a76b2ef8bfd0dfa1582a5df1fe6262fe71f62..b91416f43474e319a465b5264b6eb7838eef1786 100644 (file)
@@ -1,19 +1,21 @@
+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
 /*
 *******************************************************************************
 *
-*   Copyright (C) 2009-2010, International Business Machines
+*   Copyright (C) 2009-2014, International Business Machines
 *   Corporation and others.  All Rights Reserved.
 *
 *******************************************************************************
 *   file name:  bidiconf.cpp
-*   encoding:   US-ASCII
+*   encoding:   UTF-8
 *   tab size:   8 (not used)
 *   indentation:4
 *
 *   created on: 2009oct16
 *   created by: Markus W. Scherer
 *
-*   BiDi conformance test, using the Unicode BidiTest.txt file.
+*   BiDi conformance test, using the Unicode BidiTest.txt and BidiCharacterTest.txt files.
 */
 
 #include <stdio.h>
@@ -37,18 +39,16 @@ public:
     void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par=NULL);
 
     void TestBidiTest();
+    void TestBidiCharacterTest();
 private:
-    char *getUnidataPath(char path[]);
-
-    UBool parseLevels(const char *start);
+    UBool parseLevels(const char *&start);
     UBool parseOrdering(const char *start);
     UBool parseInputStringFromBiDiClasses(const char *&start);
 
-    UBool checkLevels(const UBiDiLevel actualLevels[], int32_t actualCount,
-                      const char *paraLevelName);
-    UBool checkOrdering(UBiDi *ubidi, const char *paraLevelName);
+    UBool checkLevels(const UBiDiLevel actualLevels[], int32_t actualCount);
+    UBool checkOrdering(UBiDi *ubidi);
 
-    void printErrorLine(const char *paraLevelName);
+    void printErrorLine();
 
     char line[10000];
     UBiDiLevel levels[1000];
@@ -59,6 +59,8 @@ private:
     int32_t orderingCount;
     int32_t errorCount;
     UnicodeString inputString;
+    const char *paraLevelName;
+    char levelNameString[12];
 };
 
 extern IntlTest *createBiDiConformanceTest() {
@@ -69,64 +71,28 @@ void BiDiConformanceTest::runIndexedTest(int32_t index, UBool exec, const char *
     if(exec) {
         logln("TestSuite BiDiConformanceTest: ");
     }
-    switch (index) {
-        TESTCASE(0, TestBidiTest);
-        default:
-            name="";
-            break; // needed to end the loop
-    }
-}
-
-// TODO: Move to a common place (IntlTest?) to avoid duplication with UnicodeTest (ucdtest.cpp).
-char *BiDiConformanceTest::getUnidataPath(char path[]) {
-    IcuTestErrorCode errorCode(*this, "getUnidataPath");
-    const int kUnicodeDataTxtLength=15;  // strlen("UnicodeData.txt")
-
-    // Look inside ICU_DATA first.
-    strcpy(path, pathToDataDirectory());
-    strcat(path, "unidata" U_FILE_SEP_STRING "UnicodeData.txt");
-    FILE *f=fopen(path, "r");
-    if(f!=NULL) {
-        fclose(f);
-        *(strchr(path, 0)-kUnicodeDataTxtLength)=0;  // Remove the basename.
-        return path;
-    }
-
-    // As a fallback, try to guess where the source data was located
-    // at the time ICU was built, and look there.
-#   ifdef U_TOPSRCDIR
-        strcpy(path, U_TOPSRCDIR  U_FILE_SEP_STRING "data");
-#   else
-        strcpy(path, loadTestData(errorCode));
-        strcat(path, U_FILE_SEP_STRING ".." U_FILE_SEP_STRING ".."
-                     U_FILE_SEP_STRING ".." U_FILE_SEP_STRING ".."
-                     U_FILE_SEP_STRING "data");
-#   endif
-    strcat(path, U_FILE_SEP_STRING);
-    strcat(path, "unidata" U_FILE_SEP_STRING "UnicodeData.txt");
-    f=fopen(path, "r");
-    if(f!=NULL) {
-        fclose(f);
-        *(strchr(path, 0)-kUnicodeDataTxtLength)=0;  // Remove the basename.
-        return path;
-    }
-    return NULL;
+    TESTCASE_AUTO_BEGIN;
+    TESTCASE_AUTO(TestBidiTest);
+    TESTCASE_AUTO(TestBidiCharacterTest);
+    TESTCASE_AUTO_END;
 }
 
 U_DEFINE_LOCAL_OPEN_POINTER(LocalStdioFilePointer, FILE, fclose);
 
-UBool BiDiConformanceTest::parseLevels(const char *start) {
+UBool BiDiConformanceTest::parseLevels(const char *&start) {
     directionBits=0;
     levelsCount=0;
-    while(*start!=0 && *(start=u_skipWhitespace(start))!=0) {
+    while(*start!=0 && *(start=u_skipWhitespace(start))!=0 && *start!=';') {
         if(*start=='x') {
             levels[levelsCount++]=UBIDI_DEFAULT_LTR;
             ++start;
         } else {
             char *end;
             uint32_t value=(uint32_t)strtoul(start, &end, 10);
-            if(end<=start || (!U_IS_INV_WHITESPACE(*end) && *end!=0) || value>(UBIDI_MAX_EXPLICIT_LEVEL+1)) {
-                errln("@Levels: parse error at %s", start);
+            if(end<=start || (!U_IS_INV_WHITESPACE(*end) && *end!=0 && *end!=';')
+                          || value>(UBIDI_MAX_EXPLICIT_LEVEL+1)) {
+                errln("\nError on line %d: Levels parse error at %s", (int)lineNumber, start);
+                printErrorLine();
                 return FALSE;
             }
             levels[levelsCount++]=(UBiDiLevel)value;
@@ -139,11 +105,12 @@ UBool BiDiConformanceTest::parseLevels(const char *start) {
 
 UBool BiDiConformanceTest::parseOrdering(const char *start) {
     orderingCount=0;
-    while(*start!=0 && *(start=u_skipWhitespace(start))!=0) {
+    while(*start!=0 && *(start=u_skipWhitespace(start))!=0 && *start!=';') {
         char *end;
         uint32_t value=(uint32_t)strtoul(start, &end, 10);
-        if(end<=start || (!U_IS_INV_WHITESPACE(*end) && *end!=0) || value>=1000) {
-            errln("@Reorder: parse error at %s", start);
+        if(end<=start || (!U_IS_INV_WHITESPACE(*end) && *end!=0 && *end!=';') || value>=1000) {
+            errln("\nError on line %d: Reorder parse error at %s", (int)lineNumber, start);
+            printErrorLine();
             return FALSE;
         }
         ordering[orderingCount++]=(int32_t)value;
@@ -171,7 +138,12 @@ static const UChar charFromBiDiClass[U_CHAR_DIRECTION_COUNT]={
     0x4f,   // 'O' for RLO
     0x2a,   // '*' for PDF
     0x60,   // '`' for NSM
-    0x7c    // '|' for BN
+    0x7c,   // '|' for BN
+    // new in Unicode 6.3/ICU 52
+    0x53,   // 'S' for FSI
+    0x69,   // 'i' for LRI
+    0x49,   // 'I' for RLI
+    0x2e    // '.' for PDI
 };
 
 U_CDECL_BEGIN
@@ -191,7 +163,7 @@ biDiConfUBiDiClassCallback(const void * /*context*/, UChar32 c) {
 U_CDECL_END
 
 static const int8_t biDiClassNameLengths[U_CHAR_DIRECTION_COUNT+1]={
-    1, 1, 2, 2, 2, 2, 2, 1, 1, 2, 2, 3, 3, 2, 3, 3, 3, 3, 2, 0
+    1, 1, 2, 2, 2, 2, 2, 1, 1, 2, 2, 3, 3, 2, 3, 3, 3, 3, 2, 3, 3, 3, 3, 0
 };
 
 UBool BiDiConformanceTest::parseInputStringFromBiDiClasses(const char *&start) {
@@ -210,6 +182,8 @@ UBool BiDiConformanceTest::parseInputStringFromBiDiClasses(const char *&start) {
             if(start[1]=='R') {
                 if(start[2]=='E') {
                     biDiClass=U_LEFT_TO_RIGHT_EMBEDDING;
+                } else if(start[2]=='I') {
+                    biDiClass=U_LEFT_TO_RIGHT_ISOLATE;
                 } else if(start[2]=='O') {
                     biDiClass=U_LEFT_TO_RIGHT_OVERRIDE;
                 }
@@ -220,6 +194,8 @@ UBool BiDiConformanceTest::parseInputStringFromBiDiClasses(const char *&start) {
             if(start[1]=='L') {
                 if(start[2]=='E') {
                     biDiClass=U_RIGHT_TO_LEFT_EMBEDDING;
+                } else if(start[2]=='I') {
+                    biDiClass=U_RIGHT_TO_LEFT_ISOLATE;
                 } else if(start[2]=='O') {
                     biDiClass=U_RIGHT_TO_LEFT_OVERRIDE;
                 }
@@ -254,21 +230,29 @@ UBool BiDiConformanceTest::parseInputStringFromBiDiClasses(const char *&start) {
             biDiClass=U_WHITE_SPACE_NEUTRAL;
         } else if(start[0]=='O' && start[1]=='N') {
             biDiClass=U_OTHER_NEUTRAL;
-        } else if(start[0]=='P' && start[1]=='D' && start[2]=='F') {
-            biDiClass=U_POP_DIRECTIONAL_FORMAT;
+        } else if(start[0]=='P' && start[1]=='D') {
+            if(start[2]=='F') {
+                biDiClass=U_POP_DIRECTIONAL_FORMAT;
+            } else if(start[2]=='I') {
+                biDiClass=U_POP_DIRECTIONAL_ISOLATE;
+            }
         } else if(start[0]=='N' && start[1]=='S' && start[2]=='M') {
             biDiClass=U_DIR_NON_SPACING_MARK;
+        } else if(start[0]=='F' && start[1]=='S' && start[2]=='I') {
+            biDiClass=U_FIRST_STRONG_ISOLATE;
         }
         // Now we verify that the class name is terminated properly,
         // and not just the start of a longer word.
         int8_t biDiClassNameLength=biDiClassNameLengths[biDiClass];
         char c=start[biDiClassNameLength];
-        if(biDiClass==U_CHAR_DIRECTION_COUNT || (!U_IS_INV_WHITESPACE(c) && c!=';' && c!=0)) {
-            errln("BiDi class string not recognized at %s", start);
-            return FALSE;
+        if(biDiClass<U_CHAR_DIRECTION_COUNT && (U_IS_INV_WHITESPACE(c) || c==';' || c==0)) {
+            inputString.append(charFromBiDiClass[biDiClass]);
+            start+=biDiClassNameLength;
+            continue;
         }
-        inputString.append(charFromBiDiClass[biDiClass]);
-        start+=biDiClassNameLength;
+        errln("\nError on line %d: BiDi class string not recognized at %s", (int)lineNumber, start);
+        printErrorLine();
+        return FALSE;
     }
     return TRUE;
 }
@@ -276,7 +260,7 @@ UBool BiDiConformanceTest::parseInputStringFromBiDiClasses(const char *&start) {
 void BiDiConformanceTest::TestBidiTest() {
     IcuTestErrorCode errorCode(*this, "TestBidiTest");
     const char *sourceTestDataPath=getSourceTestData(errorCode);
-    if(errorCode.logIfFailureAndReset("unable to find the source/test/testdata "
+    if(errorCode.errIfFailureAndReset("unable to find the source/test/testdata "
                                       "folder (getSourceTestData())")) {
         return;
     }
@@ -291,13 +275,15 @@ void BiDiConformanceTest::TestBidiTest() {
     LocalUBiDiPointer ubidi(ubidi_open());
     ubidi_setClassCallback(ubidi.getAlias(), biDiConfUBiDiClassCallback, NULL,
                            NULL, NULL, errorCode);
-    if(errorCode.logIfFailureAndReset("ubidi_setClassCallback()")) {
+    if(errorCode.errIfFailureAndReset("ubidi_setClassCallback()")) {
         return;
     }
     lineNumber=0;
     levelsCount=0;
     orderingCount=0;
     errorCount=0;
+    // paraLevelName must be initialized in case the first non-comment line is in error
+    paraLevelName="N/A";
     while(errorCount<10 && fgets(line, (int)sizeof(line), bidiTestFile.getAlias())!=NULL) {
         ++lineNumber;
         // Remove trailing comments and whitespace.
@@ -313,7 +299,8 @@ void BiDiConformanceTest::TestBidiTest() {
         if(*start=='@') {
             ++start;
             if(0==strncmp(start, "Levels:", 7)) {
-                if(!parseLevels(start+7)) {
+                start+=7;
+                if(!parseLevels(start)) {
                     return;
                 }
             } else if(0==strncmp(start, "Reorder:", 8)) {
@@ -346,17 +333,17 @@ void BiDiConformanceTest::TestBidiTest() {
                     ubidi_setPara(ubidi.getAlias(), inputString.getBuffer(), inputString.length(),
                                   paraLevels[i], NULL, errorCode);
                     const UBiDiLevel *actualLevels=ubidi_getLevels(ubidi.getAlias(), errorCode);
-                    if(errorCode.logIfFailureAndReset("ubidi_setPara() or ubidi_getLevels()")) {
+                    if(errorCode.errIfFailureAndReset("ubidi_setPara() or ubidi_getLevels()")) {
                         errln("Input line %d: %s", (int)lineNumber, line);
                         return;
                     }
-                    if(!checkLevels(actualLevels, ubidi_getProcessedLength(ubidi.getAlias()),
-                                    paraLevelNames[i])) {
+                    paraLevelName=paraLevelNames[i];
+                    if(!checkLevels(actualLevels, ubidi_getProcessedLength(ubidi.getAlias()))) {
                         // continue outerLoop;  does not exist in C++
                         // so just break out of the inner loop.
                         break;
                     }
-                    if(!checkOrdering(ubidi.getAlias(), paraLevelNames[i])) {
+                    if(!checkOrdering(ubidi.getAlias())) {
                         // continue outerLoop;  does not exist in C++
                         // so just break out of the inner loop.
                         break;
@@ -367,6 +354,291 @@ void BiDiConformanceTest::TestBidiTest() {
     }
 }
 
+/*
+*******************************************************************************
+*
+*   created on: 2013jul01
+*   created by: Matitiahu Allouche
+
+This function performs a conformance test for implementations of the
+Unicode Bidirectional Algorithm, specified in UAX #9: Unicode
+Bidirectional Algorithm, at http://www.unicode.org/unicode/reports/tr9/
+
+Each test case is represented in a single line which is read from a file
+named BidiCharacter.txt.  Empty, blank and comment lines may also appear
+in this file.
+
+The format of the test data is specified below.  Note that each test
+case constitutes a single line of text; reordering is applied within a
+single line and independently of a rendering engine, and rules L3 and L4
+are out of scope.
+
+The number sign '#' is the comment character: everything is ignored from
+the occurrence of '#' until the end of the line,
+Empty lines and lines containing only spaces and/or comments are ignored.
+
+Lines which represent test cases consist of 4 or 5 fields separated by a
+semicolon.  Each field consists of tokens separated by whitespace (space
+or Tab).  Whitespace before and after semicolons is optional.
+
+Field 0: A sequence of hexadecimal code point values separated by space
+
+Field 1: A value representing the paragraph direction, as follows:
+    - 0 represents left-to-right
+    - 1 represents right-to-left
+    - 2 represents auto-LTR according to rules P2 and P3 of the algorithm
+    - 3 represents auto-RTL according to rules P2 and P3 of the algorithm
+    - a negative number whose absolute value is taken as paragraph level;
+      this may be useful to test cases where the embedding level approaches
+      or exceeds the maximum embedding level.
+
+Field 2: The resolved paragraph embedding level.  If the input (field 0)
+         includes more than one paragraph, this field represents the
+         resolved level of the first paragraph.
+
+Field 3: An ordered list of resulting levels for each token in field 0
+         (each token represents one source character).
+         The UBA does not assign levels to certain characters (e.g. LRO);
+         characters removed in rule X9 are indicated with an 'x'.
+
+Field 4: An ordered list of indices showing the resulting visual ordering
+         from left to right; characters with a resolved level of 'x' are
+         skipped.  The number are zero-based.  Each index corresponds to
+         a character in the reordered (visual) string. It represents the
+         index of the source character in the input (field 0).
+         This field is optional.  When it is absent, the visual ordering
+         is not verified.
+
+Examples:
+
+# This is a comment line.
+L L ON R ; 0 ; 0 ; 0 0 0 1 ; 0 1 2 3
+L L ON R;0;0;0 0 0 1;0 1 2 3
+
+# Note: in the next line, 'B' represents a block separator, not the letter 'B'.
+LRE A B C PDF;2;0;x 2 0 0 x;1 2 3
+# Note: in the next line, 'b' represents the letter 'b', not a block separator.
+a b c 05d0 05d1 x ; 0 ; 0 ; 0 0 0 1 1 0 ; 0 1 2 4 3 5
+
+a R R x ; 1 ; 1 ; 2 1 1 2
+L L R R R B R R L L L B ON ON ; 3 ; 0 ; 0 0 1 1 1 0 1 1 2 2 2 1 1 1
+
+*
+*******************************************************************************
+*/
+enum { kMaxUtxt = 32, kMaxUctl = 16 };
+
+void BiDiConformanceTest::TestBidiCharacterTest() {
+    IcuTestErrorCode errorCode(*this, "TestBidiCharacterTest");
+    const char *sourceTestDataPath=getSourceTestData(errorCode);
+    if(errorCode.errIfFailureAndReset("unable to find the source/test/testdata "
+                                      "folder (getSourceTestData())")) {
+        return;
+    }
+    char bidiTestPath[400];
+    strcpy(bidiTestPath, sourceTestDataPath);
+    strcat(bidiTestPath, "BidiCharacterTest.txt");
+    LocalStdioFilePointer bidiTestFile(fopen(bidiTestPath, "r"));
+    if(bidiTestFile.isNull()) {
+        errln("unable to open %s", bidiTestPath);
+        return;
+    }
+    LocalUBiDiPointer ubidi(ubidi_open());
+    lineNumber=0;
+    levelsCount=0;
+    orderingCount=0;
+    errorCount=0;
+    while(errorCount<20 && fgets(line, (int)sizeof(line), bidiTestFile.getAlias())!=NULL) {
+        ++lineNumber;
+        paraLevelName="N/A";
+        inputString="N/A";
+        // Remove trailing comments and whitespace.
+        char *commentStart=strchr(line, '#');
+        if(commentStart!=NULL) {
+            *commentStart=0;
+        }
+        u_rtrim(line);
+        const char *start=u_skipWhitespace(line);
+        if(*start==0) {
+            continue;  // Skip empty and comment-only lines.
+        }
+        // Parse the code point string in field 0.
+        UChar *buffer=inputString.getBuffer(200);
+        int32_t length=u_parseString(start, buffer, inputString.getCapacity(), NULL, errorCode);
+        if(errorCode.errIfFailureAndReset("Invalid string in field 0")) {
+            errln("Input line %d: %s", (int)lineNumber, line);
+            inputString.remove();
+            continue;
+        }
+        inputString.releaseBuffer(length);
+        start=strchr(start, ';');
+        if(start==NULL) {
+            errorCount++;
+            errln("\nError on line %d: Missing ; separator on line: %s", (int)lineNumber, line);
+            continue;
+        }
+        start=u_skipWhitespace(start+1);
+        char *end;
+        int32_t paraDirection=(int32_t)strtol(start, &end, 10);
+        UBiDiLevel paraLevel=UBIDI_MAX_EXPLICIT_LEVEL+2;
+        if(paraDirection==0) {
+            paraLevel=0;
+            paraLevelName="LTR";
+        }
+        else if(paraDirection==1) {
+            paraLevel=1;
+            paraLevelName="RTL";
+        }
+        else if(paraDirection==2) {
+            paraLevel=UBIDI_DEFAULT_LTR;
+            paraLevelName="Auto/LTR";
+        }
+        else if(paraDirection==3) {
+            paraLevel=UBIDI_DEFAULT_RTL;
+            paraLevelName="Auto/RTL";
+        }
+        else if(paraDirection<0 && -paraDirection<=(UBIDI_MAX_EXPLICIT_LEVEL+1)) {
+            paraLevel=(UBiDiLevel)(-paraDirection);
+            sprintf(levelNameString, "%d", (int)paraLevel);
+            paraLevelName=levelNameString;
+        }
+        if(end<=start || (!U_IS_INV_WHITESPACE(*end) && *end!=';' && *end!=0) ||
+                         paraLevel==(UBIDI_MAX_EXPLICIT_LEVEL+2)) {
+            errln("\nError on line %d: Input paragraph direction incorrect at %s", (int)lineNumber, start);
+            printErrorLine();
+            continue;
+        }
+        start=u_skipWhitespace(end);
+        if(*start!=';') {
+            errorCount++;
+            errln("\nError on line %d: Missing ; separator on line: %s", (int)lineNumber, line);
+            continue;
+        }
+        start++;
+        uint32_t resolvedParaLevel=(uint32_t)strtoul(start, &end, 10);
+        if(end<=start || (!U_IS_INV_WHITESPACE(*end) && *end!=';' && *end!=0) ||
+           resolvedParaLevel>1) {
+            errln("\nError on line %d: Resolved paragraph level incorrect at %s", (int)lineNumber, start);
+            printErrorLine();
+            continue;
+        }
+        start=u_skipWhitespace(end);
+        if(*start!=';') {
+            errorCount++;
+            errln("\nError on line %d: Missing ; separator on line: %s", (int)lineNumber, line);
+            return;
+        }
+        start++;
+        if(!parseLevels(start)) {
+            continue;
+        }
+        start=u_skipWhitespace(start);
+        if(*start==';') {
+            if(!parseOrdering(start+1)) {
+                continue;
+            }
+        }
+        else
+            orderingCount=-1;
+
+        ubidi_setPara(ubidi.getAlias(), inputString.getBuffer(), inputString.length(),
+                      paraLevel, NULL, errorCode);
+        const UBiDiLevel *actualLevels=ubidi_getLevels(ubidi.getAlias(), errorCode);
+        if(errorCode.errIfFailureAndReset("ubidi_setPara() or ubidi_getLevels()")) {
+            errln("Input line %d: %s", (int)lineNumber, line);
+            continue;
+        }
+        UBiDiLevel actualLevel;
+        if((actualLevel=ubidi_getParaLevel(ubidi.getAlias()))!=resolvedParaLevel) {
+            printErrorLine();
+            errln("\nError on line %d: Wrong resolved paragraph level; expected %d actual %d",
+                   (int)lineNumber, resolvedParaLevel, actualLevel);
+            continue;
+        }
+        if(!checkLevels(actualLevels, ubidi_getProcessedLength(ubidi.getAlias()))) {
+            continue;
+        }
+        if(orderingCount>=0 && !checkOrdering(ubidi.getAlias())) {
+            continue;
+        }
+        
+        // tests for ubidi_setParaWithControls
+        // skip 2 tests known not to work (out of 91678 cases, though
+        // only 86 of those tests use controls so 2.3% of those failing),
+        // still investigating these
+        if (lineNumber>=212 && lineNumber<=213) {
+            continue;
+        }
+        
+        const UChar* ubufPtr = inputString.getBuffer();
+        int32_t ubufIdx;
+        UChar utxt[kMaxUtxt];
+        UBiDiLevel ulev[kMaxUtxt];
+        int32_t offsets[kMaxUctl];
+        UChar* uctlPtrs[kMaxUctl];
+        UChar uctl[kMaxUctl][5];
+        UChar *uctlPtr;
+        int32_t utxtLen = 0, offsetsLen = 0, ctlLen = 0;
+        UBool fail = FALSE;
+        for (ubufIdx = 0; ubufIdx < inputString.length(); ubufIdx++) {
+            UChar uc = ubufPtr[ubufIdx];
+            if ( (uc >=0x202A && uc<=0x202E) || (uc >=0x2066 && uc<=0x2069) ) {
+                // have a bidi control
+                if (ctlLen >= 4) {
+                    fail = TRUE; break;
+                }
+                if (ctlLen == 0) {
+                    // starting a new control sequence
+                    if (offsetsLen >= kMaxUctl) {
+                        fail = TRUE; break;
+                    }
+                    offsets[offsetsLen] = utxtLen;
+                    uctlPtr = &uctl[offsetsLen][0];
+                    uctlPtrs[offsetsLen] = uctlPtr;
+                    offsetsLen++;
+                }
+                uctlPtr[ctlLen++] = uc;
+                uctlPtr[ctlLen] = 0;
+            } else {
+                if (utxtLen >= kMaxUtxt) {
+                    fail = TRUE; break;
+                }
+                ctlLen = 0;
+                utxt[utxtLen] = uc;
+                levels[utxtLen] = levels[ubufIdx]; // will always have ubufIdx >= utxtLen so this is OK
+                utxtLen++;
+            }
+        }
+        levelsCount = utxtLen;
+        if (fail) {
+            logln("Skipping BidiCharacterTest unsuitable for ubidi_setParaWithControls: %d: %s", (int)lineNumber, line);
+            continue; // can't use this test
+        }
+        if (offsetsLen > 0 && offsets[offsetsLen-1] >= utxtLen) {
+            --offsetsLen;
+            ubidi_setContext(ubidi.getAlias(), NULL, 0, uctlPtrs[offsetsLen], -1, errorCode);
+        } else {
+            ubidi_setContext(ubidi.getAlias(), NULL, 0, NULL, 0, errorCode);
+        }
+        ubidi_setParaWithControls(ubidi.getAlias(), utxt, utxtLen, paraLevel,
+                                  offsets, offsetsLen, NULL, uctlPtrs, errorCode);
+        actualLevels=ubidi_getLevels(ubidi.getAlias(), errorCode);
+        if(errorCode.errIfFailureAndReset("ubidi_setContext()/ubidi_setParaWithControls()/ubidi_getLevels()")) {
+            errln("Input line %d: %s", (int)lineNumber, line);
+            continue;
+        }
+        if((actualLevel=ubidi_getParaLevel(ubidi.getAlias()))!=resolvedParaLevel) {
+            printErrorLine();
+            errln("\nError on line %d: Wrong resolved paragraph level from ubidi_setParaWithControls; expected %d actual %d",
+                   (int)lineNumber, resolvedParaLevel, actualLevel);
+            continue;
+        }
+        if(!checkLevels(actualLevels, ubidi_getProcessedLength(ubidi.getAlias()))) {
+            continue;
+        }
+    }
+}
+
 static UChar printLevel(UBiDiLevel level) {
     if(level<UBIDI_DEFAULT_LTR) {
         return 0x30+level;
@@ -383,12 +655,11 @@ static uint32_t getDirectionBits(const UBiDiLevel actualLevels[], int32_t actual
     return actualDirectionBits;
 }
 
-UBool BiDiConformanceTest::checkLevels(const UBiDiLevel actualLevels[], int32_t actualCount,
-                                       const char *paraLevelName) {
+UBool BiDiConformanceTest::checkLevels(const UBiDiLevel actualLevels[], int32_t actualCount) {
     UBool isOk=TRUE;
     if(levelsCount!=actualCount) {
-        errln("Wrong number of level values; expected %d actual %d",
-              (int)levelsCount, (int)actualCount);
+        errln("\nError on line %d: Wrong number of level values; expected %d actual %d",
+              (int)lineNumber, (int)levelsCount, (int)actualCount);
         isOk=FALSE;
     } else {
         for(int32_t i=0; i<actualCount; ++i) {
@@ -400,8 +671,8 @@ UBool BiDiConformanceTest::checkLevels(const UBiDiLevel actualLevels[], int32_t
                     // The reordering result is the same, so this is fine.
                     break;
                 } else {
-                    errln("Wrong level value at index %d; expected %d actual %d",
-                          (int)i, levels[i], actualLevels[i]);
+                    errln("\nError on line %d: Wrong level value at index %d; expected %d actual %d",
+                          (int)lineNumber, (int)i, levels[i], actualLevels[i]);
                     isOk=FALSE;
                     break;
                 }
@@ -409,7 +680,7 @@ UBool BiDiConformanceTest::checkLevels(const UBiDiLevel actualLevels[], int32_t
         }
     }
     if(!isOk) {
-        printErrorLine(paraLevelName);
+        printErrorLine();
         UnicodeString els("Expected levels:   ");
         int32_t i;
         for(i=0; i<levelsCount; ++i) {
@@ -430,16 +701,16 @@ UBool BiDiConformanceTest::checkLevels(const UBiDiLevel actualLevels[], int32_t
 // and anyway also removes LRM/RLM/ZWJ/ZWNJ which is not desirable here.
 // Therefore we just skip the indexes for BiDi controls while comparing
 // with the expected ordering that has them omitted.
-UBool BiDiConformanceTest::checkOrdering(UBiDi *ubidi, const char *paraLevelName) {
+UBool BiDiConformanceTest::checkOrdering(UBiDi *ubidi) {
     UBool isOk=TRUE;
-    IcuTestErrorCode errorCode(*this, "TestBidiTest/checkOrdering()");
+    IcuTestErrorCode errorCode(*this, "checkOrdering()");
     int32_t resultLength=ubidi_getResultLength(ubidi);  // visual length including BiDi controls
     int32_t i, visualIndex;
     // Note: It should be faster to call ubidi_countRuns()/ubidi_getVisualRun()
     // and loop over each run's indexes, but that seems unnecessary for this test code.
     for(i=visualIndex=0; i<resultLength; ++i) {
         int32_t logicalIndex=ubidi_getLogicalIndex(ubidi, i, errorCode);
-        if(errorCode.logIfFailureAndReset("ubidi_getLogicalIndex()")) {
+        if(errorCode.errIfFailureAndReset("ubidi_getLogicalIndex()")) {
             errln("Input line %d: %s", (int)lineNumber, line);
             return FALSE;
         }
@@ -447,8 +718,8 @@ UBool BiDiConformanceTest::checkOrdering(UBiDi *ubidi, const char *paraLevelName
             continue;  // BiDi control, omitted from expected ordering.
         }
         if(visualIndex<orderingCount && logicalIndex!=ordering[visualIndex]) {
-            errln("Wrong ordering value at visual index %d; expected %d actual %d",
-                  (int)visualIndex, ordering[visualIndex], logicalIndex);
+            errln("\nError on line %d: Wrong ordering value at visual index %d; expected %d actual %d",
+                  (int)lineNumber, (int)visualIndex, ordering[visualIndex], logicalIndex);
             isOk=FALSE;
             break;
         }
@@ -457,12 +728,12 @@ UBool BiDiConformanceTest::checkOrdering(UBiDi *ubidi, const char *paraLevelName
     // visualIndex is now the visual length minus the BiDi controls,
     // which should match the length of the BidiTest.txt ordering.
     if(isOk && orderingCount!=visualIndex) {
-        errln("Wrong number of ordering values; expected %d actual %d",
-              (int)orderingCount, (int)visualIndex);
+        errln("\nError on line %d: Wrong number of ordering values; expected %d actual %d",
+              (int)lineNumber, (int)orderingCount, (int)visualIndex);
         isOk=FALSE;
     }
     if(!isOk) {
-        printErrorLine(paraLevelName);
+        printErrorLine();
         UnicodeString eord("Expected ordering: ");
         for(i=0; i<orderingCount; ++i) {
             eord.append((UChar)0x20).append((UChar)(0x30+ordering[i]));
@@ -480,7 +751,7 @@ UBool BiDiConformanceTest::checkOrdering(UBiDi *ubidi, const char *paraLevelName
     return isOk;
 }
 
-void BiDiConformanceTest::printErrorLine(const char *paraLevelName) {
+void BiDiConformanceTest::printErrorLine() {
     ++errorCount;
     errln("Input line %5d:   %s", (int)lineNumber, line);
     errln(UnicodeString("Input string:       ")+inputString);