* Added a framework for unit tests in the kernel. The beast is implemented as
a driver which publishes a device as "/dev/kernel_unit_tests". Commands can be issued by writing to the device (e.g. "echo help > /dev/kernel_unit_tests"), output is written to serial port/ syslog. * Added a few tests for rw_lock. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@34827 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
parent
3ce2634533
commit
933764d70e
|
@ -85,4 +85,5 @@ SubInclude HAIKU_TOP src tests system kernel device_manager ;
|
||||||
SubInclude HAIKU_TOP src tests system kernel scheduler ;
|
SubInclude HAIKU_TOP src tests system kernel scheduler ;
|
||||||
SubInclude HAIKU_TOP src tests system kernel slab ;
|
SubInclude HAIKU_TOP src tests system kernel slab ;
|
||||||
SubInclude HAIKU_TOP src tests system kernel swap ;
|
SubInclude HAIKU_TOP src tests system kernel swap ;
|
||||||
|
SubInclude HAIKU_TOP src tests system kernel unit ;
|
||||||
SubInclude HAIKU_TOP src tests system kernel util ;
|
SubInclude HAIKU_TOP src tests system kernel util ;
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
SubDir HAIKU_TOP src tests system kernel unit ;
|
||||||
|
|
||||||
|
UsePrivateKernelHeaders ;
|
||||||
|
|
||||||
|
|
||||||
|
KernelAddon kernel_unit_tests :
|
||||||
|
kernel_unit_tests.cpp
|
||||||
|
Test.cpp
|
||||||
|
TestContext.cpp
|
||||||
|
TestError.cpp
|
||||||
|
TestManager.cpp
|
||||||
|
TestOutput.cpp
|
||||||
|
TestSuite.cpp
|
||||||
|
TestVisitor.cpp
|
||||||
|
|
||||||
|
:
|
||||||
|
<nogrist>kernel_unit_tests_lock.o
|
||||||
|
|
||||||
|
$(TARGET_STATIC_LIBSUPC++)
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
HaikuSubInclude lock ;
|
|
@ -0,0 +1,93 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "Test.h"
|
||||||
|
|
||||||
|
#include "TestVisitor.h"
|
||||||
|
|
||||||
|
|
||||||
|
// #pragma mark - Test
|
||||||
|
|
||||||
|
|
||||||
|
Test::Test(const char* name)
|
||||||
|
:
|
||||||
|
fName(name),
|
||||||
|
fSuite(NULL)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Test::~Test()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
Test::SetSuite(TestSuite* suite)
|
||||||
|
{
|
||||||
|
fSuite = suite;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
Test::IsLeafTest() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
Test::Setup(TestContext& context)
|
||||||
|
{
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
Test::Run(TestContext& context, const char* name)
|
||||||
|
{
|
||||||
|
// TODO: Report error!
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
Test::Cleanup(TestContext& context, bool setupOK)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Test*
|
||||||
|
Test::Visit(TestVisitor& visitor)
|
||||||
|
{
|
||||||
|
return visitor.VisitTest(this) ? this : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #pragma mark - StandardTestDelegate
|
||||||
|
|
||||||
|
|
||||||
|
StandardTestDelegate::StandardTestDelegate()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
StandardTestDelegate::~StandardTestDelegate()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
StandardTestDelegate::Setup(TestContext& context)
|
||||||
|
{
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
StandardTestDelegate::Cleanup(TestContext& context, bool setupOK)
|
||||||
|
{
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
#ifndef TEST_H
|
||||||
|
#define TEST_H
|
||||||
|
|
||||||
|
|
||||||
|
#include "TestContext.h"
|
||||||
|
|
||||||
|
|
||||||
|
class TestSuite;
|
||||||
|
class TestVisitor;
|
||||||
|
|
||||||
|
|
||||||
|
class Test {
|
||||||
|
public:
|
||||||
|
Test(const char* name);
|
||||||
|
virtual ~Test();
|
||||||
|
|
||||||
|
const char* Name() const { return fName; }
|
||||||
|
|
||||||
|
TestSuite* Suite() const { return fSuite; }
|
||||||
|
void SetSuite(TestSuite* suite);
|
||||||
|
|
||||||
|
virtual bool IsLeafTest() const;
|
||||||
|
virtual status_t Setup(TestContext& context);
|
||||||
|
virtual bool Run(TestContext& context) = 0;
|
||||||
|
virtual bool Run(TestContext& context, const char* name);
|
||||||
|
virtual void Cleanup(TestContext& context, bool setupOK);
|
||||||
|
|
||||||
|
virtual Test* Visit(TestVisitor& visitor);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const char* fName;
|
||||||
|
TestSuite* fSuite;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class StandardTestDelegate {
|
||||||
|
public:
|
||||||
|
StandardTestDelegate();
|
||||||
|
virtual ~StandardTestDelegate();
|
||||||
|
|
||||||
|
virtual status_t Setup(TestContext& context);
|
||||||
|
virtual void Cleanup(TestContext& context, bool setupOK);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<typename TestClass>
|
||||||
|
class StandardTest : public Test {
|
||||||
|
public:
|
||||||
|
StandardTest(const char* name,
|
||||||
|
TestClass* object,
|
||||||
|
bool (TestClass::*method)(TestContext&));
|
||||||
|
virtual ~StandardTest();
|
||||||
|
|
||||||
|
virtual status_t Setup(TestContext& context);
|
||||||
|
virtual bool Run(TestContext& context);
|
||||||
|
virtual void Cleanup(TestContext& context, bool setupOK);
|
||||||
|
|
||||||
|
private:
|
||||||
|
TestClass* fObject;
|
||||||
|
bool (TestClass::*fMethod)(TestContext&);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<typename TestClass>
|
||||||
|
StandardTest<TestClass>::StandardTest(const char* name, TestClass* object,
|
||||||
|
bool (TestClass::*method)(TestContext&))
|
||||||
|
:
|
||||||
|
Test(name),
|
||||||
|
fObject(object),
|
||||||
|
fMethod(method)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template<typename TestClass>
|
||||||
|
StandardTest<TestClass>::~StandardTest()
|
||||||
|
{
|
||||||
|
delete fObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template<typename TestClass>
|
||||||
|
status_t
|
||||||
|
StandardTest<TestClass>::Setup(TestContext& context)
|
||||||
|
{
|
||||||
|
return fObject->Setup(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template<typename TestClass>
|
||||||
|
bool
|
||||||
|
StandardTest<TestClass>::Run(TestContext& context)
|
||||||
|
{
|
||||||
|
return (fObject->*fMethod)(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template<typename TestClass>
|
||||||
|
void
|
||||||
|
StandardTest<TestClass>::Cleanup(TestContext& context, bool setupOK)
|
||||||
|
{
|
||||||
|
fObject->Cleanup(context, setupOK);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#define TEST_ASSERT(condition) \
|
||||||
|
do { \
|
||||||
|
if (!(condition)) { \
|
||||||
|
TestContext::Current()->AssertFailed(__FILE__, __LINE__, \
|
||||||
|
__PRETTY_FUNCTION__, #condition); \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
#define TEST_ASSERT_PRINT(condition, format...) \
|
||||||
|
do { \
|
||||||
|
if (!(condition)) { \
|
||||||
|
TestContext::Current()->AssertFailed(__FILE__, __LINE__, \
|
||||||
|
__PRETTY_FUNCTION__, #condition, format); \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
#define TEST_PROPAGATE(result) \
|
||||||
|
do { \
|
||||||
|
if (!(result)) \
|
||||||
|
return false; \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
|
||||||
|
#endif // TEST_H
|
|
@ -0,0 +1,283 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "TestContext.h"
|
||||||
|
|
||||||
|
#include <util/AutoLock.h>
|
||||||
|
|
||||||
|
#include "Test.h"
|
||||||
|
#include "TestError.h"
|
||||||
|
|
||||||
|
|
||||||
|
static spinlock sLock = B_SPINLOCK_INITIALIZER;
|
||||||
|
|
||||||
|
|
||||||
|
// #pragma mark - TestOptions
|
||||||
|
|
||||||
|
|
||||||
|
TestOptions::TestOptions()
|
||||||
|
:
|
||||||
|
panicOnFailure(false),
|
||||||
|
quitAfterFailure(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #pragma mark - GlobalTestContext
|
||||||
|
|
||||||
|
|
||||||
|
struct GlobalTestContext::ThreadCookie {
|
||||||
|
GlobalTestContext* context;
|
||||||
|
thread_func function;
|
||||||
|
void* arg;
|
||||||
|
|
||||||
|
ThreadCookie(GlobalTestContext* context, thread_func function, void* arg)
|
||||||
|
:
|
||||||
|
context(context),
|
||||||
|
function(function),
|
||||||
|
arg(arg)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*static*/ GlobalTestContext::ThreadEntry* GlobalTestContext::sGlobalThreads
|
||||||
|
= NULL;
|
||||||
|
|
||||||
|
|
||||||
|
GlobalTestContext::GlobalTestContext(TestOutput& output, TestOptions& options)
|
||||||
|
:
|
||||||
|
fThreads(NULL),
|
||||||
|
fThreadEntry(this),
|
||||||
|
fOutput(output),
|
||||||
|
fOptions(options),
|
||||||
|
fCurrentContext(NULL),
|
||||||
|
fTotalTests(0),
|
||||||
|
fFailedTests(0)
|
||||||
|
{
|
||||||
|
_SetCurrent(&fThreadEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GlobalTestContext::~GlobalTestContext()
|
||||||
|
{
|
||||||
|
InterruptsSpinLocker locker(sLock);
|
||||||
|
|
||||||
|
// remove all of our entries from the global list
|
||||||
|
ThreadEntry** entry = &sGlobalThreads;
|
||||||
|
while (*entry != NULL) {
|
||||||
|
if ((*entry)->context == this)
|
||||||
|
*entry = (*entry)->globalNext;
|
||||||
|
else
|
||||||
|
entry = &(*entry)->globalNext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*static*/ GlobalTestContext*
|
||||||
|
GlobalTestContext::Current()
|
||||||
|
{
|
||||||
|
thread_id thread = find_thread(NULL);
|
||||||
|
|
||||||
|
InterruptsSpinLocker locker(sLock);
|
||||||
|
|
||||||
|
ThreadEntry* entry = sGlobalThreads;
|
||||||
|
while (entry != NULL) {
|
||||||
|
if (entry->thread == thread)
|
||||||
|
return entry->context;
|
||||||
|
entry = entry->globalNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
GlobalTestContext::SetCurrentContext(TestContext* context)
|
||||||
|
{
|
||||||
|
fCurrentContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
GlobalTestContext::TestDone(bool success)
|
||||||
|
{
|
||||||
|
fTotalTests++;
|
||||||
|
if (!success)
|
||||||
|
fFailedTests++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
thread_id
|
||||||
|
GlobalTestContext::SpawnThread(thread_func function, const char* name,
|
||||||
|
int32 priority, void* arg)
|
||||||
|
{
|
||||||
|
ThreadCookie* cookie = new(std::nothrow) ThreadCookie(this, function, arg);
|
||||||
|
if (cookie == NULL)
|
||||||
|
return B_NO_MEMORY;
|
||||||
|
|
||||||
|
thread_id thread = spawn_kernel_thread(_ThreadEntry, name, priority,
|
||||||
|
cookie);
|
||||||
|
if (thread < 0) {
|
||||||
|
delete cookie;
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*static*/ void
|
||||||
|
GlobalTestContext::_SetCurrent(ThreadEntry* entry)
|
||||||
|
{
|
||||||
|
InterruptsSpinLocker locker(sLock);
|
||||||
|
|
||||||
|
entry->contextNext = entry->context->fThreads;
|
||||||
|
entry->context->fThreads = entry;
|
||||||
|
|
||||||
|
entry->globalNext = sGlobalThreads;
|
||||||
|
sGlobalThreads = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*static*/ void
|
||||||
|
GlobalTestContext::_UnsetCurrent(ThreadEntry* entryToRemove)
|
||||||
|
{
|
||||||
|
InterruptsSpinLocker locker(sLock);
|
||||||
|
|
||||||
|
// remove from the global list
|
||||||
|
ThreadEntry** entry = &sGlobalThreads;
|
||||||
|
while (*entry != NULL) {
|
||||||
|
if (*entry == entryToRemove) {
|
||||||
|
*entry = (*entry)->globalNext;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = &(*entry)->globalNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove from the context's list
|
||||||
|
entry = &entryToRemove->context->fThreads;
|
||||||
|
while (*entry != NULL) {
|
||||||
|
if (*entry == entryToRemove) {
|
||||||
|
*entry = (*entry)->contextNext;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = &(*entry)->contextNext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*static*/ status_t
|
||||||
|
GlobalTestContext::_ThreadEntry(void* data)
|
||||||
|
{
|
||||||
|
ThreadCookie* cookie = (ThreadCookie*)data;
|
||||||
|
|
||||||
|
ThreadEntry entry(cookie->context);
|
||||||
|
_SetCurrent(&entry);
|
||||||
|
|
||||||
|
thread_func function = cookie->function;
|
||||||
|
void* arg = cookie->arg;
|
||||||
|
delete cookie;
|
||||||
|
|
||||||
|
status_t result = function(arg);
|
||||||
|
|
||||||
|
_UnsetCurrent(&entry);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #pragma mark - TestContext
|
||||||
|
|
||||||
|
|
||||||
|
TestContext::TestContext(GlobalTestContext* globalContext)
|
||||||
|
:
|
||||||
|
fGlobalContext(globalContext),
|
||||||
|
fParent(NULL),
|
||||||
|
fTest(NULL),
|
||||||
|
fErrors(NULL),
|
||||||
|
fLevel(0)
|
||||||
|
{
|
||||||
|
fGlobalContext->SetCurrentContext(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TestContext::TestContext(TestContext& parent, Test* test)
|
||||||
|
:
|
||||||
|
fGlobalContext(parent.GlobalContext()),
|
||||||
|
fParent(&parent),
|
||||||
|
fTest(test),
|
||||||
|
fErrors(NULL),
|
||||||
|
fLevel(parent.Level() + 1)
|
||||||
|
{
|
||||||
|
fGlobalContext->SetCurrentContext(this);
|
||||||
|
|
||||||
|
if (fTest != NULL) {
|
||||||
|
if (fTest->IsLeafTest())
|
||||||
|
Print("%*s%s...", fLevel * 2, "", test->Name());
|
||||||
|
else
|
||||||
|
Print("%*s%s:\n", fLevel * 2, "", test->Name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TestContext::~TestContext()
|
||||||
|
{
|
||||||
|
fGlobalContext->SetCurrentContext(fParent);
|
||||||
|
|
||||||
|
while (fErrors != NULL) {
|
||||||
|
TestError* error = fErrors;
|
||||||
|
fErrors = error->ListLink();
|
||||||
|
delete error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
TestContext::TestDone(bool success)
|
||||||
|
{
|
||||||
|
if (fTest != NULL && fTest->IsLeafTest()) {
|
||||||
|
if (success) {
|
||||||
|
Print(" ok\n");
|
||||||
|
} else {
|
||||||
|
Print(" FAILED\n");
|
||||||
|
TestError* error = fErrors;
|
||||||
|
while (error != NULL) {
|
||||||
|
Print("%s", error->Message());
|
||||||
|
error = error->ListLink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fGlobalContext->TestDone(success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
TestContext::ErrorArgs(const char* format, va_list args)
|
||||||
|
{
|
||||||
|
int size = vsnprintf(NULL, 0, format, args) + 1;
|
||||||
|
char* buffer = (char*)malloc(size);
|
||||||
|
if (buffer == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
vsnprintf(buffer, size, format, args);
|
||||||
|
|
||||||
|
TestError* error = new(std::nothrow) TestError(fTest, buffer);
|
||||||
|
if (error == NULL) {
|
||||||
|
free(buffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
InterruptsSpinLocker locker(sLock);
|
||||||
|
error->ListLink() = fErrors;
|
||||||
|
fErrors = error;
|
||||||
|
|
||||||
|
if (Options().panicOnFailure)
|
||||||
|
panic("Test check failed: %s", error->Message());
|
||||||
|
}
|
|
@ -0,0 +1,191 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
#ifndef TEST_CONTEXT_H
|
||||||
|
#define TEST_CONTEXT_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <KernelExport.h>
|
||||||
|
|
||||||
|
#include "TestOutput.h"
|
||||||
|
|
||||||
|
|
||||||
|
class Test;
|
||||||
|
class TestContext;
|
||||||
|
class TestError;
|
||||||
|
|
||||||
|
|
||||||
|
struct TestOptions {
|
||||||
|
bool panicOnFailure;
|
||||||
|
bool quitAfterFailure;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TestOptions();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct GlobalTestContext {
|
||||||
|
GlobalTestContext(TestOutput& output,
|
||||||
|
TestOptions& options);
|
||||||
|
~GlobalTestContext();
|
||||||
|
|
||||||
|
static GlobalTestContext* Current();
|
||||||
|
|
||||||
|
TestOutput& Output() const { return fOutput; }
|
||||||
|
TestOptions& Options() const { return fOptions; }
|
||||||
|
|
||||||
|
TestContext* CurrentContext() const
|
||||||
|
{ return fCurrentContext; }
|
||||||
|
void SetCurrentContext(TestContext* context);
|
||||||
|
|
||||||
|
int32 TotalTests() const { return fTotalTests; }
|
||||||
|
int32 FailedTests() const { return fFailedTests; }
|
||||||
|
|
||||||
|
void TestDone(bool success);
|
||||||
|
|
||||||
|
thread_id SpawnThread(thread_func function,
|
||||||
|
const char* name, int32 priority,
|
||||||
|
void* arg);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ThreadEntry {
|
||||||
|
ThreadEntry* globalNext;
|
||||||
|
ThreadEntry* contextNext;
|
||||||
|
GlobalTestContext* context;
|
||||||
|
thread_id thread;
|
||||||
|
|
||||||
|
ThreadEntry(GlobalTestContext* context)
|
||||||
|
:
|
||||||
|
globalNext(NULL),
|
||||||
|
contextNext(NULL),
|
||||||
|
context(context),
|
||||||
|
thread(find_thread(NULL))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ThreadCookie;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void _SetCurrent(ThreadEntry* entry);
|
||||||
|
static void _UnsetCurrent(ThreadEntry* entry);
|
||||||
|
static status_t _ThreadEntry(void* data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static ThreadEntry* sGlobalThreads;
|
||||||
|
ThreadEntry* fThreads;
|
||||||
|
ThreadEntry fThreadEntry;
|
||||||
|
TestOutput& fOutput;
|
||||||
|
TestOptions& fOptions;
|
||||||
|
TestContext* fCurrentContext;
|
||||||
|
int32 fTotalTests;
|
||||||
|
int32 fFailedTests;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class TestContext {
|
||||||
|
public:
|
||||||
|
TestContext(GlobalTestContext* globalContext);
|
||||||
|
TestContext(TestContext& parent, Test* test);
|
||||||
|
~TestContext();
|
||||||
|
|
||||||
|
static TestContext* Current();
|
||||||
|
|
||||||
|
GlobalTestContext* GlobalContext() const { return fGlobalContext; }
|
||||||
|
TestContext* Parent() const { return fParent; }
|
||||||
|
int32 Level() const { return fLevel; }
|
||||||
|
TestOutput& Output() const
|
||||||
|
{ return fGlobalContext->Output(); }
|
||||||
|
TestOptions& Options() const
|
||||||
|
{ return fGlobalContext->Options(); }
|
||||||
|
|
||||||
|
inline int PrintArgs(const char* format, va_list args);
|
||||||
|
inline int Print(const char* format,...);
|
||||||
|
|
||||||
|
void ErrorArgs(const char* format, va_list args);
|
||||||
|
inline void Error(const char* format,...);
|
||||||
|
|
||||||
|
inline void AssertFailed(const char* file, int line,
|
||||||
|
const char* function,
|
||||||
|
const char* condition);
|
||||||
|
inline void AssertFailed(const char* file, int line,
|
||||||
|
const char* function,
|
||||||
|
const char* condition,
|
||||||
|
const char* format,...);
|
||||||
|
|
||||||
|
void TestDone(bool success);
|
||||||
|
|
||||||
|
private:
|
||||||
|
GlobalTestContext* fGlobalContext;
|
||||||
|
TestContext* fParent;
|
||||||
|
Test* fTest;
|
||||||
|
TestError* fErrors;
|
||||||
|
int32 fLevel;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*static*/ inline TestContext*
|
||||||
|
TestContext::Current()
|
||||||
|
{
|
||||||
|
return GlobalTestContext::Current()->CurrentContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
TestContext::PrintArgs(const char* format, va_list args)
|
||||||
|
{
|
||||||
|
return Output().PrintArgs(format, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
TestContext::Print(const char* format,...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
int result = Output().PrintArgs(format, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
TestContext::Error(const char* format,...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
ErrorArgs(format, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
TestContext::AssertFailed(const char* file, int line, const char* function,
|
||||||
|
const char* condition)
|
||||||
|
{
|
||||||
|
Error("ASSERT FAILED at %s:%d %s: %s\n", file, line, function, condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
TestContext::AssertFailed(const char* file, int line, const char* function,
|
||||||
|
const char* condition, const char* format,...)
|
||||||
|
{
|
||||||
|
char buffer[256];
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
vsnprintf(buffer, sizeof(buffer), format, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
Error("ASSERT FAILED at %s:%d %s: %s; %s\n", file, line, function,
|
||||||
|
condition, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif // TEST_CONTEXT_H
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "TestError.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
|
||||||
|
TestError::TestError(Test* test, char* message)
|
||||||
|
:
|
||||||
|
fTest(test),
|
||||||
|
fMessage(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TestError::~TestError()
|
||||||
|
{
|
||||||
|
free(fMessage);
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
#ifndef TEST_ERROR_H
|
||||||
|
#define TEST_ERROR_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <SupportDefs.h>
|
||||||
|
|
||||||
|
|
||||||
|
class Test;
|
||||||
|
|
||||||
|
|
||||||
|
class TestError {
|
||||||
|
public:
|
||||||
|
TestError(Test* test, char* message);
|
||||||
|
~TestError();
|
||||||
|
|
||||||
|
Test* GetTest() const { return fTest; }
|
||||||
|
const char* Message() const { return fMessage; }
|
||||||
|
|
||||||
|
TestError*& ListLink() { return fNext; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
TestError* fNext;
|
||||||
|
Test* fTest;
|
||||||
|
char* fMessage;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // TEST_ERROR_H
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "TestManager.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "TestOutput.h"
|
||||||
|
#include "TestVisitor.h"
|
||||||
|
|
||||||
|
|
||||||
|
TestManager::TestManager()
|
||||||
|
:
|
||||||
|
TestSuite("all")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TestManager::~TestManager()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
TestManager::ListTests(TestOutput& output)
|
||||||
|
{
|
||||||
|
struct Visitor : TestVisitor {
|
||||||
|
Visitor(TestOutput& output)
|
||||||
|
:
|
||||||
|
fOutput(output),
|
||||||
|
fLevel(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool VisitTest(Test* test)
|
||||||
|
{
|
||||||
|
fOutput.Print("%*s%s\n", fLevel * 2, "", test->Name());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool VisitTestSuitePre(TestSuite* suite)
|
||||||
|
{
|
||||||
|
if (fLevel > 0)
|
||||||
|
VisitTest(suite);
|
||||||
|
fLevel++;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool VisitTestSuitePost(TestSuite* suite)
|
||||||
|
{
|
||||||
|
fLevel--;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
TestOutput& fOutput;
|
||||||
|
int fLevel;
|
||||||
|
} visitor(output);
|
||||||
|
|
||||||
|
output.Print("Available tests:\n");
|
||||||
|
Visit(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
TestManager::RunTests(GlobalTestContext& globalContext,
|
||||||
|
const char* const* tests, int testCount)
|
||||||
|
{
|
||||||
|
TestContext context(&globalContext);
|
||||||
|
|
||||||
|
context.Print("Running tests:\n");
|
||||||
|
|
||||||
|
if (testCount == 0 || (testCount == 1 && strcmp(tests[0], "all") == 0)) {
|
||||||
|
Run(context);
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < testCount; i++) {
|
||||||
|
bool result = Run(context, tests[i]);
|
||||||
|
if (!result && context.Options().quitAfterFailure)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Print("run tests: %ld, failed tests: %ld\n",
|
||||||
|
globalContext.TotalTests(), globalContext.FailedTests());
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
#ifndef TEST_MANAGER_H
|
||||||
|
#define TEST_MANAGER_H
|
||||||
|
|
||||||
|
|
||||||
|
#include "TestSuite.h"
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalTestContext;
|
||||||
|
class TestOutput;
|
||||||
|
|
||||||
|
|
||||||
|
class TestManager : public TestSuite {
|
||||||
|
public:
|
||||||
|
TestManager();
|
||||||
|
~TestManager();
|
||||||
|
|
||||||
|
void ListTests(TestOutput& output);
|
||||||
|
void RunTests(GlobalTestContext& globalContext,
|
||||||
|
const char* const* tests, int testCount);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // TEST_MANAGER_H
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "TestOutput.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <util/AutoLock.h>
|
||||||
|
|
||||||
|
|
||||||
|
// #pragma mark - TestOutput
|
||||||
|
|
||||||
|
|
||||||
|
TestOutput::TestOutput()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TestOutput::~TestOutput()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #pragma mark - DebugTestOutput
|
||||||
|
|
||||||
|
|
||||||
|
DebugTestOutput::DebugTestOutput()
|
||||||
|
{
|
||||||
|
B_INITIALIZE_SPINLOCK(&fLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DebugTestOutput::~DebugTestOutput()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
DebugTestOutput::PrintArgs(const char* format, va_list args)
|
||||||
|
{
|
||||||
|
InterruptsSpinLocker locker(fLock);
|
||||||
|
|
||||||
|
int bytes = vsnprintf(fBuffer, sizeof(fBuffer), format, args);
|
||||||
|
dprintf("%s", fBuffer);
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
#ifndef TEST_OUTPUT_H
|
||||||
|
#define TEST_OUTPUT_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
#include <KernelExport.h>
|
||||||
|
|
||||||
|
|
||||||
|
class TestOutput {
|
||||||
|
public:
|
||||||
|
TestOutput();
|
||||||
|
virtual ~TestOutput();
|
||||||
|
|
||||||
|
virtual int PrintArgs(const char* format, va_list args) = 0;
|
||||||
|
inline int Print(const char* format,...);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class DebugTestOutput : public TestOutput {
|
||||||
|
public:
|
||||||
|
DebugTestOutput();
|
||||||
|
virtual ~DebugTestOutput();
|
||||||
|
|
||||||
|
virtual int PrintArgs(const char* format, va_list args);
|
||||||
|
|
||||||
|
private:
|
||||||
|
spinlock fLock;
|
||||||
|
char fBuffer[1024];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
TestOutput::Print(const char* format,...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
int result = PrintArgs(format, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif // TEST_OUTPUT_H
|
|
@ -0,0 +1,161 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "TestSuite.h"
|
||||||
|
|
||||||
|
#include <new>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "TestVisitor.h"
|
||||||
|
|
||||||
|
|
||||||
|
TestSuite::TestSuite(const char* name)
|
||||||
|
:
|
||||||
|
Test(name),
|
||||||
|
fTests(NULL),
|
||||||
|
fTestCount(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TestSuite::~TestSuite()
|
||||||
|
{
|
||||||
|
for (int32 i = 0; i < fTestCount; i++)
|
||||||
|
delete fTests[i];
|
||||||
|
delete[] fTests;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int32
|
||||||
|
TestSuite::CountTests() const
|
||||||
|
{
|
||||||
|
return fTestCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Test*
|
||||||
|
TestSuite::TestAt(int32 index) const
|
||||||
|
{
|
||||||
|
return index >= 0 && index < fTestCount ? fTests[index] : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Test*
|
||||||
|
TestSuite::FindTest(const char* name, int32 nameLength) const
|
||||||
|
{
|
||||||
|
if (nameLength < 0)
|
||||||
|
nameLength = strlen(name);
|
||||||
|
|
||||||
|
for (int32 i = 0; Test* test = TestAt(i); i++) {
|
||||||
|
if (strlen(test->Name()) == (size_t)nameLength
|
||||||
|
&& strncmp(test->Name(), name, nameLength) == 0) {
|
||||||
|
return test;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
TestSuite::AddTest(Test* test)
|
||||||
|
{
|
||||||
|
if (test == NULL)
|
||||||
|
return test;
|
||||||
|
|
||||||
|
Test** tests = new(std::nothrow) Test*[fTestCount + 1];
|
||||||
|
if (tests == NULL) {
|
||||||
|
delete test;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fTestCount > 0)
|
||||||
|
memcpy(tests, fTests, sizeof(Test*) * fTestCount);
|
||||||
|
|
||||||
|
delete[] fTests;
|
||||||
|
|
||||||
|
fTests = tests;
|
||||||
|
fTests[fTestCount++] = test;
|
||||||
|
|
||||||
|
test->SetSuite(this);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
TestSuite::IsLeafTest() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
TestSuite::Run(TestContext& context)
|
||||||
|
{
|
||||||
|
for (int32 i = 0; Test* test = TestAt(i); i++) {
|
||||||
|
bool result = _Run(context, test, NULL);
|
||||||
|
if (!result && context.Options().quitAfterFailure)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
TestSuite::Run(TestContext& context, const char* name)
|
||||||
|
{
|
||||||
|
const char* separator = strstr(name, "::");
|
||||||
|
Test* test = FindTest(name, separator != NULL ? separator - name : -1);
|
||||||
|
if (test == NULL) {
|
||||||
|
context.Print("No such test: \"%.*s\"\n",
|
||||||
|
int(separator != NULL ? separator - name : strlen(name)), name);
|
||||||
|
return !context.Options().quitAfterFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _Run(context, test, separator != NULL ? separator + 2 : NULL)
|
||||||
|
|| !context.Options().quitAfterFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Test*
|
||||||
|
TestSuite::Visit(TestVisitor& visitor)
|
||||||
|
{
|
||||||
|
if (visitor.VisitTestSuitePre(this))
|
||||||
|
return this;
|
||||||
|
|
||||||
|
for (int32 i = 0; Test* test = TestAt(i); i++) {
|
||||||
|
if (Test* foundTest = test->Visit(visitor))
|
||||||
|
return foundTest;
|
||||||
|
}
|
||||||
|
|
||||||
|
return visitor.VisitTestSuitePost(this) ? this : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
TestSuite::_Run(TestContext& context, Test* test, const char* name)
|
||||||
|
{
|
||||||
|
TestContext subContext(context, test);
|
||||||
|
|
||||||
|
status_t error = test->Setup(subContext);
|
||||||
|
if (error != B_OK) {
|
||||||
|
subContext.Error("setup failed\n");
|
||||||
|
test->Cleanup(subContext, false);
|
||||||
|
subContext.TestDone(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool result = name != NULL
|
||||||
|
? test->Run(subContext, name) : test->Run(subContext);
|
||||||
|
test->Cleanup(subContext, true);
|
||||||
|
|
||||||
|
subContext.TestDone(result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
#ifndef TEST_SUITE_H
|
||||||
|
#define TEST_SUITE_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <new>
|
||||||
|
|
||||||
|
#include "Test.h"
|
||||||
|
|
||||||
|
|
||||||
|
class TestSuite : public Test {
|
||||||
|
public:
|
||||||
|
TestSuite(const char* name);
|
||||||
|
virtual ~TestSuite();
|
||||||
|
|
||||||
|
int32 CountTests() const;
|
||||||
|
Test* TestAt(int32 index) const;
|
||||||
|
Test* FindTest(const char* name,
|
||||||
|
int32 nameLength = -1) const;
|
||||||
|
|
||||||
|
bool AddTest(Test* test);
|
||||||
|
|
||||||
|
virtual bool IsLeafTest() const;
|
||||||
|
|
||||||
|
virtual bool Run(TestContext& context);
|
||||||
|
virtual bool Run(TestContext& context, const char* name);
|
||||||
|
|
||||||
|
virtual Test* Visit(TestVisitor& visitor);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool _Run(TestContext& context, Test* test,
|
||||||
|
const char* name);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Test** fTests;
|
||||||
|
int32 fTestCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#define ADD_TEST(suite, test) \
|
||||||
|
do { \
|
||||||
|
if (!suite->AddTest(test)) { \
|
||||||
|
delete test; \
|
||||||
|
delete suite; \
|
||||||
|
return NULL; \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
#define ADD_STANDARD_TEST(suite, type, method) \
|
||||||
|
do { \
|
||||||
|
type* object = new(std::nothrow) type; \
|
||||||
|
if (object == NULL) \
|
||||||
|
return NULL; \
|
||||||
|
\
|
||||||
|
StandardTest<type>* test = new(std::nothrow) StandardTest<type>( \
|
||||||
|
#method, object, &type::method); \
|
||||||
|
if (test == NULL) { \
|
||||||
|
delete object; \
|
||||||
|
return NULL; \
|
||||||
|
} \
|
||||||
|
ADD_TEST(suite, test); \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
|
||||||
|
#endif // TEST_SUITE_H
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
#ifndef TEST_THREAD_H
|
||||||
|
#define TEST_THREAD_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <KernelExport.h>
|
||||||
|
|
||||||
|
#include "TestContext.h"
|
||||||
|
|
||||||
|
|
||||||
|
template<typename ObjectType, typename ParameterType>
|
||||||
|
class TestThread {
|
||||||
|
public:
|
||||||
|
TestThread(ObjectType* object,
|
||||||
|
void (ObjectType::*method)(TestContext&, ParameterType*),
|
||||||
|
ParameterType* argument)
|
||||||
|
:
|
||||||
|
fObject(object),
|
||||||
|
fMethod(method),
|
||||||
|
fArgument(argument)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_id Spawn(const char* name, int32 priority)
|
||||||
|
{
|
||||||
|
return GlobalTestContext::Current()->SpawnThread(_Entry, name, priority,
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static status_t _Entry(void* data)
|
||||||
|
{
|
||||||
|
TestThread* thread = (TestThread*)data;
|
||||||
|
(thread->fObject->*thread->fMethod)(
|
||||||
|
*GlobalTestContext::Current()->CurrentContext(), thread->fArgument);
|
||||||
|
delete thread;
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ObjectType* fObject;
|
||||||
|
void (ObjectType::*fMethod)(TestContext&, ParameterType*);
|
||||||
|
ParameterType* fArgument;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<typename ObjectType, typename ParameterType>
|
||||||
|
thread_id
|
||||||
|
SpawnThread(ObjectType* object,
|
||||||
|
void (ObjectType::*method)(TestContext&, ParameterType*), const char* name,
|
||||||
|
int32 priority, ParameterType* arg)
|
||||||
|
{
|
||||||
|
TestThread<ObjectType, ParameterType>* thread
|
||||||
|
= new(std::nothrow) TestThread<ObjectType, ParameterType>(object,
|
||||||
|
method, arg);
|
||||||
|
if (thread == NULL)
|
||||||
|
return B_NO_MEMORY;
|
||||||
|
|
||||||
|
return thread->Spawn(name, priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif // TEST_THREAD_H
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "TestVisitor.h"
|
||||||
|
|
||||||
|
|
||||||
|
TestVisitor::~TestVisitor()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
TestVisitor::VisitTest(Test* test)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
TestVisitor::VisitTestSuitePre(TestSuite* suite)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
TestVisitor::VisitTestSuitePost(TestSuite* suite)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
#ifndef TEST_VISITOR_H
|
||||||
|
#define TEST_VISITOR_H
|
||||||
|
|
||||||
|
|
||||||
|
class Test;
|
||||||
|
class TestSuite;
|
||||||
|
|
||||||
|
|
||||||
|
class TestVisitor {
|
||||||
|
public:
|
||||||
|
virtual ~TestVisitor();
|
||||||
|
|
||||||
|
virtual bool VisitTest(Test* test);
|
||||||
|
|
||||||
|
virtual bool VisitTestSuitePre(TestSuite* suite);
|
||||||
|
virtual bool VisitTestSuitePost(TestSuite* suite);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // TEST_VISITOR_H
|
|
@ -0,0 +1,321 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <Drivers.h>
|
||||||
|
#include <KernelExport.h>
|
||||||
|
|
||||||
|
#include <AutoDeleter.h>
|
||||||
|
|
||||||
|
#include <kernel.h>
|
||||||
|
#include <thread.h>
|
||||||
|
|
||||||
|
#include "TestContext.h"
|
||||||
|
#include "TestManager.h"
|
||||||
|
#include "TestOutput.h"
|
||||||
|
|
||||||
|
#include "lock/LockTestSuite.h"
|
||||||
|
|
||||||
|
|
||||||
|
int32 api_version = B_CUR_DRIVER_API_VERSION;
|
||||||
|
|
||||||
|
static const char* sDeviceNames[] = {
|
||||||
|
"kernel_unit_tests",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char* const kUsage =
|
||||||
|
"Usage:\n"
|
||||||
|
" help\n"
|
||||||
|
" Print usage info.\n"
|
||||||
|
"\n"
|
||||||
|
" list\n"
|
||||||
|
" Print available tests.\n"
|
||||||
|
"\n"
|
||||||
|
" run [ <options> ] [ <tests> ]\n"
|
||||||
|
" Run tests. The tests to be run can be given as arguments. When no "
|
||||||
|
"tests\n"
|
||||||
|
" are specified, all tests are run.\n"
|
||||||
|
"\n"
|
||||||
|
" Options:\n"
|
||||||
|
" -p - panic(), when a test check fails.\n"
|
||||||
|
" -q - Don't run any further tests after the first test failure.\n";
|
||||||
|
|
||||||
|
static TestManager* sTestManager = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
// #pragma mark -
|
||||||
|
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_command_line(char* buffer, char**& _argv, int& _argc)
|
||||||
|
{
|
||||||
|
// Process the argument string. We split at whitespace, heeding quotes and
|
||||||
|
// escaped characters. The processed arguments are written to the given
|
||||||
|
// buffer, separated by single null chars.
|
||||||
|
char* start = buffer;
|
||||||
|
char* out = buffer;
|
||||||
|
bool pendingArgument = false;
|
||||||
|
int argc = 0;
|
||||||
|
while (*start != '\0') {
|
||||||
|
// ignore whitespace
|
||||||
|
if (isspace(*start)) {
|
||||||
|
if (pendingArgument) {
|
||||||
|
*out = '\0';
|
||||||
|
out++;
|
||||||
|
argc++;
|
||||||
|
pendingArgument = false;
|
||||||
|
}
|
||||||
|
start++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingArgument = true;
|
||||||
|
|
||||||
|
if (*start == '"' || *start == '\'') {
|
||||||
|
// quoted text -- continue until closing quote
|
||||||
|
char quote = *start;
|
||||||
|
start++;
|
||||||
|
while (*start != '\0' && *start != quote) {
|
||||||
|
if (*start == '\\' && quote == '"') {
|
||||||
|
start++;
|
||||||
|
if (*start == '\0')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
*out = *start;
|
||||||
|
start++;
|
||||||
|
out++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*start != '\0')
|
||||||
|
start++;
|
||||||
|
} else {
|
||||||
|
// unquoted text
|
||||||
|
if (*start == '\\') {
|
||||||
|
// escaped char
|
||||||
|
start++;
|
||||||
|
if (start == '\0')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = *start;
|
||||||
|
start++;
|
||||||
|
out++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingArgument) {
|
||||||
|
*out = '\0';
|
||||||
|
argc++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate argument vector
|
||||||
|
char** argv = new(std::nothrow) char*[argc + 1];
|
||||||
|
if (argv == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// fill vector
|
||||||
|
start = buffer;
|
||||||
|
for (int i = 0; i < argc; i++) {
|
||||||
|
argv[i] = start;
|
||||||
|
start += strlen(start) + 1;
|
||||||
|
}
|
||||||
|
argv[argc] = NULL;
|
||||||
|
|
||||||
|
_argv = argv;
|
||||||
|
_argc = argc;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #pragma mark - device hooks
|
||||||
|
|
||||||
|
|
||||||
|
static status_t
|
||||||
|
device_open(const char* name, uint32 openMode, void** _cookie)
|
||||||
|
{
|
||||||
|
*_cookie = NULL;
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static status_t
|
||||||
|
device_close(void* cookie)
|
||||||
|
{
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static status_t
|
||||||
|
device_free(void* cookie)
|
||||||
|
{
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static status_t
|
||||||
|
device_control(void* cookie, uint32 op, void* arg, size_t length)
|
||||||
|
{
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static status_t
|
||||||
|
device_read(void* cookie, off_t position, void* data, size_t* numBytes)
|
||||||
|
{
|
||||||
|
*numBytes = 0;
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static status_t
|
||||||
|
device_write(void* cookie, off_t position, const void* data, size_t* numBytes)
|
||||||
|
{
|
||||||
|
if (position != 0)
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
|
||||||
|
// copy data to stack buffer
|
||||||
|
char* buffer = (char*)malloc(*numBytes + 1);
|
||||||
|
if (buffer == NULL)
|
||||||
|
return B_NO_MEMORY;
|
||||||
|
MemoryDeleter bufferDeleter(buffer);
|
||||||
|
|
||||||
|
struct thread* thread = thread_get_current_thread();
|
||||||
|
if ((thread->flags & THREAD_FLAGS_SYSCALL) != 0) {
|
||||||
|
if (!IS_USER_ADDRESS(data)
|
||||||
|
|| user_memcpy(buffer, data, *numBytes) != B_OK) {
|
||||||
|
return B_BAD_ADDRESS;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
memcpy(buffer, data, *numBytes);
|
||||||
|
|
||||||
|
buffer[*numBytes] = '\0';
|
||||||
|
|
||||||
|
// create an output
|
||||||
|
TestOutput* output = new(std::nothrow) DebugTestOutput;
|
||||||
|
if (output == NULL)
|
||||||
|
return B_NO_MEMORY;
|
||||||
|
ObjectDeleter<TestOutput> outputDeleter(output);
|
||||||
|
|
||||||
|
// parse arguments
|
||||||
|
char** argv;
|
||||||
|
int argc;
|
||||||
|
if (!parse_command_line(buffer, argv, argc))
|
||||||
|
return B_NO_MEMORY;
|
||||||
|
ArrayDeleter<char*> argvDeleter(argv);
|
||||||
|
|
||||||
|
if (argc == 0)
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
|
||||||
|
// execute command
|
||||||
|
if (strcmp(argv[0], "help") == 0) {
|
||||||
|
// print usage -- print individual lines to avoid dprintf() buffer
|
||||||
|
// overflows
|
||||||
|
const char* usage = kUsage;
|
||||||
|
while (*usage != '\0') {
|
||||||
|
const char* lineEnd = strchr(usage, '\n');
|
||||||
|
if (lineEnd != NULL)
|
||||||
|
lineEnd++;
|
||||||
|
else
|
||||||
|
lineEnd = usage + strlen(usage);
|
||||||
|
|
||||||
|
output->Print("%.*s", (int)(lineEnd - usage), usage);
|
||||||
|
usage = lineEnd;
|
||||||
|
}
|
||||||
|
} else if (strcmp(argv[0], "list") == 0) {
|
||||||
|
sTestManager->ListTests(*output);
|
||||||
|
} else if (strcmp(argv[0], "run") == 0) {
|
||||||
|
// parse options
|
||||||
|
TestOptions options;
|
||||||
|
int argi = 1;
|
||||||
|
while (argi < argc) {
|
||||||
|
const char* arg = argv[argi];
|
||||||
|
if (*arg == '-') {
|
||||||
|
for (arg++; *arg != '\0'; arg++) {
|
||||||
|
switch (*arg) {
|
||||||
|
case 'p':
|
||||||
|
options.panicOnFailure = true;
|
||||||
|
break;
|
||||||
|
case 'q':
|
||||||
|
options.quitAfterFailure = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
output->Print("Invalid option: \"-%c\"\n", *arg);
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
argi++;
|
||||||
|
} else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalTestContext globalContext(*output, options);
|
||||||
|
sTestManager->RunTests(globalContext, argv + argi, argc - argi);
|
||||||
|
} else {
|
||||||
|
output->Print("Invalid command \"%s\"!\n", argv[0]);
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #pragma mark - driver interface
|
||||||
|
|
||||||
|
|
||||||
|
static device_hooks sDeviceHooks = {
|
||||||
|
device_open,
|
||||||
|
device_close,
|
||||||
|
device_free,
|
||||||
|
device_control,
|
||||||
|
device_read,
|
||||||
|
device_write
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
init_hardware(void)
|
||||||
|
{
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
init_driver(void)
|
||||||
|
{
|
||||||
|
sTestManager = new(std::nothrow) TestManager;
|
||||||
|
if (sTestManager == NULL)
|
||||||
|
return B_NO_MEMORY;
|
||||||
|
|
||||||
|
// register test suites
|
||||||
|
sTestManager->AddTest(create_lock_test_suite());
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
uninit_driver(void)
|
||||||
|
{
|
||||||
|
delete sTestManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const char**
|
||||||
|
publish_devices(void)
|
||||||
|
{
|
||||||
|
return sDeviceNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
device_hooks*
|
||||||
|
find_device(const char* name)
|
||||||
|
{
|
||||||
|
return strcmp(name, sDeviceNames[0]) == 0 ? &sDeviceHooks : NULL;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
SubDir HAIKU_TOP src tests system kernel unit lock ;
|
||||||
|
|
||||||
|
UsePrivateKernelHeaders ;
|
||||||
|
|
||||||
|
SubDirHdrs [ FDirName $(SUBDIR) $(DOTDOT) ] ;
|
||||||
|
|
||||||
|
|
||||||
|
KernelMergeObject kernel_unit_tests_lock.o :
|
||||||
|
LockTestSuite.cpp
|
||||||
|
RWLockTests.cpp
|
||||||
|
;
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "LockTestSuite.h"
|
||||||
|
|
||||||
|
#include "RWLockTests.h"
|
||||||
|
|
||||||
|
|
||||||
|
TestSuite*
|
||||||
|
create_lock_test_suite()
|
||||||
|
{
|
||||||
|
TestSuite* suite = new(std::nothrow) TestSuite("lock");
|
||||||
|
|
||||||
|
ADD_TEST(suite, create_rw_lock_test_suite());
|
||||||
|
|
||||||
|
return suite;
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
#ifndef LOCK_TEST_SUITE_H
|
||||||
|
#define LOCK_TEST_SUITE_H
|
||||||
|
|
||||||
|
|
||||||
|
#include "TestSuite.h"
|
||||||
|
|
||||||
|
|
||||||
|
TestSuite* create_lock_test_suite();
|
||||||
|
|
||||||
|
|
||||||
|
#endif // LOCK_TEST_SUITE_H
|
|
@ -0,0 +1,241 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "RWLockTests.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <lock.h>
|
||||||
|
|
||||||
|
#include "TestThread.h"
|
||||||
|
|
||||||
|
|
||||||
|
static const int kConcurrentTestTime = 2000000;
|
||||||
|
|
||||||
|
|
||||||
|
class RWLockTest : public StandardTestDelegate {
|
||||||
|
public:
|
||||||
|
RWLockTest()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual status_t Setup(TestContext& context)
|
||||||
|
{
|
||||||
|
rw_lock_init(&fLock, "test r/w lock");
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Cleanup(TestContext& context, bool setupOK)
|
||||||
|
{
|
||||||
|
rw_lock_destroy(&fLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool TestSimple(TestContext& context)
|
||||||
|
{
|
||||||
|
for (int32 i = 0; i < 3; i++) {
|
||||||
|
TEST_ASSERT(rw_lock_read_lock(&fLock) == B_OK);
|
||||||
|
TEST_ASSERT(rw_lock_read_unlock(&fLock) == B_OK);
|
||||||
|
|
||||||
|
TEST_ASSERT(rw_lock_write_lock(&fLock) == B_OK);
|
||||||
|
TEST_ASSERT(rw_lock_write_unlock(&fLock) == B_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestNestedWrite(TestContext& context)
|
||||||
|
{
|
||||||
|
for (int32 i = 0; i < 10; i++)
|
||||||
|
TEST_ASSERT(rw_lock_write_lock(&fLock) == B_OK);
|
||||||
|
|
||||||
|
for (int32 i = 0; i < 10; i++)
|
||||||
|
TEST_ASSERT(rw_lock_write_unlock(&fLock) == B_OK);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestNestedWriteRead(TestContext& context)
|
||||||
|
{
|
||||||
|
TEST_ASSERT(rw_lock_write_lock(&fLock) == B_OK);
|
||||||
|
|
||||||
|
for (int32 i = 0; i < 10; i++)
|
||||||
|
TEST_ASSERT(rw_lock_read_lock(&fLock) == B_OK);
|
||||||
|
|
||||||
|
for (int32 i = 0; i < 10; i++)
|
||||||
|
TEST_ASSERT(rw_lock_read_unlock(&fLock) == B_OK);
|
||||||
|
|
||||||
|
TEST_ASSERT(rw_lock_write_unlock(&fLock) == B_OK);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestConcurrentWriteRead(TestContext& context)
|
||||||
|
{
|
||||||
|
return _RunConcurrentTest(context,
|
||||||
|
&RWLockTest::TestConcurrentWriteReadThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestConcurrentWriteNestedRead(TestContext& context)
|
||||||
|
{
|
||||||
|
return _RunConcurrentTest(context,
|
||||||
|
&RWLockTest::TestConcurrentWriteNestedReadThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// thread function wrappers
|
||||||
|
|
||||||
|
void TestConcurrentWriteReadThread(TestContext& context, void* _index)
|
||||||
|
{
|
||||||
|
if (!_TestConcurrentWriteReadThread(context, (addr_t)_index))
|
||||||
|
fTestOK = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestConcurrentWriteNestedReadThread(TestContext& context, void* _index)
|
||||||
|
{
|
||||||
|
if (!_TestConcurrentWriteNestedReadThread(context, (addr_t)_index))
|
||||||
|
fTestOK = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool _RunConcurrentTest(TestContext& context,
|
||||||
|
void (RWLockTest::*method)(TestContext&, void*))
|
||||||
|
{
|
||||||
|
const int threadCount = 8;
|
||||||
|
thread_id threads[threadCount];
|
||||||
|
for (int i = 0; i < threadCount; i++)
|
||||||
|
threads[i] = -1;
|
||||||
|
|
||||||
|
fTestOK = true;
|
||||||
|
fTestGo = false;
|
||||||
|
fLockCount = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < threadCount; i++) {
|
||||||
|
threads[i] = SpawnThread(this, method, "rw lock test",
|
||||||
|
B_NORMAL_PRIORITY, (void*)(addr_t)i);
|
||||||
|
if (threads[i] < 0) {
|
||||||
|
fTestOK = false;
|
||||||
|
context.Error("Failed to spawn thread: %s\n",
|
||||||
|
strerror(threads[i]));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < threadCount; i++)
|
||||||
|
resume_thread(threads[i]);
|
||||||
|
|
||||||
|
fTestGo = true;
|
||||||
|
|
||||||
|
for (int i = 0; i < threadCount; i++) {
|
||||||
|
if (threads[i] >= 0)
|
||||||
|
wait_for_thread(threads[i], NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fTestOK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _TestConcurrentWriteReadThread(TestContext& context, int32 threadIndex)
|
||||||
|
{
|
||||||
|
if (!fTestOK)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int bitShift = 8 * threadIndex;
|
||||||
|
|
||||||
|
while (!fTestGo) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bigtime_t startTime = system_time();
|
||||||
|
uint64 iteration = 0;
|
||||||
|
do {
|
||||||
|
for (int k = 0; fTestOK && k < 255; k++) {
|
||||||
|
TEST_ASSERT(rw_lock_read_lock(&fLock) == B_OK);
|
||||||
|
uint64 count = fLockCount;
|
||||||
|
TEST_ASSERT(rw_lock_read_unlock(&fLock) == B_OK);
|
||||||
|
|
||||||
|
TEST_ASSERT(rw_lock_write_lock(&fLock) == B_OK);
|
||||||
|
fLockCount += (uint64)1 << bitShift;
|
||||||
|
TEST_ASSERT(rw_lock_write_unlock(&fLock) == B_OK);
|
||||||
|
|
||||||
|
int value = (count >> bitShift) & 0xff;
|
||||||
|
TEST_ASSERT_PRINT(value == k,
|
||||||
|
"thread index: %" B_PRId32 ", iteration: %" B_PRId32
|
||||||
|
", value: %d vs %d, count: %#" B_PRIx64, threadIndex,
|
||||||
|
iteration, value, k, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_ASSERT(rw_lock_write_lock(&fLock) == B_OK);
|
||||||
|
fLockCount -= (uint64)255 << bitShift;
|
||||||
|
TEST_ASSERT(rw_lock_write_unlock(&fLock) == B_OK);
|
||||||
|
|
||||||
|
iteration++;
|
||||||
|
} while (fTestOK && system_time() - startTime < kConcurrentTestTime);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _TestConcurrentWriteNestedReadThread(TestContext& context,
|
||||||
|
int32 threadIndex)
|
||||||
|
{
|
||||||
|
if (!fTestOK)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int bitShift = 8 * threadIndex;
|
||||||
|
|
||||||
|
while (!fTestGo) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bigtime_t startTime = system_time();
|
||||||
|
uint64 iteration = 0;
|
||||||
|
do {
|
||||||
|
for (int k = 0; fTestOK && k < 255; k++) {
|
||||||
|
TEST_ASSERT(rw_lock_write_lock(&fLock) == B_OK);
|
||||||
|
|
||||||
|
TEST_ASSERT(rw_lock_read_lock(&fLock) == B_OK);
|
||||||
|
uint64 count = fLockCount;
|
||||||
|
TEST_ASSERT(rw_lock_read_unlock(&fLock) == B_OK);
|
||||||
|
|
||||||
|
fLockCount += (uint64)1 << bitShift;
|
||||||
|
|
||||||
|
TEST_ASSERT(rw_lock_write_unlock(&fLock) == B_OK);
|
||||||
|
|
||||||
|
int value = (count >> bitShift) & 0xff;
|
||||||
|
TEST_ASSERT_PRINT(value == k,
|
||||||
|
"thread index: %" B_PRId32 ", iteration: %" B_PRId32
|
||||||
|
", value: %d vs %d, count: %#" B_PRIx64, threadIndex,
|
||||||
|
iteration, value, k, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_ASSERT(rw_lock_write_lock(&fLock) == B_OK);
|
||||||
|
fLockCount -= (uint64)255 << bitShift;
|
||||||
|
TEST_ASSERT(rw_lock_write_unlock(&fLock) == B_OK);
|
||||||
|
|
||||||
|
iteration++;
|
||||||
|
} while (fTestOK && system_time() - startTime < kConcurrentTestTime);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
rw_lock fLock;
|
||||||
|
volatile bool fTestGo;
|
||||||
|
volatile uint64 fLockCount;
|
||||||
|
volatile bool fTestOK;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TestSuite*
|
||||||
|
create_rw_lock_test_suite()
|
||||||
|
{
|
||||||
|
TestSuite* suite = new(std::nothrow) TestSuite("rw_lock");
|
||||||
|
|
||||||
|
ADD_STANDARD_TEST(suite, RWLockTest, TestSimple);
|
||||||
|
ADD_STANDARD_TEST(suite, RWLockTest, TestNestedWrite);
|
||||||
|
ADD_STANDARD_TEST(suite, RWLockTest, TestNestedWriteRead);
|
||||||
|
ADD_STANDARD_TEST(suite, RWLockTest, TestConcurrentWriteRead);
|
||||||
|
ADD_STANDARD_TEST(suite, RWLockTest, TestConcurrentWriteNestedRead);
|
||||||
|
|
||||||
|
return suite;
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
#ifndef RW_LOCK_TESTS_H
|
||||||
|
#define RW_LOCK_TESTS_H
|
||||||
|
|
||||||
|
|
||||||
|
#include "TestSuite.h"
|
||||||
|
|
||||||
|
|
||||||
|
TestSuite* create_rw_lock_test_suite();
|
||||||
|
|
||||||
|
|
||||||
|
#endif // RW_LOCK_TESTS_H
|
Loading…
Reference in New Issue