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