X-Git-Url: https://git.saurik.com/apple/icu.git/blobdiff_plain/73c04bcfe1096173b00431f0cdc742894b15eef0..f59164e3d128c7675a4d3934206346a3384e53a5:/icuSources/test/intltest/tsmthred.cpp?ds=sidebyside diff --git a/icuSources/test/intltest/tsmthred.cpp b/icuSources/test/intltest/tsmthred.cpp index 05b29597..9fabb4af 100644 --- a/icuSources/test/intltest/tsmthred.cpp +++ b/icuSources/test/intltest/tsmthred.cpp @@ -1,14 +1,10 @@ /******************************************************************** - * COPYRIGHT: - * Copyright (c) 1999-2006, 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 "simplethread.h" #include "unicode/utypes.h" #include "unicode/ustring.h" @@ -16,65 +12,25 @@ #include "cmemory.h" #include "cstring.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" -#if !defined(U_WINDOWS) && !defined(XP_MAC) && !defined(U_RHAPSODY) -#define POSIX 1 -#endif - -#if defined(POSIX) || defined(U_SOLARIS) || defined(U_AIX) || defined(U_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 "intltest.h" #include "tsmthred.h" +#include "unicode/ushape.h" +#include "unicode/translit.h" +#include "sharedobject.h" +#include "unifiedcache.h" +#include "uassert.h" + #define TSMTHREAD_FAIL(msg) errln("%s at file %s, line %d", msg, __FILE__, __LINE__) #define TSMTHREAD_ASSERT(expr) {if (!(expr)) {TSMTHREAD_FAIL("Fail");}} +#define TSMTHREAD_ASSERT_SUCCESS(status) {if (U_FAILURE(status)) { \ + errln("file: %s:%d status = %s\n", __FILE__, __LINE__, u_errorName(status));}} MultithreadTest::MultithreadTest() { @@ -84,398 +40,23 @@ 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 "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" -//----------------------------------------------------------------------------------- -// -// class SimpleThread Of course we need a thread class first.. -// This wrapper has a ported implementation. -// -//----------------------------------------------------------------------------------- -class SimpleThread -{ -public: - SimpleThread(); - virtual ~SimpleThread(); - int32_t start(void); // start the thread - UBool isRunning(); // return true if a started thread has exited. - - virtual void run(void) = 0; // Override this to provide the code to run - // in the thread. - void *fImplementation; - -public: - static void sleep(int32_t millis); // probably shouldn't go here but oh well. - static void errorFunc(); // Empty function, provides a single convenient place - // to break on errors. -}; - -void SimpleThread::errorFunc() { - // *(char *)0 = 3; // Force entry into a debugger via a crash; -} - - - - -#ifdef U_WINDOWS -#define HAVE_IMP - -# define VC_EXTRALEAN -# define WIN32_LEAN_AND_MEAN -# define NOUSER -# define NOSERVICE -# define NOIME -# define NOMCX -#include -#include - - - -//----------------------------------------------------------------------------------- -// -// class SimpleThread Windows Implementation -// -//----------------------------------------------------------------------------------- -struct Win32ThreadImplementation -{ - HANDLE fHandle; - unsigned int fThreadID; -}; - - -extern "C" unsigned int __stdcall SimpleThreadProc(void *arg) -{ - ((SimpleThread*)arg)->run(); - return 0; -} - -SimpleThread::SimpleThread() -:fImplementation(0) -{ - Win32ThreadImplementation *imp = new Win32ThreadImplementation; - imp->fHandle = 0; - fImplementation = imp; -} - -SimpleThread::~SimpleThread() -{ - // Destructor. Because we start the thread running with _beginthreadex(), - // we own the Windows HANDLE for the thread and must - // close it here. - Win32ThreadImplementation *imp = (Win32ThreadImplementation*)fImplementation; - if (imp != 0) { - if (imp->fHandle != 0) { - CloseHandle(imp->fHandle); - imp->fHandle = 0; - } - } - 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 = (HANDLE) _beginthreadex( - NULL, // Security - 0x20000, // Stack Size - SimpleThreadProc, // Function to Run - (void *)this, // Arg List - 0, // initflag. Start running, not suspended - &imp->fThreadID // thraddr - ); - - if (imp->fHandle == 0) { - // An error occured - int err = errno; - if (err == 0) { - err = -1; - } - return err; - } - return 0; -} - - -UBool SimpleThread::isRunning() { - // - // Test whether the thread associated with the SimpleThread object is - // still actually running. - // - // NOTE: on Win64 on Itanium processors, a crashes - // occur if the main thread of a process exits concurrently with some - // other thread(s) exiting. To avoid the possibility, we wait until the - // OS indicates that all threads have terminated, rather than waiting - // only until the end of the user's Run function has been reached. - // - // I don't know whether the crashes represent a Windows bug, or whether - // main() programs are supposed to have to wait for their threads. - // - Win32ThreadImplementation *imp = (Win32ThreadImplementation*)fImplementation; - - bool success; - DWORD threadExitCode; - - if (imp->fHandle == 0) { - // No handle, thread must not be running. - return FALSE; - } - success = GetExitCodeThread(imp->fHandle, &threadExitCode) != 0; - if (! success) { - // Can't get status, thread must not be running. - return FALSE; - } - return (threadExitCode == STILL_ACTIVE); -} - - -void SimpleThread::sleep(int32_t millis) -{ - ::Sleep(millis); -} - -//----------------------------------------------------------------------------------- -// -// class SimpleThread NULL Implementation -// -//----------------------------------------------------------------------------------- -#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) -{} - -UBool -SimpleThread::isRunning() { - return FALSE; -} - -#endif - - -//----------------------------------------------------------------------------------- -// -// class SimpleThread POSIX implementation -// -// A note on the POSIX vs the Windows implementations of this class.. -// On Windows, the main thread must verify that other threads have finished -// before exiting, or crashes occasionally occur. (Seen on Itanium Win64 only) -// The function SimpleThread::isRunning() is used for this purpose. -// -// On POSIX, there is NO reliable non-blocking mechanism to determine -// whether a thread has exited. pthread_kill(thread, 0) almost works, -// but the system can recycle thread ids immediately, so seeing that a -// thread exists with this call could mean that the original thread has -// finished and a new one started with the same ID. Useless. -// -// So we need to do the check with user code, by setting a flag just before -// the thread function returns. A technique that is guaranteed to fail -// on Windows, because it indicates that the thread is done before all -// system level cleanup has happened. -// -//----------------------------------------------------------------------------------- -#if defined(POSIX)||defined(U_SOLARIS)||defined(U_AIX)||defined(U_HPUX) -#define HAVE_IMP - -struct PosixThreadImplementation -{ - pthread_t fThread; - UBool fRunning; - UBool fRan; /* True if the thread was successfully started */ -}; - -extern "C" void* SimpleThreadProc(void *arg) -{ - // This is the code that is run in the new separate thread. - SimpleThread *This = (SimpleThread *)arg; - This->run(); // Run the user code. - - // The user function has returned. Set the flag indicating that this thread - // is done. Need a mutex for memory barrier purposes only, so that other thread - // will reliably see that the flag has changed. - PosixThreadImplementation *imp = (PosixThreadImplementation*)This->fImplementation; - umtx_lock(NULL); - imp->fRunning = FALSE; - umtx_unlock(NULL); - return 0; -} - -SimpleThread::SimpleThread() -{ - PosixThreadImplementation *imp = new PosixThreadImplementation; - imp->fRunning = FALSE; - imp->fRan = FALSE; - fImplementation = imp; -} - -SimpleThread::~SimpleThread() -{ - PosixThreadImplementation *imp = (PosixThreadImplementation*)fImplementation; - if (imp->fRan) { - pthread_join(imp->fThread, NULL); - } - delete imp; - fImplementation = (void *)0xdeadbeef; -} - -int32_t SimpleThread::start() -{ - int32_t rc; - static pthread_attr_t attr; - static UBool attrIsInitialized = FALSE; - - PosixThreadImplementation *imp = (PosixThreadImplementation*)fImplementation; - imp->fRunning = TRUE; - imp->fRan = TRUE; - -#ifdef HPUX_CMA - if (attrIsInitialized == FALSE) { - rc = pthread_attr_create(&attr); - attrIsInitialized = TRUE; - } - rc = pthread_create(&(imp->fThread),attr,&SimpleThreadProc,(void*)this); -#else - if (attrIsInitialized == FALSE) { - rc = pthread_attr_init(&attr); -#if defined(OS390) - { - int detachstate = 0; /* jdc30: detach state of zero causes - threads created with this attr to be in - an undetached state. An undetached - thread will keep its resources after - termination. */ - pthread_attr_setdetachstate(&attr, &detachstate); - } -#else - // pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); -#endif - attrIsInitialized = TRUE; - } - rc = pthread_create(&(imp->fThread),&attr,&SimpleThreadProc,(void*)this); -#endif - - if (rc != 0) { - // some kind of error occured, the thread did not start. - imp->fRan = FALSE; - imp->fRunning = FALSE; - } - - return rc; -} - - -UBool -SimpleThread::isRunning() { - // Note: Mutex functions are used here not for synchronization, - // but to force memory barriors to exist, to ensure that one thread - // can see changes made by another when running on processors - // with memory models having weak coherency. - PosixThreadImplementation *imp = (PosixThreadImplementation*)fImplementation; - umtx_lock(NULL); - UBool retVal = imp->fRunning; - umtx_unlock(NULL); - return retVal; -} - - -void SimpleThread::sleep(int32_t millis) -{ -#ifdef U_SOLARIS - sigignore(SIGALRM); -#endif - -#ifdef HPUX_CMA - cma_sleep(millis/100); -#elif defined(U_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: "); @@ -511,12 +92,46 @@ void MultithreadTest::runIndexedTest( int32_t index, UBool exec, break; case 4: - name = "TestString"; + name = "TestString"; if (exec) { TestString(); } break; + case 5: + name = "TestArabicShapingThreads"; + if (exec) { + TestArabicShapingThreads(); + } + break; + + case 6: + name = "TestAnyTranslit"; + if (exec) { + TestAnyTranslit(); + } + break; + + case 7: + name = "TestConditionVariables"; + if (exec) { + TestConditionVariables(); + } + break; + case 8: + name = "TestUnifiedCache"; + if (exec) { + TestUnifiedCache(); + } + break; +#if !UCONFIG_NO_TRANSLITERATION + case 9: + name = "TestBreakTranslit"; + if (exec) { + TestBreakTranslit(); + } + break; +#endif default: name = ""; break; //needed to end loop @@ -529,26 +144,25 @@ void MultithreadTest::runIndexedTest( int32_t index, UBool exec, // 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. +// set their chars. At the end we make sure they are all set. // //----------------------------------------------------------------------------------- -#define THREADTEST_NRTHREADS 8 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; @@ -570,206 +184,211 @@ void MultithreadTest::TestThreads() else { numThreadsStarted++; } - SimpleThread::sleep(100); logln(" Subthread started."); } - logln("Waiting for threads to be set.."); - if (numThreadsStarted == 0) { - errln("No threads could be started for testing!"); + if (numThreadsStarted != THREADTEST_NRTHREADS) { + errln("Not all threads could be started for testing!"); return; } - int32_t patience = 40; // seconds to wait + 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]; + } +} - while(patience--) - { - int32_t count = 0; - umtx_lock(NULL); - 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; } - umtx_unlock(NULL); - - if(count == THREADTEST_NRTHREADS) - { - logln("->" + 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_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; +} + + +void MultithreadTest::TestArabicShapingThreads() +{ + TestArabicShapeThreads threads[30]; + + int32_t i; - logln("->" + UnicodeString(threadTestChars) + "<- Waiting.."); - SimpleThread::sleep(500); + 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); + } } - errln("->" + UnicodeString(threadTestChars) + "<- PATIENCE EXCEEDED!! Still missing some."); - for(i=0;iTestArabicShapingThreads <- Got all threads! cya"); } //----------------------------------------------------------------------- // // TestMutex - a simple (non-stress) test to verify that ICU mutexes -// are actually mutexing. Does not test the use of +// 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 UMTX gTestMutexA = NULL; -static UMTX gTestMutexB = NULL; +static UMutex gTestMutexA = U_MUTEX_INITIALIZER; +static UConditionVar gThreadsCountChanged = U_CONDITION_INITIALIZER; -static int gThreadsStarted = 0; +static int gThreadsStarted = 0; static int gThreadsInMiddle = 0; static int gThreadsDone = 0; -static const int TESTMUTEX_THREAD_COUNT = 4; - -static int safeIncr(int &var, int amt) { - // Thread safe (using global mutex) increment of a variable. - // Return the updated value. - // Can also be used as a safe load of a variable by incrementing it by 0. - Mutex m; - var += amt; - return var; -} +static const int TESTMUTEX_THREAD_COUNT = 40; class TestMutexThread : public SimpleThread { public: - virtual void run() - { + virtual void run() { // This is the code that each of the spawned threads runs. - // All of the spawned threads bunch up together at each of the two mutexes - // because the main holds the mutexes until they do. - // - safeIncr(gThreadsStarted, 1); + // All threads move together throught the started - middle - done sequence together, + // waiting for all other threads to reach each point before advancing. umtx_lock(&gTestMutexA); + gThreadsStarted += 1; + umtx_condBroadcast(&gThreadsCountChanged); + while (gThreadsStarted < TESTMUTEX_THREAD_COUNT) { + if (gThreadsInMiddle != 0) { + IntlTest::gTest->errln( + "%s:%d gThreadsInMiddle = %d. Expected 0.", __FILE__, __LINE__, gThreadsInMiddle); + return; + } + umtx_condWait(&gThreadsCountChanged, &gTestMutexA); + } + + gThreadsInMiddle += 1; + umtx_condBroadcast(&gThreadsCountChanged); + while (gThreadsInMiddle < TESTMUTEX_THREAD_COUNT) { + if (gThreadsDone != 0) { + IntlTest::gTest->errln( + "%s:%d gThreadsDone = %d. Expected 0.", __FILE__, __LINE__, gThreadsDone); + return; + } + umtx_condWait(&gThreadsCountChanged, &gTestMutexA); + } + + gThreadsDone += 1; + umtx_condBroadcast(&gThreadsCountChanged); + while (gThreadsDone < TESTMUTEX_THREAD_COUNT) { + umtx_condWait(&gThreadsCountChanged, &gTestMutexA); + } umtx_unlock(&gTestMutexA); - safeIncr(gThreadsInMiddle, 1); - umtx_lock(&gTestMutexB); - umtx_unlock(&gTestMutexB); - safeIncr(gThreadsDone, 1); } }; void MultithreadTest::TestMutex() { - // Start up the test threads. They should all pile up waiting on - // gTestMutexA, which we (the main thread) hold until the test threads - // all get there. gThreadsStarted = 0; gThreadsInMiddle = 0; gThreadsDone = 0; + int32_t i = 0; + TestMutexThread threads[TESTMUTEX_THREAD_COUNT]; umtx_lock(&gTestMutexA); - TestMutexThread *threads[TESTMUTEX_THREAD_COUNT]; - int i; - int32_t numThreadsStarted = 0; for (i=0; istart() != 0) { - errln("Error starting thread %d", i); - } - else { - numThreadsStarted++; - } - } - if (numThreadsStarted == 0) { - errln("No threads could be started for testing!"); - return; - } - - int patience = 0; - while (safeIncr(gThreadsStarted, 0) != TESTMUTEX_THREAD_COUNT) { - if (patience++ > 24) { - TSMTHREAD_FAIL("Patience Exceeded"); + if (threads[i].start() != 0) { + errln("%s:%d Error starting thread %d", __FILE__, __LINE__, i); return; } - SimpleThread::sleep(500); } - // None of the test threads should have advanced past the first mutex. - TSMTHREAD_ASSERT(gThreadsInMiddle==0); - TSMTHREAD_ASSERT(gThreadsDone==0); - - // All of the test threads have made it to the first mutex. - // We (the main thread) now let them advance to the second mutex, - // where they should all pile up again. - umtx_lock(&gTestMutexB); - umtx_unlock(&gTestMutexA); - patience = 0; - while (safeIncr(gThreadsInMiddle, 0) != TESTMUTEX_THREAD_COUNT) { - if (patience++ > 24) { - TSMTHREAD_FAIL("Patience Exceeded"); - return; - } - SimpleThread::sleep(500); + // Because we are holding gTestMutexA, all of the threads should be blocked + // at the start of their run() function. + if (gThreadsStarted != 0) { + errln("%s:%d gThreadsStarted=%d. Expected 0.", __FILE__, __LINE__, gThreadsStarted); + return; } - TSMTHREAD_ASSERT(gThreadsDone==0); - // All test threads made it to the second mutex. - // Now let them proceed from there. They will all terminate. - umtx_unlock(&gTestMutexB); - patience = 0; - while (safeIncr(gThreadsDone, 0) != TESTMUTEX_THREAD_COUNT) { - if (patience++ > 24) { - TSMTHREAD_FAIL("Patience Exceeded"); + while (gThreadsInMiddle < TESTMUTEX_THREAD_COUNT) { + if (gThreadsDone != 0) { + errln("%s:%d gThreadsDone=%d. Expected 0.", __FILE__, __LINE__, gThreadsStarted); return; } - SimpleThread::sleep(500); + umtx_condWait(&gThreadsCountChanged, &gTestMutexA); } - // All threads made it by both mutexes. - // Destroy the test mutexes. - umtx_destroy(&gTestMutexA); - umtx_destroy(&gTestMutexB); - gTestMutexA=NULL; - gTestMutexB=NULL; + while (gThreadsDone < TESTMUTEX_THREAD_COUNT) { + umtx_condWait(&gThreadsCountChanged, &gTestMutexA); + } + umtx_unlock(&gTestMutexA); for (i=0; i 0); } - UBool getError(UnicodeString& fillinError) { fillinError = fErrorString; return (fErrors > 0); } - virtual ~ThreadWithStatus(){} -protected: - ThreadWithStatus() : fErrors(0) {} - void error(const UnicodeString &error) { - fErrors++; fErrorString = error; - SimpleThread::errorFunc(); - } - void error() { error("An error occured."); } -private: - int32_t fErrors; - UnicodeString fErrorString; -}; - - - -//------------------------------------------------------------------------------------------- -// -// TestMultithreadedIntl. Test ICU Formatting n a multi-threaded environment +// TestMultithreadedIntl. Test ICU Formatting in a multi-threaded environment // //------------------------------------------------------------------------------------------- @@ -803,17 +422,14 @@ UnicodeString showDifference(const UnicodeString& expected, const UnicodeString& } - - //------------------------------------------------------------------------------------------- // // FormatThreadTest - a thread that tests performing a number of numberformats. // //------------------------------------------------------------------------------------------- -const int kFormatThreadIterations = 20; // # of iterations per thread -const int kFormatThreadThreads = 10; // # of threads to spawn -const int 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 @@ -829,7 +445,7 @@ struct FormatThreadTestData // "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, +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) { @@ -851,18 +467,115 @@ void formatErrorMessage(UErrorCode &realStatus, const UnicodeString& pattern, co MessageFormat *fmt = new MessageFormat("MessageFormat's API is broken!!!!!!!!!!!",realStatus); fmt->setLocale(theLocale); fmt->applyPattern(pattern, realStatus); - + if (U_FAILURE(realStatus)) { delete fmt; return; } - 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; +}; + +const ThreadSafeFormatSharedData *gSharedData = NULL; + +ThreadSafeFormatSharedData::ThreadSafeFormatSharedData(UErrorCode &status) { + fFormat.adoptInstead(NumberFormat::createCurrencyInstance(Locale::getUS(), status)); + static const UChar kYDD[] = { 0x59, 0x44, 0x44, 0x00 }; + static const UChar kBBD[] = { 0x42, 0x42, 0x44, 0x00 }; + 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; +} + +/** + * 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 +}; + + +ThreadSafeFormat::ThreadSafeFormat(UErrorCode &status) { + fFormat.adoptInstead(NumberFormat::createCurrencyInstance(Locale::getUS(), status)); +} + +static const UChar kUSD[] = { 0x55, 0x53, 0x44, 0x00 }; + +UBool ThreadSafeFormat::doStuff(int32_t offset, UnicodeString &appendErr, UErrorCode &status) const { + UBool okay = TRUE; + + if(u_strcmp(fFormat->getCurrency(), kUSD)) { + appendErr.append("fFormat currency != ") + .append(kUSD) + .append(", =") + .append(fFormat->getCurrency()) + .append("! "); + okay = FALSE; + } + + if(u_strcmp(gSharedData->fFormat->getCurrency(), kUSD)) { + appendErr.append("gFormat currency != ") + .append(kUSD) + .append(", =") + .append(gSharedData->fFormat->getCurrency()) + .append("! "); + 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; @@ -872,19 +585,24 @@ UBool U_CALLCONV isAcceptable(void *, const char *, const char *, const UDataInf //static UMTX gDebugMutex; -class FormatThreadTest : public ThreadWithStatus +class FormatThreadTest : public SimpleThread { public: int fNum; int fTraceInfo; + LocalPointer fTSF; + FormatThreadTest() // constructor is NOT multithread safe. - : ThreadWithStatus(), + : 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; @@ -894,12 +612,11 @@ public: virtual void run() { fTraceInfo = 1; - NumberFormat *formatter = NULL; - NumberFormat *percentFormatter = NULL; + LocalPointer percentFormatter; UErrorCode status = U_ZERO_ERROR; #if 0 - // debugging code, + // debugging code, for (int i=0; i<4000; i++) { status = U_ZERO_ERROR; UDataMemory *data1 = udata_openChoice(0, "res", "en_US", isAcceptable, 0, &status); @@ -914,8 +631,8 @@ public: return; #endif -#if 1 - // debugging code, +#if 0 + // debugging code, int m; for (m=0; m<4000; m++) { status = U_ZERO_ERROR; @@ -945,7 +662,7 @@ public: #endif // Keep this data here to avoid static initialization. - FormatThreadTestData kNumberFormatTestData[] = + FormatThreadTestData kNumberFormatTestData[] = { FormatThreadTestData((double)5.0, UnicodeString("5", "")), FormatThreadTestData( 6.0, UnicodeString("6", "")), @@ -955,74 +672,74 @@ public: FormatThreadTestData( 12345, UnicodeString("12,345", "")), FormatThreadTestData( 81890.23, UnicodeString("81,890.23", "")), }; - int32_t kNumberFormatTestDataLength = (int32_t)(sizeof(kNumberFormatTestData) / - sizeof(kNumberFormatTestData[0])); - + int32_t kNumberFormatTestDataLength = UPRV_LENGTHOF(kNumberFormatTestData); + // Keep this data here to avoid static initialization. - FormatThreadTestData kPercentFormatTestData[] = + 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%" )), + FormatThreadTestData((double)5.0, CharsToUnicodeString("500\\u00a0%")), + FormatThreadTestData( 1.0, CharsToUnicodeString("100\\u00a0%")), + FormatThreadTestData( 0.26, CharsToUnicodeString("26\\u00a0%")), + FormatThreadTestData( + 16384.99, CharsToUnicodeString("1\\u00a0638\\u00a0499\\u00a0%")), // U+00a0 = NBSP + FormatThreadTestData( + 81890.23, CharsToUnicodeString("8\\u00a0189\\u00a0023\\u00a0%")), }; - int32_t kPercentFormatTestDataLength = - (int32_t)(sizeof(kPercentFormatTestData) / sizeof(kPercentFormatTestData[0])); + int32_t kPercentFormatTestDataLength = UPRV_LENGTHOF(kPercentFormatTestData); int32_t iteration; - + status = U_ZERO_ERROR; - formatter = NumberFormat::createInstance(Locale::getEnglish(),status); + LocalPointer formatter(NumberFormat::createInstance(Locale::getEnglish(),status)); if(U_FAILURE(status)) { - error("Error on NumberFormat::createInstance()"); + IntlTest::gTest->dataerrln("%s:%d Error %s on NumberFormat::createInstance().", + __FILE__, __LINE__, u_errorName(status)); goto cleanupAndReturn; } - - percentFormatter = NumberFormat::createPercentInstance(Locale::getFrench(),status); + + percentFormatter.adoptInstead(NumberFormat::createPercentInstance(Locale::getFrench(),status)); if(U_FAILURE(status)) { - error("Error on NumberFormat::createPercentInstance()"); + IntlTest::gTest->errln("%s:%d Error %s on NumberFormat::createPercentInstance().", + __FILE__, __LINE__, u_errorName(status)); goto cleanupAndReturn; } - - for(iteration = 0;!getError() && iterationgetErrors() && iterationformat(kNumberFormatTestData[whichLine].number, output); - + if(0 != output.compare(kNumberFormatTestData[whichLine].string)) { - error("format().. expected " + kNumberFormatTestData[whichLine].string + IntlTest::gTest->errln("format().. expected " + kNumberFormatTestData[whichLine].string + " got " + output); goto cleanupAndReturn; } - + // Now check percent. output.remove(); whichLine = (iteration + fOffset)%kPercentFormatTestDataLength; - + percentFormatter->format(kPercentFormatTestData[whichLine].number, output); if(0 != output.compare(kPercentFormatTestData[whichLine].string)) { - error("percent format().. \n" + + IntlTest::gTest->errln("percent format().. \n" + showDifference(kPercentFormatTestData[whichLine].string,output)); goto cleanupAndReturn; } - - // Test message error + + // Test message error const int kNumberOfMessageTests = 3; UErrorCode statusToCheck; UnicodeString patternToCheck; Locale messageLocale; Locale countryToCheck; double currencyToCheck; - + UnicodeString expected; - + // load the cases. switch((iteration+fOffset) % kNumberOfMessageTests) { @@ -1040,11 +757,14 @@ public: 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 + 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@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 DM."; + expected= CharsToUnicodeString( + "1:A customer in Burkina Faso is receiving a #8 error - U_INDEX_OUTOFBOUNDS_ERROR. " + "Their telephone call is costing 2,32\\u00A0DM."); break; case 2: statusToCheck= U_MEMORY_ALLOCATION_ERROR; @@ -1057,10 +777,10 @@ public: 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."); + " \\u00f6S\\u00A040.193,12 on memory."); break; } - + UnicodeString result; UErrorCode status = U_ZERO_ERROR; formatErrorMessage(status,patternToCheck,messageLocale,statusToCheck, @@ -1068,26 +788,30 @@ public: if(U_FAILURE(status)) { UnicodeString tmp(u_errorName(status)); - error("Failure on message format, pattern=" + patternToCheck + + IntlTest::gTest->errln("Failure on message format, pattern=" + patternToCheck + ", error = " + tmp); goto cleanupAndReturn; } - + if(result != expected) { - error("PatternFormat: \n" + showDifference(expected,result)); + IntlTest::gTest->errln("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 */ - + + + cleanupAndReturn: - delete formatter; - delete percentFormatter; - - // while (fNum == 4) {SimpleThread::sleep(10000);} // Force a failure by preventing thread from finishing fTraceInfo = 2; } - + private: int32_t fOffset; // where we are testing from. }; @@ -1096,66 +820,35 @@ private: void MultithreadTest::TestThreadedIntl() { - int i; UnicodeString theErr; - UBool haveDisplayedInfo[kFormatThreadThreads]; - static const int32_t PATIENCE_SECONDS = 45; + + UErrorCode threadSafeErr = U_ZERO_ERROR; + + ThreadSafeFormatSharedData sharedData(threadSafeErr); + assertSuccess("initializing ThreadSafeFormat", threadSafeErr, TRUE); // // Create and start the test threads // logln("Spawning: %d threads * %d iterations each.", kFormatThreadThreads, kFormatThreadIterations); - FormatThreadTest *tests = new FormatThreadTest[kFormatThreadThreads]; - for(int32_t j = 0; j < kFormatThreadThreads; j++) { + FormatThreadTest tests[kFormatThreadThreads]; + 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); - SimpleThread::errorFunc(); - goto cleanupAndReturn; + errln("%s:%d System Error %d starting thread number %d.", + __FILE__, __LINE__, threadStatus, j); + return; } - haveDisplayedInfo[j] = FALSE; } - // Spin, waiting for the test threads to finish. - UBool stillRunning; - UDate startTime, endTime; - startTime = Calendar::getNow(); - do { - /* Spin until the test threads complete. */ - stillRunning = FALSE; - endTime = Calendar::getNow(); - if (((int32_t)(endTime - startTime)/U_MILLIS_PER_SECOND) > PATIENCE_SECONDS) { - errln("Patience exceeded. Test is taking too long."); - return; - } - /* - The following sleep must be here because the *BSD operating systems - have a brain dead thread scheduler. They starve the child threads from - CPU time. - */ - SimpleThread::sleep(1); // yield - for(i=0;i= 3 && + (s[0] == 0xfb2 || s[0] == 0xfb3) && + s[1] == 0x334 && + (s[2] == 0xf73 || s[2] == 0xf75 || s[2] == 0xf81)) { + return TRUE; + } + return FALSE; +} + +static UCollationResult +normalizeResult(int32_t result) { + return result<0 ? UCOL_LESS : result==0 ? UCOL_EQUAL : UCOL_GREATER; +} + +class CollatorThreadTest : public SimpleThread { -private: - const UCollator *coll; +private: + const Collator *coll; const Line *lines; int32_t noLines; + UBool isAtLeastUCA62; public: - CollatorThreadTest() : ThreadWithStatus(), + CollatorThreadTest() : SimpleThread(), coll(NULL), lines(NULL), - noLines(0) + noLines(0), + isAtLeastUCA62(TRUE) { }; - void setCollator(UCollator *c, Line *l, int32_t nl) + void setCollator(Collator *c, Line *l, int32_t nl, UBool atLeastUCA62) { coll = c; lines = l; noLines = nl; + isAtLeastUCA62 = atLeastUCA62; } virtual void run() { - //sleep(10000); - int32_t line = 0; - uint8_t sk1[1024], sk2[1024]; uint8_t *oldSk = NULL, *newSk = sk1; - int32_t resLen = 0, oldLen = 0; + int32_t oldLen = 0; + int32_t prev = 0; int32_t i = 0; - + for(i = 0; i < noLines; i++) { - resLen = ucol_getSortKey(coll, lines[i].buff, lines[i].buflen, newSk, 1024); - - int32_t res = 0, cmpres = 0, cmpres2 = 0; - + if(lines[i].buflen == 0) { continue; } + + if(skipLineBecauseOfBug(lines[i].buff, lines[i].buflen)) { continue; } + + int32_t resLen = coll->getSortKey(lines[i].buff, lines[i].buflen, newSk, 1024); + if(oldSk != NULL) { - res = strcmp((char *)oldSk, (char *)newSk); - cmpres = ucol_strcoll(coll, lines[i-1].buff, lines[i-1].buflen, lines[i].buff, lines[i].buflen); - cmpres2 = ucol_strcoll(coll, lines[i].buff, lines[i].buflen, lines[i-1].buff, lines[i-1].buflen); - //cmpres = res; - //cmpres2 = -cmpres; - + 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) { - error("Compare result not symmetrical on line "+ line); + IntlTest::gTest->errln(UnicodeString("Compare result not symmetrical on line ") + (i + 1)); break; } - - if(((res&0x80000000) != (cmpres&0x80000000)) || (res == 0 && cmpres != 0) || (res != 0 && cmpres == 0)) { - error(UnicodeString("Difference between ucol_strcoll and sortkey compare on line ")+ UnicodeString(line)); + + if(cmpres != normalizeResult(skres)) { + IntlTest::gTest->errln(UnicodeString("Difference between coll->compare and sortkey compare on line ") + (i + 1)); break; } - + + int32_t res = cmpres; + if(res == 0 && !isAtLeastUCA62) { + // Up to UCA 6.1, the collation test files use a custom tie-breaker, + // comparing the raw input strings. + res = u_strcmpCodePointOrder(lines[prev].buff, lines[i].buff); + // Starting with UCA 6.2, the collation test files use the standard UCA tie-breaker, + // comparing the NFD versions of the input strings, + // which we do via setting strength=identical. + } if(res > 0) { - error(UnicodeString("Line %i is not greater or equal than previous line ")+ UnicodeString(i)); + IntlTest::gTest->errln(UnicodeString("Line is not greater or equal than previous line, for line ") + (i + 1)); 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)); - break; - } } } - + oldSk = newSk; oldLen = resLen; - + (void)oldLen; // Suppress set but not used warning. + prev = i; + newSk = (newSk == sk1)?sk2:sk1; } } - }; void MultithreadTest::TestCollators() @@ -1298,134 +1010,84 @@ void MultithreadTest::TestCollators() if (testFile == 0) { *(buffer+bufLen) = 0; - errln("ERROR: could not open any of the conformance test files, tried opening base %s", buffer); - return; + 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://dev.icu-project.org/cgi-bin/viewcvs.cgi/unicodetools/com/ibm/text/data/"); + "http://source.icu-project.org/repos/icu/tools/trunk/unicodetools/com/ibm/text/data/"); } } } - Line *lines = new Line[200000]; - memset(lines, 0, sizeof(Line)*200000); + LocalArray lines(new Line[200000]); + memset(lines.getAlias(), 0, sizeof(Line)*200000); int32_t lineNum = 0; UChar bufferU[1024]; - int32_t buflen = 0; uint32_t first = 0; - uint32_t offset = 0; while (fgets(buffer, 1024, testFile) != NULL) { - offset = 0; - if(*buffer == 0 || strlen(buffer) < 3 || buffer[0] == '#') { - continue; + 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; } - offset = u_parseString(buffer, bufferU, 1024, &first, &status); - buflen = offset; - bufferU[offset++] = 0; - lines[lineNum].buflen = buflen; - //lines[lineNum].buff = new UChar[buflen+1]; - u_memcpy(lines[lineNum].buff, bufferU, buflen); lineNum++; } fclose(testFile); if(U_FAILURE(status)) { - errln("Couldn't read the test file!"); + dataerrln("Couldn't read the test file!"); return; } - UCollator *coll = ucol_open("root", &status); + 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)) { - errln("Couldn't open UCA collator"); + errcheckln(status, "Couldn't open UCA collator"); return; } - ucol_setAttribute(coll, UCOL_NORMALIZATION_MODE, UCOL_ON, &status); - ucol_setAttribute(coll, UCOL_CASE_FIRST, UCOL_OFF, &status); - ucol_setAttribute(coll, UCOL_CASE_LEVEL, UCOL_OFF, &status); - ucol_setAttribute(coll, UCOL_STRENGTH, UCOL_TERTIARY, &status); - ucol_setAttribute(coll, UCOL_ALTERNATE_HANDLING, UCOL_NON_IGNORABLE, &status); + 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 noSpawned = 0; int32_t spawnResult = 0; - CollatorThreadTest *tests; - tests = new CollatorThreadTest[kCollatorThreadThreads]; + LocalArray tests(new CollatorThreadTest[kCollatorThreadThreads]); logln(UnicodeString("Spawning: ") + kCollatorThreadThreads + " threads * " + kFormatThreadIterations + " iterations each."); int32_t j = 0; for(j = 0; j < kCollatorThreadThreads; j++) { //logln("Setting collator %i", j); - tests[j].setCollator(coll, lines, lineNum); + 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) { - infoln("THREAD INFO: Couldn't spawn more than %i threads", noSpawned); - break; + errln("%s:%d THREAD INFO: thread %d failed to start with status %d", __FILE__, __LINE__, j, spawnResult); + return; } - noSpawned++; } logln("Spawned all"); - if (noSpawned == 0) { - errln("No threads could be spawned."); - return; - } - - for(int32_t patience = kCollatorThreadPatience;patience > 0; patience --) - { - logln("Waiting..."); - - int32_t i; - int32_t terrs = 0; - int32_t completed =0; - for(i=0;ierrln("%s:%d Original string is corrupt.", __FILE__, __LINE__); break; } - UnicodeString s1 = *fSharedString; + UnicodeString s1 = *gSharedString; s1 += "cat this"; UnicodeString s2(s1); - UnicodeString s3 = *fSharedString; + UnicodeString s3 = *gSharedString; s2 = s3; s3.truncate(12); s2.truncate(0); } - // while (fNum == 4) {SimpleThread::sleep(10000);} // Force a failure by preventing thread from finishing fTraceInfo = 2; } - + }; +const UnicodeString *StringThreadTest2::gSharedString = NULL; + // ** The actual test function. + void MultithreadTest::TestString() { - int patience; - int terrs = 0; int j; + StringThreadTest2::gSharedString = new UnicodeString("This is the original test string."); + StringThreadTest2 tests[kStringThreadThreads]; - UnicodeString *testString = new UnicodeString("This is the original test string."); - - StringThreadTest2 *tests[kStringThreadThreads]; - for(j = 0; j < kStringThreadThreads; j++) { - tests[j] = new StringThreadTest2(testString, j); - } - logln(UnicodeString("Spawning: ") + kStringThreadThreads + " threads * " + kStringThreadIterations + " iterations each."); for(j = 0; j < kStringThreadThreads; j++) { - int32_t threadStatus = tests[j]->start(); + int32_t threadStatus = tests[j].start(); if (threadStatus != 0) { - errln("System Error %d starting thread number %d.", threadStatus, j); - SimpleThread::errorFunc(); - goto cleanupAndReturn; + errln("%s:%d System Error %d starting thread number %d.", __FILE__, __LINE__, threadStatus, j); } } - for(patience = kStringThreadPatience;patience > 0; patience --) + // Force a failure, to verify test is functioning and can report errors. + // const_cast(StringThreadTest2::gSharedString)->setCharAt(5, 'x'); + + for(j=0; jtransliterate(greekString); + if (greekString[0] != 0x64) // 'd'. The whole transliterated string is "diaphoretikous" (accented u). { - logln("Waiting..."); + IntlTest::gTest->errln("%s:%d Transliteration failed.", __FILE__, __LINE__); + } +} +#endif - int32_t i; - terrs = 0; - int32_t completed =0; - for(i=0;iisRunning() == FALSE) - { - completed++; - - logln(UnicodeString("Test #") + i + " is complete.. "); - - UnicodeString theErr; - if(tests[i]->getError(theErr)) - { - terrs++; - errln(UnicodeString("#") + i + ": " + theErr); - } - // print out the error, too, if any. - } +void MultithreadTest::TestAnyTranslit() { +#if !UCONFIG_NO_TRANSLITERATION + UErrorCode status = U_ZERO_ERROR; + LocalPointer tx(Transliterator::createInstance("Any-Latin", UTRANS_FORWARD, status)); + if (U_FAILURE(status)) { + dataerrln("File %s, Line %d: Error, status = %s", __FILE__, __LINE__, u_errorName(status)); + return; + } + gSharedTranslit = tx.getAlias(); + TxThread threads[4]; + int32_t i; + for (i=0; ierrln("File %s, Line %d: Error, gStartedThreads = %d, gFinishedThreads = %d", + __FILE__, __LINE__, gStartedThreads, gFinishedThreads); } - - if(completed == kStringThreadThreads) - { - logln("Done!"); - if(terrs) { - errln("There were errors."); - } - break; + umtx_condWait(&gCTConditionVar, &gCTMutex); + } + + gFinishedThreads += gConditionTestOne; + fFinished = true; + umtx_condBroadcast(&gCTConditionVar); + + while (gFinishedThreads < NUMTHREADS) { + umtx_condWait(&gCTConditionVar, &gCTMutex); + } + umtx_unlock(&gCTMutex); +} + +void MultithreadTest::TestConditionVariables() { + gStartedThreads = 0; + gFinishedThreads = 0; + int i; + + umtx_lock(&gCTMutex); + CondThread *threads[NUMTHREADS]; + for (i=0; istart(); + } + + while (gStartedThreads < NUMTHREADS) { + umtx_condWait(&gCTConditionVar, &gCTMutex); + } + + while (gFinishedThreads < NUMTHREADS) { + umtx_condWait(&gCTConditionVar, &gCTMutex); + } + + umtx_unlock(&gCTMutex); + + for (i=0; ifFinished) { + errln("File %s, Line %d: Error, threads[%d]->fFinished == false", __FILE__, __LINE__, i); } + } - SimpleThread::sleep(900); + for (i=0; ijoin(); + delete threads[i]; } +} - if (patience <= 0) { - errln("patience exceeded. "); - // while (TRUE) {SimpleThread::sleep(10000);} // TODO: for debugging. Sleep forever on failure. - terrs++; + +// +// 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 (terrs > 0) { - SimpleThread::errorFunc(); + 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; } -cleanupAndReturn: - if (terrs == 0) { - /* - Don't clean up if there are errors. This prevents crashes if the - threads are still running and using this data. This will only happen - if there is an error with the test, ICU, or the machine is too slow. - It's better to leak than crash. - */ - for(j = 0; j < kStringThreadThreads; j++) { - delete tests[j]; + umtx_lock(&gCTMutex); + bool 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) { + umtx_condWait(&gCTConditionVar, &gCTMutex); + } + } + umtx_unlock(&gCTMutex); + + 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. + umtx_lock(&gCTMutex); + if (!firstObject) { + gObjectsCreated += 1; + } + umtx_condBroadcast(&gCTConditionVar); + umtx_unlock(&gCTMutex); + + return result; +} + +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)); + if (uprv_strcmp(locale.getLanguage(), origItem->value)) { + IntlTest::gTest->errln( + "%s:%d Expected %s, got %s", __FILE__, __LINE__, + 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); + if (item != origItem) { + IntlTest::gTest->errln( + "%s:%d Expected to get the same pointer", + __FILE__, + __LINE__); + } + if (item != NULL) { + item->removeRef(); } - delete testString; } + origItem->removeRef(); +} + +void UnifiedCacheThread::run() { + // Run the exercise with 2 different locales so that we can exercise + // eviction more. If each thread exerices 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("unused values", 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( + "\\u0E42\\u0E14\\u0E22\\u0E1E\\u0E37\\u0E49\\u0E19\\u0E10\\u0E32\\u0E19\\u0E41\\u0E25\\u0E49\\u0E27,"); + input = input.unescape(); + gTranslitInput = &input; + + gSharedTransliterator = Transliterator::createInstance( + UNICODE_STRING_SIMPLE("Any-Latin; Lower; NFD; [:Diacritic:]Remove; NFC; Latin-ASCII;"), UTRANS_FORWARD, status); + if (!gSharedTransliterator) { + return; + } + TSMTHREAD_ASSERT_SUCCESS(status); + + UnicodeString expected(*gTranslitInput); + gSharedTransliterator->transliterate(expected); + gTranslitExpected = &expected; + + BreakTranslitThread threads[4]; + for (int i=0; i