X-Git-Url: https://git.saurik.com/apple/icu.git/blobdiff_plain/b75a7d8f3b4adbae880cab104ce2c6a50eee4db2..1a147d096ae81f4c8262f7bfc56bd19fc2dee932:/icuSources/test/intltest/tsmthred.cpp diff --git a/icuSources/test/intltest/tsmthred.cpp b/icuSources/test/intltest/tsmthred.cpp index 8f33caba..768821c7 100644 --- a/icuSources/test/intltest/tsmthred.cpp +++ b/icuSources/test/intltest/tsmthred.cpp @@ -1,84 +1,33 @@ +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html /******************************************************************** - * COPYRIGHT: - * Copyright (c) 1999-2003, International Business Machines Corporation and + * COPYRIGHT: + * Copyright (c) 1999-2015, International Business Machines Corporation and * others. All Rights Reserved. ********************************************************************/ -#if defined(hpux) -# ifndef _INCLUDE_POSIX_SOURCE -# define _INCLUDE_POSIX_SOURCE -# endif -#endif - -#include - -// Just turn off threads on cygwin, so that we can test -// the other stuff. This needs to be investigated further. -#if defined(U_CYGWIN) -#define ICU_USE_THREADS 0 -#endif - -#if !defined(WIN32) && !defined(XP_MAC) && !defined(U_RHAPSODY) -#define POSIX 1 -#endif - -#if defined(POSIX) || defined(U_SOLARIS) || defined(AIX) || defined(HPUX) - -#define HAVE_IMP - -#if (ICU_USE_THREADS == 1) -#include -#endif - -#if defined(__hpux) && defined(HPUX_CMA) -# if defined(read) // read being defined as cma_read causes trouble with iostream::read -# undef read -# endif -#endif - -/* Define __EXTENSIONS__ for Solaris and old friends in strict mode. */ -#ifndef __EXTENSIONS__ -#define __EXTENSIONS__ -#endif - -#include - -/* Define _XPG4_2 for Solaris and friends. */ -#ifndef _XPG4_2 -#define _XPG4_2 -#endif - -/* Define __USE_XOPEN_EXTENDED for Linux and glibc. */ -#ifndef __USE_XOPEN_EXTENDED -#define __USE_XOPEN_EXTENDED -#endif - -/* Define _INCLUDE_XOPEN_SOURCE_EXTENDED for HP/UX (11?). */ -#ifndef _INCLUDE_XOPEN_SOURCE_EXTENDED -#define _INCLUDE_XOPEN_SOURCE_EXTENDED -#endif - -#include - -#endif -/* HPUX */ -#ifdef sleep -#undef sleep -#endif +#include "simplethread.h" #include "unicode/utypes.h" - -/* APP_NO_THREADS is an old symbol. We'll honour it if present. */ -#ifdef APP_NO_THREADS -# define ICU_USE_THREADS 0 -#endif - -/* Default: use threads. */ -#ifndef ICU_USE_THREADS -# define ICU_USE_THREADS 1 -#endif - +#include "unicode/ustring.h" +#include "umutex.h" +#include "cmemory.h" +#include "cstring.h" +#include "indiancal.h" +#include "uparse.h" +#include "unicode/localpointer.h" +#include "unicode/resbund.h" +#include "unicode/udata.h" +#include "unicode/uloc.h" +#include "unicode/locid.h" +#include "putilimp.h" +#include "intltest.h" #include "tsmthred.h" +#include "unicode/ushape.h" +#include "unicode/translit.h" +#include "sharedobject.h" +#include "unifiedcache.h" +#include "uassert.h" MultithreadTest::MultithreadTest() @@ -89,277 +38,80 @@ MultithreadTest::~MultithreadTest() { } - - -#if (ICU_USE_THREADS==0) -void MultithreadTest::runIndexedTest( int32_t index, UBool exec, - const char* &name, char* par ) { - if (exec) logln("TestSuite MultithreadTest: "); - - if(index == 0) - name = "NO_THREADED_TESTS"; - else - name = ""; - - if(exec) { logln("MultithreadTest - test DISABLED. ICU_USE_THREADS set to 0, check your configuration if this is a problem.."); - } -} -#else - - - -// Note: A LOT OF THE FUNCTIONS IN THIS FILE SHOULD LIVE ELSEWHERE!!!!! -// Note: A LOT OF THE FUNCTIONS IN THIS FILE SHOULD LIVE ELSEWHERE!!!!! -// -srl - #include #include #include // tolower, toupper +#include #include "unicode/putil.h" -/* for mthreadtest*/ +// for mthreadtest #include "unicode/numfmt.h" #include "unicode/choicfmt.h" #include "unicode/msgfmt.h" #include "unicode/locid.h" -#include "unicode/ucol.h" +#include "unicode/coll.h" +#include "unicode/calendar.h" #include "ucaconf.h" -#ifdef WIN32 -#define HAVE_IMP - -# define VC_EXTRALEAN -# define WIN32_LEAN_AND_MEAN -# define NOGDI -# define NOUSER -# define NOSERVICE -# define NOIME -# define NOMCX -#include -#include - -struct Win32ThreadImplementation -{ - unsigned long fHandle; -}; - -extern "C" void __cdecl SimpleThreadProc(void *arg) -{ - ((SimpleThread*)arg)->run(); -} - -SimpleThread::SimpleThread() -:fImplementation(0) -{ - Win32ThreadImplementation *imp = new Win32ThreadImplementation; - imp->fHandle = 0; - - fImplementation = imp; -} - -SimpleThread::~SimpleThread() -{ - delete (Win32ThreadImplementation*)fImplementation; -} - -int32_t SimpleThread::start() -{ - Win32ThreadImplementation *imp = (Win32ThreadImplementation*)fImplementation; - if(imp->fHandle != NULL) { - // The thread appears to have already been started. - // This is probably an error on the part of our caller. - return -1; - } - - imp->fHandle = _beginthread( SimpleThreadProc, 0 /*stack size*/ , (void *)this ); - if (imp->fHandle == -1) { - // An error occured - int err = errno; - if (err == 0) { - err = -1; - } - return err; - } - return 0; -} - -void SimpleThread::sleep(int32_t millis) -{ - ::Sleep(millis); -} - -#elif defined XP_MAC - -// since the Mac has no preemptive threading (at least on MacOS 8), only -// cooperative threading, threads are a no-op. We have no yield() calls -// anywhere in the ICU, so we are guaranteed to be thread-safe. - -#define HAVE_IMP - -SimpleThread::SimpleThread() -{} - -SimpleThread::~SimpleThread() -{} - -int32_t -SimpleThread::start() -{ return 0; } - -void -SimpleThread::run() -{} - -void -SimpleThread::sleep(int32_t millis) -{} -#endif - - -#if defined(POSIX)||defined(U_SOLARIS)||defined(AIX)||defined(HPUX) -#define HAVE_IMP - -struct PosixThreadImplementation -{ - pthread_t fThread; -}; - -extern "C" void* SimpleThreadProc(void *arg) -{ - ((SimpleThread*)arg)->run(); - return 0; -} - -SimpleThread::SimpleThread() :fImplementation(0) -{ - PosixThreadImplementation *imp = new PosixThreadImplementation; - fImplementation = imp; -} - -SimpleThread::~SimpleThread() -{ - delete (PosixThreadImplementation*)fImplementation; -} - -int32_t SimpleThread::start() -{ - PosixThreadImplementation *imp = (PosixThreadImplementation*)fImplementation; - - int32_t rc; - - pthread_attr_t attr; - -#ifdef HPUX_CMA - rc = pthread_attr_create(&attr); - rc = pthread_create(&(imp->fThread),attr,&SimpleThreadProc,(void*)this); - pthread_attr_delete(&attr); -#else - rc = pthread_attr_init(&attr); - rc = pthread_create(&(imp->fThread),&attr,&SimpleThreadProc,(void*)this); - pthread_attr_destroy(&attr); -#endif - return rc; - -} - -void SimpleThread::sleep(int32_t millis) -{ -#ifdef U_SOLARIS - sigignore(SIGALRM); -#endif - -#ifdef HPUX_CMA - cma_sleep(millis/100); -#elif defined(HPUX) || defined(OS390) - millis *= 1000; - while(millis >= 1000000) { - usleep(999999); - millis -= 1000000; - } - if(millis > 0) { - usleep(millis); - } -#else - usleep(millis * 1000); -#endif -} - -#endif -// end POSIX - - -#ifndef HAVE_IMP -#error No implementation for threads! Cannot test. -0 = 216; //die -#endif - -// *************** end fluff ****************** - -/* now begins the real test. */ -void MultithreadTest::runIndexedTest( int32_t index, UBool exec, +void MultithreadTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) { if (exec) logln("TestSuite MultithreadTest: "); - switch (index) { - case 0: - name = "TestThreads"; - if (exec) - TestThreads(); - break; - case 1: - name = "TestMutex"; - if (exec) - TestMutex(); - break; - case 2: - name = "TestThreadedIntl"; + + TESTCASE_AUTO_BEGIN; + TESTCASE_AUTO(TestThreads); + TESTCASE_AUTO(TestMutex); #if !UCONFIG_NO_FORMATTING - if (exec) - TestThreadedIntl(); + TESTCASE_AUTO(TestThreadedIntl); #endif - break; - case 3: - name = "TestCollators"; #if !UCONFIG_NO_COLLATION - if (exec) - TestCollators(); + TESTCASE_AUTO(TestCollators); #endif /* #if !UCONFIG_NO_COLLATION */ - break; - default: - name = ""; - break; //needed to end loop - } + TESTCASE_AUTO(TestString); + TESTCASE_AUTO(TestArabicShapingThreads); + TESTCASE_AUTO(TestAnyTranslit); + TESTCASE_AUTO(TestConditionVariables); + TESTCASE_AUTO(TestUnifiedCache); +#if !UCONFIG_NO_TRANSLITERATION + TESTCASE_AUTO(TestBreakTranslit); + TESTCASE_AUTO(TestIncDec); +#if !UCONFIG_NO_FORMATTING + TESTCASE_AUTO(Test20104); +#endif /* #if !UCONFIG_NO_FORMATTING */ +#endif /* #if !UCONFIG_NO_TRANSLITERATION */ + TESTCASE_AUTO_END } -/* - TestThreads -- see if threads really work at all. - - Set up N threads pointing at N chars. When they are started, they will - each sleep 1 second and then set their chars. At the end we make sure they - are all set. - */ - -#define THREADTEST_NRTHREADS 8 +//----------------------------------------------------------------------------------- +// +// TestThreads -- see if threads really work at all. +// +// Set up N threads pointing at N chars. When they are started, they will +// set their chars. At the end we make sure they are all set. +// +//----------------------------------------------------------------------------------- class TestThreadsThread : public SimpleThread { public: TestThreadsThread(char* whatToChange) { fWhatToChange = whatToChange; } - virtual void run() { SimpleThread::sleep(1000); - Mutex m; - *fWhatToChange = '*'; + virtual void run() { Mutex m; + *fWhatToChange = '*'; } private: char *fWhatToChange; }; + void MultithreadTest::TestThreads() { + static const int32_t THREADTEST_NRTHREADS = 8; char threadTestChars[THREADTEST_NRTHREADS + 1]; SimpleThread *threads[THREADTEST_NRTHREADS]; + int32_t numThreadsStarted = 0; int32_t i; for(i=0;istart() != 0) { errln("Error starting thread %d", i); } - SimpleThread::sleep(200); + else { + numThreadsStarted++; + } logln(" Subthread started."); } + assertTrue(WHERE, THREADTEST_NRTHREADS == numThreadsStarted); + logln("Waiting for threads to be set.."); + for(i=0; ijoin(); + if (threadTestChars[i] != '*') { + errln("%s:%d Thread %d failed.", __FILE__, __LINE__, i); + } + delete threads[i]; + } +} - int32_t patience = 40; // seconds to wait - while(patience--) - { - int32_t count = 0; - umtx_lock(NULL); - for(i=0;i" + UnicodeString(threadTestChars) + "<- Got all threads! cya"); - for(i=0;ierrln("Fail: status %s\n", u_errorName(status)); + return; + } else if(length!=2) { + IntlTest::gTest->errln("Fail: len %d expected 3\n", length); + return; + } else if(u_strncmp(dst,dst_old,UPRV_LENGTHOF(dst))) { + IntlTest::gTest->errln("Fail: got U+%04X U+%04X expected U+%04X U+%04X\n", + dst[0],dst[1],dst_old[0],dst_old[1]); return; } - logln("->" + UnicodeString(threadTestChars) + "<- Waiting.."); - SimpleThread::sleep(500); - } - errln("->" + UnicodeString(threadTestChars) + "<- PATIENCE EXCEEDED!! Still missing some."); - for(i=0;ierrln("Fail: status %s\n", u_errorName(status)); + return; + } else if(length!=2) { + IntlTest::gTest->errln("Fail: len %d expected 3\n", length); + return; + } else if(u_strncmp(dst,dst_new,UPRV_LENGTHOF(dst))) { + IntlTest::gTest->errln("Fail: got U+%04X U+%04X expected U+%04X U+%04X\n", + dst[0],dst[1],dst_new[0],dst_new[1]); + return; + } } + return; } -class TestMutexThread1 : public SimpleThread +void MultithreadTest::TestArabicShapingThreads() { -public: - TestMutexThread1() : fDone(FALSE) {} - virtual void run() - { - Mutex m; // grab the lock first thing - SimpleThread::sleep(900); // then wait - fDone = TRUE; // finally, set our flag + TestArabicShapeThreads threads[30]; + + int32_t i; + + logln("-> do TestArabicShapingThreads <- Firing off threads.. "); + for(i=0; i < UPRV_LENGTHOF(threads); i++) { + if (threads[i].start() != 0) { + errln("Error starting thread %d", i); + } } -public: - UBool fDone; -}; -class TestMutexThread2 : public SimpleThread + for(i=0; i < UPRV_LENGTHOF(threads); i++) { + threads[i].join(); + } + logln("->TestArabicShapingThreads <- Got all threads! cya"); +} + + +//----------------------------------------------------------------------- +// +// TestMutex - a simple (non-stress) test to verify that ICU mutexes +// and condition variables are functioning. Does not test the use of +// mutexes within ICU services, but rather that the +// platform's mutex support is at least superficially there. +// +//---------------------------------------------------------------------- +static UMutex *gTestMutexA() { + static UMutex *m = STATIC_NEW(UMutex); + return m; +} +static std::condition_variable *gThreadsCountChanged() { + static std::condition_variable *cv = STATIC_NEW(std::condition_variable); + return cv; +} + +static int gThreadsStarted = 0; +static int gThreadsInMiddle = 0; +static int gThreadsDone = 0; + +static const int TESTMUTEX_THREAD_COUNT = 40; + +class TestMutexThread : public SimpleThread { public: - TestMutexThread2(TestMutexThread1& r) : fOtherThread(r), fDone(FALSE), fErr(FALSE) {} - virtual void run() - { - SimpleThread::sleep(500); // wait, make sure they aquire the lock - fElapsed = uprv_getUTCtime(); - { - Mutex m; // wait here - - fElapsed = uprv_getUTCtime() - fElapsed; + virtual void run() { + // This is the code that each of the spawned threads runs. + // All threads move together throught the started - middle - done sequence together, + // waiting for all other threads to reach each point before advancing. + std::unique_lock lock(gTestMutexA()->fMutex); + gThreadsStarted += 1; + gThreadsCountChanged()->notify_all(); + while (gThreadsStarted < TESTMUTEX_THREAD_COUNT) { + if (gThreadsInMiddle != 0) { + IntlTest::gTest->errln( + "%s:%d gThreadsInMiddle = %d. Expected 0.", __FILE__, __LINE__, gThreadsInMiddle); + return; + } + gThreadsCountChanged()->wait(lock); + } - if(fOtherThread.fDone == FALSE) - fErr = TRUE; // they didnt get to it yet + gThreadsInMiddle += 1; + gThreadsCountChanged()->notify_all(); + while (gThreadsInMiddle < TESTMUTEX_THREAD_COUNT) { + if (gThreadsDone != 0) { + IntlTest::gTest->errln( + "%s:%d gThreadsDone = %d. Expected 0.", __FILE__, __LINE__, gThreadsDone); + return; + } + gThreadsCountChanged()->wait(lock); + } - fDone = TRUE; // we're done. + gThreadsDone += 1; + gThreadsCountChanged()->notify_all(); + while (gThreadsDone < TESTMUTEX_THREAD_COUNT) { + gThreadsCountChanged()->wait(lock); } } -public: - TestMutexThread1 & fOtherThread; - UBool fDone, fErr; - int32_t fElapsed; -private: - /** - * The assignment operator has no real implementation. - * It is provided to make the compiler happy. Do not call. - */ - TestMutexThread2& operator=(const TestMutexThread2&) { return *this; } }; void MultithreadTest::TestMutex() { - /* this test uses printf so that we don't hang by calling UnicodeString inside of a mutex. */ - //logln("Bye."); - // printf("Warning: MultiThreadTest::Testmutex() disabled.\n"); - // return; - - if(verbose) - printf("Before mutex.\n"); - { - Mutex m; - if(verbose) - printf(" Exited 2nd mutex\n"); - } - if(verbose) - printf("exited 1st mutex. Now testing with threads:"); - - TestMutexThread1 thread1; - TestMutexThread2 thread2(thread1); - if (thread2.start() != 0 || - thread1.start() != 0 ) { - errln("Error starting threads."); - } - - for(int32_t patience = 12; patience > 0;patience--) + gThreadsStarted = 0; + gThreadsInMiddle = 0; + gThreadsDone = 0; + int32_t i = 0; + TestMutexThread threads[TESTMUTEX_THREAD_COUNT]; { - // TODO: Possible memory coherence issue in looking at fDone values - // that are set in another thread without the mutex here. - if(thread1.fDone && verbose) - printf("Thread1 done\n"); + std::unique_lock lock(gTestMutexA()->fMutex); + for (i=0; iwait(lock); + } + while (gThreadsDone < TESTMUTEX_THREAD_COUNT) { + gThreadsCountChanged()->wait(lock); + } + } + for (i=0; i - // * Show exactly where the string's differences lie. UnicodeString showDifference(const UnicodeString& expected, const UnicodeString& result) { UnicodeString res; - res = expected + " 0); } - UBool getError(UnicodeString& fillinError) { fillinError = fErrorString; return (fErrors > 0); } - virtual ~ThreadWithStatus(){} -protected: - ThreadWithStatus() : fDone(FALSE), fErrors(0) {} - void done() { fDone = TRUE; } - void error(const UnicodeString &error) { fErrors++; fErrorString = error; done(); } - void error() { error("An error occured."); } -private: - UBool fDone; - int32_t fErrors; - UnicodeString fErrorString; -}; +//------------------------------------------------------------------------------------------- +// +// FormatThreadTest - a thread that tests performing a number of numberformats. +// +//------------------------------------------------------------------------------------------- -#define kFormatThreadIterations 20 // # of iterations per thread -#define kFormatThreadThreads 10 // # of threads to spawn -#define kFormatThreadPatience 60 // time in seconds to wait for all threads +const int kFormatThreadIterations = 100; // # of iterations per thread +const int kFormatThreadThreads = 10; // # of threads to spawn #if !UCONFIG_NO_FORMATTING -// ** FormatThreadTest - a thread that tests performing a number of numberformats. struct FormatThreadTestData @@ -623,22 +391,17 @@ struct FormatThreadTestData } ; -void errorToString(UErrorCode theStatus, UnicodeString &string) -{ - string=u_errorName(theStatus); -} - // "Someone from {2} is receiving a #{0} error - {1}. Their telephone call is costing {3 number,currency}." -void formatErrorMessage(UErrorCode &realStatus, const UnicodeString& pattern, const Locale& theLocale, - UErrorCode inStatus0, /* statusString 1 */ const Locale &inCountry2, double currency3, // these numbers are the message arguments. +static void formatErrorMessage(UErrorCode &realStatus, const UnicodeString& pattern, const Locale& theLocale, + UErrorCode inStatus0, // statusString 1 + const Locale &inCountry2, double currency3, // these numbers are the message arguments. UnicodeString &result) { if(U_FAILURE(realStatus)) return; // you messed up - UnicodeString errString1; - errorToString(inStatus0, errString1); + UnicodeString errString1(u_errorName(inStatus0)); UnicodeString countryName2; inCountry2.getDisplayCountry(theLocale,countryName2); @@ -650,87 +413,242 @@ void formatErrorMessage(UErrorCode &realStatus, const UnicodeString& pattern, co Formattable(currency3)// currency3 {3,number,currency} }; - MessageFormat *fmt = new MessageFormat("MessageFormat's API is broken!!!!!!!!!!!",realStatus); - fmt->setLocale(theLocale); - fmt->applyPattern(pattern, realStatus); - + LocalPointer fmt(new MessageFormat(u"MessageFormat's API is broken!!!!!!!!!!!",realStatus), realStatus); if (U_FAILURE(realStatus)) { - delete fmt; return; } + fmt->setLocale(theLocale); + fmt->applyPattern(pattern, realStatus); - FieldPosition ignore = 0; + FieldPosition ignore = 0; fmt->format(myArgs,4,result,ignore,realStatus); +} - delete fmt; +/** + * Shared formatters & data used by instances of ThreadSafeFormat. + * Exactly one instance of this class is created, and it is then shared concurrently + * by the multiple instances of ThreadSafeFormat. + */ +class ThreadSafeFormatSharedData { + public: + ThreadSafeFormatSharedData(UErrorCode &status); + ~ThreadSafeFormatSharedData(); + LocalPointer fFormat; + Formattable fYDDThing; + Formattable fBBDThing; + UnicodeString fYDDStr; + UnicodeString fBBDStr; }; -static UMTX ftMutex; +const ThreadSafeFormatSharedData *gSharedData = NULL; -class FormatThreadTest : public ThreadWithStatus -{ -public: - FormatThreadTest() // constructor is NOT multithread safe. - : ThreadWithStatus(), - fOffset(0) - // the locale to use - { - static int32_t fgOffset = 0; - fgOffset += 3; - fOffset = fgOffset; +ThreadSafeFormatSharedData::ThreadSafeFormatSharedData(UErrorCode &status) { + fFormat.adoptInstead(NumberFormat::createCurrencyInstance(Locale::getUS(), status)); + static const UChar *kYDD = u"YDD"; + static const UChar *kBBD = u"BBD"; + fYDDThing.adoptObject(new CurrencyAmount(123.456, kYDD, status)); + fBBDThing.adoptObject(new CurrencyAmount(987.654, kBBD, status)); + if (U_FAILURE(status)) { + return; } + fFormat->format(fYDDThing, fYDDStr, NULL, status); + fFormat->format(fBBDThing, fBBDStr, NULL, status); + gSharedData = this; +} +ThreadSafeFormatSharedData::~ThreadSafeFormatSharedData() { + gSharedData = NULL; +} - virtual void run() - { - // Keep this data here to avoid static initialization. - FormatThreadTestData kNumberFormatTestData[] = - { - FormatThreadTestData((double)5.0, UnicodeString("5", "")), - FormatThreadTestData( 6.0, UnicodeString("6", "")), - FormatThreadTestData( 20.0, UnicodeString("20", "")), - FormatThreadTestData( 8.0, UnicodeString("8", "")), - FormatThreadTestData( 8.3, UnicodeString("8.3", "")), - FormatThreadTestData( 12345, UnicodeString("12,345", "")), - FormatThreadTestData( 81890.23, UnicodeString("81,890.23", "")), - }; - int32_t kNumberFormatTestDataLength = (int32_t)(sizeof(kNumberFormatTestData) / sizeof(kNumberFormatTestData[0])); +/** + * Class for thread-safe testing of format. + * Instances of this class appear as members of class FormatThreadTest. + * Multiple instances of FormatThreadTest coexist. + * ThreadSafeFormat::doStuff() is called concurrently to test the thread safety of + * various shared format operations. + */ +class ThreadSafeFormat { +public: + /* give a unique offset to each thread */ + ThreadSafeFormat(UErrorCode &status); + UBool doStuff(int32_t offset, UnicodeString &appendErr, UErrorCode &status) const; +private: + LocalPointer fFormat; // formatter - en_US constructed currency +}; - // Keep this data here to avoid static initialization. - FormatThreadTestData kPercentFormatTestData[] = - { - FormatThreadTestData((double)5.0, UnicodeString("500%", "")), - FormatThreadTestData( 1.0, UnicodeString("100%", "")), - FormatThreadTestData( 0.26, UnicodeString("26%", "")), - FormatThreadTestData( 16384.99, CharsToUnicodeString("1\\u00a0638\\u00a0499%") ), // U+00a0 = NBSP - FormatThreadTestData( 81890.23, CharsToUnicodeString("8\\u00a0189\\u00a0023%" )), - }; - int32_t kPercentFormatTestDataLength = (int32_t)(sizeof(kPercentFormatTestData) / sizeof(kPercentFormatTestData[0])); - int32_t iteration; - UErrorCode status = U_ZERO_ERROR; - NumberFormat *formatter = NumberFormat::createInstance(Locale::getEnglish(),status); +ThreadSafeFormat::ThreadSafeFormat(UErrorCode &status) { + fFormat.adoptInstead(NumberFormat::createCurrencyInstance(Locale::getUS(), status)); +} - if(U_FAILURE(status)) - { - Mutex m(&ftMutex); - error("Error on NumberFormat::createInstance()"); - return; +static const UChar *kUSD = u"USD"; + +UBool ThreadSafeFormat::doStuff(int32_t offset, UnicodeString &appendErr, UErrorCode &status) const { + UBool okay = TRUE; + + if(u_strcmp(fFormat->getCurrency(), kUSD)) { + appendErr.append(u"fFormat currency != ") + .append(kUSD) + .append(u", =") + .append(fFormat->getCurrency()) + .append(u"! "); + okay = FALSE; + } + + if(u_strcmp(gSharedData->fFormat->getCurrency(), kUSD)) { + appendErr.append(u"gFormat currency != ") + .append(kUSD) + .append(u", =") + .append(gSharedData->fFormat->getCurrency()) + .append(u"! "); + okay = FALSE; + } + UnicodeString str; + const UnicodeString *o=NULL; + Formattable f; + const NumberFormat *nf = NULL; // only operate on it as const. + switch(offset%4) { + case 0: f = gSharedData->fYDDThing; o = &gSharedData->fYDDStr; nf = gSharedData->fFormat.getAlias(); break; + case 1: f = gSharedData->fBBDThing; o = &gSharedData->fBBDStr; nf = gSharedData->fFormat.getAlias(); break; + case 2: f = gSharedData->fYDDThing; o = &gSharedData->fYDDStr; nf = fFormat.getAlias(); break; + case 3: f = gSharedData->fBBDThing; o = &gSharedData->fBBDStr; nf = fFormat.getAlias(); break; + } + nf->format(f, str, NULL, status); + + if(*o != str) { + appendErr.append(showDifference(*o, str)); + okay = FALSE; + } + return okay; +} + +UBool U_CALLCONV isAcceptable(void *, const char *, const char *, const UDataInfo *) { + return TRUE; +} + +//static UMTX debugMutex = NULL; +//static UMTX gDebugMutex; + + +class FormatThreadTest : public SimpleThread +{ +public: + int fNum; + int fTraceInfo; + + LocalPointer fTSF; + + FormatThreadTest() // constructor is NOT multithread safe. + : SimpleThread(), + fNum(0), + fTraceInfo(0), + fTSF(NULL), + fOffset(0) + // the locale to use + { + UErrorCode status = U_ZERO_ERROR; // TODO: rearrange code to allow checking of status. + fTSF.adoptInstead(new ThreadSafeFormat(status)); + static int32_t fgOffset = 0; + fgOffset += 3; + fOffset = fgOffset; + } + + + virtual void run() + { + fTraceInfo = 1; + LocalPointer percentFormatter; + UErrorCode status = U_ZERO_ERROR; + +#if 0 + // debugging code, + for (int i=0; i<4000; i++) { + status = U_ZERO_ERROR; + UDataMemory *data1 = udata_openChoice(0, "res", "en_US", isAcceptable, 0, &status); + UDataMemory *data2 = udata_openChoice(0, "res", "fr", isAcceptable, 0, &status); + udata_close(data1); + udata_close(data2); + if (U_FAILURE(status)) { + error("udata_openChoice failed.\n"); + break; + } } + return; +#endif - NumberFormat *percentFormatter = NumberFormat::createPercentInstance(Locale::getFrench(),status); +#if 0 + // debugging code, + int m; + for (m=0; m<4000; m++) { + status = U_ZERO_ERROR; + UResourceBundle *res = NULL; + const char *localeName = NULL; - if(U_FAILURE(status)) - { - { - Mutex m(&ftMutex); - error("Error on NumberFormat::createPercentInstance()"); + Locale loc = Locale::getEnglish(); + + localeName = loc.getName(); + // localeName = "en"; + + // ResourceBundle bund = ResourceBundle(0, loc, status); + //umtx_lock(&gDebugMutex); + res = ures_open(NULL, localeName, &status); + //umtx_unlock(&gDebugMutex); + + //umtx_lock(&gDebugMutex); + ures_close(res); + //umtx_unlock(&gDebugMutex); + + if (U_FAILURE(status)) { + error("Resource bundle construction failed.\n"); + break; } - delete formatter; - return; } + return; +#endif + + // Keep this data here to avoid static initialization. + FormatThreadTestData kNumberFormatTestData[] = + { + FormatThreadTestData((double)5.0, UnicodeString(u"5")), + FormatThreadTestData( 6.0, UnicodeString(u"6")), + FormatThreadTestData( 20.0, UnicodeString(u"20")), + FormatThreadTestData( 8.0, UnicodeString(u"8")), + FormatThreadTestData( 8.3, UnicodeString(u"8.3")), + FormatThreadTestData( 12345, UnicodeString(u"12,345")), + FormatThreadTestData( 81890.23, UnicodeString(u"81,890.23")), + }; + int32_t kNumberFormatTestDataLength = UPRV_LENGTHOF(kNumberFormatTestData); - for(iteration = 0;!getError() && iteration formatter(NumberFormat::createInstance(Locale::getEnglish(),status)); + if(U_FAILURE(status)) { + IntlTest::gTest->dataerrln("%s:%d Error %s on NumberFormat::createInstance().", + __FILE__, __LINE__, u_errorName(status)); + goto cleanupAndReturn; + } + + percentFormatter.adoptInstead(NumberFormat::createPercentInstance(Locale::getFrench(),status)); + if(U_FAILURE(status)) { + IntlTest::gTest->errln("%s:%d Error %s on NumberFormat::createPercentInstance().", + __FILE__, __LINE__, u_errorName(status)); + goto cleanupAndReturn; + } + + for(iteration = 0;!IntlTest::gTest->getErrors() && iterationformat(kNumberFormatTestData[whichLine].number, output); - if(0 != output.compare(kNumberFormatTestData[whichLine].string)) - { - Mutex m(&ftMutex); - error("format().. expected " + kNumberFormatTestData[whichLine].string + " got " + output); - continue; // will break + if(0 != output.compare(kNumberFormatTestData[whichLine].string)) { + IntlTest::gTest->errln("format().. expected " + kNumberFormatTestData[whichLine].string + + " got " + output); + goto cleanupAndReturn; } // Now check percent. @@ -751,16 +668,15 @@ public: whichLine = (iteration + fOffset)%kPercentFormatTestDataLength; percentFormatter->format(kPercentFormatTestData[whichLine].number, output); - if(0 != output.compare(kPercentFormatTestData[whichLine].string)) { - Mutex m(&ftMutex); - error("percent format().. \n" + showDifference(kPercentFormatTestData[whichLine].string,output)); - continue; + IntlTest::gTest->errln("percent format().. \n" + + showDifference(kPercentFormatTestData[whichLine].string,output)); + goto cleanupAndReturn; } - // Test message error -#define kNumberOfMessageTests 3 + // Test message error + const int kNumberOfMessageTests = 3; UErrorCode statusToCheck; UnicodeString patternToCheck; Locale messageLocale; @@ -775,53 +691,68 @@ public: default: case 0: statusToCheck= U_FILE_ACCESS_ERROR; - patternToCheck= "0:Someone from {2} is receiving a #{0} error - {1}. Their telephone call is costing {3,number,currency}."; // number,currency + patternToCheck= u"0:Someone from {2} is receiving a #{0}" + " error - {1}. Their telephone call is costing " + "{3,number,currency}."; // number,currency messageLocale= Locale("en","US"); countryToCheck= Locale("","HR"); currencyToCheck= 8192.77; - expected= "0:Someone from Croatia is receiving a #4 error - U_FILE_ACCESS_ERROR. Their telephone call is costing $8,192.77."; + expected= u"0:Someone from Croatia is receiving a #4 error - " + "U_FILE_ACCESS_ERROR. Their telephone call is costing $8,192.77."; break; case 1: statusToCheck= U_INDEX_OUTOFBOUNDS_ERROR; - patternToCheck= "1:A customer in {2} is receiving a #{0} error - {1}. Their telephone call is costing {3,number,currency}."; // number,currency - messageLocale= Locale("de","DE_PREEURO"); + patternToCheck= u"1:A customer in {2} is receiving a #{0} error - {1}. " + "Their telephone call is costing {3,number,currency}."; // number,currency + messageLocale= Locale("de","DE@currency=DEM"); countryToCheck= Locale("","BF"); currencyToCheck= 2.32; - expected= "1:A customer in Burkina Faso is receiving a #8 error - U_INDEX_OUTOFBOUNDS_ERROR. Their telephone call is costing $2.32."; + expected= u"1:A customer in Burkina Faso is receiving a #8 error - U_INDEX_OUTOFBOUNDS_ERROR. " + u"Their telephone call is costing 2,32\u00A0DM."; + break; case 2: statusToCheck= U_MEMORY_ALLOCATION_ERROR; - patternToCheck= "2:user in {2} is receiving a #{0} error - {1}. They insist they just spent {3,number,currency} on memory."; // number,currency - messageLocale= Locale("de","AT_PREEURO"); // Austrian German + patternToCheck= u"2:user in {2} is receiving a #{0} error - {1}. " + "They insist they just spent {3,number,currency} " + "on memory."; // number,currency + messageLocale= Locale("de","AT@currency=ATS"); // Austrian German countryToCheck= Locale("","US"); // hmm currencyToCheck= 40193.12; - expected= CharsToUnicodeString("2:user in Vereinigte Staaten is receiving a #7 error - U_MEMORY_ALLOCATION_ERROR. They insist they just spent \\u00f6S 40.193,12 on memory."); + expected= u"2:user in Vereinigte Staaten is receiving a #7 error" + u" - U_MEMORY_ALLOCATION_ERROR. They insist they just spent" + u" \u00f6S\u00A040.193,12 on memory."; break; } UnicodeString result; UErrorCode status = U_ZERO_ERROR; - formatErrorMessage(status,patternToCheck,messageLocale,statusToCheck,countryToCheck,currencyToCheck,result); + formatErrorMessage(status,patternToCheck,messageLocale,statusToCheck, + countryToCheck,currencyToCheck,result); if(U_FAILURE(status)) { - UnicodeString tmp; - errorToString(status,tmp); - Mutex m(&ftMutex); - error("Failure on message format, pattern=" + patternToCheck +", error = " + tmp); - continue; + UnicodeString tmp(u_errorName(status)); + IntlTest::gTest->errln(u"Failure on message format, pattern=" + patternToCheck + + ", error = " + tmp); + goto cleanupAndReturn; } if(result != expected) { - Mutex m(&ftMutex); - error("PatternFormat: \n" + showDifference(expected,result)); - continue; + IntlTest::gTest->errln(u"PatternFormat: \n" + showDifference(expected,result)); + goto cleanupAndReturn; } - } + // test the Thread Safe Format + UnicodeString appendErr; + if(!fTSF->doStuff(fNum, appendErr, status)) { + IntlTest::gTest->errln(appendErr); + goto cleanupAndReturn; + } + } /* end of for loop */ + + - delete formatter; - delete percentFormatter; - Mutex m(&ftMutex); - done(); +cleanupAndReturn: + fTraceInfo = 2; } private: @@ -832,335 +763,793 @@ private: void MultithreadTest::TestThreadedIntl() { - umtx_init(&ftMutex); + UnicodeString theErr; + + UErrorCode threadSafeErr = U_ZERO_ERROR; + + ThreadSafeFormatSharedData sharedData(threadSafeErr); + assertSuccess(WHERE, threadSafeErr, TRUE); + // + // Create and start the test threads + // + logln("Spawning: %d threads * %d iterations each.", + kFormatThreadThreads, kFormatThreadIterations); FormatThreadTest tests[kFormatThreadThreads]; - - logln(UnicodeString("Spawning: ") + kFormatThreadThreads + " threads * " + kFormatThreadIterations + " iterations each."); - for(int32_t j = 0; j < kFormatThreadThreads; j++) { + int32_t j; + for(j = 0; j < UPRV_LENGTHOF(tests); j++) { + tests[j].fNum = j; int32_t threadStatus = tests[j].start(); if (threadStatus != 0) { - errln("System Error %d starting thread number %d.", threadStatus, j); + errln("%s:%d System Error %d starting thread number %d.", + __FILE__, __LINE__, threadStatus, j); return; } } - int32_t patience; - for(patience = kFormatThreadPatience;patience > 0; patience --) + + for (j=0; jgetSortKey(lines[i].buff, lines[i].buflen, newSk, 1024); + + if(oldSk != NULL) { + int32_t skres = strcmp((char *)oldSk, (char *)newSk); + int32_t cmpres = coll->compare(lines[prev].buff, lines[prev].buflen, lines[i].buff, lines[i].buflen); + int32_t cmpres2 = coll->compare(lines[i].buff, lines[i].buflen, lines[prev].buff, lines[prev].buflen); + + if(cmpres != -cmpres2) { + IntlTest::gTest->errln(UnicodeString(u"Compare result not symmetrical on line ") + (i + 1)); + break; + } - int32_t i; - int32_t terrs = 0; - int32_t completed =0; + if(cmpres != normalizeResult(skres)) { + IntlTest::gTest->errln(UnicodeString(u"Difference between coll->compare and sortkey compare on line ") + (i + 1)); + break; + } - for(i=0;i 0) { + IntlTest::gTest->errln(UnicodeString(u"Line is not greater or equal than previous line, for line ") + (i + 1)); + break; } - // print out the error, too, if any. } + + oldSk = newSk; + oldLen = resLen; + (void)oldLen; // Suppress set but not used warning. + prev = i; + + newSk = (newSk == sk1)?sk2:sk1; } - - if(completed == kFormatThreadThreads) - { - logln("Done!"); + } +}; - if(terrs) - { - errln("There were errors."); +void MultithreadTest::TestCollators() +{ + + UErrorCode status = U_ZERO_ERROR; + FILE *testFile = NULL; + char testDataPath[1024]; + strcpy(testDataPath, IntlTest::getSourceTestData(status)); + if (U_FAILURE(status)) { + errln("ERROR: could not open test data %s", u_errorName(status)); + return; + } + strcat(testDataPath, "CollationTest_"); + + const char* type = "NON_IGNORABLE"; + + const char *ext = ".txt"; + if(testFile) { + fclose(testFile); + } + char buffer[1024]; + strcpy(buffer, testDataPath); + strcat(buffer, type); + size_t bufLen = strlen(buffer); + + // we try to open 3 files: + // path/CollationTest_type.txt + // path/CollationTest_type_SHORT.txt + // path/CollationTest_type_STUB.txt + // we are going to test with the first one that we manage to open. + + strcpy(buffer+bufLen, ext); + + testFile = fopen(buffer, "rb"); + + if(testFile == 0) { + strcpy(buffer+bufLen, "_SHORT"); + strcat(buffer, ext); + testFile = fopen(buffer, "rb"); + + if(testFile == 0) { + strcpy(buffer+bufLen, "_STUB"); + strcat(buffer, ext); + testFile = fopen(buffer, "rb"); + + if (testFile == 0) { + *(buffer+bufLen) = 0; + dataerrln("could not open any of the conformance test files, tried opening base %s", buffer); + return; + } else { + infoln( + "INFO: Working with the stub file.\n" + "If you need the full conformance test, please\n" + "download the appropriate data files from:\n" + "http://source.icu-project.org/repos/icu/tools/trunk/unicodetools/com/ibm/text/data/"); } + } + } - break; + LocalArray lines(new Line[200000]); + memset(lines.getAlias(), 0, sizeof(Line)*200000); + int32_t lineNum = 0; + + UChar bufferU[1024]; + uint32_t first = 0; + + while (fgets(buffer, 1024, testFile) != NULL) { + if(*buffer == 0 || buffer[0] == '#') { + // Store empty and comment lines so that errors are reported + // for the real test file lines. + lines[lineNum].buflen = 0; + lines[lineNum].buff[0] = 0; + } else { + int32_t buflen = u_parseString(buffer, bufferU, 1024, &first, &status); + lines[lineNum].buflen = buflen; + u_memcpy(lines[lineNum].buff, bufferU, buflen); + lines[lineNum].buff[buflen] = 0; } + lineNum++; + } + fclose(testFile); + if(U_FAILURE(status)) { + dataerrln("Couldn't read the test file!"); + return; + } - SimpleThread::sleep(900); + UVersionInfo uniVersion; + static const UVersionInfo v62 = { 6, 2, 0, 0 }; + u_getUnicodeVersion(uniVersion); + UBool isAtLeastUCA62 = uprv_memcmp(uniVersion, v62, 4) >= 0; + + LocalPointer coll(Collator::createInstance(Locale::getRoot(), status)); + if(U_FAILURE(status)) { + errcheckln(status, "Couldn't open UCA collator"); + return; } + coll->setAttribute(UCOL_NORMALIZATION_MODE, UCOL_ON, status); + coll->setAttribute(UCOL_CASE_FIRST, UCOL_OFF, status); + coll->setAttribute(UCOL_CASE_LEVEL, UCOL_OFF, status); + coll->setAttribute(UCOL_STRENGTH, isAtLeastUCA62 ? UCOL_IDENTICAL : UCOL_TERTIARY, status); + coll->setAttribute(UCOL_ALTERNATE_HANDLING, UCOL_NON_IGNORABLE, status); + + int32_t spawnResult = 0; + LocalArray tests(new CollatorThreadTest[kCollatorThreadThreads]); - if (patience <= 0) { - errln("patience exceeded. "); + logln(UnicodeString(u"Spawning: ") + kCollatorThreadThreads + u" threads * " + kFormatThreadIterations + u" iterations each."); + int32_t j = 0; + for(j = 0; j < kCollatorThreadThreads; j++) { + //logln("Setting collator %i", j); + tests[j].setCollator(coll.getAlias(), lines.getAlias(), lineNum, isAtLeastUCA62); + } + for(j = 0; j < kCollatorThreadThreads; j++) { + log("%i ", j); + spawnResult = tests[j].start(); + if(spawnResult != 0) { + errln("%s:%d THREAD INFO: thread %d failed to start with status %d", __FILE__, __LINE__, j, spawnResult); + return; + } + } + logln("Spawned all"); + + for(int32_t i=0;i 0) { - error(UnicodeString("Line %i is not greater or equal than previous line ")+ UnicodeString(i)); - break; - } else if(res == 0) { /* equal */ - res = u_strcmpCodePointOrder(lines[i-1].buff, lines[i].buff); - if (res == 0) { - error(UnicodeString("Probable error in test file on line %i (comparing identical strings)")+ UnicodeString(i)); - break; - } else if (res > 0) { - error(UnicodeString("Sortkeys are identical, but code point comapare gives >0 on line ")+ UnicodeString(i)); + int fNum; + int fTraceInfo; + static const UnicodeString *gSharedString; + + StringThreadTest2() // constructor is NOT multithread safe. + : SimpleThread(), + fTraceInfo(0) + { + }; + + + virtual void run() + { + fTraceInfo = 1; + int loopCount = 0; + + for (loopCount = 0; loopCount < kStringThreadIterations; loopCount++) { + if (*gSharedString != u"This is the original test string.") { + IntlTest::gTest->errln("%s:%d Original string is corrupt.", __FILE__, __LINE__); + break; + } + UnicodeString s1 = *gSharedString; + s1 += u"cat this"; + UnicodeString s2(s1); + UnicodeString s3 = *gSharedString; + s2 = s3; + s3.truncate(12); + s2.truncate(0); } - } + + fTraceInfo = 2; } - oldSk = newSk; - oldLen = resLen; +}; - newSk = (newSk == sk1)?sk2:sk1; - } +const UnicodeString *StringThreadTest2::gSharedString = NULL; - Mutex m; - done(); - } -}; +// ** The actual test function. -void MultithreadTest::TestCollators() + +void MultithreadTest::TestString() { + int j; + StringThreadTest2::gSharedString = new UnicodeString(u"This is the original test string."); + StringThreadTest2 tests[kStringThreadThreads]; - UErrorCode status = U_ZERO_ERROR; - FILE *testFile = NULL; - char testDataPath[1024]; - uprv_strcpy(testDataPath, IntlTest::loadTestData(status)); - char* index = 0; - if (U_FAILURE(status)) { - errln("ERROR: could not open test data %s", u_errorName(status)); - return; - } - index=strrchr(testDataPath,(char)U_FILE_SEP_CHAR); + logln(UnicodeString(u"Spawning: ") + kStringThreadThreads + u" threads * " + kStringThreadIterations + u" iterations each."); + for(j = 0; j < kStringThreadThreads; j++) { + int32_t threadStatus = tests[j].start(); + if (threadStatus != 0) { + errln("%s:%d System Error %d starting thread number %d.", __FILE__, __LINE__, threadStatus, j); + } + } - if((unsigned int)(index-testDataPath) != (strlen(testDataPath)-1)){ - *(index+1)=0; - } - uprv_strcat(testDataPath,".."U_FILE_SEP_STRING); - uprv_strcat(testDataPath, "CollationTest_"); + // Force a failure, to verify test is functioning and can report errors. + // const_cast(StringThreadTest2::gSharedString)->setCharAt(5, 'x'); - const char* type = "NON_IGNORABLE"; + for(j=0; jtransliterate(greekString); + IntlTest::gTest->assertEquals(WHERE, UnicodeString(u"diaphoretikoús"), greekString); +} +#endif - if(testFile == 0) { - uprv_strcpy(buffer+bufLen, "_STUB"); - uprv_strcat(buffer, ext); - testFile = fopen(buffer, "rb"); - - if (testFile == 0) { - *(buffer+bufLen) = 0; - errln("ERROR: could not open any of the conformance test files, tried opening base %s", buffer); - return; - } else { - infoln( - "INFO: Working with the stub file.\n" - "If you need the full conformance test, please\n" - "download the appropriate data files from:\n" - "http://oss.software.ibm.com/cvs/icu4j/unicodetools/com/ibm/text/data/"); - } - } - } - Line *lines = new Line[65000]; - uprv_memset(lines, 0, sizeof(Line)*65000); - int32_t lineNum = 0; +void MultithreadTest::TestAnyTranslit() { +#if !UCONFIG_NO_TRANSLITERATION + UErrorCode status = U_ZERO_ERROR; + LocalPointer tx(Transliterator::createInstance("Any-Latin", UTRANS_FORWARD, status)); + if (!assertSuccess(WHERE, status, true)) { return; } - UChar bufferU[1024]; - int32_t buflen = 0; - uint32_t first = 0; - uint32_t offset = 0; + gSharedTranslit = tx.getAlias(); + TxThread threads[4]; + int32_t i; + for (i=0; i lock(gCTMutex()->fMutex); + gStartedThreads += gConditionTestOne; + gCTConditionVar()->notify_all(); + + while (gStartedThreads < NUMTHREADS) { + if (gFinishedThreads != 0) { + IntlTest::gTest->errln("File %s, Line %d: Error, gStartedThreads = %d, gFinishedThreads = %d", + __FILE__, __LINE__, gStartedThreads, gFinishedThreads); + } + gCTConditionVar()->wait(lock); } - for(j = 0; j < kCollatorThreadThreads; j++) { - log("%i ", j); - spawnResult = tests[j].start(); - if(spawnResult != 0) { - infoln("THREAD INFO: Couldn't spawn more than %i threads", noSpawned); - break; - } - noSpawned++; + + gFinishedThreads += gConditionTestOne; + fFinished = true; + gCTConditionVar()->notify_all(); + + while (gFinishedThreads < NUMTHREADS) { + gCTConditionVar()->wait(lock); } - logln("Spawned all"); +} + +void MultithreadTest::TestConditionVariables() { + gStartedThreads = 0; + gFinishedThreads = 0; + int i; + CondThread *threads[NUMTHREADS]; - //for(int32_t patience = kCollatorThreadPatience;patience > 0; patience --) - for(;;) { - logln("Waiting..."); + std::unique_lock lock(gCTMutex()->fMutex); + for (i=0; istart(); + } - int32_t i; - int32_t terrs = 0; - int32_t completed =0; + while (gStartedThreads < NUMTHREADS) { + gCTConditionVar()->wait(lock); + } - for(i=0;iwait(lock); + } + } + + for (i=0; ifFinished); + } - //logln(UnicodeString("Test #") + i + " is complete.. "); + for (i=0; ijoin(); + delete threads[i]; + } +} - UnicodeString theErr; - if(tests[i].getError(theErr)) - { - terrs++; - errln(UnicodeString("#") + i + ": " + theErr); - } - // print out the error, too, if any. + +// +// Unified Cache Test +// + +// Each thread fetches a pair of objects. There are 8 distinct pairs: +// ("en_US", "bs"), ("en_GB", "ca"), ("fr_FR", "ca_AD") etc. +// These pairs represent 8 distinct languages + +// Note that only one value per language gets created in the cache. +// In particular each cached value can have multiple keys. +static const char *gCacheLocales[] = { + "en_US", "en_GB", "fr_FR", "fr", + "de", "sr_ME", "sr_BA", "sr_CS"}; +static const char *gCacheLocales2[] = { + "bs", "ca", "ca_AD", "ca_ES", + "en_US", "fi", "ff_CM", "ff_GN"}; + +static int32_t gObjectsCreated = 0; // protected by gCTMutex +static const int32_t CACHE_LOAD = 3; + +class UCTMultiThreadItem : public SharedObject { + public: + char *value; + UCTMultiThreadItem(const char *x) : value(NULL) { + value = uprv_strdup(x); + } + virtual ~UCTMultiThreadItem() { + uprv_free(value); + } +}; + +U_NAMESPACE_BEGIN + +template<> U_EXPORT +const UCTMultiThreadItem *LocaleCacheKey::createObject( + const void *context, UErrorCode &status) const { + const UnifiedCache *cacheContext = (const UnifiedCache *) context; + + if (uprv_strcmp(fLoc.getLanguage(), fLoc.getName()) != 0) { + const UCTMultiThreadItem *result = NULL; + if (cacheContext == NULL) { + UnifiedCache::getByLocale(fLoc.getLanguage(), result, status); + return result; + } + cacheContext->get(LocaleCacheKey(fLoc.getLanguage()), result, status); + return result; + } + + bool firstObject = false; + { + std::unique_lock lock(gCTMutex()->fMutex); + firstObject = (gObjectsCreated == 0); + if (firstObject) { + // Force the first object creation that comes through to wait + // until other have completed. Verifies that cache doesn't + // deadlock when a creation is slow. + + // Note that gObjectsCreated needs to be incremeneted from 0 to 1 + // early, to keep subsequent threads from entering this path. + gObjectsCreated = 1; + while (gObjectsCreated < 3) { + gCTConditionVar()->wait(lock); } } - logln("Completed %i tests", completed); + } - if(completed == noSpawned) - { - logln("Done! All %i tests are finished", noSpawned); + const UCTMultiThreadItem *result = + new UCTMultiThreadItem(fLoc.getLanguage()); + if (result == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + } else { + result->addRef(); + } + + // Log that we created an object. The first object was already counted, + // don't do it again. + { + std::unique_lock lock(gCTMutex()->fMutex); + if (!firstObject) { + gObjectsCreated += 1; + } + gCTConditionVar()->notify_all(); + } - if(terrs) - { - errln("There were errors."); - } - ucol_close(coll); - delete[] tests; - //for(i = 0; i < lineNum; i++) { - //delete[] lines[i].buff; - //} - delete[] lines; + return result; +} - return; +U_NAMESPACE_END + +class UnifiedCacheThread: public SimpleThread { + public: + UnifiedCacheThread( + const UnifiedCache *cache, + const char *loc, + const char *loc2) : fCache(cache), fLoc(loc), fLoc2(loc2) {}; + ~UnifiedCacheThread() {}; + void run(); + void exerciseByLocale(const Locale &); + const UnifiedCache *fCache; + Locale fLoc; + Locale fLoc2; +}; + +void UnifiedCacheThread::exerciseByLocale(const Locale &locale) { + UErrorCode status = U_ZERO_ERROR; + const UCTMultiThreadItem *origItem = NULL; + fCache->get( + LocaleCacheKey(locale), fCache, origItem, status); + U_ASSERT(U_SUCCESS(status)); + IntlTest::gTest->assertEquals(WHERE, locale.getLanguage(), origItem->value); + + // Fetch the same item again many times. We should always get the same + // pointer since this client is already holding onto it + for (int32_t i = 0; i < 1000; ++i) { + const UCTMultiThreadItem *item = NULL; + fCache->get( + LocaleCacheKey(locale), fCache, item, status); + IntlTest::gTest->assertTrue(WHERE, item == origItem); + if (item != NULL) { + item->removeRef(); } + } + origItem->removeRef(); +} - SimpleThread::sleep(900); +void UnifiedCacheThread::run() { + // Run the exercise with 2 different locales so that we can exercise + // eviction more. If each thread exercises just one locale, then + // eviction can't start until the threads end. + exerciseByLocale(fLoc); + exerciseByLocale(fLoc2); +} + +void MultithreadTest::TestUnifiedCache() { + + // Start with our own local cache so that we have complete control + // and set the eviction policy to evict starting with 2 unused + // values + UErrorCode status = U_ZERO_ERROR; + UnifiedCache::getInstance(status); + UnifiedCache cache(status); + cache.setEvictionPolicy(2, 0, status); + U_ASSERT(U_SUCCESS(status)); + + gFinishedThreads = 0; + gObjectsCreated = 0; + + UnifiedCacheThread *threads[CACHE_LOAD][UPRV_LENGTHOF(gCacheLocales)]; + for (int32_t i=0; istart(); + } + } + + for (int32_t i=0; ijoin(); + } + } + // Because of cache eviction, we can't assert exactly how many + // distinct objects get created over the course of this run. + // However we know that at least 8 objects get created because that + // is how many distinct languages we have in our test. + if (gObjectsCreated < 8) { + errln("%s:%d Too few objects created.", __FILE__, __LINE__); + } + // We know that each thread cannot create more than 2 objects in + // the cache, and there are UPRV_LENGTHOF(gCacheLocales) pairs of + // objects fetched from the cache. If the threads run in series because + // of eviction, at worst case each thread creates two objects. + if (gObjectsCreated > 2 * CACHE_LOAD * UPRV_LENGTHOF(gCacheLocales)) { + errln("%s:%d Too many objects created, got %d, expected %d", __FILE__, __LINE__, gObjectsCreated, 2 * CACHE_LOAD * UPRV_LENGTHOF(gCacheLocales)); + + } + + assertEquals(WHERE, 2, cache.unusedCount()); + + // clean up threads + for (int32_t i=0; itransliterate(s); + if (*gTranslitExpected != s) { + IntlTest::gTest->errln("%s:%d Transliteration threading failure.", __FILE__, __LINE__); + break; + } + } +} + +void MultithreadTest::TestBreakTranslit() { + UErrorCode status = U_ZERO_ERROR; + UnicodeString input( + u"\u0E42\u0E14\u0E22\u0E1E\u0E37\u0E49\u0E19\u0E10\u0E32\u0E19\u0E41\u0E25\u0E49\u0E27,"); + // Thai script, โดยพื้นฐานแล้ว + gTranslitInput = &input; + + gSharedTransliterator = Transliterator::createInstance( + UnicodeString(u"Any-Latin; Lower; NFD; [:Diacritic:]Remove; NFC; Latin-ASCII;"), UTRANS_FORWARD, status); + assertSuccess(WHERE, status); + if (!assertTrue(WHERE, gSharedTransliterator != nullptr)) { + return; + } + + UnicodeString expected(*gTranslitInput); + gSharedTransliterator->transliterate(expected); + gTranslitExpected = &expected; + + BreakTranslitThread threads[4]; + for (int i=0; idefaultCenturyStartYear(); +} + +void MultithreadTest::Test20104() { + UErrorCode status = U_ZERO_ERROR; + Locale loc("hi_IN"); + gSharedCalendar = new IndianCalendar(loc, status); + assertSuccess(WHERE, status); + + static constexpr int NUM_THREADS = 4; + Test20104Thread threads[NUM_THREADS]; + for (auto &thread:threads) { + thread.start(); + } + for (auto &thread:threads) { + thread.join(); + } + delete gSharedCalendar; + // Note: failure is reported by Thread Sanitizer. Test itself succeeds. +} +#endif /* !UCONFIG_NO_FORMATTING */ +#endif /* !UCONFIG_NO_TRANSLITERATION */