aboutsummaryrefslogtreecommitdiffstats
path: root/fastos
diff options
context:
space:
mode:
authorArne H Juul <arnej@yahoo-inc.com>2016-06-29 15:21:51 +0200
committerArne H Juul <arnej@yahoo-inc.com>2016-06-29 16:16:57 +0200
commit9f9ed5453b45c1fba47bcd4e83cf8b36afddb95a (patch)
treef209518821a795a66aceccdcedeef8a13561db9f /fastos
parent07e47f92d879cc1b7d0eb89ef8170d5366904dc7 (diff)
split test so it can run in parallel
Diffstat (limited to 'fastos')
-rw-r--r--fastos/src/tests/.gitignore10
-rw-r--r--fastos/src/tests/CMakeLists.txt35
-rw-r--r--fastos/src/tests/thread_bounce_test.cpp379
-rw-r--r--fastos/src/tests/thread_joinwait_test.cpp405
-rw-r--r--fastos/src/tests/thread_mutex_test.cpp475
-rw-r--r--fastos/src/tests/thread_sleep_test.cpp338
-rw-r--r--fastos/src/tests/thread_stats_test.cpp422
-rw-r--r--fastos/src/tests/threadtest.cpp452
8 files changed, 2058 insertions, 458 deletions
diff --git a/fastos/src/tests/.gitignore b/fastos/src/tests/.gitignore
index 2f67e70e89d..2af862d201e 100644
--- a/fastos/src/tests/.gitignore
+++ b/fastos/src/tests/.gitignore
@@ -18,12 +18,4 @@
/typetest
/typetest.log
/usecputest
-fastos_backtracetest_app
-fastos_filetest_app
-fastos_prefetchtest_app
-fastos_processtest_app
-fastos_sockettest_app
-fastos_threadtest_app
-fastos_timetest_app
-fastos_typetest_app
-fastos_usecputest_app
+*test_app
diff --git a/fastos/src/tests/CMakeLists.txt b/fastos/src/tests/CMakeLists.txt
index 8b069e15dcb..b0bd58a1ce6 100644
--- a/fastos/src/tests/CMakeLists.txt
+++ b/fastos/src/tests/CMakeLists.txt
@@ -26,6 +26,41 @@ vespa_add_executable(fastos_sockettest_app TEST
fastos
)
vespa_add_test(NAME fastos_sockettest_app NO_VALGRIND COMMAND fastos_sockettest_app)
+vespa_add_executable(fastos_thread_stats_test_app TEST
+ SOURCES
+ thread_stats_test.cpp
+ DEPENDS
+ fastos
+)
+vespa_add_test(NAME fastos_thread_stats_test_app NO_VALGRIND COMMAND fastos_thread_stats_test_app)
+vespa_add_executable(fastos_thread_sleep_test_app TEST
+ SOURCES
+ thread_sleep_test.cpp
+ DEPENDS
+ fastos
+)
+vespa_add_test(NAME fastos_thread_sleep_test_app NO_VALGRIND COMMAND fastos_thread_sleep_test_app)
+vespa_add_executable(fastos_thread_mutex_test_app TEST
+ SOURCES
+ thread_mutex_test.cpp
+ DEPENDS
+ fastos
+)
+vespa_add_test(NAME fastos_thread_mutex_test_app NO_VALGRIND COMMAND fastos_thread_mutex_test_app)
+vespa_add_executable(fastos_thread_joinwait_test_app TEST
+ SOURCES
+ thread_joinwait_test.cpp
+ DEPENDS
+ fastos
+)
+vespa_add_test(NAME fastos_thread_joinwait_test_app NO_VALGRIND COMMAND fastos_thread_joinwait_test_app)
+vespa_add_executable(fastos_thread_bounce_test_app TEST
+ SOURCES
+ thread_bounce_test.cpp
+ DEPENDS
+ fastos
+)
+vespa_add_test(NAME fastos_thread_bounce_test_app NO_VALGRIND COMMAND fastos_thread_bounce_test_app)
vespa_add_executable(fastos_threadtest_app TEST
SOURCES
threadtest.cpp
diff --git a/fastos/src/tests/thread_bounce_test.cpp b/fastos/src/tests/thread_bounce_test.cpp
new file mode 100644
index 00000000000..6d629b9f919
--- /dev/null
+++ b/fastos/src/tests/thread_bounce_test.cpp
@@ -0,0 +1,379 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <stdlib.h>
+
+#include <vespa/fastos/fastos.h>
+#include "tests.h"
+
+#define PRI_TIME_COUNT 4096
+
+enum JobCode
+{
+ PRINT_MESSAGE_AND_WAIT3SEC,
+ INCREASE_NUMBER,
+ PRIORITY_TEST,
+ WAIT_FOR_BREAK_FLAG,
+ WAIT_FOR_THREAD_TO_FINISH,
+ WAIT_FOR_CONDITION,
+ BOUNCE_CONDITIONS,
+ TEST_ID,
+ WAIT2SEC_AND_SIGNALCOND,
+ HOLD_MUTEX_FOR2SEC,
+ WAIT_2_SEC,
+ SILENTNOP,
+ NOP
+};
+
+class Job
+{
+private:
+ Job(const Job &);
+ Job &operator=(const Job&);
+
+public:
+ JobCode code;
+ char *message;
+ FastOS_Mutex *mutex;
+ FastOS_Cond *condition;
+ FastOS_BoolCond *boolcondition;
+ FastOS_ThreadInterface *otherThread, *ownThread;
+ double *timebuf;
+ double average;
+ int result;
+ FastOS_ThreadId _threadId;
+ Job *otherjob;
+ int bouncewakeupcnt;
+ bool bouncewakeup;
+
+ Job()
+ : code(NOP),
+ message(NULL),
+ mutex(NULL),
+ condition(NULL),
+ boolcondition(NULL),
+ otherThread(NULL),
+ ownThread(NULL),
+ timebuf(NULL),
+ average(0.0),
+ result(-1),
+ _threadId(),
+ otherjob(NULL),
+ bouncewakeupcnt(0),
+ bouncewakeup(false)
+ {
+ }
+
+ ~Job()
+ {
+ if(message != NULL)
+ free(message);
+ }
+};
+
+static volatile int64_t number;
+#define INCREASE_NUMBER_AMOUNT 10000
+
+#define MUTEX_TEST_THREADS 6
+#define MAX_THREADS 7
+
+
+class ThreadTest : public BaseTest, public FastOS_Runnable
+{
+private:
+ FastOS_Mutex printMutex;
+
+public:
+ ThreadTest(void)
+ : printMutex()
+ {
+ }
+ virtual ~ThreadTest() {};
+
+ void PrintProgress (char *string)
+ {
+ printMutex.Lock();
+ BaseTest::PrintProgress(string);
+ printMutex.Unlock();
+ }
+
+ void Run (FastOS_ThreadInterface *thread, void *arg);
+
+ void WaitForThreadsToFinish (Job *jobs, int count)
+ {
+ int i;
+
+ Progress(true, "Waiting for threads to finish...");
+ for(;;)
+ {
+ bool threadsFinished=true;
+
+ for(i=0; i<count; i++)
+ {
+ if(jobs[i].result == -1)
+ {
+ threadsFinished = false;
+ break;
+ }
+ }
+
+ FastOS_Thread::Sleep(500);
+
+ if(threadsFinished)
+ break;
+ }
+
+ Progress(true, "Threads finished");
+ }
+
+ int Main ();
+
+ void BounceTest(void)
+ {
+ TestHeader("Bounce Test");
+
+ FastOS_ThreadPool pool(128 * 1024);
+ FastOS_Cond cond1;
+ FastOS_Cond cond2;
+ Job job1;
+ Job job2;
+ FastOS_Time checkTime;
+ int cnt1;
+ int cnt2;
+ int cntsum;
+ int lastcntsum;
+
+ job1.code = BOUNCE_CONDITIONS;
+ job2.code = BOUNCE_CONDITIONS;
+ job1.otherjob = &job2;
+ job2.otherjob = &job1;
+ job1.condition = &cond1;
+ job2.condition = &cond2;
+
+ job1.ownThread = pool.NewThread(this, static_cast<void *>(&job1));
+ job2.ownThread = pool.NewThread(this, static_cast<void *>(&job2));
+
+ lastcntsum = -1;
+ for (int iter = 0; iter < 8; iter++) {
+ checkTime.SetNow();
+
+ int left = static_cast<int>(checkTime.MilliSecsToNow());
+ while (left < 1000) {
+ FastOS_Thread::Sleep(1000 - left);
+ left = static_cast<int>(checkTime.MilliSecsToNow());
+ }
+
+ cond1.Lock();
+ cnt1 = job1.bouncewakeupcnt;
+ cond1.Unlock();
+ cond2.Lock();
+ cnt2 = job2.bouncewakeupcnt;
+ cond2.Unlock();
+ cntsum = cnt1 + cnt2;
+ Progress(lastcntsum != cntsum, "%d bounces", cntsum);
+ lastcntsum = cntsum;
+ }
+
+ job1.ownThread->SetBreakFlag();
+ cond1.Lock();
+ job1.bouncewakeup = true;
+ cond1.Signal();
+ cond1.Unlock();
+
+ job2.ownThread->SetBreakFlag();
+ cond2.Lock();
+ job2.bouncewakeup = true;
+ cond2.Signal();
+ cond2.Unlock();
+
+ pool.Close();
+ Progress(true, "Pool closed.");
+ PrintSeparator();
+
+ }
+
+};
+
+int ThreadTest::Main ()
+{
+ printf("grep for the string '%s' to detect failures.\n\n", failString);
+ time_t before = time(0);
+
+ BounceTest();
+ { time_t now = time(0); printf("[%ld seconds]\n", now-before); before = now; }
+
+ printf("END OF TEST (%s)\n", _argv[0]);
+
+ return 0;
+}
+
+volatile int busyCnt;
+
+void ThreadTest::Run (FastOS_ThreadInterface *thread, void *arg)
+{
+ if(arg == NULL)
+ return;
+
+ Job *job = static_cast<Job *>(arg);
+ char someStack[15*1024];
+
+ memset(someStack, 0, 15*1024);
+
+ switch(job->code)
+ {
+ case SILENTNOP:
+ {
+ job->result = 1;
+ break;
+ }
+
+ case NOP:
+ {
+ Progress(true, "Doing NOP");
+ job->result = 1;
+ break;
+ }
+
+ case PRINT_MESSAGE_AND_WAIT3SEC:
+ {
+ Progress(true, "Thread printing message: [%s]", job->message);
+ job->result = strlen(job->message);
+
+ FastOS_Thread::Sleep(3000);
+ break;
+ }
+
+ case INCREASE_NUMBER:
+ {
+ int result;
+
+ if(job->mutex != NULL)
+ job->mutex->Lock();
+
+ result = static_cast<int>(number);
+
+ int sleepOn = (INCREASE_NUMBER_AMOUNT/2) * 321/10000;
+ for(int i=0; i<(INCREASE_NUMBER_AMOUNT/2); i++)
+ {
+ number = number + 2;
+
+ if(i == sleepOn)
+ FastOS_Thread::Sleep(1000);
+ }
+
+ if(job->mutex != NULL)
+ job->mutex->Unlock();
+
+ job->result = result; // This marks the end of the thread
+
+ break;
+ }
+
+ case WAIT_FOR_BREAK_FLAG:
+ {
+ for(;;)
+ {
+ FastOS_Thread::Sleep(1000);
+
+ if(thread->GetBreakFlag())
+ {
+ Progress(true, "Thread %p got breakflag", thread);
+ break;
+ }
+ }
+ break;
+ }
+
+ case WAIT_FOR_THREAD_TO_FINISH:
+ {
+ if(job->mutex)
+ job->mutex->Lock();
+
+ if(job->otherThread != NULL)
+ job->otherThread->Join();
+
+ if(job->mutex)
+ job->mutex->Unlock();
+ break;
+ }
+
+ case WAIT_FOR_CONDITION:
+ {
+ job->condition->Lock();
+
+ job->result = 1;
+
+ job->condition->Wait();
+ job->condition->Unlock();
+
+ job->result = 0;
+
+ break;
+ }
+
+ case BOUNCE_CONDITIONS:
+ {
+ while (!thread->GetBreakFlag()) {
+ job->otherjob->condition->Lock();
+ job->otherjob->bouncewakeupcnt++;
+ job->otherjob->bouncewakeup = true;
+ job->otherjob->condition->Signal();
+ job->otherjob->condition->Unlock();
+
+ job->condition->Lock();
+ while (!job->bouncewakeup)
+ job->condition->TimedWait(1);
+ job->bouncewakeup = false;
+ job->condition->Unlock();
+ }
+ break;
+ }
+
+ case TEST_ID:
+ {
+ job->mutex->Lock(); // Initially the parent threads owns the lock
+ job->mutex->Unlock(); // It is unlocked when we should start
+
+ FastOS_ThreadId currentId = FastOS_Thread::GetCurrentThreadId();
+
+ if(currentId == job->_threadId)
+ job->result = 1;
+ else
+ job->result = -1;
+ break;
+ }
+
+ case WAIT2SEC_AND_SIGNALCOND:
+ {
+ FastOS_Thread::Sleep(2000);
+ job->condition->Signal();
+ job->result = 1;
+ break;
+ }
+
+ case HOLD_MUTEX_FOR2SEC:
+ {
+ job->mutex->Lock();
+ FastOS_Thread::Sleep(2000);
+ job->mutex->Unlock();
+ job->result = 1;
+ break;
+ }
+
+ case WAIT_2_SEC:
+ {
+ FastOS_Thread::Sleep(2000);
+ job->result = 1;
+ break;
+ }
+
+ default:
+ Progress(false, "Unknown jobcode");
+ break;
+ }
+}
+
+int main (int argc, char **argv)
+{
+ ThreadTest app;
+ setvbuf(stdout, NULL, _IOLBF, 8192);
+ return app.Entry(argc, argv);
+}
diff --git a/fastos/src/tests/thread_joinwait_test.cpp b/fastos/src/tests/thread_joinwait_test.cpp
new file mode 100644
index 00000000000..a8dec6ab140
--- /dev/null
+++ b/fastos/src/tests/thread_joinwait_test.cpp
@@ -0,0 +1,405 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <stdlib.h>
+
+#include <vespa/fastos/fastos.h>
+#include "tests.h"
+
+#define PRI_TIME_COUNT 4096
+
+enum JobCode
+{
+ PRINT_MESSAGE_AND_WAIT3SEC,
+ INCREASE_NUMBER,
+ PRIORITY_TEST,
+ WAIT_FOR_BREAK_FLAG,
+ WAIT_FOR_THREAD_TO_FINISH,
+ WAIT_FOR_CONDITION,
+ BOUNCE_CONDITIONS,
+ TEST_ID,
+ WAIT2SEC_AND_SIGNALCOND,
+ HOLD_MUTEX_FOR2SEC,
+ WAIT_2_SEC,
+ SILENTNOP,
+ NOP
+};
+
+class Job
+{
+private:
+ Job(const Job &);
+ Job &operator=(const Job&);
+
+public:
+ JobCode code;
+ char *message;
+ FastOS_Mutex *mutex;
+ FastOS_Cond *condition;
+ FastOS_BoolCond *boolcondition;
+ FastOS_ThreadInterface *otherThread, *ownThread;
+ double *timebuf;
+ double average;
+ int result;
+ FastOS_ThreadId _threadId;
+ Job *otherjob;
+ int bouncewakeupcnt;
+ bool bouncewakeup;
+
+ Job()
+ : code(NOP),
+ message(NULL),
+ mutex(NULL),
+ condition(NULL),
+ boolcondition(NULL),
+ otherThread(NULL),
+ ownThread(NULL),
+ timebuf(NULL),
+ average(0.0),
+ result(-1),
+ _threadId(),
+ otherjob(NULL),
+ bouncewakeupcnt(0),
+ bouncewakeup(false)
+ {
+ }
+
+ ~Job()
+ {
+ if(message != NULL)
+ free(message);
+ }
+};
+
+static volatile int64_t number;
+#define INCREASE_NUMBER_AMOUNT 10000
+
+#define MUTEX_TEST_THREADS 6
+#define MAX_THREADS 7
+
+
+class ThreadTest : public BaseTest, public FastOS_Runnable
+{
+private:
+ FastOS_Mutex printMutex;
+
+public:
+ ThreadTest(void)
+ : printMutex()
+ {
+ }
+ virtual ~ThreadTest() {};
+
+ void PrintProgress (char *string)
+ {
+ printMutex.Lock();
+ BaseTest::PrintProgress(string);
+ printMutex.Unlock();
+ }
+
+ void Run (FastOS_ThreadInterface *thread, void *arg);
+
+ void WaitForThreadsToFinish (Job *jobs, int count)
+ {
+ int i;
+
+ Progress(true, "Waiting for threads to finish...");
+ for(;;)
+ {
+ bool threadsFinished=true;
+
+ for(i=0; i<count; i++)
+ {
+ if(jobs[i].result == -1)
+ {
+ threadsFinished = false;
+ break;
+ }
+ }
+
+ FastOS_Thread::Sleep(500);
+
+ if(threadsFinished)
+ break;
+ }
+
+ Progress(true, "Threads finished");
+ }
+
+ int Main ();
+
+ void SingleThreadJoinWaitMultipleTest(int variant)
+ {
+ bool rc=false;
+
+ char testName[300];
+
+ sprintf(testName, "Single Thread Join Wait Multiple Test %d", variant);
+ TestHeader(testName);
+
+ FastOS_ThreadPool pool(128*1024);
+
+ const int testThreads=5;
+ int lastThreadNum = testThreads-1;
+ int i;
+
+ Job jobs[testThreads];
+
+ FastOS_Mutex jobMutex;
+
+ // The mutex is used to pause the first threads until we have created
+ // the last one.
+ jobMutex.Lock();
+
+ for(i=0; i<lastThreadNum; i++)
+ {
+ jobs[i].code = WAIT_FOR_THREAD_TO_FINISH;
+ jobs[i].mutex = &jobMutex;
+ jobs[i].ownThread = pool.NewThread(this,
+ static_cast<void *>(&jobs[i]));
+
+ rc = (jobs[i].ownThread != NULL);
+ Progress(rc, "Creating Thread %d", i+1);
+
+ if(!rc)
+ break;
+ }
+
+ if(rc)
+ {
+ jobs[lastThreadNum].code = (((variant & 2) != 0) ? NOP : PRINT_MESSAGE_AND_WAIT3SEC);
+ jobs[lastThreadNum].message = strdup("This is the thread that others wait for.");
+
+ FastOS_ThreadInterface *lastThread;
+
+ lastThread = pool.NewThread(this,
+ static_cast<void *>
+ (&jobs[lastThreadNum]));
+
+ rc = (lastThread != NULL);
+ Progress(rc, "Creating last thread");
+
+ if(rc)
+ {
+ for(i=0; i<lastThreadNum; i++)
+ {
+ jobs[i].otherThread = lastThread;
+ }
+ }
+ }
+
+ jobMutex.Unlock();
+
+ if((variant & 1) != 0)
+ {
+ for(i=0; i<lastThreadNum; i++)
+ {
+ Progress(true, "Waiting for thread %d to finish using Join()", i+1);
+ jobs[i].ownThread->Join();
+ Progress(true, "Thread %d finished.", i+1);
+ }
+ }
+
+ Progress(true, "Closing pool.");
+ pool.Close();
+ Progress(true, "Pool closed.");
+ PrintSeparator();
+ }
+
+};
+
+int ThreadTest::Main ()
+{
+ printf("grep for the string '%s' to detect failures.\n\n", failString);
+ time_t before = time(0);
+
+ SingleThreadJoinWaitMultipleTest(0);
+ { time_t now = time(0); printf("[%ld seconds]\n", now-before); before = now; }
+ SingleThreadJoinWaitMultipleTest(1);
+ { time_t now = time(0); printf("[%ld seconds]\n", now-before); before = now; }
+ SingleThreadJoinWaitMultipleTest(2);
+ { time_t now = time(0); printf("[%ld seconds]\n", now-before); before = now; }
+ SingleThreadJoinWaitMultipleTest(3);
+ { time_t now = time(0); printf("[%ld seconds]\n", now-before); before = now; }
+ SingleThreadJoinWaitMultipleTest(2);
+ { time_t now = time(0); printf("[%ld seconds]\n", now-before); before = now; }
+ SingleThreadJoinWaitMultipleTest(1);
+ { time_t now = time(0); printf("[%ld seconds]\n", now-before); before = now; }
+ SingleThreadJoinWaitMultipleTest(0);
+ { time_t now = time(0); printf("[%ld seconds]\n", now-before); before = now; }
+
+ printf("END OF TEST (%s)\n", _argv[0]);
+
+ return 0;
+}
+
+volatile int busyCnt;
+
+void ThreadTest::Run (FastOS_ThreadInterface *thread, void *arg)
+{
+ if(arg == NULL)
+ return;
+
+ Job *job = static_cast<Job *>(arg);
+ char someStack[15*1024];
+
+ memset(someStack, 0, 15*1024);
+
+ switch(job->code)
+ {
+ case SILENTNOP:
+ {
+ job->result = 1;
+ break;
+ }
+
+ case NOP:
+ {
+ Progress(true, "Doing NOP");
+ job->result = 1;
+ break;
+ }
+
+ case PRINT_MESSAGE_AND_WAIT3SEC:
+ {
+ Progress(true, "Thread printing message: [%s]", job->message);
+ job->result = strlen(job->message);
+
+ FastOS_Thread::Sleep(3000);
+ break;
+ }
+
+ case INCREASE_NUMBER:
+ {
+ int result;
+
+ if(job->mutex != NULL)
+ job->mutex->Lock();
+
+ result = static_cast<int>(number);
+
+ int sleepOn = (INCREASE_NUMBER_AMOUNT/2) * 321/10000;
+ for(int i=0; i<(INCREASE_NUMBER_AMOUNT/2); i++)
+ {
+ number = number + 2;
+
+ if(i == sleepOn)
+ FastOS_Thread::Sleep(1000);
+ }
+
+ if(job->mutex != NULL)
+ job->mutex->Unlock();
+
+ job->result = result; // This marks the end of the thread
+
+ break;
+ }
+
+ case WAIT_FOR_BREAK_FLAG:
+ {
+ for(;;)
+ {
+ FastOS_Thread::Sleep(1000);
+
+ if(thread->GetBreakFlag())
+ {
+ Progress(true, "Thread %p got breakflag", thread);
+ break;
+ }
+ }
+ break;
+ }
+
+ case WAIT_FOR_THREAD_TO_FINISH:
+ {
+ if(job->mutex)
+ job->mutex->Lock();
+
+ if(job->otherThread != NULL)
+ job->otherThread->Join();
+
+ if(job->mutex)
+ job->mutex->Unlock();
+ break;
+ }
+
+ case WAIT_FOR_CONDITION:
+ {
+ job->condition->Lock();
+
+ job->result = 1;
+
+ job->condition->Wait();
+ job->condition->Unlock();
+
+ job->result = 0;
+
+ break;
+ }
+
+ case BOUNCE_CONDITIONS:
+ {
+ while (!thread->GetBreakFlag()) {
+ job->otherjob->condition->Lock();
+ job->otherjob->bouncewakeupcnt++;
+ job->otherjob->bouncewakeup = true;
+ job->otherjob->condition->Signal();
+ job->otherjob->condition->Unlock();
+
+ job->condition->Lock();
+ while (!job->bouncewakeup)
+ job->condition->TimedWait(1);
+ job->bouncewakeup = false;
+ job->condition->Unlock();
+ }
+ break;
+ }
+
+ case TEST_ID:
+ {
+ job->mutex->Lock(); // Initially the parent threads owns the lock
+ job->mutex->Unlock(); // It is unlocked when we should start
+
+ FastOS_ThreadId currentId = FastOS_Thread::GetCurrentThreadId();
+
+ if(currentId == job->_threadId)
+ job->result = 1;
+ else
+ job->result = -1;
+ break;
+ }
+
+ case WAIT2SEC_AND_SIGNALCOND:
+ {
+ FastOS_Thread::Sleep(2000);
+ job->condition->Signal();
+ job->result = 1;
+ break;
+ }
+
+ case HOLD_MUTEX_FOR2SEC:
+ {
+ job->mutex->Lock();
+ FastOS_Thread::Sleep(2000);
+ job->mutex->Unlock();
+ job->result = 1;
+ break;
+ }
+
+ case WAIT_2_SEC:
+ {
+ FastOS_Thread::Sleep(2000);
+ job->result = 1;
+ break;
+ }
+
+ default:
+ Progress(false, "Unknown jobcode");
+ break;
+ }
+}
+
+int main (int argc, char **argv)
+{
+ ThreadTest app;
+ setvbuf(stdout, NULL, _IOLBF, 8192);
+ return app.Entry(argc, argv);
+}
diff --git a/fastos/src/tests/thread_mutex_test.cpp b/fastos/src/tests/thread_mutex_test.cpp
new file mode 100644
index 00000000000..636edae2139
--- /dev/null
+++ b/fastos/src/tests/thread_mutex_test.cpp
@@ -0,0 +1,475 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <stdlib.h>
+
+#include <vespa/fastos/fastos.h>
+#include "tests.h"
+
+#define PRI_TIME_COUNT 4096
+
+enum JobCode
+{
+ PRINT_MESSAGE_AND_WAIT3SEC,
+ INCREASE_NUMBER,
+ PRIORITY_TEST,
+ WAIT_FOR_BREAK_FLAG,
+ WAIT_FOR_THREAD_TO_FINISH,
+ WAIT_FOR_CONDITION,
+ BOUNCE_CONDITIONS,
+ TEST_ID,
+ WAIT2SEC_AND_SIGNALCOND,
+ HOLD_MUTEX_FOR2SEC,
+ WAIT_2_SEC,
+ SILENTNOP,
+ NOP
+};
+
+class Job
+{
+private:
+ Job(const Job &);
+ Job &operator=(const Job&);
+
+public:
+ JobCode code;
+ char *message;
+ FastOS_Mutex *mutex;
+ FastOS_Cond *condition;
+ FastOS_BoolCond *boolcondition;
+ FastOS_ThreadInterface *otherThread, *ownThread;
+ double *timebuf;
+ double average;
+ int result;
+ FastOS_ThreadId _threadId;
+ Job *otherjob;
+ int bouncewakeupcnt;
+ bool bouncewakeup;
+
+ Job()
+ : code(NOP),
+ message(NULL),
+ mutex(NULL),
+ condition(NULL),
+ boolcondition(NULL),
+ otherThread(NULL),
+ ownThread(NULL),
+ timebuf(NULL),
+ average(0.0),
+ result(-1),
+ _threadId(),
+ otherjob(NULL),
+ bouncewakeupcnt(0),
+ bouncewakeup(false)
+ {
+ }
+
+ ~Job()
+ {
+ if(message != NULL)
+ free(message);
+ }
+};
+
+static volatile int64_t number;
+#define INCREASE_NUMBER_AMOUNT 10000
+
+#define MUTEX_TEST_THREADS 6
+#define MAX_THREADS 7
+
+
+class ThreadTest : public BaseTest, public FastOS_Runnable
+{
+private:
+ FastOS_Mutex printMutex;
+
+public:
+ ThreadTest(void)
+ : printMutex()
+ {
+ }
+ virtual ~ThreadTest() {};
+
+ void PrintProgress (char *string)
+ {
+ printMutex.Lock();
+ BaseTest::PrintProgress(string);
+ printMutex.Unlock();
+ }
+
+ void Run (FastOS_ThreadInterface *thread, void *arg);
+
+ void WaitForThreadsToFinish (Job *jobs, int count)
+ {
+ int i;
+
+ Progress(true, "Waiting for threads to finish...");
+ for(;;)
+ {
+ bool threadsFinished=true;
+
+ for(i=0; i<count; i++)
+ {
+ if(jobs[i].result == -1)
+ {
+ threadsFinished = false;
+ break;
+ }
+ }
+
+ FastOS_Thread::Sleep(500);
+
+ if(threadsFinished)
+ break;
+ }
+
+ Progress(true, "Threads finished");
+ }
+
+ int Main ();
+
+ void MutexTest (bool usingMutex)
+ {
+ if(usingMutex)
+ TestHeader("Mutex Test");
+ else
+ TestHeader("Not Using Mutex Test");
+
+
+ FastOS_ThreadPool *pool = new FastOS_ThreadPool(128*1024, MAX_THREADS);
+
+ if(Progress(pool != NULL, "Allocating ThreadPool"))
+ {
+ int i;
+ Job jobs[MUTEX_TEST_THREADS];
+ FastOS_Mutex *myMutex=NULL;
+
+ if(usingMutex)
+ myMutex = new FastOS_Mutex();
+
+ for(i=0; i<MUTEX_TEST_THREADS; i++)
+ {
+ jobs[i].code = INCREASE_NUMBER;
+ jobs[i].mutex = myMutex;
+ }
+
+ number = 0;
+
+ for(i=0; i<MUTEX_TEST_THREADS; i++)
+ {
+ bool rc = (NULL != pool->NewThread(this,
+ static_cast<void *>(&jobs[i])));
+ Progress(rc, "Creating Thread with%s mutex", (usingMutex ? "" : "out"));
+ };
+
+ WaitForThreadsToFinish(jobs, MUTEX_TEST_THREADS);
+
+
+ for(i=0; i<MUTEX_TEST_THREADS; i++)
+ {
+ Progress(true, "Thread returned with resultcode %d", jobs[i].result);
+ }
+
+ bool wasOk=true;
+ int concurrentHits=0;
+
+ for(i=0; i<MUTEX_TEST_THREADS; i++)
+ {
+ int result = jobs[i].result;
+
+ if(usingMutex)
+ {
+ if((result % INCREASE_NUMBER_AMOUNT) != 0)
+ {
+ wasOk = false;
+ Progress(false, "Mutex locking did not work (%d).", result);
+ break;
+ }
+ }
+ else
+ {
+ if((result != 0) &&
+ (result != INCREASE_NUMBER_AMOUNT*MUTEX_TEST_THREADS) &&
+ (result % INCREASE_NUMBER_AMOUNT) == 0)
+ {
+ if((++concurrentHits) == 2)
+ {
+ wasOk = false;
+ Progress(false, "Very unlikely that threads are running "
+ "concurrently (%d)", jobs[i].result);
+ break;
+ }
+ }
+ }
+ }
+
+ if(wasOk)
+ {
+ if(usingMutex)
+ {
+ Progress(true, "Using the mutex, the returned numbers were alligned.");
+ }
+ else
+ {
+ Progress(true, "Returned numbers were not alligned. "
+ "This was the expected result.");
+ }
+ }
+
+ if(myMutex != NULL)
+ delete(myMutex);
+
+ Progress(true, "Closing threadpool...");
+ pool->Close();
+
+ Progress(true, "Deleting threadpool...");
+ delete(pool);
+ }
+ PrintSeparator();
+ }
+
+ void TryLockTest ()
+ {
+ TestHeader("Mutex TryLock Test");
+
+ FastOS_ThreadPool pool(128*1024);
+ Job job;
+ FastOS_Mutex mtx;
+
+ job.code = HOLD_MUTEX_FOR2SEC;
+ job.result = -1;
+ job.mutex = &mtx;
+ job.ownThread = pool.NewThread(this,
+ static_cast<void *>(&job));
+
+ Progress(job.ownThread !=NULL, "Creating thread");
+
+ if(job.ownThread != NULL)
+ {
+ bool lockrc;
+
+ FastOS_Thread::Sleep(1000);
+
+ for(int i=0; i<5; i++)
+ {
+ lockrc = mtx.TryLock();
+ Progress(!lockrc, "We should not get the mutex lock just yet (%s)",
+ lockrc ? "got it" : "didn't get it");
+ if(lockrc) {
+ mtx.Unlock();
+ break;
+ }
+ }
+
+ FastOS_Thread::Sleep(2000);
+
+ lockrc = mtx.TryLock();
+ Progress(lockrc, "We should get the mutex lock now (%s)",
+ lockrc ? "got it" : "didn't get it");
+
+ if(lockrc)
+ mtx.Unlock();
+
+ Progress(true, "Attempting to do normal lock...");
+ mtx.Lock();
+ Progress(true, "Got lock. Attempt to do normal unlock...");
+ mtx.Unlock();
+ Progress(true, "Unlock OK.");
+ }
+
+ Progress(true, "Waiting for threads to finish using pool.Close()...");
+ pool.Close();
+ Progress(true, "Pool closed.");
+
+ PrintSeparator();
+ }
+
+};
+
+int ThreadTest::Main ()
+{
+ printf("grep for the string '%s' to detect failures.\n\n", failString);
+ time_t before = time(0);
+
+ MutexTest(true);
+ { time_t now = time(0); printf("[%ld seconds]\n", now-before); before = now; }
+ MutexTest(false);
+ { time_t now = time(0); printf("[%ld seconds]\n", now-before); before = now; }
+ TryLockTest();
+ { time_t now = time(0); printf("[%ld seconds]\n", now-before); before = now; }
+
+ printf("END OF TEST (%s)\n", _argv[0]);
+
+ return 0;
+}
+
+volatile int busyCnt;
+
+void ThreadTest::Run (FastOS_ThreadInterface *thread, void *arg)
+{
+ if(arg == NULL)
+ return;
+
+ Job *job = static_cast<Job *>(arg);
+ char someStack[15*1024];
+
+ memset(someStack, 0, 15*1024);
+
+ switch(job->code)
+ {
+ case SILENTNOP:
+ {
+ job->result = 1;
+ break;
+ }
+
+ case NOP:
+ {
+ Progress(true, "Doing NOP");
+ job->result = 1;
+ break;
+ }
+
+ case PRINT_MESSAGE_AND_WAIT3SEC:
+ {
+ Progress(true, "Thread printing message: [%s]", job->message);
+ job->result = strlen(job->message);
+
+ FastOS_Thread::Sleep(3000);
+ break;
+ }
+
+ case INCREASE_NUMBER:
+ {
+ int result;
+
+ if(job->mutex != NULL)
+ job->mutex->Lock();
+
+ result = static_cast<int>(number);
+
+ int sleepOn = (INCREASE_NUMBER_AMOUNT/2) * 321/10000;
+ for(int i=0; i<(INCREASE_NUMBER_AMOUNT/2); i++)
+ {
+ number = number + 2;
+
+ if(i == sleepOn)
+ FastOS_Thread::Sleep(1000);
+ }
+
+ if(job->mutex != NULL)
+ job->mutex->Unlock();
+
+ job->result = result; // This marks the end of the thread
+
+ break;
+ }
+
+ case WAIT_FOR_BREAK_FLAG:
+ {
+ for(;;)
+ {
+ FastOS_Thread::Sleep(1000);
+
+ if(thread->GetBreakFlag())
+ {
+ Progress(true, "Thread %p got breakflag", thread);
+ break;
+ }
+ }
+ break;
+ }
+
+ case WAIT_FOR_THREAD_TO_FINISH:
+ {
+ if(job->mutex)
+ job->mutex->Lock();
+
+ if(job->otherThread != NULL)
+ job->otherThread->Join();
+
+ if(job->mutex)
+ job->mutex->Unlock();
+ break;
+ }
+
+ case WAIT_FOR_CONDITION:
+ {
+ job->condition->Lock();
+
+ job->result = 1;
+
+ job->condition->Wait();
+ job->condition->Unlock();
+
+ job->result = 0;
+
+ break;
+ }
+
+ case BOUNCE_CONDITIONS:
+ {
+ while (!thread->GetBreakFlag()) {
+ job->otherjob->condition->Lock();
+ job->otherjob->bouncewakeupcnt++;
+ job->otherjob->bouncewakeup = true;
+ job->otherjob->condition->Signal();
+ job->otherjob->condition->Unlock();
+
+ job->condition->Lock();
+ while (!job->bouncewakeup)
+ job->condition->TimedWait(1);
+ job->bouncewakeup = false;
+ job->condition->Unlock();
+ }
+ break;
+ }
+
+ case TEST_ID:
+ {
+ job->mutex->Lock(); // Initially the parent threads owns the lock
+ job->mutex->Unlock(); // It is unlocked when we should start
+
+ FastOS_ThreadId currentId = FastOS_Thread::GetCurrentThreadId();
+
+ if(currentId == job->_threadId)
+ job->result = 1;
+ else
+ job->result = -1;
+ break;
+ }
+
+ case WAIT2SEC_AND_SIGNALCOND:
+ {
+ FastOS_Thread::Sleep(2000);
+ job->condition->Signal();
+ job->result = 1;
+ break;
+ }
+
+ case HOLD_MUTEX_FOR2SEC:
+ {
+ job->mutex->Lock();
+ FastOS_Thread::Sleep(2000);
+ job->mutex->Unlock();
+ job->result = 1;
+ break;
+ }
+
+ case WAIT_2_SEC:
+ {
+ FastOS_Thread::Sleep(2000);
+ job->result = 1;
+ break;
+ }
+
+ default:
+ Progress(false, "Unknown jobcode");
+ break;
+ }
+}
+
+int main (int argc, char **argv)
+{
+ ThreadTest app;
+ setvbuf(stdout, NULL, _IOLBF, 8192);
+ return app.Entry(argc, argv);
+}
diff --git a/fastos/src/tests/thread_sleep_test.cpp b/fastos/src/tests/thread_sleep_test.cpp
new file mode 100644
index 00000000000..031ac30e5c9
--- /dev/null
+++ b/fastos/src/tests/thread_sleep_test.cpp
@@ -0,0 +1,338 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <stdlib.h>
+
+#include <vespa/fastos/fastos.h>
+#include "tests.h"
+
+#define PRI_TIME_COUNT 4096
+
+enum JobCode
+{
+ PRINT_MESSAGE_AND_WAIT3SEC,
+ INCREASE_NUMBER,
+ PRIORITY_TEST,
+ WAIT_FOR_BREAK_FLAG,
+ WAIT_FOR_THREAD_TO_FINISH,
+ WAIT_FOR_CONDITION,
+ BOUNCE_CONDITIONS,
+ TEST_ID,
+ WAIT2SEC_AND_SIGNALCOND,
+ HOLD_MUTEX_FOR2SEC,
+ WAIT_2_SEC,
+ SILENTNOP,
+ NOP
+};
+
+class Job
+{
+private:
+ Job(const Job &);
+ Job &operator=(const Job&);
+
+public:
+ JobCode code;
+ char *message;
+ FastOS_Mutex *mutex;
+ FastOS_Cond *condition;
+ FastOS_BoolCond *boolcondition;
+ FastOS_ThreadInterface *otherThread, *ownThread;
+ double *timebuf;
+ double average;
+ int result;
+ FastOS_ThreadId _threadId;
+ Job *otherjob;
+ int bouncewakeupcnt;
+ bool bouncewakeup;
+
+ Job()
+ : code(NOP),
+ message(NULL),
+ mutex(NULL),
+ condition(NULL),
+ boolcondition(NULL),
+ otherThread(NULL),
+ ownThread(NULL),
+ timebuf(NULL),
+ average(0.0),
+ result(-1),
+ _threadId(),
+ otherjob(NULL),
+ bouncewakeupcnt(0),
+ bouncewakeup(false)
+ {
+ }
+
+ ~Job()
+ {
+ if(message != NULL)
+ free(message);
+ }
+};
+
+static volatile int64_t number;
+#define INCREASE_NUMBER_AMOUNT 10000
+
+#define MUTEX_TEST_THREADS 6
+#define MAX_THREADS 7
+
+
+class ThreadTest : public BaseTest, public FastOS_Runnable
+{
+private:
+ FastOS_Mutex printMutex;
+
+public:
+ ThreadTest(void)
+ : printMutex()
+ {
+ }
+ virtual ~ThreadTest() {};
+
+ void PrintProgress (char *string)
+ {
+ printMutex.Lock();
+ BaseTest::PrintProgress(string);
+ printMutex.Unlock();
+ }
+
+ void Run (FastOS_ThreadInterface *thread, void *arg);
+
+ void WaitForThreadsToFinish (Job *jobs, int count)
+ {
+ int i;
+
+ Progress(true, "Waiting for threads to finish...");
+ for(;;)
+ {
+ bool threadsFinished=true;
+
+ for(i=0; i<count; i++)
+ {
+ if(jobs[i].result == -1)
+ {
+ threadsFinished = false;
+ break;
+ }
+ }
+
+ FastOS_Thread::Sleep(500);
+
+ if(threadsFinished)
+ break;
+ }
+
+ Progress(true, "Threads finished");
+ }
+
+ int Main ();
+
+ void CreateSingleThread ()
+ {
+ TestHeader("Create Single Thread Test");
+
+ FastOS_ThreadPool *pool = new FastOS_ThreadPool(128*1024);
+
+ if(Progress(pool != NULL, "Allocating ThreadPool"))
+ {
+ bool rc = (NULL != pool->NewThread(this, NULL));
+ Progress(rc, "Creating Thread");
+
+ Progress(true, "Sleeping 3 seconds");
+ FastOS_Thread::Sleep(3000);
+ }
+
+ Progress(true, "Closing threadpool...");
+ pool->Close();
+
+ Progress(true, "Deleting threadpool...");
+ delete(pool);
+ PrintSeparator();
+ }
+
+};
+
+int ThreadTest::Main ()
+{
+ printf("grep for the string '%s' to detect failures.\n\n", failString);
+ time_t before = time(0);
+
+ CreateSingleThread();
+ { time_t now = time(0); printf("[%ld seconds]\n", now-before); before = now; }
+
+ printf("END OF TEST (%s)\n", _argv[0]);
+
+ return 0;
+}
+
+volatile int busyCnt;
+
+void ThreadTest::Run (FastOS_ThreadInterface *thread, void *arg)
+{
+ if(arg == NULL)
+ return;
+
+ Job *job = static_cast<Job *>(arg);
+ char someStack[15*1024];
+
+ memset(someStack, 0, 15*1024);
+
+ switch(job->code)
+ {
+ case SILENTNOP:
+ {
+ job->result = 1;
+ break;
+ }
+
+ case NOP:
+ {
+ Progress(true, "Doing NOP");
+ job->result = 1;
+ break;
+ }
+
+ case PRINT_MESSAGE_AND_WAIT3SEC:
+ {
+ Progress(true, "Thread printing message: [%s]", job->message);
+ job->result = strlen(job->message);
+
+ FastOS_Thread::Sleep(3000);
+ break;
+ }
+
+ case INCREASE_NUMBER:
+ {
+ int result;
+
+ if(job->mutex != NULL)
+ job->mutex->Lock();
+
+ result = static_cast<int>(number);
+
+ int sleepOn = (INCREASE_NUMBER_AMOUNT/2) * 321/10000;
+ for(int i=0; i<(INCREASE_NUMBER_AMOUNT/2); i++)
+ {
+ number = number + 2;
+
+ if(i == sleepOn)
+ FastOS_Thread::Sleep(1000);
+ }
+
+ if(job->mutex != NULL)
+ job->mutex->Unlock();
+
+ job->result = result; // This marks the end of the thread
+
+ break;
+ }
+
+ case WAIT_FOR_BREAK_FLAG:
+ {
+ for(;;)
+ {
+ FastOS_Thread::Sleep(1000);
+
+ if(thread->GetBreakFlag())
+ {
+ Progress(true, "Thread %p got breakflag", thread);
+ break;
+ }
+ }
+ break;
+ }
+
+ case WAIT_FOR_THREAD_TO_FINISH:
+ {
+ if(job->mutex)
+ job->mutex->Lock();
+
+ if(job->otherThread != NULL)
+ job->otherThread->Join();
+
+ if(job->mutex)
+ job->mutex->Unlock();
+ break;
+ }
+
+ case WAIT_FOR_CONDITION:
+ {
+ job->condition->Lock();
+
+ job->result = 1;
+
+ job->condition->Wait();
+ job->condition->Unlock();
+
+ job->result = 0;
+
+ break;
+ }
+
+ case BOUNCE_CONDITIONS:
+ {
+ while (!thread->GetBreakFlag()) {
+ job->otherjob->condition->Lock();
+ job->otherjob->bouncewakeupcnt++;
+ job->otherjob->bouncewakeup = true;
+ job->otherjob->condition->Signal();
+ job->otherjob->condition->Unlock();
+
+ job->condition->Lock();
+ while (!job->bouncewakeup)
+ job->condition->TimedWait(1);
+ job->bouncewakeup = false;
+ job->condition->Unlock();
+ }
+ break;
+ }
+
+ case TEST_ID:
+ {
+ job->mutex->Lock(); // Initially the parent threads owns the lock
+ job->mutex->Unlock(); // It is unlocked when we should start
+
+ FastOS_ThreadId currentId = FastOS_Thread::GetCurrentThreadId();
+
+ if(currentId == job->_threadId)
+ job->result = 1;
+ else
+ job->result = -1;
+ break;
+ }
+
+ case WAIT2SEC_AND_SIGNALCOND:
+ {
+ FastOS_Thread::Sleep(2000);
+ job->condition->Signal();
+ job->result = 1;
+ break;
+ }
+
+ case HOLD_MUTEX_FOR2SEC:
+ {
+ job->mutex->Lock();
+ FastOS_Thread::Sleep(2000);
+ job->mutex->Unlock();
+ job->result = 1;
+ break;
+ }
+
+ case WAIT_2_SEC:
+ {
+ FastOS_Thread::Sleep(2000);
+ job->result = 1;
+ break;
+ }
+
+ default:
+ Progress(false, "Unknown jobcode");
+ break;
+ }
+}
+
+int main (int argc, char **argv)
+{
+ ThreadTest app;
+ setvbuf(stdout, NULL, _IOLBF, 8192);
+ return app.Entry(argc, argv);
+}
diff --git a/fastos/src/tests/thread_stats_test.cpp b/fastos/src/tests/thread_stats_test.cpp
new file mode 100644
index 00000000000..e38fb49a787
--- /dev/null
+++ b/fastos/src/tests/thread_stats_test.cpp
@@ -0,0 +1,422 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <stdlib.h>
+
+#include <vespa/fastos/fastos.h>
+#include "tests.h"
+
+#define PRI_TIME_COUNT 4096
+
+enum JobCode
+{
+ PRINT_MESSAGE_AND_WAIT3SEC,
+ INCREASE_NUMBER,
+ PRIORITY_TEST,
+ WAIT_FOR_BREAK_FLAG,
+ WAIT_FOR_THREAD_TO_FINISH,
+ WAIT_FOR_CONDITION,
+ BOUNCE_CONDITIONS,
+ TEST_ID,
+ WAIT2SEC_AND_SIGNALCOND,
+ HOLD_MUTEX_FOR2SEC,
+ WAIT_2_SEC,
+ SILENTNOP,
+ NOP
+};
+
+class Job
+{
+private:
+ Job(const Job &);
+ Job &operator=(const Job&);
+
+public:
+ JobCode code;
+ char *message;
+ FastOS_Mutex *mutex;
+ FastOS_Cond *condition;
+ FastOS_BoolCond *boolcondition;
+ FastOS_ThreadInterface *otherThread, *ownThread;
+ double *timebuf;
+ double average;
+ int result;
+ FastOS_ThreadId _threadId;
+ Job *otherjob;
+ int bouncewakeupcnt;
+ bool bouncewakeup;
+
+ Job()
+ : code(NOP),
+ message(NULL),
+ mutex(NULL),
+ condition(NULL),
+ boolcondition(NULL),
+ otherThread(NULL),
+ ownThread(NULL),
+ timebuf(NULL),
+ average(0.0),
+ result(-1),
+ _threadId(),
+ otherjob(NULL),
+ bouncewakeupcnt(0),
+ bouncewakeup(false)
+ {
+ }
+
+ ~Job()
+ {
+ if(message != NULL)
+ free(message);
+ }
+};
+
+static volatile int64_t number;
+#define INCREASE_NUMBER_AMOUNT 10000
+
+#define MUTEX_TEST_THREADS 6
+#define MAX_THREADS 7
+
+
+class ThreadTest : public BaseTest, public FastOS_Runnable
+{
+private:
+ FastOS_Mutex printMutex;
+
+public:
+ ThreadTest(void)
+ : printMutex()
+ {
+ }
+ virtual ~ThreadTest() {};
+
+ void PrintProgress (char *string)
+ {
+ printMutex.Lock();
+ BaseTest::PrintProgress(string);
+ printMutex.Unlock();
+ }
+
+ void Run (FastOS_ThreadInterface *thread, void *arg);
+
+ void WaitForThreadsToFinish (Job *jobs, int count)
+ {
+ int i;
+
+ Progress(true, "Waiting for threads to finish...");
+ for(;;)
+ {
+ bool threadsFinished=true;
+
+ for(i=0; i<count; i++)
+ {
+ if(jobs[i].result == -1)
+ {
+ threadsFinished = false;
+ break;
+ }
+ }
+
+ FastOS_Thread::Sleep(500);
+
+ if(threadsFinished)
+ break;
+ }
+
+ Progress(true, "Threads finished");
+ }
+
+
+ void ThreadStatsTest ()
+ {
+ int inactiveThreads;
+ int activeThreads;
+ int startedThreads;
+
+ TestHeader("Thread Statistics Test");
+
+ FastOS_ThreadPool pool(128*1024);
+ Job job[2];
+
+ inactiveThreads = pool.GetNumInactiveThreads();
+ Progress(inactiveThreads == 0, "Initial inactive threads = %d",
+ inactiveThreads);
+ activeThreads = pool.GetNumActiveThreads();
+ Progress(activeThreads == 0, "Initial active threads = %d",
+ activeThreads);
+ startedThreads = pool.GetNumStartedThreads();
+ Progress(startedThreads == 0, "Initial started threads = %d",
+ startedThreads);
+
+ job[0].code = WAIT_FOR_BREAK_FLAG;
+ job[0].ownThread = pool.NewThread(this,
+ static_cast<void *>(&job[0]));
+
+ FastOS_Thread::Sleep(1000);
+
+ inactiveThreads = pool.GetNumInactiveThreads();
+ Progress(inactiveThreads == 0, "Inactive threads = %d", inactiveThreads);
+ activeThreads = pool.GetNumActiveThreads();
+ Progress(activeThreads == 1, "Active threads = %d", activeThreads);
+ startedThreads = pool.GetNumStartedThreads();
+ Progress(startedThreads == 1, "Started threads = %d", startedThreads);
+
+ job[1].code = WAIT_FOR_BREAK_FLAG;
+ job[1].ownThread = pool.NewThread(this,
+ static_cast<void *>(&job[1]));
+
+ FastOS_Thread::Sleep(1000);
+
+ inactiveThreads = pool.GetNumInactiveThreads();
+ Progress(inactiveThreads == 0, "Inactive threads = %d", inactiveThreads);
+ activeThreads = pool.GetNumActiveThreads();
+ Progress(activeThreads == 2, "Active threads = %d", activeThreads);
+ startedThreads = pool.GetNumStartedThreads();
+ Progress(startedThreads == 2, "Started threads = %d", startedThreads);
+
+ Progress(true, "Setting breakflag on threads...");
+ job[0].ownThread->SetBreakFlag();
+ job[1].ownThread->SetBreakFlag();
+
+ FastOS_Thread::Sleep(3000);
+
+ inactiveThreads = pool.GetNumInactiveThreads();
+ Progress(inactiveThreads == 2, "Inactive threads = %d", inactiveThreads);
+ activeThreads = pool.GetNumActiveThreads();
+ Progress(activeThreads == 0, "Active threads = %d", activeThreads);
+ startedThreads = pool.GetNumStartedThreads();
+ Progress(startedThreads == 2, "Started threads = %d", startedThreads);
+
+
+ Progress(true, "Repeating process in the same pool...");
+
+ job[0].code = WAIT_FOR_BREAK_FLAG;
+ job[0].ownThread = pool.NewThread(this, static_cast<void *>(&job[0]));
+
+ FastOS_Thread::Sleep(1000);
+
+ inactiveThreads = pool.GetNumInactiveThreads();
+ Progress(inactiveThreads == 1, "Inactive threads = %d", inactiveThreads);
+ activeThreads = pool.GetNumActiveThreads();
+ Progress(activeThreads == 1, "Active threads = %d", activeThreads);
+ startedThreads = pool.GetNumStartedThreads();
+ Progress(startedThreads == 3, "Started threads = %d", startedThreads);
+
+ job[1].code = WAIT_FOR_BREAK_FLAG;
+ job[1].ownThread = pool.NewThread(this, static_cast<void *>(&job[1]));
+
+ FastOS_Thread::Sleep(1000);
+
+ inactiveThreads = pool.GetNumInactiveThreads();
+ Progress(inactiveThreads == 0, "Inactive threads = %d", inactiveThreads);
+ activeThreads = pool.GetNumActiveThreads();
+ Progress(activeThreads == 2, "Active threads = %d", activeThreads);
+ startedThreads = pool.GetNumStartedThreads();
+ Progress(startedThreads == 4, "Started threads = %d", startedThreads);
+
+ Progress(true, "Setting breakflag on threads...");
+ job[0].ownThread->SetBreakFlag();
+ job[1].ownThread->SetBreakFlag();
+
+ FastOS_Thread::Sleep(3000);
+
+ inactiveThreads = pool.GetNumInactiveThreads();
+ Progress(inactiveThreads == 2, "Inactive threads = %d", inactiveThreads);
+ activeThreads = pool.GetNumActiveThreads();
+ Progress(activeThreads == 0, "Active threads = %d", activeThreads);
+ startedThreads = pool.GetNumStartedThreads();
+ Progress(startedThreads == 4, "Started threads = %d", startedThreads);
+
+
+ pool.Close();
+ Progress(true, "Pool closed.");
+
+ PrintSeparator();
+ }
+
+ int Main ();
+};
+
+int ThreadTest::Main ()
+{
+ printf("grep for the string '%s' to detect failures.\n\n", failString);
+ time_t before = time(0);
+
+ ThreadStatsTest();
+ { time_t now = time(0); printf("[%ld seconds]\n", now-before); before = now; }
+
+ printf("END OF TEST (%s)\n", _argv[0]);
+
+ return 0;
+}
+
+volatile int busyCnt;
+
+void ThreadTest::Run (FastOS_ThreadInterface *thread, void *arg)
+{
+ if(arg == NULL)
+ return;
+
+ Job *job = static_cast<Job *>(arg);
+ char someStack[15*1024];
+
+ memset(someStack, 0, 15*1024);
+
+ switch(job->code)
+ {
+ case SILENTNOP:
+ {
+ job->result = 1;
+ break;
+ }
+
+ case NOP:
+ {
+ Progress(true, "Doing NOP");
+ job->result = 1;
+ break;
+ }
+
+ case PRINT_MESSAGE_AND_WAIT3SEC:
+ {
+ Progress(true, "Thread printing message: [%s]", job->message);
+ job->result = strlen(job->message);
+
+ FastOS_Thread::Sleep(3000);
+ break;
+ }
+
+ case INCREASE_NUMBER:
+ {
+ int result;
+
+ if(job->mutex != NULL)
+ job->mutex->Lock();
+
+ result = static_cast<int>(number);
+
+ int sleepOn = (INCREASE_NUMBER_AMOUNT/2) * 321/10000;
+ for(int i=0; i<(INCREASE_NUMBER_AMOUNT/2); i++)
+ {
+ number = number + 2;
+
+ if(i == sleepOn)
+ FastOS_Thread::Sleep(1000);
+ }
+
+ if(job->mutex != NULL)
+ job->mutex->Unlock();
+
+ job->result = result; // This marks the end of the thread
+
+ break;
+ }
+
+ case WAIT_FOR_BREAK_FLAG:
+ {
+ for(;;)
+ {
+ FastOS_Thread::Sleep(1000);
+
+ if(thread->GetBreakFlag())
+ {
+ Progress(true, "Thread %p got breakflag", thread);
+ break;
+ }
+ }
+ break;
+ }
+
+ case WAIT_FOR_THREAD_TO_FINISH:
+ {
+ if(job->mutex)
+ job->mutex->Lock();
+
+ if(job->otherThread != NULL)
+ job->otherThread->Join();
+
+ if(job->mutex)
+ job->mutex->Unlock();
+ break;
+ }
+
+ case WAIT_FOR_CONDITION:
+ {
+ job->condition->Lock();
+
+ job->result = 1;
+
+ job->condition->Wait();
+ job->condition->Unlock();
+
+ job->result = 0;
+
+ break;
+ }
+
+ case BOUNCE_CONDITIONS:
+ {
+ while (!thread->GetBreakFlag()) {
+ job->otherjob->condition->Lock();
+ job->otherjob->bouncewakeupcnt++;
+ job->otherjob->bouncewakeup = true;
+ job->otherjob->condition->Signal();
+ job->otherjob->condition->Unlock();
+
+ job->condition->Lock();
+ while (!job->bouncewakeup)
+ job->condition->TimedWait(1);
+ job->bouncewakeup = false;
+ job->condition->Unlock();
+ }
+ break;
+ }
+
+ case TEST_ID:
+ {
+ job->mutex->Lock(); // Initially the parent threads owns the lock
+ job->mutex->Unlock(); // It is unlocked when we should start
+
+ FastOS_ThreadId currentId = FastOS_Thread::GetCurrentThreadId();
+
+ if(currentId == job->_threadId)
+ job->result = 1;
+ else
+ job->result = -1;
+ break;
+ }
+
+ case WAIT2SEC_AND_SIGNALCOND:
+ {
+ FastOS_Thread::Sleep(2000);
+ job->condition->Signal();
+ job->result = 1;
+ break;
+ }
+
+ case HOLD_MUTEX_FOR2SEC:
+ {
+ job->mutex->Lock();
+ FastOS_Thread::Sleep(2000);
+ job->mutex->Unlock();
+ job->result = 1;
+ break;
+ }
+
+ case WAIT_2_SEC:
+ {
+ FastOS_Thread::Sleep(2000);
+ job->result = 1;
+ break;
+ }
+
+ default:
+ Progress(false, "Unknown jobcode");
+ break;
+ }
+}
+
+int main (int argc, char **argv)
+{
+ ThreadTest app;
+ setvbuf(stdout, NULL, _IOLBF, 8192);
+ return app.Entry(argc, argv);
+}
diff --git a/fastos/src/tests/threadtest.cpp b/fastos/src/tests/threadtest.cpp
index ed39f8df189..88254108557 100644
--- a/fastos/src/tests/threadtest.cpp
+++ b/fastos/src/tests/threadtest.cpp
@@ -124,106 +124,7 @@ public:
Progress(true, "Threads finished");
}
- void MutexTest (bool usingMutex)
- {
- if(usingMutex)
- TestHeader("Mutex Test");
- else
- TestHeader("Not Using Mutex Test");
-
-
- FastOS_ThreadPool *pool = new FastOS_ThreadPool(128*1024, MAX_THREADS);
-
- if(Progress(pool != NULL, "Allocating ThreadPool"))
- {
- int i;
- Job jobs[MUTEX_TEST_THREADS];
- FastOS_Mutex *myMutex=NULL;
-
- if(usingMutex)
- myMutex = new FastOS_Mutex();
-
- for(i=0; i<MUTEX_TEST_THREADS; i++)
- {
- jobs[i].code = INCREASE_NUMBER;
- jobs[i].mutex = myMutex;
- }
-
- number = 0;
-
- for(i=0; i<MUTEX_TEST_THREADS; i++)
- {
- bool rc = (NULL != pool->NewThread(this,
- static_cast<void *>(&jobs[i])));
- Progress(rc, "Creating Thread with%s mutex", (usingMutex ? "" : "out"));
- };
-
- WaitForThreadsToFinish(jobs, MUTEX_TEST_THREADS);
-
-
- for(i=0; i<MUTEX_TEST_THREADS; i++)
- {
- Progress(true, "Thread returned with resultcode %d", jobs[i].result);
- }
-
- bool wasOk=true;
- int concurrentHits=0;
-
- for(i=0; i<MUTEX_TEST_THREADS; i++)
- {
- int result = jobs[i].result;
-
- if(usingMutex)
- {
- if((result % INCREASE_NUMBER_AMOUNT) != 0)
- {
- wasOk = false;
- Progress(false, "Mutex locking did not work (%d).", result);
- break;
- }
- }
- else
- {
- if((result != 0) &&
- (result != INCREASE_NUMBER_AMOUNT*MUTEX_TEST_THREADS) &&
- (result % INCREASE_NUMBER_AMOUNT) == 0)
- {
- if((++concurrentHits) == 2)
- {
- wasOk = false;
- Progress(false, "Very unlikely that threads are running "
- "concurrently (%d)", jobs[i].result);
- break;
- }
- }
- }
- }
-
- if(wasOk)
- {
- if(usingMutex)
- {
- Progress(true, "Using the mutex, the returned numbers were alligned.");
- }
- else
- {
- Progress(true, "Returned numbers were not alligned. "
- "This was the expected result.");
- }
- }
-
- if(myMutex != NULL)
- delete(myMutex);
-
- Progress(true, "Closing threadpool...");
- pool->Close();
-
- Progress(true, "Deleting threadpool...");
- delete(pool);
- }
- PrintSeparator();
- }
-
+ int Main ();
void TooManyThreadsTest ()
{
@@ -335,29 +236,6 @@ public:
PrintSeparator();
}
- void CreateSingleThread ()
- {
- TestHeader("Create Single Thread Test");
-
- FastOS_ThreadPool *pool = new FastOS_ThreadPool(128*1024);
-
- if(Progress(pool != NULL, "Allocating ThreadPool"))
- {
- bool rc = (NULL != pool->NewThread(this, NULL));
- Progress(rc, "Creating Thread");
-
- Progress(true, "Sleeping 3 seconds");
- FastOS_Thread::Sleep(3000);
- }
-
- Progress(true, "Closing threadpool...");
- pool->Close();
-
- Progress(true, "Deleting threadpool...");
- delete(pool);
- PrintSeparator();
- }
-
void CreateSingleThreadAndJoin ()
{
TestHeader("Create Single Thread And Join Test");
@@ -522,84 +400,6 @@ public:
PrintSeparator();
}
- void SingleThreadJoinWaitMultipleTest(int variant)
- {
- bool rc=false;
-
- char testName[300];
-
- sprintf(testName, "Single Thread Join Wait Multiple Test %d", variant);
- TestHeader(testName);
-
- FastOS_ThreadPool pool(128*1024);
-
- const int testThreads=5;
- int lastThreadNum = testThreads-1;
- int i;
-
- Job jobs[testThreads];
-
- FastOS_Mutex jobMutex;
-
- // The mutex is used to pause the first threads until we have created
- // the last one.
- jobMutex.Lock();
-
- for(i=0; i<lastThreadNum; i++)
- {
- jobs[i].code = WAIT_FOR_THREAD_TO_FINISH;
- jobs[i].mutex = &jobMutex;
- jobs[i].ownThread = pool.NewThread(this,
- static_cast<void *>(&jobs[i]));
-
- rc = (jobs[i].ownThread != NULL);
- Progress(rc, "Creating Thread %d", i+1);
-
- if(!rc)
- break;
- }
-
- if(rc)
- {
- jobs[lastThreadNum].code = (((variant & 2) != 0) ? NOP : PRINT_MESSAGE_AND_WAIT3SEC);
- jobs[lastThreadNum].message = strdup("This is the thread that others wait for.");
-
- FastOS_ThreadInterface *lastThread;
-
- lastThread = pool.NewThread(this,
- static_cast<void *>
- (&jobs[lastThreadNum]));
-
- rc = (lastThread != NULL);
- Progress(rc, "Creating last thread");
-
- if(rc)
- {
- for(i=0; i<lastThreadNum; i++)
- {
- jobs[i].otherThread = lastThread;
- }
- }
- }
-
- jobMutex.Unlock();
-
- if((variant & 1) != 0)
- {
- for(i=0; i<lastThreadNum; i++)
- {
- Progress(true, "Waiting for thread %d to finish using Join()", i+1);
- jobs[i].ownThread->Join();
- Progress(true, "Thread %d finished.", i+1);
- }
- }
-
- Progress(true, "Closing pool.");
- pool.Close();
- Progress(true, "Pool closed.");
- PrintSeparator();
- }
-
void WaitForXThreadsToHaveWait (Job *jobs,
int jobCount,
FastOS_Cond *condition,
@@ -658,70 +458,6 @@ public:
FastOS_Thread::Sleep(1000);
}
- void BounceTest(void)
- {
- TestHeader("Bounce Test");
-
- FastOS_ThreadPool pool(128 * 1024);
- FastOS_Cond cond1;
- FastOS_Cond cond2;
- Job job1;
- Job job2;
- FastOS_Time checkTime;
- int cnt1;
- int cnt2;
- int cntsum;
- int lastcntsum;
-
- job1.code = BOUNCE_CONDITIONS;
- job2.code = BOUNCE_CONDITIONS;
- job1.otherjob = &job2;
- job2.otherjob = &job1;
- job1.condition = &cond1;
- job2.condition = &cond2;
-
- job1.ownThread = pool.NewThread(this, static_cast<void *>(&job1));
- job2.ownThread = pool.NewThread(this, static_cast<void *>(&job2));
-
- lastcntsum = -1;
- for (int iter = 0; iter < 8; iter++) {
- checkTime.SetNow();
-
- int left = static_cast<int>(checkTime.MilliSecsToNow());
- while (left < 1000) {
- FastOS_Thread::Sleep(1000 - left);
- left = static_cast<int>(checkTime.MilliSecsToNow());
- }
-
- cond1.Lock();
- cnt1 = job1.bouncewakeupcnt;
- cond1.Unlock();
- cond2.Lock();
- cnt2 = job2.bouncewakeupcnt;
- cond2.Unlock();
- cntsum = cnt1 + cnt2;
- Progress(lastcntsum != cntsum, "%d bounces", cntsum);
- lastcntsum = cntsum;
- }
-
- job1.ownThread->SetBreakFlag();
- cond1.Lock();
- job1.bouncewakeup = true;
- cond1.Signal();
- cond1.Unlock();
-
- job2.ownThread->SetBreakFlag();
- cond2.Lock();
- job2.bouncewakeup = true;
- cond2.Signal();
- cond2.Unlock();
-
- pool.Close();
- Progress(true, "Pool closed.");
- PrintSeparator();
-
- }
-
void SignalTest ()
{
const int numThreads = 5;
@@ -857,171 +593,6 @@ public:
PrintSeparator();
}
- void TryLockTest ()
- {
- TestHeader("Mutex TryLock Test");
-
- FastOS_ThreadPool pool(128*1024);
- Job job;
- FastOS_Mutex mtx;
-
- job.code = HOLD_MUTEX_FOR2SEC;
- job.result = -1;
- job.mutex = &mtx;
- job.ownThread = pool.NewThread(this,
- static_cast<void *>(&job));
-
- Progress(job.ownThread !=NULL, "Creating thread");
-
- if(job.ownThread != NULL)
- {
- bool lockrc;
-
- FastOS_Thread::Sleep(1000);
-
- for(int i=0; i<5; i++)
- {
- lockrc = mtx.TryLock();
- Progress(!lockrc, "We should not get the mutex lock just yet (%s)",
- lockrc ? "got it" : "didn't get it");
- if(lockrc) {
- mtx.Unlock();
- break;
- }
- }
-
- FastOS_Thread::Sleep(2000);
-
- lockrc = mtx.TryLock();
- Progress(lockrc, "We should get the mutex lock now (%s)",
- lockrc ? "got it" : "didn't get it");
-
- if(lockrc)
- mtx.Unlock();
-
- Progress(true, "Attempting to do normal lock...");
- mtx.Lock();
- Progress(true, "Got lock. Attempt to do normal unlock...");
- mtx.Unlock();
- Progress(true, "Unlock OK.");
- }
-
- Progress(true, "Waiting for threads to finish using pool.Close()...");
- pool.Close();
- Progress(true, "Pool closed.");
-
- PrintSeparator();
- }
-
- void ThreadStatsTest ()
- {
- int inactiveThreads;
- int activeThreads;
- int startedThreads;
-
- TestHeader("Thread Statistics Test");
-
- FastOS_ThreadPool pool(128*1024);
- Job job[2];
-
- inactiveThreads = pool.GetNumInactiveThreads();
- Progress(inactiveThreads == 0, "Initial inactive threads = %d",
- inactiveThreads);
- activeThreads = pool.GetNumActiveThreads();
- Progress(activeThreads == 0, "Initial active threads = %d",
- activeThreads);
- startedThreads = pool.GetNumStartedThreads();
- Progress(startedThreads == 0, "Initial started threads = %d",
- startedThreads);
-
- job[0].code = WAIT_FOR_BREAK_FLAG;
- job[0].ownThread = pool.NewThread(this,
- static_cast<void *>(&job[0]));
-
- FastOS_Thread::Sleep(1000);
-
- inactiveThreads = pool.GetNumInactiveThreads();
- Progress(inactiveThreads == 0, "Inactive threads = %d", inactiveThreads);
- activeThreads = pool.GetNumActiveThreads();
- Progress(activeThreads == 1, "Active threads = %d", activeThreads);
- startedThreads = pool.GetNumStartedThreads();
- Progress(startedThreads == 1, "Started threads = %d", startedThreads);
-
- job[1].code = WAIT_FOR_BREAK_FLAG;
- job[1].ownThread = pool.NewThread(this,
- static_cast<void *>(&job[1]));
-
- FastOS_Thread::Sleep(1000);
-
- inactiveThreads = pool.GetNumInactiveThreads();
- Progress(inactiveThreads == 0, "Inactive threads = %d", inactiveThreads);
- activeThreads = pool.GetNumActiveThreads();
- Progress(activeThreads == 2, "Active threads = %d", activeThreads);
- startedThreads = pool.GetNumStartedThreads();
- Progress(startedThreads == 2, "Started threads = %d", startedThreads);
-
- Progress(true, "Setting breakflag on threads...");
- job[0].ownThread->SetBreakFlag();
- job[1].ownThread->SetBreakFlag();
-
- FastOS_Thread::Sleep(3000);
-
- inactiveThreads = pool.GetNumInactiveThreads();
- Progress(inactiveThreads == 2, "Inactive threads = %d", inactiveThreads);
- activeThreads = pool.GetNumActiveThreads();
- Progress(activeThreads == 0, "Active threads = %d", activeThreads);
- startedThreads = pool.GetNumStartedThreads();
- Progress(startedThreads == 2, "Started threads = %d", startedThreads);
-
-
- Progress(true, "Repeating process in the same pool...");
-
- job[0].code = WAIT_FOR_BREAK_FLAG;
- job[0].ownThread = pool.NewThread(this, static_cast<void *>(&job[0]));
-
- FastOS_Thread::Sleep(1000);
-
- inactiveThreads = pool.GetNumInactiveThreads();
- Progress(inactiveThreads == 1, "Inactive threads = %d", inactiveThreads);
- activeThreads = pool.GetNumActiveThreads();
- Progress(activeThreads == 1, "Active threads = %d", activeThreads);
- startedThreads = pool.GetNumStartedThreads();
- Progress(startedThreads == 3, "Started threads = %d", startedThreads);
-
- job[1].code = WAIT_FOR_BREAK_FLAG;
- job[1].ownThread = pool.NewThread(this, static_cast<void *>(&job[1]));
-
- FastOS_Thread::Sleep(1000);
-
- inactiveThreads = pool.GetNumInactiveThreads();
- Progress(inactiveThreads == 0, "Inactive threads = %d", inactiveThreads);
- activeThreads = pool.GetNumActiveThreads();
- Progress(activeThreads == 2, "Active threads = %d", activeThreads);
- startedThreads = pool.GetNumStartedThreads();
- Progress(startedThreads == 4, "Started threads = %d", startedThreads);
-
- Progress(true, "Setting breakflag on threads...");
- job[0].ownThread->SetBreakFlag();
- job[1].ownThread->SetBreakFlag();
-
- FastOS_Thread::Sleep(3000);
-
- inactiveThreads = pool.GetNumInactiveThreads();
- Progress(inactiveThreads == 2, "Inactive threads = %d", inactiveThreads);
- activeThreads = pool.GetNumActiveThreads();
- Progress(activeThreads == 0, "Active threads = %d", activeThreads);
- startedThreads = pool.GetNumStartedThreads();
- Progress(startedThreads == 4, "Started threads = %d", startedThreads);
-
-
- pool.Close();
- Progress(true, "Pool closed.");
-
- PrintSeparator();
- }
-
- int Main ();
-
void LeakTest ()
{
TestHeader("Leak Test");
@@ -1111,42 +682,25 @@ public:
int ThreadTest::Main ()
{
printf("grep for the string '%s' to detect failures.\n\n", failString);
+ time_t before = time(0);
// HowManyThreadsTest();
SynchronizationStressTest();
LeakTest();
- ThreadStatsTest();
- TryLockTest();
TimedWaitTest();
ThreadIdTest();
SignalTest();
BroadcastTest();
- CreateSingleThread();
CreateSingleThreadAndJoin();
TooManyThreadsTest();
- MutexTest(false);
- MutexTest(true);
ClosePoolTest();
BreakFlagTest();
- SingleThreadJoinWaitMultipleTest(0);
- SingleThreadJoinWaitMultipleTest(1);
- SingleThreadJoinWaitMultipleTest(2);
- SingleThreadJoinWaitMultipleTest(3);
- SingleThreadJoinWaitMultipleTest(2);
- SingleThreadJoinWaitMultipleTest(1);
- SingleThreadJoinWaitMultipleTest(0);
- BreakFlagTest();
- ClosePoolTest();
- MutexTest(true);
- MutexTest(false);
- TooManyThreadsTest();
CreateSingleThreadAndJoin();
- CreateSingleThread();
BroadcastTest();
SignalTest();
ThreadCreatePerformance(false, 500, 100);
ClosePoolStability();
- BounceTest();
+ { time_t now = time(0); printf("[%ld seconds]\n", now-before); before = now; }
printf("END OF TEST (%s)\n", _argv[0]);