1 #include <map> 2 #include <set> 3 #include <string> 4 #include <vector> 5 6 #include <Autolock.h> 7 #include <Directory.h> 8 #include <Entry.h> 9 #include <image.h> 10 #include <Locker.h> 11 #include <Path.h> 12 #include <TLS.h> 13 14 #include <cppunit/Exception.h> 15 #include <cppunit/Test.h> 16 #include <cppunit/TestAssert.h> 17 #include <cppunit/TestFailure.h> 18 #include <cppunit/TestResult.h> 19 #include <cppunit/TestSuite.h> 20 21 #include <TestShell.h> 22 #include <TestListener.h> 23 24 #include <ElfSymbolPatcher.h> 25 26 BTestShell *BTestShell::fGlobalShell = NULL; 27 const char BTestShell::indent[] = " "; 28 29 BTestShell::BTestShell(const std::string &description, SyncObject *syncObject) 30 : fVerbosityLevel(v2) 31 , fTestResults(syncObject) 32 , fDescription(description) 33 , fListTestsAndExit(false) 34 , fTestDir(NULL) 35 , fPatchGroupLocker(new(nothrow) BLocker) 36 , fPatchGroup(NULL) 37 , fOldDebuggerHook(NULL) 38 , fOldLoadAddOnHook(NULL) 39 , fOldUnloadAddOnHook(NULL) 40 { 41 fTLSDebuggerCall = tls_allocate(); 42 } 43 44 BTestShell::~BTestShell() { 45 delete fTestDir; 46 delete fPatchGroupLocker; 47 } 48 49 50 status_t 51 BTestShell::AddSuite(BTestSuite *suite) { 52 if (suite) { 53 if (Verbosity() >= v3) 54 cout << "Adding suite '" << suite->getName() << "'" << endl; 55 56 // Add the suite 57 fSuites[suite->getName()] = suite; 58 59 // Add its tests 60 const TestMap &map = suite->getTests(); 61 for (TestMap::const_iterator i = map.begin(); 62 i != map.end(); 63 i++) { 64 AddTest(i->first, i->second); 65 if (Verbosity() >= v4 && i->second) 66 cout << " " << i->first << endl; 67 } 68 69 return B_OK; 70 } else 71 return B_BAD_VALUE; 72 } 73 74 void 75 BTestShell::AddTest(const std::string &name, CppUnit::Test *test) { 76 if (test != NULL) 77 fTests[name] = test; 78 else 79 fTests.erase(name); 80 } 81 82 int32 83 BTestShell::LoadSuitesFrom(BDirectory *libDir) { 84 if (!libDir || libDir->InitCheck() != B_OK) 85 return 0; 86 87 BEntry addonEntry; 88 BPath addonPath; 89 image_id addonImage; 90 int count = 0; 91 92 typedef BTestSuite* (*suiteFunc)(void); 93 suiteFunc func; 94 95 while (libDir->GetNextEntry(&addonEntry, true) == B_OK) { 96 status_t err; 97 err = addonEntry.GetPath(&addonPath); 98 if (!err) { 99 // cout << "Checking " << addonPath.Path() << "..." << flush; 100 addonImage = load_add_on(addonPath.Path()); 101 err = (addonImage > 0 ? B_OK : B_ERROR); 102 } 103 if (!err) { 104 // cout << "..." << endl; 105 err = get_image_symbol(addonImage, 106 "getTestSuite", 107 B_SYMBOL_TYPE_TEXT, 108 reinterpret_cast<void **>(&func)); 109 } else { 110 // cout << " !!! err == " << err << endl; 111 } 112 if (!err) 113 err = AddSuite(func()); 114 if (!err) 115 count++; 116 } 117 return count; 118 } 119 120 int 121 BTestShell::Run(int argc, char *argv[]) { 122 // Make note of which directory we started in 123 UpdateTestDir(argv); 124 125 // Parse the command line args 126 if (!ProcessArguments(argc, argv)) 127 return 0; 128 129 // Load any dynamically loadable tests we can find 130 LoadDynamicSuites(); 131 132 // See if the user requested a list of tests. If so, 133 // print and bail. 134 if (fListTestsAndExit) { 135 PrintInstalledTests(); 136 return 0; 137 } 138 139 // Add the proper tests to our suite (or exit if there 140 // are no tests installed). 141 CppUnit::TestSuite suite; 142 if (fTests.empty()) { 143 144 // No installed tests whatsoever, so bail 145 cout << "ERROR: No installed tests to run!" << endl; 146 return 0; 147 148 } else if (fSuitesToRun.empty() && fTestsToRun.empty()) { 149 150 // None specified, so run them all 151 TestMap::iterator i; 152 for (i = fTests.begin(); i != fTests.end(); ++i) 153 suite.addTest( i->second ); 154 155 } else { 156 std::set<std::string>::const_iterator i; 157 std::set<std::string> suitesToRemove; 158 159 // Add all the tests from any specified suites to the list of 160 // tests to run (since we use a set, this eliminates the concern 161 // of having duplicate entries). 162 for (i = fTestsToRun.begin(); i != fTestsToRun.end(); ++i) { 163 // See if it's a suite (since it may just be a single test) 164 if (fSuites.find(*i) != fSuites.end()) { 165 // Note the suite name for later removal unless the 166 // name is also the name of an available individual test 167 if (fTests.find(*i) == fTests.end()) { 168 suitesToRemove.insert(*i); 169 } 170 const TestMap &tests = fSuites[*i]->getTests(); 171 TestMap::const_iterator j; 172 for (j = tests.begin(); j != tests.end(); j++) { 173 fTestsToRun.insert( j->first ); 174 } 175 } 176 } 177 178 // Remove the names of all of the suites we discovered from the 179 // list of tests to run (unless there's also an installed individual 180 // test of the same name). 181 for (i = suitesToRemove.begin(); i != suitesToRemove.end(); i++) { 182 fTestsToRun.erase(*i); 183 } 184 185 // Everything still in fTestsToRun must then be an explicit test 186 for (i = fTestsToRun.begin(); i != fTestsToRun.end(); ++i) { 187 // Make sure it's a valid test 188 if (fTests.find(*i) != fTests.end()) { 189 suite.addTest( fTests[*i] ); 190 } else { 191 cout << endl << "ERROR: Invalid argument \"" << *i << "\"" << endl; 192 PrintHelp(); 193 return 0; 194 } 195 } 196 197 } 198 199 // Run all the tests 200 InitOutput(); 201 InstallPatches(); 202 suite.run(&fTestResults); 203 UninstallPatches(); 204 PrintResults(); 205 206 return 0; 207 } 208 209 BTestShell::VerbosityLevel 210 BTestShell::Verbosity() const { 211 return fVerbosityLevel; 212 } 213 214 const char* 215 BTestShell::TestDir() const { 216 return (fTestDir ? fTestDir->Path() : NULL); 217 } 218 219 // ExpectDebuggerCall 220 /*! \brief Marks the current thread as ready for a debugger() call. 221 222 A subsequent call of debugger() will be intercepted and a respective 223 flag will be set. WasDebuggerCalled() will then return \c true. 224 */ 225 void 226 BTestShell::ExpectDebuggerCall() 227 { 228 void *var = tls_get(fTLSDebuggerCall); 229 ::CppUnit::Asserter::failIf(var, "ExpectDebuggerCall(): Already expecting " 230 "a debugger() call."); 231 tls_set(fTLSDebuggerCall, (void*)1); 232 } 233 234 // WasDebuggerCalled 235 /*! \brief Returns whether the current thread has invoked debugger() since 236 the last ExpectDebuggerCall() invocation and resets the mode so 237 that subsequent debugger() calls will hit the debugger. 238 \return \c true, if debugger() has been called by the current thread since 239 the last invocation of ExpectDebuggerCall(), \c false otherwise. 240 */ 241 bool 242 BTestShell::WasDebuggerCalled() 243 { 244 void *var = tls_get(fTLSDebuggerCall); 245 tls_set(fTLSDebuggerCall, NULL); 246 return ((int)var > 1); 247 } 248 249 void 250 BTestShell::PrintDescription(int argc, char *argv[]) { 251 cout << endl << fDescription; 252 } 253 254 void 255 BTestShell::PrintHelp() { 256 cout << endl; 257 cout << "VALID ARGUMENTS: " << endl; 258 PrintValidArguments(); 259 cout << endl; 260 261 } 262 263 void 264 BTestShell::PrintValidArguments() { 265 cout << indent << "--help Displays this help text plus some other garbage" << endl; 266 cout << indent << "--list Lists the names of classes with installed tests" << endl; 267 cout << indent << "-v0 Sets verbosity level to 0 (concise summary only)" << endl; 268 cout << indent << "-v1 Sets verbosity level to 1 (complete summary only)" << endl; 269 cout << indent << "-v2 Sets verbosity level to 2 (*default* -- per-test results plus" << endl; 270 cout << indent << " complete summary)" << endl; 271 cout << indent << "-v3 Sets verbosity level to 3 (partial dynamic loading information, " << endl; 272 cout << indent << " per-test results and timing info, plus complete summary)" << endl; 273 cout << indent << "-v4 Sets verbosity level to 4 (complete dynamic loading information, " << endl; 274 cout << indent << " per-test results and timing info, plus complete summary)" << endl; 275 cout << indent << "NAME Instructs the program to run the test for the given class or all" << endl; 276 cout << indent << " the tests for the given suite. If some bonehead adds both a class" << endl; 277 cout << indent << " and a suite with the same name, the suite will be run, not the class" << endl; 278 cout << indent << " (unless the class is part of the suite with the same name :-). If no" << endl; 279 cout << indent << " classes or suites are specified, all available tests are run" << endl; 280 cout << indent << "-lPATH Adds PATH to the search path for dynamically loadable test" << endl; 281 cout << indent << " libraries" << endl; 282 } 283 284 void 285 BTestShell::PrintInstalledTests() { 286 // Print out the list of installed suites 287 cout << "------------------------------------------------------------------------------" << endl; 288 cout << "Available Suites:" << endl; 289 cout << "------------------------------------------------------------------------------" << endl; 290 SuiteMap::const_iterator j; 291 for (j = fSuites.begin(); j != fSuites.end(); ++j) 292 cout << j->first << endl; 293 cout << endl; 294 295 // Print out the list of installed tests 296 cout << "------------------------------------------------------------------------------" << endl; 297 cout << "Available Tests:" << endl; 298 cout << "------------------------------------------------------------------------------" << endl; 299 TestMap::const_iterator i; 300 for (i = fTests.begin(); i != fTests.end(); ++i) 301 cout << i->first << endl; 302 cout << endl; 303 } 304 305 bool 306 BTestShell::ProcessArguments(int argc, char *argv[]) { 307 // If we're given no parameters, the default settings 308 // will do just fine 309 if (argc < 2) 310 return true; 311 312 // Handle each command line argument (skipping the first 313 // which is just the app name) 314 for (int i = 1; i < argc; i++) { 315 std::string str(argv[i]); 316 317 if (!ProcessArgument(str, argc, argv)) 318 return false; 319 } 320 321 return true; 322 } 323 324 bool 325 BTestShell::ProcessArgument(std::string arg, int argc, char *argv[]) { 326 if (arg == "--help") { 327 PrintDescription(argc, argv); 328 PrintHelp(); 329 return false; 330 } else if (arg == "--list") { 331 fListTestsAndExit = true; 332 } else if (arg == "-v0") { 333 fVerbosityLevel = v0; 334 } else if (arg == "-v1") { 335 fVerbosityLevel = v1; 336 } else if (arg == "-v2") { 337 fVerbosityLevel = v2; 338 } else if (arg == "-v3") { 339 fVerbosityLevel = v3; 340 } else if (arg == "-v4") { 341 fVerbosityLevel = v4; 342 } else if (arg.length() >= 2 && arg[0] == '-' && arg[1] == 'l') { 343 fLibDirs.insert(arg.substr(2, arg.size()-2)); 344 } else { 345 fTestsToRun.insert(arg); 346 } 347 return true; 348 } 349 350 void 351 BTestShell::InitOutput() { 352 // For vebosity level 2, we output info about each test 353 // as we go. This involves a custom CppUnit::TestListener 354 // class. 355 if (fVerbosityLevel >= v2) { 356 cout << "------------------------------------------------------------------------------" << endl; 357 cout << "Tests" << endl; 358 cout << "------------------------------------------------------------------------------" << endl; 359 fTestResults.addListener(new BTestListener); 360 } 361 fTestResults.addListener(&fResultsCollector); 362 } 363 364 void 365 BTestShell::PrintResults() { 366 367 if (fVerbosityLevel > v0) { 368 // Print out detailed results for verbosity levels > 0 369 cout << "------------------------------------------------------------------------------" << endl; 370 cout << "Results " << endl; 371 cout << "------------------------------------------------------------------------------" << endl; 372 373 // Print failures and errors if there are any, otherwise just say "PASSED" 374 ::CppUnit::TestResultCollector::TestFailures::const_iterator iFailure; 375 if (fResultsCollector.testFailuresTotal() > 0) { 376 if (fResultsCollector.testFailures() > 0) { 377 cout << "- FAILURES: " << fResultsCollector.testFailures() << endl; 378 for (iFailure = fResultsCollector.failures().begin(); 379 iFailure != fResultsCollector.failures().end(); 380 ++iFailure) 381 { 382 if (!(*iFailure)->isError()) 383 cout << " " << (*iFailure)->toString() << endl; 384 } 385 } 386 if (fResultsCollector.testErrors() > 0) { 387 cout << "- ERRORS: " << fResultsCollector.testErrors() << endl; 388 for (iFailure = fResultsCollector.failures().begin(); 389 iFailure != fResultsCollector.failures().end(); 390 ++iFailure) 391 { 392 if ((*iFailure)->isError()) 393 cout << " " << (*iFailure)->toString() << endl; 394 } 395 } 396 397 } 398 else 399 cout << "+ PASSED" << endl; 400 401 cout << endl; 402 403 } 404 else { 405 // Print out concise results for verbosity level == 0 406 if (fResultsCollector.testFailuresTotal() > 0) 407 cout << "- FAILED" << endl; 408 else 409 cout << "+ PASSED" << endl; 410 } 411 412 } 413 414 void 415 BTestShell::LoadDynamicSuites() { 416 if (Verbosity() >= v3) { 417 cout << "------------------------------------------------------------------------------" << endl; 418 cout << "Loading " << endl; 419 cout << "------------------------------------------------------------------------------" << endl; 420 } 421 422 std::set<std::string>::iterator i; 423 for (i = fLibDirs.begin(); i != fLibDirs.end(); i++) { 424 BDirectory libDir((*i).c_str()); 425 if (Verbosity() >= v3) 426 cout << "Checking " << *i << endl; 427 /* int count =*/ LoadSuitesFrom(&libDir); 428 if (Verbosity() >= v3) { 429 // cout << "Loaded " << count << " suite" << (count == 1 ? "" : "s"); 430 // cout << " from " << *i << endl; 431 } 432 } 433 434 if (Verbosity() >= v3) 435 cout << endl; 436 437 // Look for suites and tests with the same name and give a 438 // warning, as this is only asking for trouble... :-) 439 for (SuiteMap::const_iterator i = fSuites.begin(); i != fSuites.end(); i++) { 440 if (fTests.find(i->first) != fTests.end() && Verbosity() > v0) { 441 cout << "WARNING: '" << i->first << "' refers to both a test suite *and* an individual" << 442 endl << " test. Both will be executed, but it is reccommended you rename" << 443 endl << " one of them to resolve the conflict." << 444 endl << endl; 445 } 446 } 447 448 } 449 450 void 451 BTestShell::UpdateTestDir(char *argv[]) { 452 BPath path(argv[0]); 453 if (path.InitCheck() == B_OK) { 454 delete fTestDir; 455 fTestDir = new BPath(); 456 if (path.GetParent(fTestDir) != B_OK) 457 cout << "Couldn't get test dir." << endl; 458 } else 459 cout << "Couldn't find the path to the test app." << endl; 460 } 461 462 // InstallPatches 463 /*! \brief Patches the debugger() function. 464 465 load_add_on() and unload_add_on() are patches as well, to keep the 466 patch group up to date, when images are loaded/unloaded. 467 */ 468 void 469 BTestShell::InstallPatches() 470 { 471 if (fPatchGroup) { 472 cerr << "BTestShell::InstallPatches(): Patch group already exist!" 473 << endl; 474 return; 475 } 476 BAutolock locker(fPatchGroupLocker); 477 if (!locker.IsLocked()) { 478 cerr << "BTestShell::InstallPatches(): Failed to acquire patch " 479 "group lock!" << endl; 480 return; 481 } 482 fPatchGroup = new(nothrow) ElfSymbolPatchGroup; 483 // init the symbol patch group 484 if (!fPatchGroup) { 485 cerr << "BTestShell::InstallPatches(): Failed to allocate patch " 486 "group!" << endl; 487 return; 488 } 489 if (// debugger() 490 fPatchGroup->AddPatch("debugger", (void*)&_DebuggerHook, 491 (void**)&fOldDebuggerHook) == B_OK 492 // load_add_on() 493 && fPatchGroup->AddPatch("load_add_on", (void*)&_LoadAddOnHook, 494 (void**)&fOldLoadAddOnHook) == B_OK 495 // unload_add_on() 496 && fPatchGroup->AddPatch("unload_add_on", (void*)&_UnloadAddOnHook, 497 (void**)&fOldUnloadAddOnHook) == B_OK 498 ) { 499 // everything went fine 500 fPatchGroup->Patch(); 501 } else { 502 cerr << "BTestShell::InstallPatches(): Failed to patch all symbols!" 503 << endl; 504 UninstallPatches(); 505 } 506 } 507 508 // UninstallPatches 509 /*! \brief Undoes the patches applied by InstallPatches(). 510 */ 511 void 512 BTestShell::UninstallPatches() 513 { 514 BAutolock locker(fPatchGroupLocker); 515 if (!locker.IsLocked()) { 516 cerr << "BTestShell::UninstallPatches(): " 517 "Failed to acquire patch group lock!" << endl; 518 return; 519 } 520 if (fPatchGroup) { 521 fPatchGroup->Restore(); 522 delete fPatchGroup; 523 fPatchGroup = NULL; 524 } 525 } 526 527 // _Debugger 528 void 529 BTestShell::_Debugger(const char *message) 530 { 531 if (!this || !fPatchGroup) { 532 debugger(message); 533 return; 534 } 535 BAutolock locker(fPatchGroupLocker); 536 if (!locker.IsLocked() || !fPatchGroup) { 537 debugger(message); 538 return; 539 } 540 cout << "debugger() called: " << message << endl; 541 void *var = tls_get(fTLSDebuggerCall); 542 if (var) 543 tls_set(fTLSDebuggerCall, (void*)((int)var + 1)); 544 else 545 (*fOldDebuggerHook)(message); 546 } 547 548 // _LoadAddOn 549 image_id 550 BTestShell::_LoadAddOn(const char *path) 551 { 552 if (!this || !fPatchGroup) 553 return load_add_on(path); 554 BAutolock locker(fPatchGroupLocker); 555 if (!locker.IsLocked() || !fPatchGroup) 556 return load_add_on(path); 557 image_id result = (*fOldLoadAddOnHook)(path); 558 fPatchGroup->Update(); 559 return result; 560 } 561 562 // _UnloadAddOn 563 status_t 564 BTestShell::_UnloadAddOn(image_id image) 565 { 566 if (!this || !fPatchGroup) 567 return unload_add_on(image); 568 BAutolock locker(fPatchGroupLocker); 569 if (!locker.IsLocked() || !fPatchGroup) 570 return unload_add_on(image); 571 572 if (!this || !fPatchGroup) 573 return unload_add_on(image); 574 status_t result = (*fOldUnloadAddOnHook)(image); 575 fPatchGroup->Update(); 576 return result; 577 } 578 579 // _DebuggerHook 580 void 581 BTestShell::_DebuggerHook(const char *message) 582 { 583 fGlobalShell->_Debugger(message); 584 } 585 586 // _LoadAddOnHook 587 image_id 588 BTestShell::_LoadAddOnHook(const char *path) 589 { 590 return fGlobalShell->_LoadAddOn(path); 591 } 592 593 // _UnloadAddOnHook 594 status_t 595 BTestShell::_UnloadAddOnHook(image_id image) 596 { 597 return fGlobalShell->_UnloadAddOn(image); 598 } 599 600