diff --git a/headers/tools/cppunit/TestShell.h b/headers/tools/cppunit/TestShell.h index c815e8971f..2069ebe65a 100644 --- a/headers/tools/cppunit/TestShell.h +++ b/headers/tools/cppunit/TestShell.h @@ -7,14 +7,18 @@ #include #include #include +#include #include #include #include #include class BDirectory; +class BLocker; class BPath; +class ElfSymbolPatchGroup; + // Defines SuiteFunction to be a pointer to a function that // takes no arguments and returns a pointer to a CppUnit::Test typedef CppUnit::Test* (*SuiteFunction)(void); @@ -22,6 +26,19 @@ typedef CppUnit::Test* (*SuiteFunction)(void); // This is just absurd to have to type... typedef CppUnit::SynchronizedObject::SynchronizationObject SyncObject; +/*! \brief Executes a statement that is supposed to call debugger(). + An exception is thrown if the debugger is not invoked by the + statement. +*/ +#define CPPUNIT_ASSERT_DEBUGGER(statement) \ + BTestShell::GlobalShell()->ExpectDebuggerCall(); \ + statement; \ + ::CppUnit::Asserter::failIf( \ + !BTestShell::GlobalShell()->WasDebuggerCalled(), \ + (#statement), \ + CPPUNIT_SOURCELINE() ); + + //! BeOS savvy command line interface for the CppUnit testing framework. /*! This class provides a fully functional command-line testing interface built on top of the CppUnit testing library. You add named test suites @@ -83,6 +100,8 @@ public: const char* TestDir() const; static const char* GlobalTestDir() { return (fGlobalShell ? fGlobalShell->TestDir() : NULL); }; + void ExpectDebuggerCall(); + bool WasDebuggerCalled(); protected: typedef std::map TestMap; @@ -101,6 +120,13 @@ protected: static const char indent[]; bool fListTestsAndExit; BPath *fTestDir; + BLocker *fPatchGroupLocker; + int32 fTLSDebuggerCall; + ElfSymbolPatchGroup *fPatchGroup; + void (*fOldDebuggerHook)(const char*); + image_id (*fOldLoadAddOnHook)(const char*); + status_t (*fOldUnloadAddOnHook)(image_id); + //! Prints a brief description of the program. virtual void PrintDescription(int argc, char *argv[]); @@ -140,15 +166,25 @@ protected: //! Sets the current test directory. void UpdateTestDir(char *argv[]); - - -private: - //! Prevents the use of the copy constructor. - BTestShell( const BTestShell © ); - - //! Prevents the use of the copy operator. - void operator =( const BTestShell © ); + void InstallPatches(); + void UninstallPatches(); + +private: + //! Prevents the use of the copy constructor. + BTestShell( const BTestShell © ); + + //! Prevents the use of the copy operator. + void operator =( const BTestShell © ); + + void _Debugger(const char* message); + image_id _LoadAddOn(const char* path); + status_t _UnloadAddOn(image_id image); + + static void _DebuggerHook(const char* message); + static image_id _LoadAddOnHook(const char* path); + static status_t _UnloadAddOnHook(image_id image); + }; // class BTestShell #endif // _beos_test_shell_h_ diff --git a/src/tools/cppunit/Jamfile b/src/tools/cppunit/Jamfile index 8f72fe3ae1..75fc4664dd 100644 --- a/src/tools/cppunit/Jamfile +++ b/src/tools/cppunit/Jamfile @@ -1,5 +1,7 @@ SubDir OBOS_TOP src tools cppunit ; +UseHeaders [ FDirName $(OBOS_TOP) headers tools elfsymbolpatcher ] ; + rule CppUnitLibrary { # CppUnitLibrary ; @@ -49,4 +51,5 @@ CppUnitLibrary LinkSharedOSLibs libcppunit.so : stdc++.r4 /boot/develop/lib/x86/libbe.so + libelfsymbolpatcher.a ; diff --git a/src/tools/cppunit/TestShell.cpp b/src/tools/cppunit/TestShell.cpp index cef05034c0..31247f8c65 100644 --- a/src/tools/cppunit/TestShell.cpp +++ b/src/tools/cppunit/TestShell.cpp @@ -1,19 +1,27 @@ -#include +#include +#include +#include +#include -#include -#include -#include -#include -#include +#include #include #include #include +#include #include +#include + +#include +#include +#include +#include +#include +#include + +#include #include -#include -#include -#include -#include + +#include BTestShell *BTestShell::fGlobalShell = NULL; const char BTestShell::indent[] = " "; @@ -24,11 +32,18 @@ BTestShell::BTestShell(const std::string &description, SyncObject *syncObject) , fDescription(description) , fListTestsAndExit(false) , fTestDir(NULL) + , fPatchGroupLocker(new(nothrow) BLocker) + , fPatchGroup(NULL) + , fOldDebuggerHook(NULL) + , fOldLoadAddOnHook(NULL) + , fOldUnloadAddOnHook(NULL) { -}; + fTLSDebuggerCall = tls_allocate(); +} BTestShell::~BTestShell() { delete fTestDir; + delete fPatchGroupLocker; } @@ -183,7 +198,9 @@ BTestShell::Run(int argc, char *argv[]) { // Run all the tests InitOutput(); + InstallPatches(); suite.run(&fTestResults); + UninstallPatches(); PrintResults(); return 0; @@ -199,6 +216,36 @@ BTestShell::TestDir() const { return (fTestDir ? fTestDir->Path() : NULL); } +// ExpectDebuggerCall +/*! \brief Marks the current thread as ready for a debugger() call. + + A subsequent call of debugger() will be intercepted and a respective + flag will be set. WasDebuggerCalled() will then return \c true. +*/ +void +BTestShell::ExpectDebuggerCall() +{ + void *var = tls_get(fTLSDebuggerCall); + ::CppUnit::Asserter::failIf(var, "ExpectDebuggerCall(): Already expecting " + "a debugger() call."); + tls_set(fTLSDebuggerCall, (void*)1); +} + +// WasDebuggerCalled +/*! \brief Returns whether the current thread has invoked debugger() since + the last ExpectDebuggerCall() invocation and resets the mode so + that subsequent debugger() calls will hit the debugger. + \return \c true, if debugger() has been called by the current thread since + the last invocation of ExpectDebuggerCall(), \c false otherwise. +*/ +bool +BTestShell::WasDebuggerCalled() +{ + void *var = tls_get(fTLSDebuggerCall); + tls_set(fTLSDebuggerCall, NULL); + return ((int)var > 1); +} + void BTestShell::PrintDescription(int argc, char *argv[]) { cout << endl << fDescription; @@ -412,3 +459,141 @@ BTestShell::UpdateTestDir(char *argv[]) { cout << "Couldn't find the path to the test app." << endl; } +// InstallPatches +/*! \brief Patches the debugger() function. + + load_add_on() and unload_add_on() are patches as well, to keep the + patch group up to date, when images are loaded/unloaded. +*/ +void +BTestShell::InstallPatches() +{ + if (fPatchGroup) { + cerr << "BTestShell::InstallPatches(): Patch group already exist!" + << endl; + return; + } + BAutolock locker(fPatchGroupLocker); + if (!locker.IsLocked()) { + cerr << "BTestShell::InstallPatches(): Failed to acquire patch " + "group lock!" << endl; + return; + } + fPatchGroup = new(nothrow) ElfSymbolPatchGroup; + // init the symbol patch group + if (!fPatchGroup) { + cerr << "BTestShell::InstallPatches(): Failed to allocate patch " + "group!" << endl; + return; + } + if (// debugger() + fPatchGroup->AddPatch("debugger", (void*)&_DebuggerHook, + (void**)&fOldDebuggerHook) == B_OK + // load_add_on() + && fPatchGroup->AddPatch("load_add_on", (void*)&_LoadAddOnHook, + (void**)&fOldLoadAddOnHook) == B_OK + // unload_add_on() + && fPatchGroup->AddPatch("unload_add_on", (void*)&_UnloadAddOnHook, + (void**)&fOldUnloadAddOnHook) == B_OK + ) { + // everything went fine + fPatchGroup->Patch(); + } else { + cerr << "BTestShell::InstallPatches(): Failed to patch all symbols!" + << endl; + UninstallPatches(); + } +} + +// UninstallPatches +/*! \brief Undoes the patches applied by InstallPatches(). +*/ +void +BTestShell::UninstallPatches() +{ + BAutolock locker(fPatchGroupLocker); + if (!locker.IsLocked()) { + cerr << "BTestShell::UninstallPatches(): " + "Failed to acquire patch group lock!" << endl; + return; + } + if (fPatchGroup) { + fPatchGroup->Restore(); + delete fPatchGroup; + fPatchGroup = NULL; + } +} + +// _Debugger +void +BTestShell::_Debugger(const char *message) +{ + if (!this || !fPatchGroup) { + debugger(message); + return; + } + BAutolock locker(fPatchGroupLocker); + if (!locker.IsLocked() || !fPatchGroup) { + debugger(message); + return; + } +cout << "debugger() called: " << message << endl; + void *var = tls_get(fTLSDebuggerCall); + if (var) + tls_set(fTLSDebuggerCall, (void*)((int)var + 1)); + else + (*fOldDebuggerHook)(message); +} + +// _LoadAddOn +image_id +BTestShell::_LoadAddOn(const char *path) +{ + if (!this || !fPatchGroup) + return load_add_on(path); + BAutolock locker(fPatchGroupLocker); + if (!locker.IsLocked() || !fPatchGroup) + return load_add_on(path); + image_id result = (*fOldLoadAddOnHook)(path); + fPatchGroup->Update(); + return result; +} + +// _UnloadAddOn +status_t +BTestShell::_UnloadAddOn(image_id image) +{ + if (!this || !fPatchGroup) + return unload_add_on(image); + BAutolock locker(fPatchGroupLocker); + if (!locker.IsLocked() || !fPatchGroup) + return unload_add_on(image); + + if (!this || !fPatchGroup) + return unload_add_on(image); + status_t result = (*fOldUnloadAddOnHook)(image); + fPatchGroup->Update(); + return result; +} + +// _DebuggerHook +void +BTestShell::_DebuggerHook(const char *message) +{ + fGlobalShell->_Debugger(message); +} + +// _LoadAddOnHook +image_id +BTestShell::_LoadAddOnHook(const char *path) +{ + return fGlobalShell->_LoadAddOn(path); +} + +// _UnloadAddOnHook +status_t +BTestShell::_UnloadAddOnHook(image_id image) +{ + return fGlobalShell->_UnloadAddOn(image); +} +