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