* 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:
Ingo Weinhold 2009-12-30 19:38:41 +00:00
parent 3ce2634533
commit 933764d70e
23 changed files with 1971 additions and 0 deletions

View File

@ -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 slab ;
SubInclude HAIKU_TOP src tests system kernel swap ;
SubInclude HAIKU_TOP src tests system kernel unit ;
SubInclude HAIKU_TOP src tests system kernel util ;

View File

@ -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 ;

View File

@ -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)
{
}

View File

@ -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

View File

@ -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());
}

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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());
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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
;

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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