// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/********************************************************************
 * COPYRIGHT:
 * Copyright (c) 1997-2016, International Business Machines Corporation and
 * others. All Rights Reserved.
 ********************************************************************/


#include "unicode/utypes.h"

/**
 * IntlTest is a base class for tests.
 */

#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cmath>
#include <math.h>

#include "unicode/ctest.h" // for str_timeDelta
#include "unicode/curramt.h"
#include "unicode/locid.h"
#include "unicode/putil.h"
#include "unicode/smpdtfmt.h"
#include "unicode/timezone.h"
#include "unicode/uclean.h"
#include "unicode/ucnv.h"
#include "unicode/unistr.h"
#include "unicode/ures.h"
#include "unicode/utf16.h"

#include "intltest.h"

#include "caltztst.h"
#include "cmemory.h"
#include "cstring.h"
#include "itmajor.h"
#include "mutex.h"
#include "putilimp.h" // for uprv_getRawUTCtime()
#include "uassert.h"
#include "udbgutil.h"
#include "umutex.h"
#include "uoptions.h"
#include "number_decnum.h"

#ifdef XP_MAC_CONSOLE
#include <console.h>
#include "Files.h"
#endif


static char* _testDataPath=NULL;

// Static list of errors found
static UnicodeString errorList;
static void *knownList = NULL; // known issues
static UBool noKnownIssues = FALSE; // if TRUE, don't emit known issues

//-----------------------------------------------------------------------------
//convenience classes to ease porting code that uses the Java
//string-concatenation operator (moved from findword test by rtg)

// [LIU] Just to get things working
UnicodeString
UCharToUnicodeString(UChar c)
{ return UnicodeString(c); }

// [rtg] Just to get things working
UnicodeString
operator+(const UnicodeString& left,
      long num)
{
    char buffer[64];    // nos changed from 10 to 64
    char danger = 'p';  // guard against overrunning the buffer (rtg)

    sprintf(buffer, "%ld", num);
    assert(danger == 'p');

    return left + buffer;
}

UnicodeString
operator+(const UnicodeString& left,
      unsigned long num)
{
    char buffer[64];    // nos changed from 10 to 64
    char danger = 'p';  // guard against overrunning the buffer (rtg)

    sprintf(buffer, "%lu", num);
    assert(danger == 'p');

    return left + buffer;
}

UnicodeString
Int64ToUnicodeString(int64_t num)
{
    char buffer[64];    // nos changed from 10 to 64
    char danger = 'p';  // guard against overrunning the buffer (rtg)

#if defined(_MSC_VER)
    sprintf(buffer, "%I64d", num);
#else
    sprintf(buffer, "%lld", (long long)num);
#endif
    assert(danger == 'p');

    return buffer;
}

UnicodeString
DoubleToUnicodeString(double num)
{
    char buffer[64];    // nos changed from 10 to 64
    char danger = 'p';  // guard against overrunning the buffer (rtg)

    sprintf(buffer, "%1.14e", num);
    assert(danger == 'p');

    return buffer;
}

// [LIU] Just to get things working
UnicodeString
operator+(const UnicodeString& left,
      double num)
{
    char buffer[64];   // was 32, made it arbitrarily bigger (rtg)
    char danger = 'p'; // guard against overrunning the buffer (rtg)

    // IEEE floating point has 52 bits of mantissa, plus one assumed bit
    //  53*log(2)/log(10) = 15.95
    // so there is no need to show more than 16 digits. [alan]

    sprintf(buffer, "%.17g", num);
    assert(danger == 'p');

    return left + buffer;
}

#if 0
UnicodeString
operator+(const UnicodeString& left,
          int64_t num) {
  return left + Int64ToUnicodeString(num);
}
#endif

#if !UCONFIG_NO_FORMATTING

/**
 * Return a string display for this, without surrounding braces.
 */
UnicodeString _toString(const Formattable& f) {
    UnicodeString s;
    switch (f.getType()) {
    case Formattable::kDate:
        {
            UErrorCode status = U_ZERO_ERROR;
            SimpleDateFormat fmt(status);
            if (U_SUCCESS(status)) {
                FieldPosition pos;
                fmt.format(f.getDate(), s, pos);
                s.insert(0, "Date:");
            } else {
                s = UnicodeString("Error creating date format]");
            }
        }
        break;
    case Formattable::kDouble:
        s = UnicodeString("double:") + f.getDouble();
        break;
    case Formattable::kLong:
        s = UnicodeString("long:") + f.getLong();
        break;

    case Formattable::kInt64:
        s = UnicodeString("int64:") + Int64ToUnicodeString(f.getInt64());
        break;

    case Formattable::kString:
        f.getString(s);
        s.insert(0, "String:");
        break;
    case Formattable::kArray:
        {
            int32_t i, n;
            const Formattable* array = f.getArray(n);
            s.insert(0, UnicodeString("Array:"));
            UnicodeString delim(", ");
            for (i=0; i<n; ++i) {
                if (i > 0) {
                    s.append(delim);
                }
                s = s + _toString(array[i]);
            }
        }
        break;
    case Formattable::kObject: {
        const CurrencyAmount* c = dynamic_cast<const CurrencyAmount*>(f.getObject());
        if (c != NULL) {
            s = _toString(c->getNumber()) + " " + UnicodeString(c->getISOCurrency());
        } else {
            s = UnicodeString("Unknown UObject");
        }
        break;
    }
    default:
        s = UnicodeString("Unknown Formattable type=") + (int32_t)f.getType();
        break;
    }
    return s;
}

/**
 * Originally coded this as operator+, but that makes the expression
 * + char* ambiguous. - liu
 */
UnicodeString toString(const Formattable& f) {
    UnicodeString s((UChar)91/*[*/);
    s.append(_toString(f));
    s.append((UChar)0x5d/*]*/);
    return s;
}

#endif

// useful when operator+ won't cooperate
UnicodeString toString(int32_t n) {
    return UnicodeString() + (long)n;
}



UnicodeString toString(UBool b) {
  return b ? UnicodeString("TRUE"):UnicodeString("FALSE");
}

UnicodeString toString(const UnicodeSet& uniset, UErrorCode& status) {
    UnicodeString result;
    uniset.toPattern(result, status);
    return result;
}

// stephen - cleaned up 05/05/99
UnicodeString operator+(const UnicodeString& left, char num)
{ return left + (long)num; }
UnicodeString operator+(const UnicodeString& left, short num)
{ return left + (long)num; }
UnicodeString operator+(const UnicodeString& left, int num)
{ return left + (long)num; }
UnicodeString operator+(const UnicodeString& left, unsigned char num)
{ return left + (unsigned long)num; }
UnicodeString operator+(const UnicodeString& left, unsigned short num)
{ return left + (unsigned long)num; }
UnicodeString operator+(const UnicodeString& left, unsigned int num)
{ return left + (unsigned long)num; }
UnicodeString operator+(const UnicodeString& left, float num)
{ return left + (double)num; }

//------------------

// Append a hex string to the target
UnicodeString&
IntlTest::appendHex(uint32_t number,
            int32_t digits,
            UnicodeString& target)
{
    static const UChar digitString[] = {
        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
        0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0
    }; /* "0123456789ABCDEF" */

    if (digits < 0) {  // auto-digits
        digits = 2;
        uint32_t max = 0xff;
        while (number > max) {
            digits += 2;
            max = (max << 8) | 0xff;
        }
    }
    switch (digits)
    {
    case 8:
        target += digitString[(number >> 28) & 0xF];
        U_FALLTHROUGH;
    case 7:
        target += digitString[(number >> 24) & 0xF];
        U_FALLTHROUGH;
    case 6:
        target += digitString[(number >> 20) & 0xF];
        U_FALLTHROUGH;
    case 5:
        target += digitString[(number >> 16) & 0xF];
        U_FALLTHROUGH;
    case 4:
        target += digitString[(number >> 12) & 0xF];
        U_FALLTHROUGH;
    case 3:
        target += digitString[(number >>  8) & 0xF];
        U_FALLTHROUGH;
    case 2:
        target += digitString[(number >>  4) & 0xF];
        U_FALLTHROUGH;
    case 1:
        target += digitString[(number >>  0) & 0xF];
        break;
    default:
        target += "**";
    }
    return target;
}

UnicodeString
IntlTest::toHex(uint32_t number, int32_t digits) {
    UnicodeString result;
    appendHex(number, digits, result);
    return result;
}

static inline UBool isPrintable(UChar32 c) {
    return c <= 0x7E && (c >= 0x20 || c == 9 || c == 0xA || c == 0xD);
}

// Replace nonprintable characters with unicode escapes
UnicodeString&
IntlTest::prettify(const UnicodeString &source,
           UnicodeString &target)
{
    int32_t i;

    target.remove();
    target += "\"";

    for (i = 0; i < source.length(); )
    {
        UChar32 ch = source.char32At(i);
        i += U16_LENGTH(ch);

        if (!isPrintable(ch))
        {
            if (ch <= 0xFFFF) {
                target += "\\u";
                appendHex(ch, 4, target);
            } else {
                target += "\\U";
                appendHex(ch, 8, target);
            }
        }
        else
        {
            target += ch;
        }
    }

    target += "\"";

    return target;
}

// Replace nonprintable characters with unicode escapes
UnicodeString
IntlTest::prettify(const UnicodeString &source, UBool parseBackslash)
{
    int32_t i;
    UnicodeString target;
    target.remove();
    target += "\"";

    for (i = 0; i < source.length();)
    {
        UChar32 ch = source.char32At(i);
        i += U16_LENGTH(ch);

        if (!isPrintable(ch))
        {
            if (parseBackslash) {
                // If we are preceded by an odd number of backslashes,
                // then this character has already been backslash escaped.
                // Delete a backslash.
                int32_t backslashCount = 0;
                for (int32_t j=target.length()-1; j>=0; --j) {
                    if (target.charAt(j) == (UChar)92) {
                        ++backslashCount;
                    } else {
                        break;
                    }
                }
                if ((backslashCount % 2) == 1) {
                    target.truncate(target.length() - 1);
                }
            }
            if (ch <= 0xFFFF) {
                target += "\\u";
                appendHex(ch, 4, target);
            } else {
                target += "\\U";
                appendHex(ch, 8, target);
            }
        }
        else
        {
            target += ch;
        }
    }

    target += "\"";

    return target;
}

/*  IntlTest::setICU_DATA  - if the ICU_DATA environment variable is not already
 *                       set, try to deduce the directory in which ICU was built,
 *                       and set ICU_DATA to "icu/source/data" in that location.
 *                       The intent is to allow the tests to have a good chance
 *                       of running without requiring that the user manually set
 *                       ICU_DATA.  Common data isn't a problem, since it is
 *                       picked up via a static (build time) reference, but the
 *                       tests dynamically load some data.
 */
void IntlTest::setICU_DATA() {
    const char *original_ICU_DATA = getenv("ICU_DATA");

    if (original_ICU_DATA != NULL && *original_ICU_DATA != 0) {
        /*  If the user set ICU_DATA, don't second-guess the person. */
        return;
    }

    // U_TOPBUILDDIR is set by the makefiles on UNIXes when building cintltst and intltst
    //              to point to the top of the build hierarchy, which may or
    //              may not be the same as the source directory, depending on
    //              the configure options used.  At any rate,
    //              set the data path to the built data from this directory.
    //              The value is complete with quotes, so it can be used
    //              as-is as a string constant.

#if defined (U_TOPBUILDDIR)
    {
        static char env_string[] = U_TOPBUILDDIR "data" U_FILE_SEP_STRING "out" U_FILE_SEP_STRING;
        u_setDataDirectory(env_string);
        return;
    }

#else
    // Use #else so we don't get compiler warnings due to the return above.

    /* On Windows, the file name obtained from __FILE__ includes a full path.
     *             This file is "wherever\icu\source\test\cintltst\cintltst.c"
     *             Change to    "wherever\icu\source\data"
     */
    {
        char p[sizeof(__FILE__) + 10];
        char *pBackSlash;
        int i;

        strcpy(p, __FILE__);
        /* We want to back over three '\' chars.                            */
        /*   Only Windows should end up here, so looking for '\' is safe.   */
        for (i=1; i<=3; i++) {
            pBackSlash = strrchr(p, U_FILE_SEP_CHAR);
            if (pBackSlash != NULL) {
                *pBackSlash = 0;        /* Truncate the string at the '\'   */
            }
        }

        if (pBackSlash != NULL) {
            /* We found and truncated three names from the path.
             *  Now append "source\data" and set the environment
             */
            strcpy(pBackSlash, U_FILE_SEP_STRING "data" U_FILE_SEP_STRING "out" U_FILE_SEP_STRING);
            u_setDataDirectory(p);     /*  p is "ICU_DATA=wherever\icu\source\data"    */
            return;
        }
        else {
            /* __FILE__ on MSVC7 does not contain the directory */
            u_setDataDirectory(".." U_FILE_SEP_STRING ".." U_FILE_SEP_STRING "data" U_FILE_SEP_STRING "out" U_FILE_SEP_STRING);
            return;
        }
    }
#endif

    /* No location for the data dir was identifiable.
     *   Add other fallbacks for the test data location here if the need arises
     */
}


//--------------------------------------------------------------------------------------

static const int32_t indentLevel_offset = 3;
static const char delim = '/';

IntlTest* IntlTest::gTest = NULL;

static int32_t execCount = 0;

void it_log( UnicodeString message )
{
    if (IntlTest::gTest)
        IntlTest::gTest->log( message );
}

void it_logln( UnicodeString message )
{
    if (IntlTest::gTest)
        IntlTest::gTest->logln( message );
}

void it_logln( void )
{
    if (IntlTest::gTest)
        IntlTest::gTest->logln();
}

void it_info( UnicodeString message )
{
    if (IntlTest::gTest)
        IntlTest::gTest->info( message );
}

void it_infoln( UnicodeString message )
{
    if (IntlTest::gTest)
        IntlTest::gTest->infoln( message );
}

void it_infoln( void )
{
    if (IntlTest::gTest)
        IntlTest::gTest->infoln();
}

void it_err()
{
    if (IntlTest::gTest)
        IntlTest::gTest->err();
}

void it_err( UnicodeString message )
{
    if (IntlTest::gTest)
        IntlTest::gTest->err( message );
}

void it_errln( UnicodeString message )
{
    if (IntlTest::gTest)
        IntlTest::gTest->errln( message );
}

void it_dataerr( UnicodeString message )
{
    if (IntlTest::gTest)
        IntlTest::gTest->dataerr( message );
}

void it_dataerrln( UnicodeString message )
{
    if (IntlTest::gTest)
        IntlTest::gTest->dataerrln( message );
}

IntlTest::IntlTest()
{
    caller = NULL;
    testPath = NULL;
    LL_linestart = TRUE;
    errorCount = 0;
    dataErrorCount = 0;
    verbose = FALSE;
    no_time = FALSE;
    no_err_msg = FALSE;
    warn_on_missing_data = FALSE;
    quick = FALSE;
    leaks = FALSE;
    threadCount = 12;
    testoutfp = stdout;
    LL_indentlevel = indentLevel_offset;
    numProps = 0;
    strcpy(basePath, "/");
    currName[0]=0;
}

void IntlTest::setCaller( IntlTest* callingTest )
{
    caller = callingTest;
    if (caller) {
        warn_on_missing_data = caller->warn_on_missing_data;
        verbose = caller->verbose;
        no_err_msg = caller->no_err_msg;
        quick = caller->quick;
        threadCount = caller->threadCount;
        testoutfp = caller->testoutfp;
        write_golden_data = caller->write_golden_data;
        LL_indentlevel = caller->LL_indentlevel + indentLevel_offset;
        numProps = caller->numProps;
        for (int32_t i = 0; i < numProps; i++) {
            proplines[i] = caller->proplines[i];
        }
    }
}

UBool IntlTest::callTest( IntlTest& testToBeCalled, char* par )
{
    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.
    return result;
}

void IntlTest::setPath( char* pathVal )
{
    this->testPath = pathVal;
}

UBool IntlTest::setVerbose( UBool verboseVal )
{
    UBool rval = this->verbose;
    this->verbose = verboseVal;
    return rval;
}

UBool IntlTest::setNotime( UBool no_time )
{
    UBool rval = this->no_time;
    this->no_time = no_time;
    return rval;
}

UBool IntlTest::setWarnOnMissingData( UBool warn_on_missing_dataVal )
{
    UBool rval = this->warn_on_missing_data;
    this->warn_on_missing_data = warn_on_missing_dataVal;
    return rval;
}

UBool IntlTest::setWriteGoldenData( UBool write_golden_data )
{
    UBool rval = this->write_golden_data;
    this->write_golden_data = write_golden_data;
    return rval;
}

UBool IntlTest::setNoErrMsg( UBool no_err_msgVal )
{
    UBool rval = this->no_err_msg;
    this->no_err_msg = no_err_msgVal;
    return rval;
}

UBool IntlTest::setQuick( UBool quickVal )
{
    UBool rval = this->quick;
    this->quick = quickVal;
    return rval;
}

UBool IntlTest::setLeaks( UBool leaksVal )
{
    UBool rval = this->leaks;
    this->leaks = leaksVal;
    return rval;
}

int32_t IntlTest::setThreadCount( int32_t count )
{
    int32_t rval = this->threadCount;
    this->threadCount = count;
    return rval;
}

int32_t IntlTest::getErrors( void )
{
    return errorCount;
}

int32_t IntlTest::getDataErrors( void )
{
    return dataErrorCount;
}

UBool IntlTest::runTest( char* name, char* par, char *baseName )
{
    UBool rval;
    char* pos = NULL;

    char* baseNameBuffer = NULL;

    if(baseName == NULL) {
      baseNameBuffer = (char*)malloc(1024);
      baseName=baseNameBuffer;
      strcpy(baseName, "/");
    }

    if (name)
        pos = strchr( name, delim ); // check if name contains path (by looking for '/')
    if (pos) {
        testPath = pos+1;   // store subpath for calling subtest
        *pos = 0;       // split into two strings
    }else{
        testPath = NULL;
    }

    if (!name || (name[0] == 0) || (strcmp(name, "*") == 0)) {
      rval = runTestLoop( NULL, par, baseName );

    }else if (strcmp( name, "LIST" ) == 0) {
        this->usage();
        rval = TRUE;

    }else{
      rval = runTestLoop( name, par, baseName );
    }

    if (pos)
        *pos = delim;  // restore original value at pos
    if(baseNameBuffer!=NULL) {
      free(baseNameBuffer);
    }
    return rval;
}

// call individual tests, to be overridden to call implementations
void IntlTest::runIndexedTest( int32_t /*index*/, UBool /*exec*/, const char* & /*name*/, char* /*par*/ )
{
    // to be overridden by a method like:
    /*
    switch (index) {
        case 0: name = "First Test"; if (exec) FirstTest( par ); break;
        case 1: name = "Second Test"; if (exec) SecondTest( par ); break;
        default: name = ""; break;
    }
    */
    this->errln("*** runIndexedTest needs to be overridden! ***");
}


UBool IntlTest::runTestLoop( char* testname, char* par, char *baseName )
{
    int32_t    index = 0;
    const char*   name;
    UBool  run_this_test;
    int32_t    lastErrorCount;
    UBool  rval = FALSE;
    UBool   lastTestFailed;

    if(baseName == NULL) {
      printf("ERROR: baseName can't be null.\n");
      return FALSE;
    } else {
      if ((char *)this->basePath != baseName) {
        strcpy(this->basePath, baseName);
      }
    }

    char * saveBaseLoc = baseName+strlen(baseName);

    IntlTest* saveTest = gTest;
    gTest = this;
    do {
        this->runIndexedTest( index, FALSE, name, par );
        if (strcmp(name,"skip") == 0) {
            run_this_test = FALSE;
        } else {
            if (!name || (name[0] == 0))
                break;
            if (!testname) {
                run_this_test = TRUE;
            }else{
                run_this_test = (UBool) (strcmp( name, testname ) == 0);
            }
        }
        if (run_this_test) {
            lastErrorCount = errorCount;
            execCount++;
            char msg[256];
            sprintf(msg, "%s {", name);
            LL_message(msg, TRUE);
            UDate timeStart = uprv_getRawUTCtime();
            strcpy(saveBaseLoc,name);
            strcat(saveBaseLoc,"/");

            strcpy(currName, name); // set
            this->runIndexedTest( index, TRUE, name, par );
            currName[0]=0; // reset

            UDate timeStop = uprv_getRawUTCtime();
            rval = TRUE; // at least one test has been called
            char secs[256];
            if(!no_time) {
              sprintf(secs, "%f", (timeStop-timeStart)/1000.0);
            } else {
              secs[0]=0;
            }
            

            strcpy(saveBaseLoc,name);


            ctest_xml_testcase(baseName, name, secs, (lastErrorCount!=errorCount)?"err":NULL);
            

            saveBaseLoc[0]=0; /* reset path */
            
            if (lastErrorCount == errorCount) {
                sprintf( msg, "   } OK:   %s ", name );
                if(!no_time) str_timeDelta(msg+strlen(msg),timeStop-timeStart);
                lastTestFailed = FALSE;
            }else{
                sprintf(msg,  "   } ERRORS (%li) in %s", (long)(errorCount-lastErrorCount), name);
                if(!no_time) str_timeDelta(msg+strlen(msg),timeStop-timeStart);

                for(int i=0;i<LL_indentlevel;i++) {
                    errorList += " ";
                }
                errorList += name;
                errorList += "\n";
                lastTestFailed = TRUE;
            }
            LL_indentlevel -= 3;
            if (lastTestFailed) {
                LL_message( "", TRUE);
            }
            LL_message( msg, TRUE);
            if (lastTestFailed) {
                LL_message( "", TRUE);
            }
            LL_indentlevel += 3;
        }
        index++;
    }while(name);

    *saveBaseLoc = 0;

    gTest = saveTest;
    return rval;
}


/**
* Adds given string to the log if we are in verbose mode.
*/
void IntlTest::log( const UnicodeString &message )
{
    if( verbose ) {
        LL_message( message, FALSE );
    }
}

/**
* Adds given string to the log if we are in verbose mode. Adds a new line to
* the given message.
*/
void IntlTest::logln( const UnicodeString &message )
{
    if( verbose ) {
        LL_message( message, TRUE );
    }
}

void IntlTest::logln( void )
{
    if( verbose ) {
        LL_message( "", TRUE );
    }
}

/**
* Unconditionally adds given string to the log.
*/
void IntlTest::info( const UnicodeString &message )
{
  LL_message( message, FALSE );
}

/**
* Unconditionally adds given string to the log. Adds a new line to
* the given message.
*/
void IntlTest::infoln( const UnicodeString &message )
{
  LL_message( message, TRUE );
}

void IntlTest::infoln( void )
{
  LL_message( "", TRUE );
}

int32_t IntlTest::IncErrorCount( void )
{
    errorCount++;
    if (caller) caller->IncErrorCount();
    return errorCount;
}

int32_t IntlTest::IncDataErrorCount( void )
{
    dataErrorCount++;
    if (caller) caller->IncDataErrorCount();
    return dataErrorCount;
}

void IntlTest::err()
{
    IncErrorCount();
}

void IntlTest::err( const UnicodeString &message )
{
    IncErrorCount();
    if (!no_err_msg) LL_message( message, FALSE );
}

void IntlTest::errln( const UnicodeString &message )
{
    IncErrorCount();
    if (!no_err_msg) LL_message( message, TRUE );
}

void IntlTest::dataerr( const UnicodeString &message )
{
    IncDataErrorCount();

    if (!warn_on_missing_data) {
        IncErrorCount();
    }

    if (!no_err_msg) LL_message( message, FALSE );
}

void IntlTest::dataerrln( const UnicodeString &message )
{
    int32_t errCount = IncDataErrorCount();
    UnicodeString msg;
    if (!warn_on_missing_data) {
        IncErrorCount();
        msg = message;
    } else {
        msg = UnicodeString("[DATA] " + message);
    }

    if (!no_err_msg) {
      if ( errCount == 1) {
          LL_message( msg + " - (Are you missing data?)", TRUE ); // only show this message the first time
      } else {
          LL_message( msg , TRUE );
      }
    }
}

void IntlTest::errcheckln(UErrorCode status, const UnicodeString &message ) {
    if (status == U_FILE_ACCESS_ERROR || status == U_MISSING_RESOURCE_ERROR) {
        dataerrln(message);
    } else {
        errln(message);
    }
}

/* convenience functions that include sprintf formatting */
void IntlTest::log(const char *fmt, ...)
{
    char buffer[4000];
    va_list ap;

    va_start(ap, fmt);
    /* sprintf it just to make sure that the information is valid */
    vsprintf(buffer, fmt, ap);
    va_end(ap);
    if( verbose ) {
        log(UnicodeString(buffer, (const char *)NULL));
    }
}

void IntlTest::logln(const char *fmt, ...)
{
    char buffer[4000];
    va_list ap;

    va_start(ap, fmt);
    /* sprintf it just to make sure that the information is valid */
    vsprintf(buffer, fmt, ap);
    va_end(ap);
    if( verbose ) {
        logln(UnicodeString(buffer, (const char *)NULL));
    }
}

UBool IntlTest::logKnownIssue(const char *ticket, const char *fmt, ...)
{
    char buffer[4000];
    va_list ap;

    va_start(ap, fmt);
    /* sprintf it just to make sure that the information is valid */
    vsprintf(buffer, fmt, ap);
    va_end(ap);
    return logKnownIssue(ticket, UnicodeString(buffer, (const char *)NULL));
}

UBool IntlTest::logKnownIssue(const char *ticket) {
  return logKnownIssue(ticket, UnicodeString());
}

UBool IntlTest::logKnownIssue(const char *ticket, const UnicodeString &msg) {
  if(noKnownIssues) return FALSE;

  char fullpath[2048];
  strcpy(fullpath, basePath);
  strcat(fullpath, currName);
  UnicodeString msg2 = msg;
  UBool firstForTicket = TRUE, firstForWhere = TRUE;
  knownList = udbg_knownIssue_openU(knownList, ticket, fullpath, msg2.getTerminatedBuffer(), &firstForTicket, &firstForWhere);

  msg2 = UNICODE_STRING_SIMPLE("(Known issue ") +
      UnicodeString(ticket, -1, US_INV) + UNICODE_STRING_SIMPLE(") ") + msg;
  if(firstForTicket || firstForWhere) {
    infoln(msg2);
  } else {
    logln(msg2);
  }

  return TRUE;
}

/* convenience functions that include sprintf formatting */
void IntlTest::info(const char *fmt, ...)
{
    char buffer[4000];
    va_list ap;

    va_start(ap, fmt);
    /* sprintf it just to make sure that the information is valid */
    vsprintf(buffer, fmt, ap);
    va_end(ap);
    info(UnicodeString(buffer, (const char *)NULL));
}

void IntlTest::infoln(const char *fmt, ...)
{
    char buffer[4000];
    va_list ap;

    va_start(ap, fmt);
    /* sprintf it just to make sure that the information is valid */
    vsprintf(buffer, fmt, ap);
    va_end(ap);
    infoln(UnicodeString(buffer, (const char *)NULL));
}

void IntlTest::err(const char *fmt, ...)
{
    char buffer[4000];
    va_list ap;

    va_start(ap, fmt);
    vsprintf(buffer, fmt, ap);
    va_end(ap);
    err(UnicodeString(buffer, (const char *)NULL));
}

void IntlTest::errln(const char *fmt, ...)
{
    char buffer[4000];
    va_list ap;

    va_start(ap, fmt);
    vsprintf(buffer, fmt, ap);
    va_end(ap);
    errln(UnicodeString(buffer, (const char *)NULL));
}

void IntlTest::dataerrln(const char *fmt, ...)
{
    char buffer[4000];
    va_list ap;

    va_start(ap, fmt);
    vsprintf(buffer, fmt, ap);
    va_end(ap);
    dataerrln(UnicodeString(buffer, (const char *)NULL));
}

void IntlTest::errcheckln(UErrorCode status, const char *fmt, ...)
{
    char buffer[4000];
    va_list ap;

    va_start(ap, fmt);
    vsprintf(buffer, fmt, ap);
    va_end(ap);
    
    if (status == U_FILE_ACCESS_ERROR || status == U_MISSING_RESOURCE_ERROR) {
        dataerrln(UnicodeString(buffer, (const char *)NULL));
    } else {
        errln(UnicodeString(buffer, (const char *)NULL));
    }
}

void IntlTest::printErrors()
{
     IntlTest::LL_message(errorList, TRUE);
}

UBool IntlTest::printKnownIssues()
{
  if(knownList != NULL) {
    udbg_knownIssue_print(knownList);
    udbg_knownIssue_close(knownList);
    return TRUE;
  } else {
    return FALSE;
  }
}


void IntlTest::LL_message( UnicodeString message, UBool newline )
{
    // Synchronize this function.
    // All error messages generated by tests funnel through here.
    // Multithreaded tests can concurrently generate errors, requiring synchronization
    // to keep each message together.
    static UMutex messageMutex;
    Mutex lock(&messageMutex);

    // string that starts with a LineFeed character and continues
    // with spaces according to the current indentation
    static const UChar indentUChars[] = {
        '\n',
        32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
        32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
        32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
        32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
        32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
        32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
        32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
        32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
        32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
        32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32
    };
    U_ASSERT(1 + LL_indentlevel <= UPRV_LENGTHOF(indentUChars));
    UnicodeString indent(FALSE, indentUChars, 1 + LL_indentlevel);

    char buffer[30000];
    int32_t length;

    // stream out the indentation string first if necessary
    length = indent.extract(1, indent.length(), buffer, sizeof(buffer));
    if (length > 0) {
        fwrite(buffer, sizeof(*buffer), length, (FILE *)testoutfp);
    }

    // replace each LineFeed by the indentation string
    message.findAndReplace(UnicodeString((UChar)'\n'), indent);

    // stream out the message
    length = message.extract(0, message.length(), buffer, sizeof(buffer));
    if (length > 0) {
        length = length > 30000 ? 30000 : length;
        fwrite(buffer, sizeof(*buffer), length, (FILE *)testoutfp);
    }

    if (newline) {
        char newLine = '\n';
        fwrite(&newLine, sizeof(newLine), 1, (FILE *)testoutfp);
    }

    // A newline usually flushes the buffer, but
    // flush the message just in case of a core dump.
    fflush((FILE *)testoutfp);
}

/**
* Print a usage message for this test class.
*/
void IntlTest::usage( void )
{
    UBool save_verbose = setVerbose( TRUE );
    logln("Test names:");
    logln("-----------");

    int32_t index = 0;
    const char* name = NULL;
    do{
        this->runIndexedTest( index, FALSE, name );
        if (!name) break;
        logln(name);
        index++;
    }while (name && (name[0] != 0));
    setVerbose( save_verbose );
}


// memory leak reporting software will be able to take advantage of the testsuite
// being run a second time local to a specific method in order to report only actual leaks
UBool
IntlTest::run_phase2( char* name, char* par ) // supports reporting memory leaks
{
    UnicodeString* strLeak = new UnicodeString("forced leak"); // for verifying purify filter
    strLeak->append(" for verifying purify filter");
    return this->runTest( name, par );
}


#if UCONFIG_NO_LEGACY_CONVERSION
#   define TRY_CNV_1 "iso-8859-1"
#   define TRY_CNV_2 "ibm-1208"
#else
#   define TRY_CNV_1 "iso-8859-7"
#   define TRY_CNV_2 "sjis"
#endif

#ifdef UNISTR_COUNT_FINAL_STRING_LENGTHS
U_CAPI void unistr_printLengths();
#endif

int
main(int argc, char* argv[])
{
    UBool syntax = FALSE;
    UBool all = FALSE;
    UBool verbose = FALSE;
    UBool no_err_msg = FALSE;
    UBool no_time = FALSE;
    UBool quick = TRUE;
    UBool name = FALSE;
    UBool leaks = FALSE;
    UBool utf8 = FALSE;
    const char *summary_file = NULL;
    UBool warnOnMissingData = FALSE;
    UBool writeGoldenData = FALSE;
    UBool defaultDataFound = FALSE;
    int32_t threadCount = 12;
    UErrorCode errorCode = U_ZERO_ERROR;
    UConverter *cnv = NULL;
    const char *warnOrErr = "Failure";
    UDate startTime, endTime;
    int32_t diffTime;
    const char *props[IntlTest::kMaxProps];
    int32_t nProps = 0;

    U_MAIN_INIT_ARGS(argc, argv);

    startTime = uprv_getRawUTCtime();

    for (int i = 1; i < argc; ++i) {
        if (argv[i][0] == '-') {
            const char* str = argv[i] + 1;
            if (strcmp("verbose", str) == 0 ||
                strcmp("v", str) == 0)
                verbose = TRUE;
            else if (strcmp("noerrormsg", str) == 0 ||
                     strcmp("n", str) == 0)
                no_err_msg = TRUE;
            else if (strcmp("exhaustive", str) == 0 ||
                     strcmp("e", str) == 0)
                quick = FALSE;
            else if (strcmp("all", str) == 0 ||
                     strcmp("a", str) == 0)
                all = TRUE;
            else if (strcmp("utf-8", str) == 0 ||
                     strcmp("u", str) == 0)
                utf8 = TRUE;
            else if (strcmp("noknownissues", str) == 0 ||
                     strcmp("K", str) == 0)
                noKnownIssues = TRUE;
            else if (strcmp("leaks", str) == 0 ||
                     strcmp("l", str) == 0)
                leaks = TRUE;
            else if (strcmp("notime", str) == 0 ||
                     strcmp("T", str) == 0)
                no_time = TRUE;
            else if (strcmp("goldens", str) == 0 ||
                     strcmp("G", str) == 0)
                writeGoldenData = TRUE;
            else if (strncmp("E", str, 1) == 0)
                summary_file = str+1;
            else if (strcmp("x", str)==0) {
              if(++i>=argc) {
                printf("* Error: '-x' option requires an argument. usage: '-x outfile.xml'.\n");
                syntax = TRUE;
              }
              if(ctest_xml_setFileName(argv[i])) { /* set the name */
                return 1; /* error */
              }
            } else if (strcmp("w", str) == 0) {
              warnOnMissingData = TRUE;
              warnOrErr = "WARNING";
            }
            else if (strncmp("threads:", str, 8) == 0) {
                threadCount = atoi(str + 8);
            }
            else if (strncmp("prop:", str, 5) == 0) {
                if (nProps < IntlTest::kMaxProps) {
                    props[nProps] = str + 5;
                }
                nProps++;
            }
            else {
                syntax = TRUE;
            }
        }else{
            name = TRUE;
        }
    }

    if (!all && !name) {
        all = TRUE;
    } else if (all && name) {
        syntax = TRUE;
    }

    if (syntax) {
        fprintf(stdout,
                "### Syntax:\n"
                "### IntlTest [-option1 -option2 ...] [testname1 testname2 ...] \n"
                "### \n"
                "### Options are: verbose (v), all (a), noerrormsg (n), \n"
                "### exhaustive (e), leaks (l), -x xmlfile.xml, prop:<propery>=<value>, \n"
                "### notime (T), \n"
                "### threads:<threadCount>\n"
                "###     (The default thread count is 12.),\n"
                "### (Specify either -all (shortcut -a) or a test name). \n"
                "### -all will run all of the tests.\n"
                "### \n"
                "### To get a list of the test names type: intltest LIST \n"
                "### To run just the utility tests type: intltest utility \n"
                "### \n"
                "### Test names can be nested using slashes (\"testA/subtest1\") \n"
                "### For example to list the utility tests type: intltest utility/LIST \n"
                "### To run just the Locale test type: intltest utility/LocaleTest \n"
                "### \n"
                "### A parameter can be specified for a test by appending '@' and the value \n"
                "### to the testname. \n\n");
        return 1;
    }

    if (nProps > IntlTest::kMaxProps) {
        fprintf(stdout, "### Too many properties.  Exiting.\n");
    }

    MajorTestLevel major;
    major.setVerbose( verbose );
    major.setNoErrMsg( no_err_msg );
    major.setQuick( quick );
    major.setLeaks( leaks );
    major.setThreadCount( threadCount );
    major.setWarnOnMissingData( warnOnMissingData );
    major.setWriteGoldenData( writeGoldenData );
    major.setNotime (no_time);
    for (int32_t i = 0; i < nProps; i++) {
        major.setProperty(props[i]);
    }


    fprintf(stdout, "-----------------------------------------------\n");
    fprintf(stdout, " IntlTest (C++) Test Suite for                 \n");
    fprintf(stdout, "   International Components for Unicode %s\n", U_ICU_VERSION);


    {
	const char *charsetFamily = "Unknown";
        int32_t voidSize = (int32_t)sizeof(void*);
        int32_t bits = voidSize * 8;
        if(U_CHARSET_FAMILY==U_ASCII_FAMILY) {
           charsetFamily="ASCII";
        } else if(U_CHARSET_FAMILY==U_EBCDIC_FAMILY) {
           charsetFamily="EBCDIC";
        }
        fprintf(stdout, 
                    "   Bits: %d, Byte order: %s, Chars: %s\n",
                     bits, U_IS_BIG_ENDIAN?"Big endian":"Little endian",
                     charsetFamily);
    }
    fprintf(stdout, "-----------------------------------------------\n");
    fprintf(stdout, " Options:                                       \n");
    fprintf(stdout, "   all (a)                  : %s\n", (all?               "On" : "Off"));
    fprintf(stdout, "   Verbose (v)              : %s\n", (verbose?           "On" : "Off"));
    fprintf(stdout, "   No error messages (n)    : %s\n", (no_err_msg?        "On" : "Off"));
    fprintf(stdout, "   Exhaustive (e)           : %s\n", (!quick?            "On" : "Off"));
    fprintf(stdout, "   Leaks (l)                : %s\n", (leaks?             "On" : "Off"));
    fprintf(stdout, "   utf-8 (u)                : %s\n", (utf8?              "On" : "Off"));
    fprintf(stdout, "   notime (T)               : %s\n", (no_time?           "On" : "Off"));
    fprintf(stdout, "   noknownissues (K)        : %s\n", (noKnownIssues?     "On" : "Off"));
    fprintf(stdout, "   Warn on missing data (w) : %s\n", (warnOnMissingData? "On" : "Off"));
    fprintf(stdout, "   Write golden data (G)    : %s\n", (writeGoldenData?   "On" : "Off"));
    fprintf(stdout, "   Threads                  : %d\n", threadCount);
    for (int32_t i = 0; i < nProps; i++) {
        fprintf(stdout, "   Custom property (prop:)  : %s\n", props[i]);
    }
    fprintf(stdout, "-----------------------------------------------\n");

    if(utf8) {
      ucnv_setDefaultName("utf-8");
    }
    /* Check whether ICU will initialize without forcing the build data directory into
     *  the ICU_DATA path.  Success here means either the data dll contains data, or that
     *  this test program was run with ICU_DATA set externally.  Failure of this check
     *  is normal when ICU data is not packaged into a shared library.
     *
     *  Whether or not this test succeeds, we want to cleanup and reinitialize
     *  with a data path so that data loading from individual files can be tested.
     */
    u_init(&errorCode);
    if (U_FAILURE(errorCode)) {
        fprintf(stderr,
            "#### Note:  ICU Init without build-specific setDataDirectory() failed.\n");
        defaultDataFound = FALSE;
    }
    else {
        defaultDataFound = TRUE;
    }
    u_cleanup();
    if(utf8) {
      ucnv_setDefaultName("utf-8");
    }
    errorCode = U_ZERO_ERROR;

    /* Initialize ICU */
    if (!defaultDataFound) {
        IntlTest::setICU_DATA();   // Must set data directory before u_init() is called.
    }
    u_init(&errorCode);
    if (U_FAILURE(errorCode)) {
        fprintf(stderr,
            "#### ERROR! %s: u_init() failed with status = \"%s\".\n"
            "*** Check the ICU_DATA environment variable and \n"
            "*** check that the data files are present.\n", argv[0], u_errorName(errorCode));
            if(warnOnMissingData == 0) {
                fprintf(stderr, "*** Exiting.  Use the '-w' option if data files were\n*** purposely removed, to continue test anyway.\n");
                u_cleanup();
                return 1;
            }
    }

    // initial check for the default converter
    errorCode = U_ZERO_ERROR;
    cnv = ucnv_open(0, &errorCode);
    if(cnv != 0) {
        // ok
        ucnv_close(cnv);
    } else {
        fprintf(stdout,
                "*** %s! The default converter [%s] cannot be opened.\n"
                "*** Check the ICU_DATA environment variable and\n"
                "*** check that the data files are present.\n",
                warnOrErr, ucnv_getDefaultName());
        if(!warnOnMissingData) {
          fprintf(stdout, "*** Exiting.  Use the '-w' option if data files were\n*** purposely removed, to continue test anyway.\n");
          return 1;
        }
    }

    // try more data
    cnv = ucnv_open(TRY_CNV_2, &errorCode);
    if(cnv != 0) {
        // ok
        ucnv_close(cnv);
    } else {
        fprintf(stdout,
                "*** %s! The converter for " TRY_CNV_2 " cannot be opened.\n"
                "*** Check the ICU_DATA environment variable and \n"
                "*** check that the data files are present.\n", warnOrErr);
        if(!warnOnMissingData) {
          fprintf(stdout, "*** Exiting.  Use the '-w' option if data files were\n*** purposely removed, to continue test anyway.\n");
          return 1;
        }
    }

    UResourceBundle *rb = ures_open(0, "en", &errorCode);
    ures_close(rb);
    if(U_FAILURE(errorCode)) {
        fprintf(stdout,
                "*** %s! The \"en\" locale resource bundle cannot be opened.\n"
                "*** Check the ICU_DATA environment variable and \n"
                "*** check that the data files are present.\n", warnOrErr);
        if(!warnOnMissingData) {
          fprintf(stdout, "*** Exiting.  Use the '-w' option if data files were\n*** purposely removed, to continue test anyway.\n");
          return 1;
        }
    }

    Locale originalLocale;  // Save the default locale for comparison later on.

    if(ctest_xml_init("intltest")) 
      return 1;


    /* TODO: Add option to call u_cleanup and rerun tests. */
    if (all) {
        major.runTest();
        if (leaks) {
            major.run_phase2( NULL, NULL );
        }
    }else{
        for (int i = 1; i < argc; ++i) {
            if (argv[i][0] != '-') {
                char* name = argv[i];
                fprintf(stdout, "\n=== Handling test: %s: ===\n", name);

                char baseName[1024];
                sprintf(baseName, "/%s/", name);

                char* parameter = strchr( name, '@' );
                if (parameter) {
                    *parameter = 0;
                    parameter += 1;
                }
                execCount = 0;
                UBool res = major.runTest( name, parameter, baseName );
                if (leaks && res) {
                    major.run_phase2( name, parameter );
                }
                if (!res || (execCount <= 0)) {
                    fprintf(stdout, "\n---ERROR: Test doesn't exist: %s!\n", name);
                }
            } else if(!strcmp(argv[i],"-x")) {
              i++;
            }
        }
    }


#if !UCONFIG_NO_FORMATTING
    CalendarTimeZoneTest::cleanup();
#endif

    free(_testDataPath);
    _testDataPath = 0;

    Locale lastDefaultLocale;
    if (originalLocale != lastDefaultLocale) {
        major.errln("FAILURE: A test changed the default locale without resetting it.");
    }

    fprintf(stdout, "\n--------------------------------------\n");
    if( major.printKnownIssues() ) {
      fprintf(stdout, " To run suppressed tests, use the -K option. \n");
    }
    if (major.getErrors() == 0) {
        /* Call it twice to make sure that the defaults were reset. */
        /* Call it before the OK message to verify proper cleanup. */
        u_cleanup();
        u_cleanup();

        fprintf(stdout, "OK: All tests passed without error.\n");

        if (major.getDataErrors() != 0) {
            fprintf(stdout, "\t*WARNING* some data-loading errors were ignored by the -w option.\n");
        }
    }else{
        fprintf(stdout, "Errors in total: %ld.\n", (long)major.getErrors());
        major.printErrors();

        if(summary_file != NULL) {
          FILE *summf = fopen(summary_file, "w");
          if( summf != NULL) {
            char buf[10000];
            int32_t length = errorList.extract(0, errorList.length(), buf, sizeof(buf));
            fwrite(buf, sizeof(*buf), length, (FILE*)summf);
            fclose(summf);
          }
        }


        if (major.getDataErrors() != 0) {
            fprintf(stdout, "\t*Note* some errors are data-loading related. If the data used is not the \n"
                    "\tstock ICU data (i.e some have been added or removed), consider using\n"
                    "\tthe '-w' option to turn these errors into warnings.\n");
        }

        /* Call afterwards to display errors. */
        u_cleanup();
    }

#ifdef UNISTR_COUNT_FINAL_STRING_LENGTHS
    unistr_printLengths();
#endif

    fprintf(stdout, "--------------------------------------\n");

    if (execCount <= 0) {
        fprintf(stdout, "***** Not all called tests actually exist! *****\n");
    }
    if(!no_time) {
      endTime = uprv_getRawUTCtime();
      diffTime = (int32_t)(endTime - startTime);
      printf("Elapsed Time: %02d:%02d:%02d.%03d\n",
             (int)((diffTime%U_MILLIS_PER_DAY)/U_MILLIS_PER_HOUR),
             (int)((diffTime%U_MILLIS_PER_HOUR)/U_MILLIS_PER_MINUTE),
             (int)((diffTime%U_MILLIS_PER_MINUTE)/U_MILLIS_PER_SECOND),
             (int)(diffTime%U_MILLIS_PER_SECOND));
    }

    if(ctest_xml_fini())
      return 1;

    return major.getErrors();
}

const char* IntlTest::loadTestData(UErrorCode& err){
    if( _testDataPath == NULL){
        const char*      directory=NULL;
        UResourceBundle* test =NULL;
        char* tdpath=NULL;
        const char* tdrelativepath;

#if defined (U_TOPBUILDDIR)
        tdrelativepath = "test" U_FILE_SEP_STRING "testdata" U_FILE_SEP_STRING "out" U_FILE_SEP_STRING;
        directory = U_TOPBUILDDIR;
#else
        tdrelativepath = ".." U_FILE_SEP_STRING "test" U_FILE_SEP_STRING "testdata" U_FILE_SEP_STRING "out" U_FILE_SEP_STRING;
        directory = pathToDataDirectory();
#endif

        tdpath = (char*) malloc(sizeof(char) *(( strlen(directory) * strlen(tdrelativepath)) + 100));


        /* u_getDataDirectory shoul return \source\data ... set the
         * directory to ..\source\data\..\test\testdata\out\testdata
         */
        strcpy(tdpath, directory);
        strcat(tdpath, tdrelativepath);
        strcat(tdpath,"testdata");

        test=ures_open(tdpath, "testtypes", &err);

        if(U_FAILURE(err)){
            err = U_FILE_ACCESS_ERROR;
            it_dataerrln((UnicodeString)"Could not load testtypes.res in testdata bundle with path " + tdpath + (UnicodeString)" - " + u_errorName(err));
            return "";
        }
        ures_close(test);
        _testDataPath = tdpath;
        return _testDataPath;
    }
    return _testDataPath;
}

const char* IntlTest::getTestDataPath(UErrorCode& err) {
    return loadTestData(err);
}

/* Returns the path to icu/source/test/testdata/ */
const char *IntlTest::getSourceTestData(UErrorCode& /*err*/) {
    const char *srcDataDir = NULL;
#ifdef U_TOPSRCDIR
    srcDataDir = U_TOPSRCDIR U_FILE_SEP_STRING"test" U_FILE_SEP_STRING "testdata" U_FILE_SEP_STRING;
#else
    srcDataDir = ".." U_FILE_SEP_STRING ".." U_FILE_SEP_STRING "test" U_FILE_SEP_STRING "testdata" U_FILE_SEP_STRING;
    FILE *f = fopen(".." U_FILE_SEP_STRING ".." U_FILE_SEP_STRING "test" U_FILE_SEP_STRING "testdata" U_FILE_SEP_STRING "rbbitst.txt", "r");
    if (f) {
        /* We're in icu/source/test/intltest/ */
        fclose(f);
    }
    else {
        /* We're in icu/source/test/intltest/Platform/(Debug|Release) */
        srcDataDir = ".." U_FILE_SEP_STRING ".." U_FILE_SEP_STRING ".." U_FILE_SEP_STRING ".." U_FILE_SEP_STRING
                     "test" U_FILE_SEP_STRING "testdata" U_FILE_SEP_STRING;
    }
#endif
    return srcDataDir;
}

char *IntlTest::getUnidataPath(char path[]) {
    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
        UErrorCode errorCode = U_ZERO_ERROR;
        const char *testDataPath = loadTestData(errorCode);
        if(U_FAILURE(errorCode)) {
            it_errln(UnicodeString(
                        "unable to find path to source/data/unidata/ and loadTestData() failed: ") +
                    u_errorName(errorCode));
            return NULL;
        }
        strcpy(path, testDataPath);
        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;
}

const char* IntlTest::fgDataDir = NULL;

/* returns the path to icu/source/data */
const char *  IntlTest::pathToDataDirectory()
{

    if(fgDataDir != NULL) {
        return fgDataDir;
    }

    /* U_TOPSRCDIR is set by the makefiles on UNIXes when building cintltst and intltst
    //              to point to the top of the build hierarchy, which may or
    //              may not be the same as the source directory, depending on
    //              the configure options used.  At any rate,
    //              set the data path to the built data from this directory.
    //              The value is complete with quotes, so it can be used
    //              as-is as a string constant.
    */
#if defined (U_TOPSRCDIR)
    {
        fgDataDir = U_TOPSRCDIR  U_FILE_SEP_STRING "data" U_FILE_SEP_STRING;
    }
#else

    /* On Windows, the file name obtained from __FILE__ includes a full path.
     *             This file is "wherever\icu\source\test\cintltst\cintltst.c"
     *             Change to    "wherever\icu\source\data"
     */
    {
        static char p[sizeof(__FILE__) + 10];
        char *pBackSlash;
        int i;

        strcpy(p, __FILE__);
        /* We want to back over three '\' chars.                            */
        /*   Only Windows should end up here, so looking for '\' is safe.   */
        for (i=1; i<=3; i++) {
            pBackSlash = strrchr(p, U_FILE_SEP_CHAR);
            if (pBackSlash != NULL) {
                *pBackSlash = 0;        /* Truncate the string at the '\'   */
            }
        }

        if (pBackSlash != NULL) {
            /* We found and truncated three names from the path.
            *  Now append "source\data" and set the environment
            */
            strcpy(pBackSlash, U_FILE_SEP_STRING "data" U_FILE_SEP_STRING );
            fgDataDir = p;
        }
        else {
            /* __FILE__ on MSVC7 does not contain the directory */
            FILE *file = fopen(".." U_FILE_SEP_STRING ".." U_FILE_SEP_STRING "data" U_FILE_SEP_STRING "Makefile.in", "r");
            if (file) {
                fclose(file);
                fgDataDir = ".." U_FILE_SEP_STRING ".." U_FILE_SEP_STRING "data" U_FILE_SEP_STRING;
            }
            else {
                fgDataDir = ".." U_FILE_SEP_STRING ".." U_FILE_SEP_STRING ".." U_FILE_SEP_STRING ".." U_FILE_SEP_STRING "data" U_FILE_SEP_STRING;
            }
        }
    }
#endif

    return fgDataDir;

}

/*
 * This is a variant of cintltst/ccolltst.c:CharsToUChars().
 * It converts an invariant-character string into a UnicodeString, with
 * unescaping \u sequences.
 */
UnicodeString CharsToUnicodeString(const char* chars){
    return UnicodeString(chars, -1, US_INV).unescape();
}

UnicodeString ctou(const char* chars) {
    return CharsToUnicodeString(chars);
}

#define RAND_M  (714025)
#define RAND_IA (1366)
#define RAND_IC (150889)

static int32_t RAND_SEED;

/**
 * Returns a uniform random value x, with 0.0 <= x < 1.0.  Use
 * with care: Does not return all possible values; returns one of
 * 714,025 values, uniformly spaced.  However, the period is
 * effectively infinite.  See: Numerical Recipes, section 7.1.
 *
 * @param seedp pointer to seed. Set *seedp to any negative value
 * to restart the sequence.
 */
float IntlTest::random(int32_t* seedp) {
    static int32_t iy, ir[98];
    static UBool first=TRUE;
    int32_t j;
    if (*seedp < 0 || first) {
        first = FALSE;
        if ((*seedp=(RAND_IC-(*seedp)) % RAND_M) < 0) *seedp = -(*seedp);
        for (j=1;j<=97;++j) {
            *seedp=(RAND_IA*(*seedp)+RAND_IC) % RAND_M;
            ir[j]=(*seedp);
        }
        *seedp=(RAND_IA*(*seedp)+RAND_IC) % RAND_M;
        iy=(*seedp);
    }
    j=(int32_t)(1 + 97.0*iy/RAND_M);
    U_ASSERT(j>=1 && j<=97);
    iy=ir[j];
    *seedp=(RAND_IA*(*seedp)+RAND_IC) % RAND_M;
    ir[j]=(*seedp);
    return (float) iy/RAND_M;
}

/**
 * Convenience method using a global seed.
 */
float IntlTest::random() {
    return random(&RAND_SEED);
}


/*
 * Integer random number class implementation.
 * Similar to C++ std::minstd_rand, with the same algorithm & constants.
 */
IntlTest::icu_rand::icu_rand(uint32_t seed) {
    seed = seed % 2147483647UL;
    if (seed == 0) {
        seed = 1;
    }
    fLast = seed;
}

IntlTest::icu_rand::~icu_rand() {}

void IntlTest::icu_rand::seed(uint32_t seed) {
    if (seed == 0) {
        seed = 1;
    }
    fLast = seed;
}

uint32_t IntlTest::icu_rand::operator() () {
    fLast = ((uint64_t)fLast * 48271UL) % 2147483647UL;
    return fLast;
}

uint32_t IntlTest::icu_rand::getSeed() {
    return (uint32_t) fLast;
}



static inline UChar toHex(int32_t i) {
    return (UChar)(i + (i < 10 ? 0x30 : (0x41 - 10)));
}

static UnicodeString& escape(const UnicodeString& s, UnicodeString& result) {
    for (int32_t i=0; i<s.length(); ++i) {
        UChar c = s[i];
        if (c <= (UChar)0x7F) {
            result += c;
        } else {
            result += (UChar)0x5c;
            result += (UChar)0x75;
            result += toHex((c >> 12) & 0xF);
            result += toHex((c >>  8) & 0xF);
            result += toHex((c >>  4) & 0xF);
            result += toHex( c        & 0xF);
        }
    }
    return result;
}

#define VERBOSE_ASSERTIONS

UBool IntlTest::assertTrue(const char* message, UBool condition, UBool quiet, UBool possibleDataError, const char *file, int line) {
    if (file != NULL) {
        if (!condition) {
            if (possibleDataError) {
                dataerrln("%s:%d: FAIL: assertTrue() failed: %s", file, line, message);
            } else {
                errln("%s:%d: FAIL: assertTrue() failed: %s", file, line, message);
            }
        } else if (!quiet) {
            logln("%s:%d: Ok: %s", file, line, message);
        }
    } else {
        if (!condition) {
            if (possibleDataError) {
                dataerrln("FAIL: assertTrue() failed: %s", message);
            } else {
                errln("FAIL: assertTrue() failed: %s", message);
            }
        } else if (!quiet) {
            logln("Ok: %s", message);
        }

    }
    return condition;
}

UBool IntlTest::assertFalse(const char* message, UBool condition, UBool quiet, UBool possibleDataError) {
    if (condition) {
        if (possibleDataError) {
            dataerrln("FAIL: assertTrue() failed: %s", message);
        } else {
            errln("FAIL: assertTrue() failed: %s", message);
        }
    } else if (!quiet) {
        logln("Ok: %s", message);
    }
    return !condition;
}

UBool IntlTest::assertSuccess(const char* message, UErrorCode ec, UBool possibleDataError, const char *file, int line) {
    if( file==NULL ) {
      file = ""; // prevent failure if no file given
    }
    if (U_FAILURE(ec)) {
        if (possibleDataError) {
          dataerrln("FAIL: %s:%d: %s (%s)", file, line, message, u_errorName(ec));
        } else {
          errcheckln(ec, "FAIL: %s:%d: %s (%s)", file, line, message, u_errorName(ec));
        }
        return FALSE;
    } else {
      logln("OK: %s:%d: %s - (%s)", file, line, message, u_errorName(ec));
    }
    return TRUE;
}

UBool IntlTest::assertEquals(const char* message,
                             const UnicodeString& expected,
                             const UnicodeString& actual,
                             UBool possibleDataError) {
    if (expected != actual) {
        if (possibleDataError) {
            dataerrln((UnicodeString)"FAIL: " + message + "; got " +
                  prettify(actual) +
                  "; expected " + prettify(expected));
        } else {
            errln((UnicodeString)"FAIL: " + message + "; got " +
                  prettify(actual) +
                  "; expected " + prettify(expected));
        }
        return FALSE;
    }
#ifdef VERBOSE_ASSERTIONS
    else {
        logln((UnicodeString)"Ok: " + message + "; got " + prettify(actual));
    }
#endif
    return TRUE;
}

UBool IntlTest::assertEquals(const char* message,
                             const char* expected,
                             const char* actual) {
    U_ASSERT(expected != nullptr);
    U_ASSERT(actual != nullptr);
    if (uprv_strcmp(expected, actual) != 0) {
        errln((UnicodeString)"FAIL: " + message + "; got \"" +
              actual +
              "\"; expected \"" + expected + "\"");
        return FALSE;
    }
#ifdef VERBOSE_ASSERTIONS
    else {
        logln((UnicodeString)"Ok: " + message + "; got \"" + actual + "\"");
    }
#endif
    return TRUE;
}

UBool IntlTest::assertEquals(const char* message,
                             int32_t expected,
                             int32_t actual) {
    if (expected != actual) {
        errln((UnicodeString)"FAIL: " + message + "; got " +
              actual + "=0x" + toHex(actual) +
              "; expected " + expected + "=0x" + toHex(expected));
        return FALSE;
    }
#ifdef VERBOSE_ASSERTIONS
    else {
        logln((UnicodeString)"Ok: " + message + "; got " + actual + "=0x" + toHex(actual));
    }
#endif
    return TRUE;
}

UBool IntlTest::assertEquals(const char* message,
                             int64_t expected,
                             int64_t actual) {
    if (expected != actual) {
        errln((UnicodeString)"FAIL: " + message + "; got int64 " +
              Int64ToUnicodeString(actual) + 
              "; expected " + Int64ToUnicodeString(expected) );
        return FALSE;
    }
#ifdef VERBOSE_ASSERTIONS
    else {
      logln((UnicodeString)"Ok: " + message + "; got int64 " + Int64ToUnicodeString(actual));
    }
#endif
    return TRUE;
}

UBool IntlTest::assertEquals(const char* message,
                             double expected,
                             double actual) {
    bool bothNaN = std::isnan(expected) && std::isnan(actual);
    if (expected != actual && !bothNaN) {
        errln((UnicodeString)"FAIL: " + message + "; got " +
              actual + 
              "; expected " + expected);
        return FALSE;
    }
#ifdef VERBOSE_ASSERTIONS
    else {
        logln((UnicodeString)"Ok: " + message + "; got " + actual);
    }
#endif
    return TRUE;
}

UBool IntlTest::assertEquals(const char* message,
                             UBool expected,
                             UBool actual) {
    if (expected != actual) {
        errln((UnicodeString)"FAIL: " + message + "; got " +
              toString(actual) +
              "; expected " + toString(expected));
        return FALSE;
    }
#ifdef VERBOSE_ASSERTIONS
    else {
      logln((UnicodeString)"Ok: " + message + "; got " + toString(actual));
    }
#endif
    return TRUE;
}


UBool IntlTest::assertEquals(const char* message,
                             UErrorCode expected,
                             UErrorCode actual) {
    if (expected != actual) {
        errln((UnicodeString)"FAIL: " + message + "; got " +
              u_errorName(actual) + 
              "; expected " + u_errorName(expected));
        return FALSE;
    }
#ifdef VERBOSE_ASSERTIONS
    else {
        logln((UnicodeString)"Ok: " + message + "; got " + u_errorName(actual));
    }
#endif
    return TRUE;
}

UBool IntlTest::assertEquals(const char* message,
                             const UnicodeSet& expected,
                             const UnicodeSet& actual) {
    IcuTestErrorCode status(*this, "assertEqualsUniSet");
    if (expected != actual) {
        errln((UnicodeString)"FAIL: " + message + "; got " +
              toString(actual, status) +
              "; expected " + toString(expected, status));
        return FALSE;
    }
#ifdef VERBOSE_ASSERTIONS
    else {
        logln((UnicodeString)"Ok: " + message + "; got " + toString(actual, status));
    }
#endif
    return TRUE;
}


#if !UCONFIG_NO_FORMATTING
UBool IntlTest::assertEquals(const char* message,
                             const Formattable& expected,
                             const Formattable& actual,
                             UBool possibleDataError) {
    if (expected != actual) {
        if (possibleDataError) {
            dataerrln((UnicodeString)"FAIL: " + message + "; got " +
                  toString(actual) +
                  "; expected " + toString(expected));
        } else {
            errln((UnicodeString)"FAIL: " + message + "; got " +
                  toString(actual) +
                  "; expected " + toString(expected));
        }
        return FALSE;
    }
#ifdef VERBOSE_ASSERTIONS
    else {
        logln((UnicodeString)"Ok: " + message + "; got " + toString(actual));
    }
#endif
    return TRUE;
}
#endif

std::string vectorToString(const std::vector<std::string>& strings) {
    std::string result = "{";
    bool first = true;
    for (auto element : strings) {
        if (first) {
            first = false;
        } else {
            result += ", ";
        }
        result += "\"";
        result += element;
        result += "\"";
    }
    result += "}";
    return result;
}

UBool IntlTest::assertEquals(const char* message,
                             const std::vector<std::string>& expected,
                             const std::vector<std::string>& actual) {
    if (expected != actual) {
        std::string expectedAsString = vectorToString(expected);
        std::string actualAsString = vectorToString(actual);
        errln((UnicodeString)"FAIL: " + message +
            "; got " + actualAsString.c_str() +
            "; expected " + expectedAsString.c_str());
        return FALSE;
    }
#ifdef VERBOSE_ASSERTIONS
    else {
        logln((UnicodeString)"Ok: " + message + "; got " + vectorToString(actual).c_str());
    }
#endif
    return TRUE;
}

UBool IntlTest::assertNotEquals(const char* message,
                                int32_t expectedNot,
                                int32_t actual) {
    if (expectedNot == actual) {
        errln((UnicodeString)("FAIL: ") + message + "; got " + actual + "=0x" + toHex(actual) +
              "; expected != " + expectedNot);
        return FALSE;
    }
#ifdef VERBOSE_ASSERTIONS
    else {
        logln((UnicodeString)("Ok: ") + message + "; got " + actual + "=0x" + toHex(actual) +
              " != " + expectedNot);
    }
#endif
    return TRUE;
}

UBool IntlTest::assertEqualsNear(const char* message,
                                 double expected,
                                 double actual,
                                 double delta) {
    if (std::isnan(delta) || std::isinf(delta)) {
        errln((UnicodeString)("FAIL: ") + message + "; nonsensical delta " + delta +
              " - delta may not be NaN or Inf");
        return FALSE;
    }
    bool bothNaN = std::isnan(expected) && std::isnan(actual);
    double difference = std::abs(expected - actual);
    if (expected != actual && (difference > delta || std::isnan(difference)) && !bothNaN) {
        errln((UnicodeString)("FAIL: ") + message + "; got " + actual + "; expected " + expected +
              "; acceptable delta " + delta);
        return FALSE;
    }
#ifdef VERBOSE_ASSERTIONS
    else {
        logln((UnicodeString)("Ok: ") + message + "; got " + actual);
    }
#endif
    return TRUE;
}

static char ASSERT_BUF[256];

static const char* extractToAssertBuf(const UnicodeString& message) {
    UnicodeString buf;
    escape(message, buf);
    buf.extract(0, 0x7FFFFFFF, ASSERT_BUF, sizeof(ASSERT_BUF)-1, 0);
    ASSERT_BUF[sizeof(ASSERT_BUF)-1] = 0;
    return ASSERT_BUF;
}

UBool IntlTest::assertTrue(const UnicodeString& message, UBool condition, UBool quiet, UBool possibleDataError) {
    return assertTrue(extractToAssertBuf(message), condition, quiet, possibleDataError);
}

UBool IntlTest::assertFalse(const UnicodeString& message, UBool condition, UBool quiet, UBool possibleDataError) {
    return assertFalse(extractToAssertBuf(message), condition, quiet, possibleDataError);
}

UBool IntlTest::assertSuccess(const UnicodeString& message, UErrorCode ec) {
    return assertSuccess(extractToAssertBuf(message), ec);
}

UBool IntlTest::assertEquals(const UnicodeString& message,
                             const UnicodeString& expected,
                             const UnicodeString& actual,
                             UBool possibleDataError) {
    return assertEquals(extractToAssertBuf(message), expected, actual, possibleDataError);
}

UBool IntlTest::assertEquals(const UnicodeString& message,
                             const char* expected,
                             const char* actual) {
    return assertEquals(extractToAssertBuf(message), expected, actual);
}
UBool IntlTest::assertEquals(const UnicodeString& message,
                             UBool expected,
                             UBool actual) {
    return assertEquals(extractToAssertBuf(message), expected, actual);
}
UBool IntlTest::assertEquals(const UnicodeString& message,
                             int32_t expected,
                             int32_t actual) {
    return assertEquals(extractToAssertBuf(message), expected, actual);
}
UBool IntlTest::assertEquals(const UnicodeString& message,
                             int64_t expected,
                             int64_t actual) {
    return assertEquals(extractToAssertBuf(message), expected, actual);
}
UBool IntlTest::assertEquals(const UnicodeString& message,
                             double expected,
                             double actual) {
    return assertEquals(extractToAssertBuf(message), expected, actual);
}
UBool IntlTest::assertEquals(const UnicodeString& message,
                             UErrorCode expected,
                             UErrorCode actual) {
    return assertEquals(extractToAssertBuf(message), expected, actual);
}
UBool IntlTest::assertEquals(const UnicodeString& message,
                             const UnicodeSet& expected,
                             const UnicodeSet& actual) {
    return assertEquals(extractToAssertBuf(message), expected, actual);
}
UBool IntlTest::assertEquals(const UnicodeString& message,
                             const std::vector<std::string>& expected,
                             const std::vector<std::string>& actual) {
    return assertEquals(extractToAssertBuf(message), expected, actual);
}
UBool IntlTest::assertNotEquals(const UnicodeString &message,
                                int32_t expectedNot,
                                int32_t actual) {
    return assertNotEquals(extractToAssertBuf(message), expectedNot, actual);
}
UBool IntlTest::assertEqualsNear(const UnicodeString& message,
                                 double expected,
                                 double actual,
                                 double delta) {
    return assertEqualsNear(extractToAssertBuf(message), expected, actual, delta);
}

#if !UCONFIG_NO_FORMATTING
UBool IntlTest::assertEquals(const UnicodeString& message,
                             const Formattable& expected,
                             const Formattable& actual) {
    return assertEquals(extractToAssertBuf(message), expected, actual);
}
#endif

void IntlTest::setProperty(const char* propline) {
    if (numProps < kMaxProps) {
        proplines[numProps] = propline;
    }
    numProps++;
}

const char* IntlTest::getProperty(const char* prop) {
    const char* val = NULL;
    for (int32_t i = 0; i < numProps; i++) {
        int32_t plen = static_cast<int32_t>(uprv_strlen(prop));
        if ((int32_t)uprv_strlen(proplines[i]) > plen + 1
                && proplines[i][plen] == '='
                && uprv_strncmp(proplines[i], prop, plen) == 0) {
            val = &(proplines[i][plen+1]);
            break;
        }
    }
    return val;
}

/*
 * Hey, Emacs, please set the following:
 *
 * Local Variables:
 * indent-tabs-mode: nil
 * End:
 *
 */
