| Index: source/test/intltest/tsmthred.cpp
|
| diff --git a/source/test/intltest/tsmthred.cpp b/source/test/intltest/tsmthred.cpp
|
| index e253235fc69f8456500cc291f610449851fbd675..f63990118a1d5451c53f31f16e61146749ff085f 100644
|
| --- a/source/test/intltest/tsmthred.cpp
|
| +++ b/source/test/intltest/tsmthred.cpp
|
| @@ -1,6 +1,6 @@
|
| /********************************************************************
|
| * COPYRIGHT:
|
| - * Copyright (c) 1999-2013, International Business Machines Corporation and
|
| + * Copyright (c) 1999-2014, International Business Machines Corporation and
|
| * others. All Rights Reserved.
|
| ********************************************************************/
|
|
|
| @@ -27,7 +27,10 @@
|
| #include "intltest.h"
|
| #include "tsmthred.h"
|
| #include "unicode/ushape.h"
|
| -
|
| +#include "unicode/translit.h"
|
| +#include "sharedobject.h"
|
| +#include "unifiedcache.h"
|
| +#include "uassert.h"
|
|
|
| #if U_PLATFORM_USES_ONLY_WIN32_API
|
| /* Prefer native Windows APIs even if POSIX is implemented (i.e., on Cygwin). */
|
| @@ -38,8 +41,6 @@
|
| # undef POSIX
|
| #endif
|
|
|
| -
|
| -#define LENGTHOF(array) (int32_t)(sizeof(array)/sizeof((array)[0]))
|
| /* Needed by z/OS to get usleep */
|
| #if U_PLATFORM == U_PF_OS390
|
| #define __DOT1 1
|
| @@ -188,14 +189,32 @@ void MultithreadTest::runIndexedTest( int32_t index, UBool exec,
|
| }
|
| break;
|
|
|
| - case 5:
|
| + 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;
|
| default:
|
| name = "";
|
| break; //needed to end loop
|
| @@ -257,7 +276,7 @@ private:
|
| IntlTest inteltst = IntlTest();
|
|
|
| status = U_ZERO_ERROR;
|
| - length = u_shapeArabic(src, -1, dst, LENGTHOF(dst),
|
| + length = u_shapeArabic(src, -1, dst, UPRV_LENGTHOF(dst),
|
| U_SHAPE_LETTERS_SHAPE|U_SHAPE_SEEN_TWOCELL_NEAR, &status);
|
| if(U_FAILURE(status)) {
|
| inteltst.errln("Fail: status %s\n", u_errorName(status));
|
| @@ -265,7 +284,7 @@ private:
|
| } else if(length!=2) {
|
| inteltst.errln("Fail: len %d expected 3\n", length);
|
| return FALSE;
|
| - } else if(u_strncmp(dst,dst_old,LENGTHOF(dst))) {
|
| + } else if(u_strncmp(dst,dst_old,UPRV_LENGTHOF(dst))) {
|
| inteltst.errln("Fail: got U+%04X U+%04X expected U+%04X U+%04X\n",
|
| dst[0],dst[1],dst_old[0],dst_old[1]);
|
| return FALSE;
|
| @@ -274,7 +293,7 @@ private:
|
|
|
| //"Trying new tail
|
| status = U_ZERO_ERROR;
|
| - length = u_shapeArabic(src, -1, dst, LENGTHOF(dst),
|
| + length = u_shapeArabic(src, -1, dst, UPRV_LENGTHOF(dst),
|
| U_SHAPE_LETTERS_SHAPE|U_SHAPE_SEEN_TWOCELL_NEAR|U_SHAPE_TAIL_NEW_UNICODE, &status);
|
| if(U_FAILURE(status)) {
|
| inteltst.errln("Fail: status %s\n", u_errorName(status));
|
| @@ -282,7 +301,7 @@ private:
|
| } else if(length!=2) {
|
| inteltst.errln("Fail: len %d expected 3\n", length);
|
| return FALSE;
|
| - } else if(u_strncmp(dst,dst_new,LENGTHOF(dst))) {
|
| + } else if(u_strncmp(dst,dst_new,UPRV_LENGTHOF(dst))) {
|
| inteltst.errln("Fail: got U+%04X U+%04X expected U+%04X U+%04X\n",
|
| dst[0],dst[1],dst_new[0],dst_new[1]);
|
| return FALSE;
|
| @@ -629,7 +648,6 @@ UnicodeString showDifference(const UnicodeString& expected, const UnicodeString&
|
|
|
| const int kFormatThreadIterations = 100; // # of iterations per thread
|
| const int kFormatThreadThreads = 10; // # of threads to spawn
|
| -const int kFormatThreadPatience = 60; // time in seconds to wait for all threads
|
|
|
| #if !UCONFIG_NO_FORMATTING
|
|
|
| @@ -961,7 +979,7 @@ public:
|
| countryToCheck= Locale("","BF");
|
| currencyToCheck= 2.32;
|
| expected= CharsToUnicodeString(
|
| - "1:A customer in Burkina Faso is receiving a #8 error - U_INDEX_OUTOFBOUNDS_ERROR. Their telephone call is costing 2,32\\u00A0DEM.");
|
| + "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;
|
| @@ -1521,4 +1539,266 @@ cleanupAndReturn:
|
| }
|
| }
|
|
|
| +
|
| +// Test for ticket #10673, race in cache code in AnyTransliterator.
|
| +// It's difficult to make the original unsafe code actually fail, but
|
| +// this test will fairly reliably take the code path for races in
|
| +// populating the cache.
|
| +
|
| +#if !UCONFIG_NO_TRANSLITERATION
|
| +class TxThread: public SimpleThread {
|
| + private:
|
| + Transliterator *fSharedTranslit;
|
| + public:
|
| + UBool fSuccess;
|
| + TxThread(Transliterator *tx) : fSharedTranslit(tx), fSuccess(FALSE) {};
|
| + ~TxThread();
|
| + void run();
|
| +};
|
| +
|
| +TxThread::~TxThread() {}
|
| +void TxThread::run() {
|
| + UnicodeString greekString("\\u03B4\\u03B9\\u03B1\\u03C6\\u03BF\\u03C1\\u03B5\\u03C4\\u03B9\\u03BA\\u03BF\\u03CD\\u03C2");
|
| + greekString = greekString.unescape();
|
| + fSharedTranslit->transliterate(greekString);
|
| + fSuccess = greekString[0] == 0x64; // 'd'. The whole transliterated string is "diaphoretikous" (accented u).
|
| +}
|
| +#endif
|
| +
|
| +
|
| +void MultithreadTest::TestAnyTranslit() {
|
| +#if !UCONFIG_NO_TRANSLITERATION
|
| + UErrorCode status = U_ZERO_ERROR;
|
| + LocalPointer<Transliterator> 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;
|
| + }
|
| + TxThread * threads[4];
|
| + int32_t i;
|
| + for (i=0; i<4; i++) {
|
| + threads[i] = new TxThread(tx.getAlias());
|
| + }
|
| + for (i=0; i<4; i++) {
|
| + threads[i]->start();
|
| + }
|
| + int32_t patience = 100;
|
| + UBool success;
|
| + UBool someThreadRunning;
|
| + do {
|
| + someThreadRunning = FALSE;
|
| + success = TRUE;
|
| + for (i=0; i<4; i++) {
|
| + if (threads[i]->isRunning()) {
|
| + someThreadRunning = TRUE;
|
| + SimpleThread::sleep(10);
|
| + break;
|
| + } else {
|
| + if (threads[i]->fSuccess == FALSE) {
|
| + success = FALSE;
|
| + }
|
| + }
|
| + }
|
| + } while (someThreadRunning && --patience > 0);
|
| +
|
| + if (patience <= 0) {
|
| + errln("File %s, Line %d: Error, one or more threads did not complete.", __FILE__, __LINE__);
|
| + }
|
| + if (success == FALSE) {
|
| + errln("File %s, Line %d: Error, transliteration result incorrect.", __FILE__, __LINE__);
|
| + }
|
| +
|
| + for (i=0; i<4; i++) {
|
| + delete threads[i];
|
| + }
|
| +#endif // !UCONFIG_NO_TRANSLITERATION
|
| +}
|
| +
|
| +
|
| +// Condition Variables Test
|
| +// Create a swarm of threads.
|
| +// Using a mutex and a condition variables each thread
|
| +// Increments a global count of started threads.
|
| +// Broadcasts that it has started.
|
| +// Waits on the condition that all threads have started.
|
| +// Increments a global count of finished threads.
|
| +// Waits on the condition that all threads have finished.
|
| +// Exits.
|
| +
|
| +class CondThread: public SimpleThread {
|
| + public:
|
| + CondThread() :fFinished(false) {};
|
| + ~CondThread() {};
|
| + void run();
|
| + bool fFinished;
|
| +};
|
| +
|
| +static UMutex gCTMutex = U_MUTEX_INITIALIZER;
|
| +static UConditionVar gCTConditionVar = U_CONDITION_INITIALIZER;
|
| +int gConditionTestOne = 1; // Value one. Non-const, extern linkage to inhibit
|
| + // compiler assuming a known value.
|
| +int gStartedThreads;
|
| +int gFinishedThreads;
|
| +static const int NUMTHREADS = 10;
|
| +
|
| +static MultithreadTest *gThisTest = NULL; // Make test frame work functions available to
|
| + // non-member functions.
|
| +
|
| +// Worker thread function.
|
| +void CondThread::run() {
|
| + umtx_lock(&gCTMutex);
|
| + gStartedThreads += gConditionTestOne;
|
| + umtx_condBroadcast(&gCTConditionVar);
|
| +
|
| + while (gStartedThreads < NUMTHREADS) {
|
| + if (gFinishedThreads != 0) {
|
| + gThisTest->errln("File %s, Line %d: Error, gStartedThreads = %d, gFinishedThreads = %d",
|
| + __FILE__, __LINE__, gStartedThreads, gFinishedThreads);
|
| + }
|
| + umtx_condWait(&gCTConditionVar, &gCTMutex);
|
| + }
|
| +
|
| + gFinishedThreads += gConditionTestOne;
|
| + fFinished = true;
|
| + umtx_condBroadcast(&gCTConditionVar);
|
| +
|
| + while (gFinishedThreads < NUMTHREADS) {
|
| + umtx_condWait(&gCTConditionVar, &gCTMutex);
|
| + }
|
| + umtx_unlock(&gCTMutex);
|
| +}
|
| +
|
| +void MultithreadTest::TestConditionVariables() {
|
| + gThisTest = this;
|
| + gStartedThreads = 0;
|
| + gFinishedThreads = 0;
|
| + int i;
|
| +
|
| + umtx_lock(&gCTMutex);
|
| + CondThread *threads[NUMTHREADS];
|
| + for (i=0; i<NUMTHREADS; ++i) {
|
| + threads[i] = new CondThread;
|
| + threads[i]->start();
|
| + }
|
| +
|
| + while (gStartedThreads < NUMTHREADS) {
|
| + umtx_condWait(&gCTConditionVar, &gCTMutex);
|
| + }
|
| +
|
| + while (gFinishedThreads < NUMTHREADS) {
|
| + umtx_condWait(&gCTConditionVar, &gCTMutex);
|
| + }
|
| +
|
| + umtx_unlock(&gCTMutex);
|
| +
|
| + for (i=0; i<NUMTHREADS; ++i) {
|
| + if (!threads[i]->fFinished) {
|
| + errln("File %s, Line %d: Error, threads[%d]->fFinished == false", __FILE__, __LINE__, i);
|
| + }
|
| + delete threads[i];
|
| + }
|
| +}
|
| +
|
| +static const char *gCacheLocales[] = {"en_US", "en_GB", "fr_FR", "fr"};
|
| +static int32_t gObjectsCreated = 0;
|
| +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<UCTMultiThreadItem>::createObject(
|
| + const void * /*unused*/, UErrorCode & /* status */) const {
|
| + // Since multiple threads are hitting the cache for the first time,
|
| + // no objects should be created yet.
|
| + umtx_lock(&gCTMutex);
|
| + if (gObjectsCreated != 0) {
|
| + gThisTest->errln("Expected no objects to be created yet.");
|
| + }
|
| + umtx_unlock(&gCTMutex);
|
| +
|
| + // Big, expensive object that takes 1 second to create.
|
| + SimpleThread::sleep(1000);
|
| +
|
| + // Log that we created an object.
|
| + umtx_lock(&gCTMutex);
|
| + ++gObjectsCreated;
|
| + umtx_unlock(&gCTMutex);
|
| + UCTMultiThreadItem *result = new UCTMultiThreadItem(fLoc.getName());
|
| + result->addRef();
|
| + return result;
|
| +}
|
| +
|
| +U_NAMESPACE_END
|
| +
|
| +class UnifiedCacheThread: public SimpleThread {
|
| + public:
|
| + UnifiedCacheThread(const char *loc) : fLoc(loc) {};
|
| + ~UnifiedCacheThread() {};
|
| + void run();
|
| + const char *fLoc;
|
| +};
|
| +
|
| +void UnifiedCacheThread::run() {
|
| + UErrorCode status = U_ZERO_ERROR;
|
| + const UnifiedCache *cache = UnifiedCache::getInstance(status);
|
| + U_ASSERT(status == U_ZERO_ERROR);
|
| + const UCTMultiThreadItem *item = NULL;
|
| + cache->get(LocaleCacheKey<UCTMultiThreadItem>(fLoc), item, status);
|
| + U_ASSERT(item != NULL);
|
| + if (uprv_strcmp(fLoc, item->value)) {
|
| + gThisTest->errln("Expected %s, got %s", fLoc, item->value);
|
| + }
|
| + item->removeRef();
|
| +
|
| + // Mark this thread as finished
|
| + umtx_lock(&gCTMutex);
|
| + ++gFinishedThreads;
|
| + umtx_condBroadcast(&gCTConditionVar);
|
| + umtx_unlock(&gCTMutex);
|
| +}
|
| +
|
| +void MultithreadTest::TestUnifiedCache() {
|
| + UErrorCode status = U_ZERO_ERROR;
|
| + const UnifiedCache *cache = UnifiedCache::getInstance(status);
|
| + U_ASSERT(cache != NULL);
|
| + cache->flush();
|
| + gThisTest = this;
|
| + gFinishedThreads = 0;
|
| + gObjectsCreated = 0;
|
| +
|
| + UnifiedCacheThread *threads[CACHE_LOAD][UPRV_LENGTHOF(gCacheLocales)];
|
| + for (int32_t i=0; i<CACHE_LOAD; ++i) {
|
| + for (int32_t j=0; j<UPRV_LENGTHOF(gCacheLocales); ++j) {
|
| + threads[i][j] = new UnifiedCacheThread(gCacheLocales[j]);
|
| + threads[i][j]->start();
|
| + }
|
| + }
|
| + // Wait on all the threads to complete verify that LENGTHOF(gCacheLocales)
|
| + // objects were created.
|
| + umtx_lock(&gCTMutex);
|
| + while (gFinishedThreads < CACHE_LOAD*UPRV_LENGTHOF(gCacheLocales)) {
|
| + umtx_condWait(&gCTConditionVar, &gCTMutex);
|
| + }
|
| + assertEquals("Objects created", UPRV_LENGTHOF(gCacheLocales), gObjectsCreated);
|
| + umtx_unlock(&gCTMutex);
|
| +
|
| + // clean up threads
|
| + for (int32_t i=0; i<CACHE_LOAD; ++i) {
|
| + for (int32_t j=0; j<UPRV_LENGTHOF(gCacheLocales); ++j) {
|
| + delete threads[i][j];
|
| + }
|
| + }
|
| +}
|
| +
|
| #endif // ICU_USE_THREADS
|
|
|