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