Android patch: Android support for ICU4C tests (Part 2)

- Add stacktrace into the XML test report. (tested intltest only)
- Improve test reporting for Android's consumption
  - Include a test id in the classname for test re-run
    - intltest supports test id, e.g. utility/UnicodeStringTest/TestSizeofUnicodeString
    - To run only the individual test, run cmd "intltest utility/UnicodeStringTest/TestSizeofUnicodeString"
    - The test id is also useful for include-filter option configed in ICU4CTest
    - May be useful for supporting test id in atest, e.g. atest intltest:ICU4CTest.
      But it's NOT implemented yet.
- Call android_icu_register() to initialize the data file.
  - Android has more one data file, i.e. core data (icudt64l.dat) and time zone data.
  - It's important because it ensures libicuuc and libicui18n on device works
    with the given data in the system image.

Bug: 120776993
Test: atest intltest (still 14 failures)
Change-Id: Ide027b4451be6acd380c0fcefec2fe301119191a
diff --git a/icu4c/source/test/cintltst/cintltst.c b/icu4c/source/test/cintltst/cintltst.c
index 37f3cfe..ad75dca 100644
--- a/icu4c/source/test/cintltst/cintltst.c
+++ b/icu4c/source/test/cintltst/cintltst.c
@@ -42,6 +42,13 @@
 #   include <console.h>
 #endif
 
+// ANDROID_USE_ICU_REG is defined when building against the Android OS source tree.
+// androidicuinit is a static library which helps initialize ICU4C using the data
+// on the device.
+#if defined(__ANDROID__) && defined(ANDROID_USE_ICU_REG)
+#include <androidicuinit/android_icu_reg.h>
+#endif
+
 #define CTST_MAX_ALLOC 8192
 /* Array used as a queue */
 static void * ctst_allocated_stuff[CTST_MAX_ALLOC] = {0};
@@ -249,7 +256,11 @@
         (int)((diffTime%U_MILLIS_PER_MINUTE)/U_MILLIS_PER_SECOND),
         (int)(diffTime%U_MILLIS_PER_SECOND));
 
+#ifdef ZERO_EXIT_CODE_FOR_FAILURES
+    return 0;
+#else
     return nerrors ? 1 : 0;
+#endif
 }
 
 /*
@@ -301,7 +312,11 @@
  *                       tests dynamically load some data.
  */
 void ctest_setICU_DATA() {
+    #if defined(__ANDROID__) && defined(ANDROID_USE_ICU_REG)
+    android_icu_register();
+    #else
     u_setDataDirectory(ctest_dataOutDir());
+    #endif
 }
 
 /*  These tests do cleanup and reinitialize ICU in the course of their operation.
@@ -322,12 +337,16 @@
     UErrorCode   status = U_ZERO_ERROR;
     char         *dataDir = safeGetICUDataDirectory();
 
+    #if defined(__ANDROID__) && defined(ANDROID_USE_ICU_REG)
+    android_icu_deregister();
+    #else
     u_cleanup();
+    #endif
     if (!initArgs(gOrigArgc, gOrigArgv, NULL, NULL)) {
         /* Error already displayed. */
         return FALSE;
     }
-    u_setDataDirectory(dataDir);
+    ctest_setICU_DATA();
     free(dataDir);
     u_init(&status);
     if (U_FAILURE(status)) {
diff --git a/icu4c/source/test/intltest/intltest.cpp b/icu4c/source/test/intltest/intltest.cpp
index 55f8e0f..d76c207 100644
--- a/icu4c/source/test/intltest/intltest.cpp
+++ b/icu4c/source/test/intltest/intltest.cpp
@@ -19,6 +19,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <cmath>
+#include <iostream>
 
 #include "unicode/ctest.h" // for str_timeDelta
 #include "unicode/curramt.h"
@@ -45,6 +46,13 @@
 #include "umutex.h"
 #include "uoptions.h"
 
+// ANDROID_USE_ICU_REG is defined when building against the Android OS source tree.
+// androidicuinit is a static library which helps initialize ICU4C using the data
+// on the device.
+#if defined(__ANDROID__) && defined(ANDROID_USE_ICU_REG)
+#include <androidicuinit/android_icu_reg.h>
+#endif
+
 #ifdef XP_MAC_CONSOLE
 #include <console.h>
 #include "Files.h"
@@ -419,7 +427,11 @@
  *                       tests dynamically load some data.
  */
 void IntlTest::setICU_DATA() {
+    #if defined(__ANDROID__) && defined(ANDROID_USE_ICU_REG)
+    android_icu_register();
+    #else
     u_setDataDirectory(ctest_dataOutDir());
+    #endif
 }
 
 
@@ -538,13 +550,15 @@
     }
 }
 
-UBool IntlTest::callTest( IntlTest& testToBeCalled, char* par )
+UBool IntlTest::callTest( IntlTest& testToBeCalled, char* par, const char* basename)
 {
     execCount--; // correct a previously assumed test-exec, as this only calls a subtest
     testToBeCalled.setCaller( this );
-    strcpy(testToBeCalled.basePath, this->basePath );
-    UBool result = testToBeCalled.runTest( testPath, par, testToBeCalled.basePath );
-    strcpy(testToBeCalled.basePath, this->basePath ); // reset it.
+    strcpy(testToBeCalled.basePath, basename);
+    strcat(testToBeCalled.basePath, this->basePath);
+    UBool result = testToBeCalled.runTest( testPath, par, testToBeCalled.basePath);
+    strcpy(testToBeCalled.basePath, basename); // reset it.
+    strcat(testToBeCalled.basePath, this->basePath);
     return result;
 }
 
@@ -675,6 +689,27 @@
 }
 
 
+static std::string string_replace_all(std::string str, const std::string& from, const std::string& to) {
+    size_t start = 0;
+    while((start = str.find(from, start)) != std::string::npos) {
+        str.replace(start, from.length(), to);
+        start += to.length();
+    }
+    return str;
+}
+
+/**
+ * Escape some known characters, but the list is not perfect.
+ */
+static std::string escape_xml_attribute(std::string str) {
+    str = string_replace_all(str, "&", "&amp;");
+    str = string_replace_all(str, "\"", "&quot;");
+    str = string_replace_all(str, "'", "&apos;");
+    str = string_replace_all(str, "<", "&lt;");
+    str = string_replace_all(str, ">", "&gt;");
+    return str;
+}
+
 UBool IntlTest::runTestLoop( char* testname, char* par, char *baseName )
 {
     int32_t    index = 0;
@@ -720,6 +755,7 @@
             strcpy(saveBaseLoc,name);
             strcat(saveBaseLoc,"/");
 
+            currErr = ""; // Reset the current error message
             strcpy(currName, name); // set
             this->runIndexedTest( index, TRUE, name, par );
             currName[0]=0; // reset
@@ -737,7 +773,9 @@
             strcpy(saveBaseLoc,name);
 
 
-            ctest_xml_testcase(baseName, name, secs, (lastErrorCount!=errorCount)?"err":NULL);
+            std::string err = currErr;
+            err = escape_xml_attribute(err);
+            ctest_xml_testcase(name, baseName, secs, (lastErrorCount!=errorCount)?err.c_str():NULL);
             
 
             saveBaseLoc[0]=0; /* reset path */
@@ -849,13 +887,13 @@
 void IntlTest::err( const UnicodeString &message )
 {
     IncErrorCount();
-    if (!no_err_msg) LL_message( message, FALSE );
+    if (!no_err_msg) LL_err_message( message, FALSE );
 }
 
 void IntlTest::errln( const UnicodeString &message )
 {
     IncErrorCount();
-    if (!no_err_msg) LL_message( message, TRUE );
+    if (!no_err_msg) LL_err_message( message, TRUE );
 }
 
 void IntlTest::dataerr( const UnicodeString &message )
@@ -866,7 +904,7 @@
         IncErrorCount();
     }
 
-    if (!no_err_msg) LL_message( message, FALSE );
+    if (!no_err_msg) LL_err_message( message, FALSE );
 }
 
 void IntlTest::dataerrln( const UnicodeString &message )
@@ -882,9 +920,9 @@
 
     if (!no_err_msg) {
       if ( errCount == 1) {
-          LL_message( msg + " - (Are you missing data?)", TRUE ); // only show this message the first time
+          LL_err_message( msg + " - (Are you missing data?)", TRUE ); // only show this message the first time
       } else {
-          LL_message( msg , TRUE );
+          LL_err_message( msg , TRUE );
       }
     }
 }
@@ -1039,7 +1077,7 @@
 
 void IntlTest::printErrors()
 {
-     IntlTest::LL_message(errorList, TRUE);
+     IntlTest::LL_err_message(errorList, TRUE);
 }
 
 UBool IntlTest::printKnownIssues()
@@ -1053,8 +1091,12 @@
   }
 }
 
+void IntlTest::LL_err_message( const UnicodeString& message, UBool newline ) {
+    this->LL_message(message, newline, true);
+}
 
-void IntlTest::LL_message( UnicodeString message, UBool newline )
+
+void IntlTest::LL_message( UnicodeString message, UBool newline, UBool isErr )
 {
     // Synchronize this function.
     // All error messages generated by tests funnel through here.
@@ -1088,6 +1130,7 @@
     length = indent.extract(1, indent.length(), buffer, sizeof(buffer));
     if (length > 0) {
         fwrite(buffer, sizeof(*buffer), length, (FILE *)testoutfp);
+        if (isErr) currErr.append(buffer, length);
     }
 
     // replace each LineFeed by the indentation string
@@ -1098,11 +1141,13 @@
     if (length > 0) {
         length = length > 30000 ? 30000 : length;
         fwrite(buffer, sizeof(*buffer), length, (FILE *)testoutfp);
+        if (isErr) currErr.append(buffer, length);
     }
 
     if (newline) {
         char newLine = '\n';
         fwrite(&newLine, sizeof(newLine), 1, (FILE *)testoutfp);
+        if (isErr) currErr += newLine;
     }
 
     // A newline usually flushes the buffer, but
@@ -1438,8 +1483,7 @@
                 char* name = argv[i];
                 fprintf(stdout, "\n=== Handling test: %s: ===\n", name);
 
-                char baseName[1024];
-                sprintf(baseName, "/%s/", name);
+                char baseName[1024] = "/";
 
                 char* parameter = strchr( name, '@' );
                 if (parameter) {
@@ -1532,7 +1576,12 @@
     if(ctest_xml_fini())
       return 1;
 
+#ifdef ZERO_EXIT_CODE_FOR_FAILURES
+    // Exit code 0 to indicate the test completed.
+    return 0;
+#else
     return major.getErrors();
+#endif
 }
 
 const char* IntlTest::loadTestData(UErrorCode& err){
diff --git a/icu4c/source/test/intltest/intltest.h b/icu4c/source/test/intltest/intltest.h
index c6b91f7..b8650b0 100644
--- a/icu4c/source/test/intltest/intltest.h
+++ b/icu4c/source/test/intltest/intltest.h
@@ -332,7 +332,7 @@
 
     virtual int32_t IncDataErrorCount( void );
 
-    virtual UBool callTest( IntlTest& testToBeCalled, char* par );
+    virtual UBool callTest( IntlTest& testToBeCalled, char* par, const char* basename = "");
 
 
     UBool       verbose;
@@ -363,8 +363,10 @@
     int32_t     numProps;
 
 protected:
+    std::string   currErr; // Error message of the current test case
 
-    virtual void LL_message( UnicodeString message, UBool newline );
+    virtual void LL_err_message( const UnicodeString& message, UBool newline );
+    virtual void LL_message( UnicodeString message, UBool newlin, UBool isErr = FALSE );
 
     // used for collation result reporting, defined here for convenience
 
diff --git a/icu4c/source/test/intltest/itmajor.cpp b/icu4c/source/test/intltest/itmajor.cpp
index 84bee28..c7b54bd 100644
--- a/icu4c/source/test/intltest/itmajor.cpp
+++ b/icu4c/source/test/intltest/itmajor.cpp
@@ -56,7 +56,7 @@
                 if (exec) {
                     logln("TestSuite Utilities---"); logln();
                     IntlTestUtilities test;
-                    callTest( test, par );
+                    callTest( test, par, name );
                 }
                 break;
 
@@ -65,7 +65,7 @@
                 if (exec) {
                     logln("TestSuite Normalize---"); logln();
                     IntlTestNormalize test;
-                    callTest( test, par );
+                    callTest( test, par, name );
                 }
 #endif
                 break;
@@ -75,7 +75,7 @@
                 if (exec) {
                     logln("TestSuite Collator---"); logln();
                     IntlTestCollator test;
-                    callTest( test, par );
+                    callTest( test, par, name );
                 }
 #endif
                 break;
@@ -85,7 +85,7 @@
                 if (exec) {
                     logln("TestSuite Regex---"); logln();
                     RegexTest test;
-                    callTest( test, par );
+                    callTest( test, par, name );
                 }
 #endif
                 break;
@@ -95,7 +95,7 @@
                 if (exec) {
                     logln("TestSuite Format---"); logln();
                     IntlTestFormat test;
-                    callTest( test, par );
+                    callTest( test, par, name );
                 }
 #endif
                 break;
@@ -105,7 +105,7 @@
                 if (exec) {
                     logln("TestSuite Transliterator---"); logln();
                     IntlTestTransliterator test;
-                    callTest( test, par );
+                    callTest( test, par, name );
                 }
 #endif
                 break;
@@ -115,7 +115,7 @@
                 if (exec) {
                     logln("TestSuite RuleBasedBreakIterator---"); logln();
                     IntlTestRBBI test;
-                    callTest( test, par );
+                    callTest( test, par, name );
                 }
 #endif
                 break;
@@ -124,7 +124,7 @@
                 if (exec) {
                     logln("TestSuite RuleBasedNumberFormat----"); logln();
                     IntlTestRBNF test;
-                    callTest(test, par);
+                    callTest(test, par, name);
                 }
 #endif
                 break;
@@ -133,7 +133,7 @@
                 if (exec) {
                     logln("TestSuite RuleBasedNumberFormat RT----"); logln();
                     RbnfRoundTripTest test;
-                    callTest(test, par);
+                    callTest(test, par, name);
                 }
 #endif
                 break;
@@ -143,7 +143,7 @@
                 if (exec) {
                     logln("TestSuite ICUService---"); logln();
                     ICUServiceTest test;
-                    callTest(test, par);
+                    callTest(test, par, name);
                 }
 #endif
                 break;
@@ -152,7 +152,7 @@
             if(exec){
                 logln("TestSuite IDNA----"); logln();
                 TestIDNA test;
-                callTest(test,par);
+                callTest(test,par, name);
             }
 #endif
             break;
@@ -161,7 +161,7 @@
                 if (exec) {
                     logln("TestSuite Conversion---"); logln();
                     ConversionTest test;
-                    callTest( test, par );
+                    callTest( test, par, name );
                 }
 #endif
                 break;
@@ -171,7 +171,7 @@
                 if (exec) {
                     logln("TestSuite RuleBasedNumberParse ----"); logln();
                     IntlTestRBNFParse test;
-                    callTest(test, par);
+                    callTest(test, par, name);
                 }
 #endif
                 break;
@@ -180,7 +180,7 @@
                 if (exec) {
                     logln("TestSuite CharsetDetection---"); logln();
                     CharsetDetectionTest test;
-                    callTest(test, par);
+                    callTest(test, par, name);
                 }
 
                 break;
@@ -191,7 +191,7 @@
                 if (exec) {
                     logln("TestSuite SpoofDetection---"); logln();
                     IntlTestSpoof test;
-                    callTest(test, par);
+                    callTest(test, par, name);
                 }
 #else
                 name = "skip";
@@ -202,7 +202,7 @@
                 if (exec) {
                     logln("TestSuite bidi---"); logln();
                     LocalPointer<IntlTest> test(createBiDiConformanceTest());
-                    callTest(*test, par);
+                    callTest(*test, par, name);
                 }
 
                 break;
diff --git a/icu4c/source/tools/ctestfw/ctest.c b/icu4c/source/tools/ctestfw/ctest.c
index b56c85b..7608c28 100644
--- a/icu4c/source/tools/ctestfw/ctest.c
+++ b/icu4c/source/tools/ctestfw/ctest.c
@@ -1334,7 +1334,7 @@
 ctest_xml_testcase(const char *classname, const char *name, const char *timeSeconds, const char *failMsg) {
   if(!XML_FILE) return 0;
 
-  fprintf(XML_FILE, "\t<testcase classname=\"%s:%s\" name=\"%s:%s\" time=\"%s\"", XML_PREFIX, classname, XML_PREFIX, name, timeSeconds);
+  fprintf(XML_FILE, "\t<testcase classname=\"%s\" name=\"%s\" time=\"%s\"", name, classname, timeSeconds);
   if(failMsg) {
     fprintf(XML_FILE, ">\n\t\t<failure type=\"err\" message=\"%s\"/>\n\t</testcase>\n", failMsg);
   } else {