1 #include <TestShell.h> 2 3 #include <cppunit/Exception.h> 4 #include <cppunit/Test.h> 5 #include <cppunit/TestFailure.h> 6 #include <cppunit/TestResult.h> 7 #include <cppunit/TestSuite.h> 8 #include <Directory.h> 9 #include <Entry.h> 10 #include <image.h> 11 #include <Path.h> 12 #include <TestListener.h> 13 #include <set> 14 #include <map> 15 #include <string> 16 #include <vector> 17 18 BTestShell *BTestShell::fGlobalShell = NULL; 19 const char BTestShell::indent[] = " "; 20 21 BTestShell::BTestShell(const std::string &description, SyncObject *syncObject) 22 : fVerbosityLevel(v2) 23 , fDescription(description) 24 , fTestResults(syncObject) 25 , fListTestsAndExit(false) 26 , fTestDir(NULL) 27 { 28 }; 29 30 BTestShell::~BTestShell() { 31 delete fTestDir; 32 } 33 34 35 status_t 36 BTestShell::AddSuite(BTestSuite *suite) { 37 if (suite) { 38 if (Verbosity() >= v3) 39 cout << "Adding suite '" << suite->getName() << "'" << endl; 40 41 // Add the suite 42 fSuites[suite->getName()] = suite; 43 44 // Add its tests 45 bool first = true; 46 const TestMap &map = suite->getTests(); 47 for (TestMap::const_iterator i = map.begin(); 48 i != map.end(); 49 i++) { 50 AddTest(i->first, i->second); 51 if (Verbosity() >= v4 && i->second) 52 cout << " " << i->first << endl; 53 } 54 55 return B_OK; 56 } else 57 return B_BAD_VALUE; 58 } 59 60 void 61 BTestShell::AddTest(const std::string &name, CppUnit::Test *test) { 62 if (test != NULL) 63 fTests[name] = test; 64 else 65 fTests.erase(name); 66 } 67 68 int32 69 BTestShell::LoadSuitesFrom(BDirectory *libDir) { 70 if (!libDir || libDir->InitCheck() != B_OK) 71 return 0; 72 73 BEntry addonEntry; 74 BPath addonPath; 75 image_id addonImage; 76 int count = 0; 77 78 typedef BTestSuite* (*suiteFunc)(void); 79 suiteFunc func; 80 81 while (libDir->GetNextEntry(&addonEntry, true) == B_OK) { 82 status_t err; 83 err = addonEntry.GetPath(&addonPath); 84 if (!err) { 85 // cout << "Checking " << addonPath.Path() << "..." << flush; 86 addonImage = load_add_on(addonPath.Path()); 87 err = (addonImage > 0 ? B_OK : B_ERROR); 88 } 89 if (!err) { 90 // cout << "..." << endl; 91 err = get_image_symbol(addonImage, 92 "getTestSuite", 93 B_SYMBOL_TYPE_TEXT, 94 reinterpret_cast<void **>(&func)); 95 } else { 96 // cout << " !!! err == " << err << endl; 97 } 98 if (!err) 99 err = AddSuite(func()); 100 if (!err) 101 count++; 102 } 103 return count; 104 } 105 106 int 107 BTestShell::Run(int argc, char *argv[]) { 108 // Make note of which directory we started in 109 UpdateTestDir(argv); 110 111 // Parse the command line args 112 if (!ProcessArguments(argc, argv)) 113 return 0; 114 115 // Load any dynamically loadable tests we can find 116 LoadDynamicSuites(); 117 118 // See if the user requested a list of tests. If so, 119 // print and bail. 120 if (fListTestsAndExit) { 121 PrintInstalledTests(); 122 return 0; 123 } 124 125 // Add the proper tests to our suite (or exit if there 126 // are no tests installed). 127 CppUnit::TestSuite suite; 128 if (fTests.empty()) { 129 130 // No installed tests whatsoever, so bail 131 cout << "ERROR: No installed tests to run!" << endl; 132 return 0; 133 134 } else if (fSuitesToRun.empty() && fTestsToRun.empty()) { 135 136 // None specified, so run them all 137 TestMap::iterator i; 138 for (i = fTests.begin(); i != fTests.end(); ++i) 139 suite.addTest( i->second ); 140 141 } else { 142 std::set<std::string>::const_iterator i; 143 std::set<std::string> suitesToRemove; 144 145 // Add all the tests from any specified suites to the list of 146 // tests to run (since we use a set, this eliminates the concern 147 // of having duplicate entries). 148 for (i = fTestsToRun.begin(); i != fTestsToRun.end(); ++i) { 149 // See if it's a suite (since it may just be a single test) 150 if (fSuites.find(*i) != fSuites.end()) { 151 // Note the suite name for later removal unless the 152 // name is also the name of an available individual test 153 if (fTests.find(*i) == fTests.end()) { 154 suitesToRemove.insert(*i); 155 } 156 const TestMap &tests = fSuites[*i]->getTests(); 157 TestMap::const_iterator j; 158 for (j = tests.begin(); j != tests.end(); j++) { 159 fTestsToRun.insert( j->first ); 160 } 161 } 162 } 163 164 // Remove the names of all of the suites we discovered from the 165 // list of tests to run (unless there's also an installed individual 166 // test of the same name). 167 for (i = suitesToRemove.begin(); i != suitesToRemove.end(); i++) { 168 fTestsToRun.erase(*i); 169 } 170 171 // Everything still in fTestsToRun must then be an explicit test 172 for (i = fTestsToRun.begin(); i != fTestsToRun.end(); ++i) { 173 // Make sure it's a valid test 174 if (fTests.find(*i) != fTests.end()) { 175 suite.addTest( fTests[*i] ); 176 } else { 177 cout << endl << "ERROR: Invalid argument \"" << *i << "\"" << endl; 178 PrintHelp(); 179 return 0; 180 } 181 } 182 183 } 184 185 // Run all the tests 186 InitOutput(); 187 suite.run(&fTestResults); 188 PrintResults(); 189 190 return 0; 191 } 192 193 BTestShell::VerbosityLevel 194 BTestShell::Verbosity() const { 195 return fVerbosityLevel; 196 } 197 198 const char* 199 BTestShell::TestDir() const { 200 return (fTestDir ? fTestDir->Path() : NULL); 201 } 202 203 void 204 BTestShell::PrintDescription(int argc, char *argv[]) { 205 cout << endl << fDescription; 206 } 207 208 void 209 BTestShell::PrintHelp() { 210 cout << endl; 211 cout << "VALID ARGUMENTS: " << endl; 212 PrintValidArguments(); 213 cout << endl; 214 215 } 216 217 void 218 BTestShell::PrintValidArguments() { 219 cout << indent << "--help Displays this help text plus some other garbage" << endl; 220 cout << indent << "--list Lists the names of classes with installed tests" << endl; 221 cout << indent << "-v0 Sets verbosity level to 0 (concise summary only)" << endl; 222 cout << indent << "-v1 Sets verbosity level to 1 (complete summary only)" << endl; 223 cout << indent << "-v2 Sets verbosity level to 2 (*default* -- per-test results plus" << endl; 224 cout << indent << " complete summary)" << endl; 225 cout << indent << "-v3 Sets verbosity level to 3 (partial dynamic loading information, " << endl; 226 cout << indent << " per-test results and timing info, plus complete summary)" << endl; 227 cout << indent << "-v4 Sets verbosity level to 4 (complete dynamic loading information, " << endl; 228 cout << indent << " per-test results and timing info, plus complete summary)" << endl; 229 cout << indent << "NAME Instructs the program to run the test for the given class or all" << endl; 230 cout << indent << " the tests for the given suite. If some bonehead adds both a class" << endl; 231 cout << indent << " and a suite with the same name, the suite will be run, not the class" << endl; 232 cout << indent << " (unless the class is part of the suite with the same name :-). If no" << endl; 233 cout << indent << " classes or suites are specified, all available tests are run" << endl; 234 cout << indent << "-lPATH Adds PATH to the search path for dynamically loadable test" << endl; 235 cout << indent << " libraries" << endl; 236 } 237 238 void 239 BTestShell::PrintInstalledTests() { 240 // Print out the list of installed suites 241 cout << "------------------------------------------------------------------------------" << endl; 242 cout << "Available Suites:" << endl; 243 cout << "------------------------------------------------------------------------------" << endl; 244 SuiteMap::const_iterator j; 245 for (j = fSuites.begin(); j != fSuites.end(); ++j) 246 cout << j->first << endl; 247 cout << endl; 248 249 // Print out the list of installed tests 250 cout << "------------------------------------------------------------------------------" << endl; 251 cout << "Available Tests:" << endl; 252 cout << "------------------------------------------------------------------------------" << endl; 253 TestMap::const_iterator i; 254 for (i = fTests.begin(); i != fTests.end(); ++i) 255 cout << i->first << endl; 256 cout << endl; 257 } 258 259 bool 260 BTestShell::ProcessArguments(int argc, char *argv[]) { 261 // If we're given no parameters, the default settings 262 // will do just fine 263 if (argc < 2) 264 return true; 265 266 // Handle each command line argument (skipping the first 267 // which is just the app name) 268 for (int i = 1; i < argc; i++) { 269 std::string str(argv[i]); 270 271 if (!ProcessArgument(str, argc, argv)) 272 return false; 273 } 274 275 return true; 276 } 277 278 bool 279 BTestShell::ProcessArgument(std::string arg, int argc, char *argv[]) { 280 if (arg == "--help") { 281 PrintDescription(argc, argv); 282 PrintHelp(); 283 return false; 284 } else if (arg == "--list") { 285 fListTestsAndExit = true; 286 } else if (arg == "-v0") { 287 fVerbosityLevel = v0; 288 } else if (arg == "-v1") { 289 fVerbosityLevel = v1; 290 } else if (arg == "-v2") { 291 fVerbosityLevel = v2; 292 } else if (arg == "-v3") { 293 fVerbosityLevel = v3; 294 } else if (arg == "-v4") { 295 fVerbosityLevel = v4; 296 } else if (arg.length() >= 2 && arg[0] == '-' && arg[1] == 'l') { 297 fLibDirs.insert(arg.substr(2, arg.size()-2)); 298 } else { 299 fTestsToRun.insert(arg); 300 } 301 return true; 302 } 303 304 void 305 BTestShell::InitOutput() { 306 // For vebosity level 2, we output info about each test 307 // as we go. This involves a custom CppUnit::TestListener 308 // class. 309 if (fVerbosityLevel >= v2) { 310 cout << "------------------------------------------------------------------------------" << endl; 311 cout << "Tests" << endl; 312 cout << "------------------------------------------------------------------------------" << endl; 313 fTestResults.addListener(new BTestListener); 314 } 315 fTestResults.addListener(&fResultsCollector); 316 } 317 318 void 319 BTestShell::PrintResults() { 320 321 if (fVerbosityLevel > v0) { 322 // Print out detailed results for verbosity levels > 0 323 cout << "------------------------------------------------------------------------------" << endl; 324 cout << "Results " << endl; 325 cout << "------------------------------------------------------------------------------" << endl; 326 327 // Print failures and errors if there are any, otherwise just say "PASSED" 328 ::CppUnit::TestResultCollector::TestFailures::const_iterator iFailure; 329 if (fResultsCollector.testFailuresTotal() > 0) { 330 if (fResultsCollector.testFailures() > 0) { 331 cout << "- FAILURES: " << fResultsCollector.testFailures() << endl; 332 for (iFailure = fResultsCollector.failures().begin(); 333 iFailure != fResultsCollector.failures().end(); 334 ++iFailure) 335 { 336 if (!(*iFailure)->isError()) 337 cout << " " << (*iFailure)->toString() << endl; 338 } 339 } 340 if (fResultsCollector.testErrors() > 0) { 341 cout << "- ERRORS: " << fResultsCollector.testErrors() << endl; 342 for (iFailure = fResultsCollector.failures().begin(); 343 iFailure != fResultsCollector.failures().end(); 344 ++iFailure) 345 { 346 if ((*iFailure)->isError()) 347 cout << " " << (*iFailure)->toString() << endl; 348 } 349 } 350 351 } 352 else 353 cout << "+ PASSED" << endl; 354 355 cout << endl; 356 357 } 358 else { 359 // Print out concise results for verbosity level == 0 360 if (fResultsCollector.testFailuresTotal() > 0) 361 cout << "- FAILED" << endl; 362 else 363 cout << "+ PASSED" << endl; 364 } 365 366 } 367 368 void 369 BTestShell::LoadDynamicSuites() { 370 if (Verbosity() >= v3) { 371 cout << "------------------------------------------------------------------------------" << endl; 372 cout << "Loading " << endl; 373 cout << "------------------------------------------------------------------------------" << endl; 374 } 375 376 std::set<std::string>::iterator i; 377 for (i = fLibDirs.begin(); i != fLibDirs.end(); i++) { 378 BDirectory libDir((*i).c_str()); 379 if (Verbosity() >= v3) 380 cout << "Checking " << *i << endl; 381 int count = LoadSuitesFrom(&libDir); 382 if (Verbosity() >= v3) { 383 // cout << "Loaded " << count << " suite" << (count == 1 ? "" : "s"); 384 // cout << " from " << *i << endl; 385 } 386 } 387 388 if (Verbosity() >= v3) 389 cout << endl; 390 391 // Look for suites and tests with the same name and give a 392 // warning, as this is only asking for trouble... :-) 393 for (SuiteMap::const_iterator i = fSuites.begin(); i != fSuites.end(); i++) { 394 if (fTests.find(i->first) != fTests.end() && Verbosity() > v0) { 395 cout << "WARNING: '" << i->first << "' refers to both a test suite *and* an individual" << 396 endl << " test. Both will be executed, but it is reccommended you rename" << 397 endl << " one of them to resolve the conflict." << 398 endl << endl; 399 } 400 } 401 402 } 403 404 void 405 BTestShell::UpdateTestDir(char *argv[]) { 406 BPath path(argv[0]); 407 if (path.InitCheck() == B_OK) { 408 delete fTestDir; 409 fTestDir = new BPath(); 410 if (path.GetParent(fTestDir) != B_OK) 411 cout << "Couldn't get test dir." << endl; 412 } else 413 cout << "Couldn't find the path to the test app." << endl; 414 } 415 416