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