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