diff --git a/src/tests/system/kernel/Jamfile b/src/tests/system/kernel/Jamfile index af70579298..5535410030 100644 --- a/src/tests/system/kernel/Jamfile +++ b/src/tests/system/kernel/Jamfile @@ -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 ; diff --git a/src/tests/system/kernel/unit/Jamfile b/src/tests/system/kernel/unit/Jamfile new file mode 100644 index 0000000000..131a7a3bb3 --- /dev/null +++ b/src/tests/system/kernel/unit/Jamfile @@ -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 + + : + kernel_unit_tests_lock.o + + $(TARGET_STATIC_LIBSUPC++) +; + + +HaikuSubInclude lock ; diff --git a/src/tests/system/kernel/unit/Test.cpp b/src/tests/system/kernel/unit/Test.cpp new file mode 100644 index 0000000000..ef5ae42343 --- /dev/null +++ b/src/tests/system/kernel/unit/Test.cpp @@ -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) +{ +} diff --git a/src/tests/system/kernel/unit/Test.h b/src/tests/system/kernel/unit/Test.h new file mode 100644 index 0000000000..113c755fbe --- /dev/null +++ b/src/tests/system/kernel/unit/Test.h @@ -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 +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 +StandardTest::StandardTest(const char* name, TestClass* object, + bool (TestClass::*method)(TestContext&)) + : + Test(name), + fObject(object), + fMethod(method) +{ +} + + +template +StandardTest::~StandardTest() +{ + delete fObject; +} + + +template +status_t +StandardTest::Setup(TestContext& context) +{ + return fObject->Setup(context); +} + + +template +bool +StandardTest::Run(TestContext& context) +{ + return (fObject->*fMethod)(context); +} + + +template +void +StandardTest::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 diff --git a/src/tests/system/kernel/unit/TestContext.cpp b/src/tests/system/kernel/unit/TestContext.cpp new file mode 100644 index 0000000000..fe1478b8e9 --- /dev/null +++ b/src/tests/system/kernel/unit/TestContext.cpp @@ -0,0 +1,283 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + + +#include "TestContext.h" + +#include + +#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()); +} diff --git a/src/tests/system/kernel/unit/TestContext.h b/src/tests/system/kernel/unit/TestContext.h new file mode 100644 index 0000000000..50a681fbf3 --- /dev/null +++ b/src/tests/system/kernel/unit/TestContext.h @@ -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 +#include + +#include + +#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 diff --git a/src/tests/system/kernel/unit/TestError.cpp b/src/tests/system/kernel/unit/TestError.cpp new file mode 100644 index 0000000000..d1295646d6 --- /dev/null +++ b/src/tests/system/kernel/unit/TestError.cpp @@ -0,0 +1,23 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + + +#include "TestError.h" + +#include + + +TestError::TestError(Test* test, char* message) + : + fTest(test), + fMessage(message) +{ +} + + +TestError::~TestError() +{ + free(fMessage); +} diff --git a/src/tests/system/kernel/unit/TestError.h b/src/tests/system/kernel/unit/TestError.h new file mode 100644 index 0000000000..8232072c8d --- /dev/null +++ b/src/tests/system/kernel/unit/TestError.h @@ -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 + + +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 diff --git a/src/tests/system/kernel/unit/TestManager.cpp b/src/tests/system/kernel/unit/TestManager.cpp new file mode 100644 index 0000000000..67b3c71a92 --- /dev/null +++ b/src/tests/system/kernel/unit/TestManager.cpp @@ -0,0 +1,88 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + + +#include "TestManager.h" + +#include + +#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()); +} diff --git a/src/tests/system/kernel/unit/TestManager.h b/src/tests/system/kernel/unit/TestManager.h new file mode 100644 index 0000000000..8410a7f346 --- /dev/null +++ b/src/tests/system/kernel/unit/TestManager.h @@ -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 diff --git a/src/tests/system/kernel/unit/TestOutput.cpp b/src/tests/system/kernel/unit/TestOutput.cpp new file mode 100644 index 0000000000..8ad3500c98 --- /dev/null +++ b/src/tests/system/kernel/unit/TestOutput.cpp @@ -0,0 +1,50 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + + +#include "TestOutput.h" + +#include + +#include + + +// #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; +} diff --git a/src/tests/system/kernel/unit/TestOutput.h b/src/tests/system/kernel/unit/TestOutput.h new file mode 100644 index 0000000000..eb0084eafb --- /dev/null +++ b/src/tests/system/kernel/unit/TestOutput.h @@ -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 + +#include + + +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 diff --git a/src/tests/system/kernel/unit/TestSuite.cpp b/src/tests/system/kernel/unit/TestSuite.cpp new file mode 100644 index 0000000000..79c575bd5c --- /dev/null +++ b/src/tests/system/kernel/unit/TestSuite.cpp @@ -0,0 +1,161 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + + +#include "TestSuite.h" + +#include + +#include + +#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; +} diff --git a/src/tests/system/kernel/unit/TestSuite.h b/src/tests/system/kernel/unit/TestSuite.h new file mode 100644 index 0000000000..fb7192d72e --- /dev/null +++ b/src/tests/system/kernel/unit/TestSuite.h @@ -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 + +#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* test = new(std::nothrow) StandardTest( \ + #method, object, &type::method); \ + if (test == NULL) { \ + delete object; \ + return NULL; \ + } \ + ADD_TEST(suite, test); \ + } while (false) + + +#endif // TEST_SUITE_H diff --git a/src/tests/system/kernel/unit/TestThread.h b/src/tests/system/kernel/unit/TestThread.h new file mode 100644 index 0000000000..167ec9be7e --- /dev/null +++ b/src/tests/system/kernel/unit/TestThread.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 + +#include "TestContext.h" + + +template +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 +thread_id +SpawnThread(ObjectType* object, + void (ObjectType::*method)(TestContext&, ParameterType*), const char* name, + int32 priority, ParameterType* arg) +{ + TestThread* thread + = new(std::nothrow) TestThread(object, + method, arg); + if (thread == NULL) + return B_NO_MEMORY; + + return thread->Spawn(name, priority); +} + + +#endif // TEST_THREAD_H diff --git a/src/tests/system/kernel/unit/TestVisitor.cpp b/src/tests/system/kernel/unit/TestVisitor.cpp new file mode 100644 index 0000000000..92ec81d2e0 --- /dev/null +++ b/src/tests/system/kernel/unit/TestVisitor.cpp @@ -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; +} diff --git a/src/tests/system/kernel/unit/TestVisitor.h b/src/tests/system/kernel/unit/TestVisitor.h new file mode 100644 index 0000000000..631105b163 --- /dev/null +++ b/src/tests/system/kernel/unit/TestVisitor.h @@ -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 diff --git a/src/tests/system/kernel/unit/kernel_unit_tests.cpp b/src/tests/system/kernel/unit/kernel_unit_tests.cpp new file mode 100644 index 0000000000..e056c3b39a --- /dev/null +++ b/src/tests/system/kernel/unit/kernel_unit_tests.cpp @@ -0,0 +1,321 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + + +#include +#include + +#include +#include + +#include + +#include +#include + +#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 [ ] [ ]\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 outputDeleter(output); + + // parse arguments + char** argv; + int argc; + if (!parse_command_line(buffer, argv, argc)) + return B_NO_MEMORY; + ArrayDeleter 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; +} diff --git a/src/tests/system/kernel/unit/lock/Jamfile b/src/tests/system/kernel/unit/lock/Jamfile new file mode 100644 index 0000000000..bb4f63c859 --- /dev/null +++ b/src/tests/system/kernel/unit/lock/Jamfile @@ -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 +; diff --git a/src/tests/system/kernel/unit/lock/LockTestSuite.cpp b/src/tests/system/kernel/unit/lock/LockTestSuite.cpp new file mode 100644 index 0000000000..e92c87ee00 --- /dev/null +++ b/src/tests/system/kernel/unit/lock/LockTestSuite.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; +} diff --git a/src/tests/system/kernel/unit/lock/LockTestSuite.h b/src/tests/system/kernel/unit/lock/LockTestSuite.h new file mode 100644 index 0000000000..cd464a8c4f --- /dev/null +++ b/src/tests/system/kernel/unit/lock/LockTestSuite.h @@ -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 diff --git a/src/tests/system/kernel/unit/lock/RWLockTests.cpp b/src/tests/system/kernel/unit/lock/RWLockTests.cpp new file mode 100644 index 0000000000..3a3930fd06 --- /dev/null +++ b/src/tests/system/kernel/unit/lock/RWLockTests.cpp @@ -0,0 +1,241 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + + +#include "RWLockTests.h" + +#include + +#include + +#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; +} diff --git a/src/tests/system/kernel/unit/lock/RWLockTests.h b/src/tests/system/kernel/unit/lock/RWLockTests.h new file mode 100644 index 0000000000..0f91aa2371 --- /dev/null +++ b/src/tests/system/kernel/unit/lock/RWLockTests.h @@ -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