xref: /haiku/src/tools/cppunit/TestShell.cpp (revision 4bd0c1066b227cec4b79883bdef697c7a27f2e90)
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
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
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() << "..." << 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
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
222 BTestShell::Verbosity() const {
223 	return fVerbosityLevel;
224 }
225 
226 _EXPORT
227 const char*
228 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
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
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
266 BTestShell::PrintDescription(int argc, char *argv[]) {
267 	cout << endl << fDescription;
268 }
269 
270 _EXPORT
271 void
272 BTestShell::PrintHelp() {
273 	cout << endl;
274 	cout << "VALID ARGUMENTS:     " << endl;
275 	PrintValidArguments();
276 	cout << endl;
277 
278 }
279 
280 _EXPORT
281 void
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
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
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
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
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
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
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
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
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
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
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*)((int)var + 1));
578 	else
579 		(*fOldDebuggerHook)(message);
580 }
581 
582 // _LoadAddOn
583 _EXPORT
584 image_id
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
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
618 BTestShell::_DebuggerHook(const char *message)
619 {
620 	fGlobalShell->_Debugger(message);
621 }
622 
623 // _LoadAddOnHook
624 _EXPORT
625 image_id
626 BTestShell::_LoadAddOnHook(const char *path)
627 {
628 	return fGlobalShell->_LoadAddOn(path);
629 }
630 
631 // _UnloadAddOnHook
632 _EXPORT
633 status_t
634 BTestShell::_UnloadAddOnHook(image_id image)
635 {
636 	return fGlobalShell->_UnloadAddOn(image);
637 }
638 
639 #endif // ! NO_ELF_SYMBOL_PATCHING
640