#include <iostream>
#include <map>
#include <set>
#include <string>
#include <vector>
#include <Autolock.h>
#include <Directory.h>
#include <Entry.h>
#include <image.h>
#include <Locker.h>
#include <Path.h>
#include <TLS.h>
#include <cppunit/Exception.h>
#include <cppunit/Test.h>
#include <cppunit/TestAssert.h>
#include <cppunit/TestFailure.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestSuite.h>
#include <TestShell.h>
#include <TestListener.h>
using std::cout;
using std::endl;
using std::set;
_EXPORT BTestShell *BTestShell::fGlobalShell = NULL;
const char BTestShell::indent[] = " ";
_EXPORT
BTestShell::BTestShell(const string &description, SyncObject *syncObject)
: fVerbosityLevel(v2)
, fTestResults(syncObject)
, fDescription(description)
, fListTestsAndExit(false)
, fTestDir(NULL)
#ifndef NO_ELF_SYMBOL_PATCHING
, fPatchGroupLocker(new BLocker)
, fPatchGroup(NULL)
, fOldDebuggerHook(NULL)
, fOldLoadAddOnHook(NULL)
, fOldUnloadAddOnHook(NULL)
#endif
{
fTLSDebuggerCall = tls_allocate();
}
_EXPORT
BTestShell::~BTestShell() {
delete fTestDir;
#ifndef NO_ELF_SYMBOL_PATCHING
delete fPatchGroupLocker;
#endif
}
_EXPORT
status_t
BTestShell::AddSuite(BTestSuite *suite) {
if (suite) {
if (Verbosity() >= v3)
cout << "Adding suite '" << suite->getName() << "'" << endl;
fSuites[suite->getName()] = suite;
const TestMap &map = suite->getTests();
for (TestMap::const_iterator i = map.begin();
i != map.end();
i++) {
AddTest(i->first, i->second);
if (Verbosity() >= v4 && i->second)
cout << " " << i->first << endl;
}
return B_OK;
} else
return B_BAD_VALUE;
}
_EXPORT
void
BTestShell::AddTest(const string &name, CppUnit::Test *test) {
if (test != NULL)
fTests[name] = test;
else
fTests.erase(name);
}
_EXPORT
int32
BTestShell::LoadSuitesFrom(BDirectory *libDir) {
if (!libDir || libDir->InitCheck() != B_OK)
return 0;
BEntry addonEntry;
BPath addonPath;
image_id addonImage;
int count = 0;
typedef BTestSuite* (*suiteFunc)(void);
suiteFunc func;
while (libDir->GetNextEntry(&addonEntry, true) == B_OK) {
status_t err;
err = addonEntry.GetPath(&addonPath);
if (!err) {
addonImage = load_add_on(addonPath.Path());
err = (addonImage >= 0 ? B_OK : B_ERROR);
}
if (err == B_OK) {
err = get_image_symbol(addonImage, "getTestSuite",
B_SYMBOL_TYPE_TEXT, reinterpret_cast<void **>(&func));
} else {
}
if (err == B_OK)
err = AddSuite(func());
if (err == B_OK)
count++;
}
return count;
}
_EXPORT
int
BTestShell::Run(int argc, char *argv[]) {
UpdateTestDir(argv);
if (!ProcessArguments(argc, argv))
return 0;
LoadDynamicSuites();
if (fListTestsAndExit) {
PrintInstalledTests();
return 0;
}
CppUnit::TestSuite suite;
if (fTests.empty()) {
cout << "ERROR: No installed tests to run!" << endl;
return 0;
} else if (fSuitesToRun.empty() && fTestsToRun.empty()) {
TestMap::iterator i;
for (i = fTests.begin(); i != fTests.end(); ++i)
suite.addTest( i->second );
} else {
set<string>::const_iterator i;
set<string> suitesToRemove;
for (i = fTestsToRun.begin(); i != fTestsToRun.end(); ++i) {
if (fSuites.find(*i) != fSuites.end()) {
if (fTests.find(*i) == fTests.end()) {
suitesToRemove.insert(*i);
}
const TestMap &tests = fSuites[*i]->getTests();
TestMap::const_iterator j;
for (j = tests.begin(); j != tests.end(); j++) {
fTestsToRun.insert( j->first );
}
}
}
for (i = suitesToRemove.begin(); i != suitesToRemove.end(); i++) {
fTestsToRun.erase(*i);
}
for (i = fTestsToRun.begin(); i != fTestsToRun.end(); ++i) {
if (fTests.find(*i) != fTests.end()) {
suite.addTest( fTests[*i] );
} else {
cout << endl << "ERROR: Invalid argument \"" << *i << "\"" << endl;
PrintHelp();
return 0;
}
}
}
InitOutput();
InstallPatches();
suite.run(&fTestResults);
UninstallPatches();
PrintResults();
return 0;
}
_EXPORT
BTestShell::VerbosityLevel
BTestShell::Verbosity() const {
return fVerbosityLevel;
}
_EXPORT
const char*
BTestShell::TestDir() const {
return (fTestDir ? fTestDir->Path() : NULL);
}
A subsequent call of debugger() will be intercepted and a respective
flag will be set. WasDebuggerCalled() will then return \c true.
*/
_EXPORT
void
BTestShell::ExpectDebuggerCall()
{
void *var = tls_get(fTLSDebuggerCall);
::CppUnit::Asserter::failIf(var, "ExpectDebuggerCall(): Already expecting "
"a debugger() call.");
tls_set(fTLSDebuggerCall, (void*)1);
}
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.
*/
_EXPORT
bool
BTestShell::WasDebuggerCalled()
{
void *var = tls_get(fTLSDebuggerCall);
tls_set(fTLSDebuggerCall, NULL);
return ((addr_t)var > 1);
}
_EXPORT
void
BTestShell::PrintDescription(int argc, char *argv[]) {
cout << endl << fDescription;
}
_EXPORT
void
BTestShell::PrintHelp() {
cout << endl;
cout << "VALID ARGUMENTS: " << endl;
PrintValidArguments();
cout << endl;
}
_EXPORT
void
BTestShell::PrintValidArguments() {
cout << indent << "--help Displays this help text plus some other garbage" << endl;
cout << indent << "--list Lists the names of classes with installed tests" << endl;
cout << indent << "-v0 Sets verbosity level to 0 (concise summary only)" << endl;
cout << indent << "-v1 Sets verbosity level to 1 (complete summary only)" << endl;
cout << indent << "-v2 Sets verbosity level to 2 (*default* -- per-test results plus" << endl;
cout << indent << " complete summary)" << endl;
cout << indent << "-v3 Sets verbosity level to 3 (partial dynamic loading information, " << endl;
cout << indent << " per-test results and timing info, plus complete summary)" << endl;
cout << indent << "-v4 Sets verbosity level to 4 (complete dynamic loading information, " << endl;
cout << indent << " per-test results and timing info, plus complete summary)" << endl;
cout << indent << "NAME Instructs the program to run the test for the given class or all" << endl;
cout << indent << " the tests for the given suite. If some bonehead adds both a class" << endl;
cout << indent << " and a suite with the same name, the suite will be run, not the class" << endl;
cout << indent << " (unless the class is part of the suite with the same name :-). If no" << endl;
cout << indent << " classes or suites are specified, all available tests are run" << endl;
cout << indent << "-lPATH Adds PATH to the search path for dynamically loadable test" << endl;
cout << indent << " libraries" << endl;
}
_EXPORT
void
BTestShell::PrintInstalledTests() {
cout << "------------------------------------------------------------------------------" << endl;
cout << "Available Suites:" << endl;
cout << "------------------------------------------------------------------------------" << endl;
SuiteMap::const_iterator j;
for (j = fSuites.begin(); j != fSuites.end(); ++j)
cout << j->first << endl;
cout << endl;
cout << "------------------------------------------------------------------------------" << endl;
cout << "Available Tests:" << endl;
cout << "------------------------------------------------------------------------------" << endl;
TestMap::const_iterator i;
for (i = fTests.begin(); i != fTests.end(); ++i)
cout << i->first << endl;
cout << endl;
}
_EXPORT
bool
BTestShell::ProcessArguments(int argc, char *argv[]) {
if (argc < 2)
return true;
for (int i = 1; i < argc; i++) {
string str(argv[i]);
if (!ProcessArgument(str, argc, argv))
return false;
}
return true;
}
_EXPORT
bool
BTestShell::ProcessArgument(string arg, int argc, char *argv[]) {
if (arg == "--help") {
PrintDescription(argc, argv);
PrintHelp();
return false;
} else if (arg == "--list") {
fListTestsAndExit = true;
} else if (arg == "-v0") {
fVerbosityLevel = v0;
} else if (arg == "-v1") {
fVerbosityLevel = v1;
} else if (arg == "-v2") {
fVerbosityLevel = v2;
} else if (arg == "-v3") {
fVerbosityLevel = v3;
} else if (arg == "-v4") {
fVerbosityLevel = v4;
} else if (arg.length() >= 2 && arg[0] == '-' && arg[1] == 'l') {
fLibDirs.insert(arg.substr(2, arg.size()-2));
} else {
fTestsToRun.insert(arg);
}
return true;
}
_EXPORT
void
BTestShell::InitOutput() {
if (fVerbosityLevel >= v2) {
cout << "------------------------------------------------------------------------------" << endl;
cout << "Tests" << endl;
cout << "------------------------------------------------------------------------------" << endl;
fTestResults.addListener(new BTestListener);
}
fTestResults.addListener(&fResultsCollector);
}
_EXPORT
void
BTestShell::PrintResults() {
if (fVerbosityLevel > v0) {
cout << "------------------------------------------------------------------------------" << endl;
cout << "Results " << endl;
cout << "------------------------------------------------------------------------------" << endl;
::CppUnit::TestResultCollector::TestFailures::const_iterator iFailure;
if (fResultsCollector.testFailuresTotal() > 0) {
if (fResultsCollector.testFailures() > 0) {
cout << "- FAILURES: " << fResultsCollector.testFailures() << endl;
for (iFailure = fResultsCollector.failures().begin();
iFailure != fResultsCollector.failures().end();
++iFailure)
{
if (!(*iFailure)->isError())
cout << " " << (*iFailure)->toString() << endl;
}
}
if (fResultsCollector.testErrors() > 0) {
cout << "- ERRORS: " << fResultsCollector.testErrors() << endl;
for (iFailure = fResultsCollector.failures().begin();
iFailure != fResultsCollector.failures().end();
++iFailure)
{
if ((*iFailure)->isError())
cout << " " << (*iFailure)->toString() << endl;
}
}
}
else
cout << "+ PASSED" << endl;
cout << endl;
}
else {
if (fResultsCollector.testFailuresTotal() > 0)
cout << "- FAILED" << endl;
else
cout << "+ PASSED" << endl;
}
}
_EXPORT
void
BTestShell::LoadDynamicSuites() {
if (Verbosity() >= v3) {
cout << "------------------------------------------------------------------------------" << endl;
cout << "Loading " << endl;
cout << "------------------------------------------------------------------------------" << endl;
}
set<string>::iterator i;
for (i = fLibDirs.begin(); i != fLibDirs.end(); i++) {
BDirectory libDir((*i).c_str());
if (Verbosity() >= v3)
cout << "Checking " << *i << endl;
LoadSuitesFrom(&libDir);
if (Verbosity() >= v3) {
}
}
if (Verbosity() >= v3)
cout << endl;
for (SuiteMap::const_iterator i = fSuites.begin(); i != fSuites.end(); i++) {
if (fTests.find(i->first) != fTests.end() && Verbosity() > v0) {
cout << "WARNING: '" << i->first << "' refers to both a test suite *and* an individual" <<
endl << " test. Both will be executed, but it is reccommended you rename" <<
endl << " one of them to resolve the conflict." <<
endl << endl;
}
}
}
_EXPORT
void
BTestShell::UpdateTestDir(char *argv[]) {
BPath path(argv[0]);
if (path.InitCheck() == B_OK) {
delete fTestDir;
fTestDir = new BPath();
if (path.GetParent(fTestDir) != B_OK)
cout << "Couldn't get test dir." << endl;
} else
cout << "Couldn't find the path to the test app." << endl;
}
load_add_on() and unload_add_on() are patches as well, to keep the
patch group up to date, when images are loaded/unloaded.
*/
_EXPORT
void
BTestShell::InstallPatches()
{
#ifndef NO_ELF_SYMBOL_PATCHING
if (fPatchGroup) {
std::cerr << "BTestShell::InstallPatches(): Patch group already exist!"
<< endl;
return;
}
BAutolock locker(fPatchGroupLocker);
if (!locker.IsLocked()) {
std::cerr << "BTestShell::InstallPatches(): Failed to acquire patch "
"group lock!" << endl;
return;
}
fPatchGroup = new(std::nothrow) ElfSymbolPatchGroup;
if (!fPatchGroup) {
std::cerr << "BTestShell::InstallPatches(): Failed to allocate patch "
"group!" << endl;
return;
}
if (
fPatchGroup->AddPatch("debugger", (void*)&_DebuggerHook,
(void**)&fOldDebuggerHook) == B_OK
&& fPatchGroup->AddPatch("load_add_on", (void*)&_LoadAddOnHook,
(void**)&fOldLoadAddOnHook) == B_OK
&& fPatchGroup->AddPatch("unload_add_on", (void*)&_UnloadAddOnHook,
(void**)&fOldUnloadAddOnHook) == B_OK
) {
fPatchGroup->Patch();
} else {
std::cerr << "BTestShell::InstallPatches(): Failed to patch all "
"symbols!" << endl;
UninstallPatches();
}
#endif
}
*/
_EXPORT
void
BTestShell::UninstallPatches()
{
#ifndef NO_ELF_SYMBOL_PATCHING
BAutolock locker(fPatchGroupLocker);
if (!locker.IsLocked()) {
std::cerr << "BTestShell::UninstallPatches(): "
"Failed to acquire patch group lock!" << endl;
return;
}
if (fPatchGroup) {
fPatchGroup->Restore();
delete fPatchGroup;
fPatchGroup = NULL;
}
#endif
}
#ifndef NO_ELF_SYMBOL_PATCHING
_EXPORT
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*)((addr_t)var + 1));
else
(*fOldDebuggerHook)(message);
}
_EXPORT
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;
}
_EXPORT
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;
}
_EXPORT
void
BTestShell::_DebuggerHook(const char *message)
{
fGlobalShell->_Debugger(message);
}
_EXPORT
image_id
BTestShell::_LoadAddOnHook(const char *path)
{
return fGlobalShell->_LoadAddOn(path);
}
_EXPORT
status_t
BTestShell::_UnloadAddOnHook(image_id image)
{
return fGlobalShell->_UnloadAddOn(image);
}
#endif