ICU-11654 Remove test/threadtest. Obsolete, abandoned code.
X-SVN-Rev: 37392
This commit is contained in:
parent
e1c9c699b9
commit
673942389f
1
.gitignore
vendored
1
.gitignore
vendored
@ -614,7 +614,6 @@ icu4c/source/test/testmap/Release
|
||||
icu4c/source/test/testmap/testmap
|
||||
icu4c/source/test/testmap/testmap.plg
|
||||
icu4c/source/test/thaitest/Makefile
|
||||
icu4c/source/test/threadtest/Makefile
|
||||
icu4c/source/test/tmp
|
||||
icu4c/source/tools/Makefile
|
||||
icu4c/source/tools/ctestfw/*.ao
|
||||
|
@ -1,90 +0,0 @@
|
||||
## Makefile.in for ICU - test/threadtest
|
||||
## Copyright (c) 2001-2011, International Business Machines Corporation and
|
||||
## others. All Rights Reserved.
|
||||
|
||||
## Source directory information
|
||||
srcdir = @srcdir@
|
||||
top_srcdir = @top_srcdir@
|
||||
|
||||
top_builddir = ../..
|
||||
|
||||
include $(top_builddir)/icudefs.mk
|
||||
|
||||
## Platform-specific setup
|
||||
include @platform_make_fragment@
|
||||
|
||||
## Build directory information
|
||||
subdir = test/threadtest
|
||||
|
||||
## Extra files to remove for 'make clean'
|
||||
CLEANFILES = *~ $(DEPS)
|
||||
|
||||
## Target information
|
||||
TARGET = threadtest
|
||||
|
||||
DEFS = @DEFS@
|
||||
CPPFLAGS = @CPPFLAGS@ -I$(top_srcdir)/common -I$(top_srcdir)/i18n
|
||||
CFLAGS = @CFLAGS@
|
||||
CXXFLAGS = @CXXFLAGS@
|
||||
ENABLE_RPATH = @ENABLE_RPATH@
|
||||
ifeq ($(ENABLE_RPATH),YES)
|
||||
RPATHLDFLAGS = $(LD_RPATH)$(LD_RPATH_PRE)$(libdir)
|
||||
endif
|
||||
LDFLAGS = @LDFLAGS@ $(RPATHLDFLAGS)
|
||||
LIBS = $(LIBICUI18N) $(LIBICUUC) @LIBS@ @LIB_M@
|
||||
|
||||
OBJECTS = threadtest.o stringtest.o converttest.o
|
||||
|
||||
DEPS = $(OBJECTS:.o=.d)
|
||||
|
||||
## List of phony targets
|
||||
.PHONY : all all-local install install-local clean clean-local \
|
||||
distclean distclean-local dist dist-local check check-local
|
||||
|
||||
## Clear suffix list
|
||||
.SUFFIXES :
|
||||
|
||||
## List of standard targets
|
||||
all: all-local
|
||||
install: install-local
|
||||
clean: clean-local
|
||||
distclean : distclean-local
|
||||
dist: dist-local
|
||||
check: all check-local
|
||||
|
||||
all-local: $(TARGET)
|
||||
|
||||
install-local:
|
||||
|
||||
dist-local:
|
||||
|
||||
clean-local:
|
||||
test -z "$(CLEANFILES)" || $(RMV) $(CLEANFILES)
|
||||
$(RMV) $(OBJECTS) $(TARGET)
|
||||
|
||||
distclean-local: clean-local
|
||||
$(RMV) Makefile
|
||||
|
||||
check-local: all-local
|
||||
|
||||
Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
|
||||
cd $(top_builddir) \
|
||||
&& CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= $(SHELL) ./config.status
|
||||
|
||||
$(TARGET) : $(OBJECTS)
|
||||
$(LINK.cc) -o $@ $^ $(LIBS)
|
||||
$(POST_BUILD_STEP)
|
||||
|
||||
invoke:
|
||||
ICU_DATA=$${ICU_DATA:-$(top_builddir)/data/} TZ=PST8PDT $(INVOKE) $(INVOCATION)
|
||||
|
||||
ifeq (,$(MAKECMDGOALS))
|
||||
-include $(DEPS)
|
||||
else
|
||||
ifneq ($(patsubst %clean,,$(MAKECMDGOALS)),)
|
||||
ifneq ($(patsubst %install,,$(MAKECMDGOALS)),)
|
||||
-include $(DEPS)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
@ -1,88 +0,0 @@
|
||||
//
|
||||
//********************************************************************
|
||||
// Copyright (C) 2002-2003, International Business Machines
|
||||
// Corporation and others. All Rights Reserved.
|
||||
//********************************************************************
|
||||
//
|
||||
// File converttest.cpp
|
||||
//
|
||||
|
||||
#include "threadtest.h"
|
||||
#include "unicode/utypes.h"
|
||||
#include "unicode/ucnv.h"
|
||||
#include "unicode/uclean.h"
|
||||
#include "stdio.h"
|
||||
|
||||
U_CAPI UBool U_EXPORT2 ucnv_cleanup();
|
||||
|
||||
class ConvertThreadTest: public AbstractThreadTest {
|
||||
public:
|
||||
ConvertThreadTest();
|
||||
virtual ~ConvertThreadTest();
|
||||
virtual void check();
|
||||
virtual void runOnce();
|
||||
|
||||
private:
|
||||
UConverter *fCnv;
|
||||
};
|
||||
|
||||
|
||||
ConvertThreadTest::ConvertThreadTest() {
|
||||
UErrorCode err = U_ZERO_ERROR;
|
||||
|
||||
fCnv = ucnv_open("gb18030", &err);
|
||||
if (U_FAILURE(err)) {
|
||||
fprintf(stderr, "ConvertTest - could not ucnv_open(\"gb18030\")\n");
|
||||
fCnv = NULL;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
ConvertThreadTest::~ConvertThreadTest() {
|
||||
ucnv_close(fCnv);
|
||||
fCnv = 0;
|
||||
}
|
||||
|
||||
void ConvertThreadTest::runOnce() {
|
||||
UErrorCode err = U_ZERO_ERROR;
|
||||
UConverter *cnv1;
|
||||
UConverter *cnv2;
|
||||
char buf[U_CNV_SAFECLONE_BUFFERSIZE];
|
||||
int32_t bufSize = U_CNV_SAFECLONE_BUFFERSIZE;
|
||||
|
||||
cnv1 = ucnv_open("shift_jis", &err);
|
||||
if (U_FAILURE(err)) {
|
||||
fprintf(stderr, "ucnv_open(\"shift_jis\") failed.\n");
|
||||
}
|
||||
|
||||
cnv2 = ucnv_safeClone(fCnv, // The source converter, common to all threads.
|
||||
buf,
|
||||
&bufSize,
|
||||
&err);
|
||||
if (U_FAILURE(err)) {
|
||||
fprintf(stderr, "ucnv_safeClone() failed.\n");
|
||||
}
|
||||
ucnv_close(cnv1);
|
||||
ucnv_close(cnv2);
|
||||
ucnv_flushCache();
|
||||
}
|
||||
|
||||
void ConvertThreadTest::check() {
|
||||
UErrorCode err = U_ZERO_ERROR;
|
||||
|
||||
if (fCnv) {ucnv_close(fCnv);}
|
||||
//if (ucnv_cleanup () == FALSE) {
|
||||
// fprintf(stderr, "ucnv_cleanup() failed - cache was not empty.\n");
|
||||
//}
|
||||
fCnv = ucnv_open("gb18030", &err);
|
||||
if (U_FAILURE(err)) {
|
||||
fprintf(stderr, "ConvertTest::check() - could not redo ucnv_open(\"gb18030\")\n");
|
||||
fCnv = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AbstractThreadTest *createConvertTest() {
|
||||
return new ConvertThreadTest();
|
||||
}
|
||||
|
@ -1,143 +0,0 @@
|
||||
//
|
||||
//********************************************************************
|
||||
// Copyright (C) 2002, International Business Machines
|
||||
// Corporation and others. All Rights Reserved.
|
||||
//********************************************************************
|
||||
//
|
||||
// File stringtest.cpp
|
||||
//
|
||||
|
||||
#include "threadtest.h"
|
||||
#include "unicode/unistr.h"
|
||||
#include "stdio.h"
|
||||
|
||||
class StringThreadTest: public AbstractThreadTest {
|
||||
public:
|
||||
StringThreadTest();
|
||||
virtual ~StringThreadTest();
|
||||
virtual void check();
|
||||
virtual void runOnce();
|
||||
void makeStringCopies(int recursionCount);
|
||||
|
||||
private:
|
||||
UnicodeString *fCleanStrings;
|
||||
UnicodeString *fSourceStrings;
|
||||
};
|
||||
|
||||
StringThreadTest::StringThreadTest() {
|
||||
// cleanStrings and sourceStrings are separately initialized to the same values.
|
||||
// cleanStrings are never touched after in any remotely unsafe way.
|
||||
// sourceStrings are copied during the test, which will run their buffer's reference
|
||||
// counts all over the place.
|
||||
fCleanStrings = new UnicodeString[5];
|
||||
fSourceStrings = new UnicodeString[5];
|
||||
|
||||
fCleanStrings[0] = "When sorrows come, they come not single spies, but in batallions.";
|
||||
fSourceStrings[0] = "When sorrows come, they come not single spies, but in batallions.";
|
||||
fCleanStrings[1] = "Away, you scullion! You rampallion! You fustilarion! I'll tickle your catastrophe!";
|
||||
fSourceStrings[1] = "Away, you scullion! You rampallion! You fustilarion! I'll tickle your catastrophe!";
|
||||
fCleanStrings[2] = "hot";
|
||||
fSourceStrings[2] = "hot";
|
||||
fCleanStrings[3] = "";
|
||||
fSourceStrings[3] = "";
|
||||
fCleanStrings[4] = "Tomorrow, and tomorrow, and tomorrow,\n"
|
||||
"Creeps in this petty pace from day to day\n"
|
||||
"To the last syllable of recorded time;\n"
|
||||
"And all our yesterdays have lighted fools \n"
|
||||
"The way to dusty death. Out, out brief candle!\n"
|
||||
"Life's but a walking shadow, a poor player\n"
|
||||
"That struts and frets his hour upon the stage\n"
|
||||
"And then is heard no more. It is a tale\n"
|
||||
"Told by and idiot, full of sound and fury,\n"
|
||||
"Signifying nothing.\n";
|
||||
fSourceStrings[4] = "Tomorrow, and tomorrow, and tomorrow,\n"
|
||||
"Creeps in this petty pace from day to day\n"
|
||||
"To the last syllable of recorded time;\n"
|
||||
"And all our yesterdays have lighted fools \n"
|
||||
"The way to dusty death. Out, out brief candle!\n"
|
||||
"Life's but a walking shadow, a poor player\n"
|
||||
"That struts and frets his hour upon the stage\n"
|
||||
"And then is heard no more. It is a tale\n"
|
||||
"Told by and idiot, full of sound and fury,\n"
|
||||
"Signifying nothing.\n";
|
||||
};
|
||||
|
||||
|
||||
StringThreadTest::~StringThreadTest() {
|
||||
delete [] fCleanStrings;
|
||||
delete [] fSourceStrings;
|
||||
}
|
||||
|
||||
|
||||
void StringThreadTest::runOnce() {
|
||||
makeStringCopies(25);
|
||||
}
|
||||
|
||||
void StringThreadTest::makeStringCopies(int recursionCount) {
|
||||
UnicodeString firstGeneration[5];
|
||||
UnicodeString secondGeneration[5];
|
||||
UnicodeString thirdGeneration[5];
|
||||
UnicodeString fourthGeneration[5];
|
||||
|
||||
// Make four generations of copies of the source strings, in slightly variant ways.
|
||||
//
|
||||
int i;
|
||||
for (i=0; i<5; i++) {
|
||||
firstGeneration[i] = fSourceStrings[i];
|
||||
secondGeneration[i] = firstGeneration[i];
|
||||
thirdGeneration[i] = UnicodeString(secondGeneration[i]);
|
||||
// fourthGeneration[i] = UnicodeString("Lay on, MacDuff, And damn'd be him that first cries, \"Hold, enough!\"");
|
||||
fourthGeneration[i] = UnicodeString();
|
||||
fourthGeneration[i] = thirdGeneration[i];
|
||||
}
|
||||
|
||||
|
||||
// Recurse to make even more copies of the strings/
|
||||
//
|
||||
if (recursionCount > 0) {
|
||||
makeStringCopies(recursionCount-1);
|
||||
}
|
||||
|
||||
|
||||
// Verify that all four generations are equal.
|
||||
for (i=0; i<5; i++) {
|
||||
if (firstGeneration[i] != fSourceStrings[i] ||
|
||||
firstGeneration[i] != secondGeneration[i] ||
|
||||
firstGeneration[i] != thirdGeneration[i] ||
|
||||
firstGeneration[i] != fourthGeneration[i])
|
||||
{
|
||||
fprintf(stderr, "Error, strings don't compare equal.\n");
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
void StringThreadTest::check() {
|
||||
//
|
||||
// Check that the reference counts on the buffers for all of the source strings
|
||||
// are one. The ref counts will have run way up while the test threads
|
||||
// make numerous copies of the strings, but at the top of the loop all
|
||||
// of the copies should be gone.
|
||||
//
|
||||
int i;
|
||||
|
||||
for (i=0; i<5; i++) {
|
||||
if (fSourceStrings[i].fFlags & UnicodeString::kRefCounted) {
|
||||
const UChar *buf = fSourceStrings[i].getBuffer();
|
||||
uint32_t refCount = fSourceStrings[i].refCount();
|
||||
if (refCount != 1) {
|
||||
fprintf(stderr, "\nFailure. SourceString Ref Count was %d, should be 1.\n", refCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Factory functoin for StringThreadTest.
|
||||
// C function lets ThreadTest create StringTests without needing StringThreadTest header.
|
||||
//
|
||||
AbstractThreadTest *createStringTest() {
|
||||
return new StringThreadTest();
|
||||
};
|
@ -1,537 +0,0 @@
|
||||
//
|
||||
//********************************************************************
|
||||
// Copyright (C) 2002-2011, International Business Machines
|
||||
// Corporation and others. All Rights Reserved.
|
||||
//********************************************************************
|
||||
//
|
||||
// File threadtest.cpp
|
||||
//
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "unicode/utypes.h"
|
||||
#include "unicode/uclean.h"
|
||||
#include "umutex.h"
|
||||
#include "threadtest.h"
|
||||
|
||||
AbstractThreadTest::~AbstractThreadTest() {}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Factory functions for creating different test types.
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
extern AbstractThreadTest *createStringTest();
|
||||
extern AbstractThreadTest *createConvertTest();
|
||||
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Windows specific code for starting threads
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
#if U_PLATFORM_USES_ONLY_WIN32_API
|
||||
|
||||
#include "Windows.h"
|
||||
#include "process.h"
|
||||
|
||||
|
||||
|
||||
typedef void (*ThreadFunc)(void *);
|
||||
|
||||
class ThreadFuncs // This class isolates OS dependent threading
|
||||
{ // functions from the rest of ThreadTest program.
|
||||
public:
|
||||
static void Sleep(int millis) {::Sleep(millis);};
|
||||
static void startThread(ThreadFunc, void *param);
|
||||
static unsigned long getCurrentMillis();
|
||||
static void yield() {::Sleep(0);};
|
||||
};
|
||||
|
||||
void ThreadFuncs::startThread(ThreadFunc func, void *param)
|
||||
{
|
||||
unsigned long x;
|
||||
x = _beginthread(func, 0x10000, param);
|
||||
if (x == -1)
|
||||
{
|
||||
fprintf(stderr, "Error starting thread. Errno = %d\n", errno);
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long ThreadFuncs::getCurrentMillis()
|
||||
{
|
||||
return (unsigned long)::GetTickCount();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// #elif defined (POSIX)
|
||||
#else
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// UNIX specific code for starting threads
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sched.h>
|
||||
#include <sys/timeb.h>
|
||||
|
||||
|
||||
extern "C" {
|
||||
|
||||
|
||||
typedef void (*ThreadFunc)(void *);
|
||||
typedef void *(*pthreadfunc)(void *);
|
||||
|
||||
class ThreadFuncs // This class isolates OS dependent threading
|
||||
{ // functions from the rest of ThreadTest program.
|
||||
public:
|
||||
static void Sleep(int millis);
|
||||
static void startThread(ThreadFunc, void *param);
|
||||
static unsigned long getCurrentMillis();
|
||||
static void yield() {sched_yield();};
|
||||
};
|
||||
|
||||
void ThreadFuncs::Sleep(int millis)
|
||||
{
|
||||
int seconds = millis/1000;
|
||||
if (seconds <= 0) seconds = 1;
|
||||
::sleep(seconds);
|
||||
}
|
||||
|
||||
|
||||
void ThreadFuncs::startThread(ThreadFunc func, void *param)
|
||||
{
|
||||
unsigned long x;
|
||||
|
||||
pthread_t tId;
|
||||
//thread_t tId;
|
||||
#if defined(_HP_UX) && defined(XML_USE_DCE)
|
||||
x = pthread_create( &tId, pthread_attr_default, (pthreadfunc)func, param);
|
||||
#else
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
x = pthread_create( &tId, &attr, (pthreadfunc)func, param);
|
||||
#endif
|
||||
if (x == -1)
|
||||
{
|
||||
fprintf(stderr, "Error starting thread. Errno = %d\n", errno);
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long ThreadFuncs::getCurrentMillis() {
|
||||
timeb aTime;
|
||||
ftime(&aTime);
|
||||
return (unsigned long)(aTime.time*1000 + aTime.millitm);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// #else
|
||||
// #error This platform is not supported
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// struct runInfo Holds the info extracted from the command line and data
|
||||
// that is shared by all threads.
|
||||
// There is only one of these, and it is static.
|
||||
// During the test, the threads will access this info without
|
||||
// any synchronization.
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
const int MAXINFILES = 25;
|
||||
struct RunInfo
|
||||
{
|
||||
bool quiet;
|
||||
bool verbose;
|
||||
int numThreads;
|
||||
int totalTime;
|
||||
int checkTime;
|
||||
AbstractThreadTest *fTest;
|
||||
bool stopFlag;
|
||||
bool exitFlag;
|
||||
int32_t runningThreads;
|
||||
};
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// struct threadInfo Holds information specific to an individual thread.
|
||||
// One of these is set up for each thread in the test.
|
||||
// The main program monitors the threads by looking
|
||||
// at the status stored in these structs.
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
struct ThreadInfo
|
||||
{
|
||||
bool fHeartBeat; // Set true by the thread each time it finishes
|
||||
// a test.
|
||||
unsigned int fCycles; // Number of cycles completed.
|
||||
int fThreadNum; // Identifying number for this thread.
|
||||
ThreadInfo() {
|
||||
fHeartBeat = false;
|
||||
fCycles = 0;
|
||||
fThreadNum = -1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Global Data
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
RunInfo gRunInfo;
|
||||
ThreadInfo *gThreadInfo;
|
||||
UMTX gStopMutex; // Lets main thread suspend test threads.
|
||||
UMTX gInfoMutex; // Synchronize access to data passed between
|
||||
// worker threads and the main thread
|
||||
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
//
|
||||
// parseCommandLine Read through the command line, and save all
|
||||
// of the options in the gRunInfo struct.
|
||||
//
|
||||
// Display the usage message if the command line
|
||||
// is no good.
|
||||
//
|
||||
// Probably ought to be a member function of RunInfo.
|
||||
//
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void parseCommandLine(int argc, char **argv)
|
||||
{
|
||||
gRunInfo.quiet = false; // Set up defaults for run.
|
||||
gRunInfo.verbose = false;
|
||||
gRunInfo.numThreads = 2;
|
||||
gRunInfo.totalTime = 0;
|
||||
gRunInfo.checkTime = 10;
|
||||
|
||||
try // Use exceptions for command line syntax errors.
|
||||
{
|
||||
int argnum = 1;
|
||||
while (argnum < argc)
|
||||
{
|
||||
if (strcmp(argv[argnum], "-quiet") == 0)
|
||||
gRunInfo.quiet = true;
|
||||
else if (strcmp(argv[argnum], "-verbose") == 0)
|
||||
gRunInfo.verbose = true;
|
||||
else if (strcmp(argv[argnum], "--help") == 0 ||
|
||||
(strcmp(argv[argnum], "?") == 0)) {throw 1; }
|
||||
|
||||
else if (strcmp(argv[argnum], "-threads") == 0)
|
||||
{
|
||||
++argnum;
|
||||
if (argnum >= argc)
|
||||
throw 1;
|
||||
gRunInfo.numThreads = atoi(argv[argnum]);
|
||||
if (gRunInfo.numThreads < 0)
|
||||
throw 1;
|
||||
}
|
||||
else if (strcmp(argv[argnum], "-time") == 0)
|
||||
{
|
||||
++argnum;
|
||||
if (argnum >= argc)
|
||||
throw 1;
|
||||
gRunInfo.totalTime = atoi(argv[argnum]);
|
||||
if (gRunInfo.totalTime < 1)
|
||||
throw 1;
|
||||
}
|
||||
else if (strcmp(argv[argnum], "-ctime") == 0)
|
||||
{
|
||||
++argnum;
|
||||
if (argnum >= argc)
|
||||
throw 1;
|
||||
gRunInfo.checkTime = atoi(argv[argnum]);
|
||||
if (gRunInfo.checkTime < 1)
|
||||
throw 1;
|
||||
}
|
||||
else if (strcmp(argv[argnum], "string") == 0)
|
||||
{
|
||||
gRunInfo.fTest = createStringTest();
|
||||
}
|
||||
else if (strcmp(argv[argnum], "convert") == 0)
|
||||
{
|
||||
gRunInfo.fTest = createConvertTest();
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Unrecognized command line option. Scanning \"%s\"\n",
|
||||
argv[argnum]);
|
||||
throw 1;
|
||||
}
|
||||
argnum++;
|
||||
}
|
||||
// We've reached the end of the command line parameters.
|
||||
// Fail if no test name was specified.
|
||||
if (gRunInfo.fTest == NULL) {
|
||||
fprintf(stderr, "No test specified.\n");
|
||||
throw 1;
|
||||
}
|
||||
|
||||
}
|
||||
catch (int)
|
||||
{
|
||||
fprintf(stderr, "usage: threadtest [-threads nnn] [-time nnn] [-quiet] [-verbose] test-name\n"
|
||||
" -quiet Suppress periodic status display. \n"
|
||||
" -verbose Display extra messages. \n"
|
||||
" -threads nnn Number of threads. Default is 2. \n"
|
||||
" -time nnn Total time to run, in seconds. Default is forever.\n"
|
||||
" -ctime nnn Time between extra consistency checks, in seconds. Default 10\n"
|
||||
" testname string | convert\n"
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
//
|
||||
// threadMain The main function for each of the swarm of test threads.
|
||||
// Run in a loop, executing the runOnce() test function each time.
|
||||
//
|
||||
//
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
extern "C" {
|
||||
|
||||
void threadMain (void *param)
|
||||
{
|
||||
ThreadInfo *thInfo = (ThreadInfo *)param;
|
||||
|
||||
if (gRunInfo.verbose)
|
||||
printf("Thread #%d: starting\n", thInfo->fThreadNum);
|
||||
umtx_atomic_inc(&gRunInfo.runningThreads);
|
||||
|
||||
//
|
||||
//
|
||||
while (true)
|
||||
{
|
||||
if (gRunInfo.verbose )
|
||||
printf("Thread #%d: starting loop\n", thInfo->fThreadNum);
|
||||
|
||||
//
|
||||
// If the main thread is asking us to wait, do so by locking gStopMutex
|
||||
// which will block us, since the main thread will be holding it already.
|
||||
//
|
||||
umtx_lock(&gInfoMutex);
|
||||
UBool stop = gRunInfo.stopFlag; // Need mutex for processors with flakey memory models.
|
||||
umtx_unlock(&gInfoMutex);
|
||||
|
||||
if (stop) {
|
||||
if (gRunInfo.verbose) {
|
||||
fprintf(stderr, "Thread #%d: suspending\n", thInfo->fThreadNum);
|
||||
}
|
||||
umtx_atomic_dec(&gRunInfo.runningThreads);
|
||||
while (gRunInfo.stopFlag) {
|
||||
umtx_lock(&gStopMutex);
|
||||
umtx_unlock(&gStopMutex);
|
||||
}
|
||||
umtx_atomic_inc(&gRunInfo.runningThreads);
|
||||
if (gRunInfo.verbose) {
|
||||
fprintf(stderr, "Thread #%d: restarting\n", thInfo->fThreadNum);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// The real work of the test happens here.
|
||||
//
|
||||
gRunInfo.fTest->runOnce();
|
||||
|
||||
umtx_lock(&gInfoMutex);
|
||||
thInfo->fHeartBeat = true;
|
||||
thInfo->fCycles++;
|
||||
UBool exitNow = gRunInfo.exitFlag;
|
||||
umtx_unlock(&gInfoMutex);
|
||||
|
||||
//
|
||||
// If main thread says it's time to exit, break out of the loop.
|
||||
//
|
||||
if (exitNow) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
umtx_atomic_dec(&gRunInfo.runningThreads);
|
||||
|
||||
// Returning will kill the thread.
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
//
|
||||
// main
|
||||
//
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
//
|
||||
// Parse the command line options, and create the specified kind of test.
|
||||
//
|
||||
parseCommandLine(argc, argv);
|
||||
|
||||
|
||||
//
|
||||
// Fire off the requested number of parallel threads
|
||||
//
|
||||
|
||||
if (gRunInfo.numThreads == 0)
|
||||
exit(0);
|
||||
|
||||
gRunInfo.exitFlag = FALSE;
|
||||
gRunInfo.stopFlag = TRUE; // Will cause the new threads to block
|
||||
umtx_lock(&gStopMutex);
|
||||
|
||||
gThreadInfo = new ThreadInfo[gRunInfo.numThreads];
|
||||
int threadNum;
|
||||
for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++)
|
||||
{
|
||||
gThreadInfo[threadNum].fThreadNum = threadNum;
|
||||
ThreadFuncs::startThread(threadMain, &gThreadInfo[threadNum]);
|
||||
}
|
||||
|
||||
|
||||
unsigned long startTime = ThreadFuncs::getCurrentMillis();
|
||||
int elapsedSeconds = 0;
|
||||
int timeSinceCheck = 0;
|
||||
|
||||
//
|
||||
// Unblock the threads.
|
||||
//
|
||||
gRunInfo.stopFlag = FALSE; // Unblocks the worker threads.
|
||||
umtx_unlock(&gStopMutex);
|
||||
|
||||
//
|
||||
// Loop, watching the heartbeat of the worker threads.
|
||||
// Each second,
|
||||
// display "+" if all threads have completed at least one loop
|
||||
// display "." if some thread hasn't since previous "+"
|
||||
// Each "ctime" seconds,
|
||||
// Stop all the worker threads at the top of their loop, then
|
||||
// call the test's check function.
|
||||
//
|
||||
while (gRunInfo.totalTime == 0 || gRunInfo.totalTime > elapsedSeconds)
|
||||
{
|
||||
ThreadFuncs::Sleep(1000); // We sleep while threads do their work ...
|
||||
|
||||
if (gRunInfo.quiet == false && gRunInfo.verbose == false)
|
||||
{
|
||||
char c = '+';
|
||||
int threadNum;
|
||||
umtx_lock(&gInfoMutex);
|
||||
for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++)
|
||||
{
|
||||
if (gThreadInfo[threadNum].fHeartBeat == false)
|
||||
{
|
||||
c = '.';
|
||||
break;
|
||||
};
|
||||
}
|
||||
umtx_unlock(&gInfoMutex);
|
||||
fputc(c, stdout);
|
||||
fflush(stdout);
|
||||
if (c == '+')
|
||||
for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++)
|
||||
gThreadInfo[threadNum].fHeartBeat = false;
|
||||
}
|
||||
|
||||
//
|
||||
// Update running times.
|
||||
//
|
||||
timeSinceCheck -= elapsedSeconds;
|
||||
elapsedSeconds = (ThreadFuncs::getCurrentMillis() - startTime) / 1000;
|
||||
timeSinceCheck += elapsedSeconds;
|
||||
|
||||
//
|
||||
// Call back to the test to let it check its internal validity
|
||||
//
|
||||
if (timeSinceCheck >= gRunInfo.checkTime) {
|
||||
if (gRunInfo.verbose) {
|
||||
fprintf(stderr, "Main: suspending all threads\n");
|
||||
}
|
||||
umtx_lock(&gStopMutex); // Block the worker threads at the top of their loop
|
||||
gRunInfo.stopFlag = TRUE;
|
||||
for (;;) {
|
||||
umtx_lock(&gInfoMutex);
|
||||
UBool done = gRunInfo.runningThreads == 0;
|
||||
umtx_unlock(&gInfoMutex);
|
||||
if (done) { break;}
|
||||
ThreadFuncs::yield();
|
||||
}
|
||||
|
||||
|
||||
|
||||
gRunInfo.fTest->check();
|
||||
if (gRunInfo.quiet == false && gRunInfo.verbose == false) {
|
||||
fputc('C', stdout);
|
||||
}
|
||||
|
||||
if (gRunInfo.verbose) {
|
||||
fprintf(stderr, "Main: starting all threads.\n");
|
||||
}
|
||||
gRunInfo.stopFlag = FALSE; // Unblock the worker threads.
|
||||
umtx_unlock(&gStopMutex);
|
||||
timeSinceCheck = 0;
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Time's up, we are done. (We only get here if this was a timed run)
|
||||
// Tell the threads to exit.
|
||||
//
|
||||
gRunInfo.exitFlag = true;
|
||||
for (;;) {
|
||||
umtx_lock(&gInfoMutex);
|
||||
UBool done = gRunInfo.runningThreads == 0;
|
||||
umtx_unlock(&gInfoMutex);
|
||||
if (done) { break;}
|
||||
ThreadFuncs::yield();
|
||||
}
|
||||
|
||||
//
|
||||
// Tally up the total number of cycles completed by each of the threads.
|
||||
//
|
||||
double totalCyclesCompleted = 0;
|
||||
for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++) {
|
||||
totalCyclesCompleted += gThreadInfo[threadNum].fCycles;
|
||||
}
|
||||
|
||||
double cyclesPerMinute = totalCyclesCompleted / (double(gRunInfo.totalTime) / double(60));
|
||||
printf("\n%8.1f cycles per minute.", cyclesPerMinute);
|
||||
|
||||
//
|
||||
// Memory should be clean coming out
|
||||
//
|
||||
delete gRunInfo.fTest;
|
||||
delete [] gThreadInfo;
|
||||
umtx_destroy(&gInfoMutex);
|
||||
umtx_destroy(&gStopMutex);
|
||||
u_cleanup();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1,166 +0,0 @@
|
||||
# Microsoft Developer Studio Project File - Name="threadtest" - Package Owner=<4>
|
||||
# Microsoft Developer Studio Generated Build File, Format Version 6.00
|
||||
# ** DO NOT EDIT **
|
||||
|
||||
# TARGTYPE "Win32 (x86) Console Application" 0x0103
|
||||
|
||||
CFG=threadtest - Win32 Debug
|
||||
!MESSAGE This is not a valid makefile. To build this project using NMAKE,
|
||||
!MESSAGE use the Export Makefile command and run
|
||||
!MESSAGE
|
||||
!MESSAGE NMAKE /f "threadtest.mak".
|
||||
!MESSAGE
|
||||
!MESSAGE You can specify a configuration when running NMAKE
|
||||
!MESSAGE by defining the macro CFG on the command line. For example:
|
||||
!MESSAGE
|
||||
!MESSAGE NMAKE /f "threadtest.mak" CFG="threadtest - Win32 Debug"
|
||||
!MESSAGE
|
||||
!MESSAGE Possible choices for configuration are:
|
||||
!MESSAGE
|
||||
!MESSAGE "threadtest - Win32 Release" (based on "Win32 (x86) Console Application")
|
||||
!MESSAGE "threadtest - Win32 Debug" (based on "Win32 (x86) Console Application")
|
||||
!MESSAGE "threadtest - Win64 Release" (based on "Win32 (x86) Console Application")
|
||||
!MESSAGE "threadtest - Win64 Debug" (based on "Win32 (x86) Console Application")
|
||||
!MESSAGE
|
||||
|
||||
# Begin Project
|
||||
# PROP AllowPerConfigDependencies 0
|
||||
# PROP Scc_ProjName ""
|
||||
# PROP Scc_LocalPath ""
|
||||
CPP=cl.exe
|
||||
RSC=rc.exe
|
||||
|
||||
!IF "$(CFG)" == "threadtest - Win32 Release"
|
||||
|
||||
# PROP BASE Use_MFC 0
|
||||
# PROP BASE Use_Debug_Libraries 0
|
||||
# PROP BASE Output_Dir "Release"
|
||||
# PROP BASE Intermediate_Dir "Release"
|
||||
# PROP BASE Target_Dir ""
|
||||
# PROP Use_MFC 0
|
||||
# PROP Use_Debug_Libraries 0
|
||||
# PROP Output_Dir "Release"
|
||||
# PROP Intermediate_Dir "Release"
|
||||
# PROP Ignore_Export_Lib 0
|
||||
# PROP Target_Dir ""
|
||||
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FD /c
|
||||
# ADD CPP /nologo /G6 /MD /W3 /GX /O2 /Ob2 /I "..\..\..\include" /I "..\..\..\source\common" /I "..\..\..\source\i18n" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FD /c
|
||||
# ADD BASE RSC /l 0x409 /d "NDEBUG"
|
||||
# ADD RSC /l 0x409 /d "NDEBUG"
|
||||
BSC32=bscmake.exe
|
||||
# ADD BASE BSC32 /nologo
|
||||
# ADD BSC32 /nologo
|
||||
LINK32=link.exe
|
||||
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
|
||||
# ADD LINK32 kernel32.lib user32.lib shell32.lib icuuc.lib icuin.lib icutu.lib /nologo /subsystem:console /machine:I386 /libpath:"..\..\..\lib\\"
|
||||
|
||||
!ELSEIF "$(CFG)" == "threadtest - Win32 Debug"
|
||||
|
||||
# PROP BASE Use_MFC 0
|
||||
# PROP BASE Use_Debug_Libraries 1
|
||||
# PROP BASE Output_Dir "Debug"
|
||||
# PROP BASE Intermediate_Dir "Debug"
|
||||
# PROP BASE Target_Dir ""
|
||||
# PROP Use_MFC 0
|
||||
# PROP Use_Debug_Libraries 1
|
||||
# PROP Output_Dir "Debug"
|
||||
# PROP Intermediate_Dir "Debug"
|
||||
# PROP Ignore_Export_Lib 0
|
||||
# PROP Target_Dir ""
|
||||
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FD /GZ /c
|
||||
# ADD CPP /nologo /G6 /MDd /W3 /Gm /GX /ZI /Od /I "..\..\..\include" /I "..\..\..\source\common" /I "..\..\..\source\i18n" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FR /FD /GZ /c
|
||||
# ADD BASE RSC /l 0x409 /d "_DEBUG"
|
||||
# ADD RSC /l 0x409 /d "_DEBUG"
|
||||
BSC32=bscmake.exe
|
||||
# ADD BASE BSC32 /nologo
|
||||
# ADD BSC32 /nologo
|
||||
LINK32=link.exe
|
||||
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
|
||||
# ADD LINK32 kernel32.lib user32.lib shell32.lib icuucd.lib icuind.lib icutud.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept /libpath:"..\..\..\lib\\"
|
||||
|
||||
!ELSEIF "$(CFG)" == "threadtest - Win64 Release"
|
||||
|
||||
# PROP BASE Use_MFC 0
|
||||
# PROP BASE Use_Debug_Libraries 0
|
||||
# PROP BASE Output_Dir "Release"
|
||||
# PROP BASE Intermediate_Dir "Release"
|
||||
# PROP BASE Target_Dir ""
|
||||
# PROP Use_MFC 0
|
||||
# PROP Use_Debug_Libraries 0
|
||||
# PROP Output_Dir "Release"
|
||||
# PROP Intermediate_Dir "Release"
|
||||
# PROP Ignore_Export_Lib 0
|
||||
# PROP Target_Dir ""
|
||||
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN64" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FD /c
|
||||
# ADD CPP /nologo /MD /W3 /I "..\..\..\include" /I "..\..\..\source\common" /I "..\..\..\source\i18n" /D"WIN64" /D"NDEBUG" /D"_CONSOLE" /D"_MBCS" /FD /c /O2 /GX /Op /D"_IA64_" /Zi /D"WIN64" /D"WIN32" /D"_AFX_NO_DAO_SUPPORT" /Zm600
|
||||
# ADD BASE RSC /l 0x409 /d "NDEBUG"
|
||||
# ADD RSC /l 0x409 /d "NDEBUG"
|
||||
BSC32=bscmake.exe
|
||||
# ADD BASE BSC32 /nologo
|
||||
# ADD BSC32 /nologo
|
||||
LINK32=link.exe
|
||||
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:IA64
|
||||
# ADD LINK32 kernel32.lib user32.lib shell32.lib icuuc.lib icuin.lib icutu.lib /nologo /subsystem:console /machine:IA64 /libpath:"..\..\..\lib\\" /incremental:no
|
||||
|
||||
!ELSEIF "$(CFG)" == "threadtest - Win64 Debug"
|
||||
|
||||
# PROP BASE Use_MFC 0
|
||||
# PROP BASE Use_Debug_Libraries 1
|
||||
# PROP BASE Output_Dir "Debug"
|
||||
# PROP BASE Intermediate_Dir "Debug"
|
||||
# PROP BASE Target_Dir ""
|
||||
# PROP Use_MFC 0
|
||||
# PROP Use_Debug_Libraries 1
|
||||
# PROP Output_Dir "Debug"
|
||||
# PROP Intermediate_Dir "Debug"
|
||||
# PROP Ignore_Export_Lib 0
|
||||
# PROP Target_Dir ""
|
||||
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN64" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FD /GZ /c
|
||||
# ADD CPP /nologo /MDd /W3 /Gm /I "..\..\..\include" /I "..\..\..\source\common" /I "..\..\..\source\i18n" /D"WIN64" /D"_DEBUG" /D"_CONSOLE" /D"_MBCS" /FR /FD /GZ /c /Od /GX /Op /D"_IA64_" /Zi /D"WIN64" /D"WIN32" /D"_AFX_NO_DAO_SUPPORT" /Zm600
|
||||
# ADD BASE RSC /l 0x409 /d "_DEBUG"
|
||||
# ADD RSC /l 0x409 /d "_DEBUG"
|
||||
BSC32=bscmake.exe
|
||||
# ADD BASE BSC32 /nologo
|
||||
# ADD BSC32 /nologo
|
||||
LINK32=link.exe
|
||||
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:IA64 /pdbtype:sept
|
||||
# ADD LINK32 kernel32.lib user32.lib shell32.lib icuucd.lib icuind.lib icutud.lib /nologo /subsystem:console /debug /machine:IA64 /pdbtype:sept /libpath:"..\..\..\lib\\" /incremental:no
|
||||
|
||||
!ENDIF
|
||||
|
||||
# Begin Target
|
||||
|
||||
# Name "threadtest - Win32 Release"
|
||||
# Name "threadtest - Win32 Debug"
|
||||
# Name "threadtest - Win64 Release"
|
||||
# Name "threadtest - Win64 Debug"
|
||||
# Begin Group "Source Files"
|
||||
|
||||
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\converttest.cpp
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\stringtest.cpp
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\threadtest.cpp
|
||||
# End Source File
|
||||
# End Group
|
||||
# Begin Group "Header Files"
|
||||
|
||||
# PROP Default_Filter "h;hpp;hxx;hm;inl"
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\threadtest.h
|
||||
# End Source File
|
||||
# End Group
|
||||
# Begin Group "Resource Files"
|
||||
|
||||
# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
|
||||
# End Group
|
||||
# End Target
|
||||
# End Project
|
@ -1,29 +0,0 @@
|
||||
Microsoft Developer Studio Workspace File, Format Version 6.00
|
||||
# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
|
||||
|
||||
###############################################################################
|
||||
|
||||
Project: "threadtest"=.\threadtest.dsp - Package Owner=<4>
|
||||
|
||||
Package=<5>
|
||||
{{{
|
||||
}}}
|
||||
|
||||
Package=<4>
|
||||
{{{
|
||||
}}}
|
||||
|
||||
###############################################################################
|
||||
|
||||
Global:
|
||||
|
||||
Package=<5>
|
||||
{{{
|
||||
}}}
|
||||
|
||||
Package=<3>
|
||||
{{{
|
||||
}}}
|
||||
|
||||
###############################################################################
|
||||
|
@ -1,45 +0,0 @@
|
||||
//
|
||||
//********************************************************************
|
||||
// Copyright (C) 2002-2011, International Business Machines
|
||||
// Corporation and others. All Rights Reserved.
|
||||
//********************************************************************
|
||||
//
|
||||
// File threadtest.h
|
||||
//
|
||||
#ifndef ABSTRACTTHREADTEST_H
|
||||
#define ABSTRACTTHREADTEST_H
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// class AbstractThreadTest Base class for threading tests.
|
||||
// Use of this abstract base isolates the part of the
|
||||
// program that nows how to spin up and control threads
|
||||
// from the specific stuff being tested, and (hopefully)
|
||||
// simplifies adding new threading tests for different parts
|
||||
// of ICU.
|
||||
//
|
||||
// Derived classes: A running test will have exactly one instance of a
|
||||
// derived class, which will persist for the duration of the
|
||||
// test and be shared among all of the threads involved in
|
||||
// the test.
|
||||
//
|
||||
// The constructor will be called in a single-threaded environment,
|
||||
// and should set up any data that will need to persist for the
|
||||
// duration.
|
||||
//
|
||||
// runOnce() will be called repeatedly by the working threads of
|
||||
// the test in the full multi-threaded environment.
|
||||
//
|
||||
// check() will be called periodically in a single threaded
|
||||
// environment, with the worker threads temporarily suspended between
|
||||
// between calls to runOnce(). Do consistency checks here.
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
class AbstractThreadTest {
|
||||
public:
|
||||
AbstractThreadTest() {};
|
||||
virtual ~AbstractThreadTest();
|
||||
virtual void check() = 0;
|
||||
virtual void runOnce() = 0;
|
||||
};
|
||||
|
||||
#endif // ABSTRACTTHREADTEST_H
|
Loading…
Reference in New Issue
Block a user