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
25using std::cout;
26using std::endl;
27using std::set;
28
29_EXPORT BTestShell *BTestShell::fGlobalShell = NULL;
30const char BTestShell::indent[] = "  ";
31
32_EXPORT
33BTestShell::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
51BTestShell::~BTestShell() {
52	delete fTestDir;
53#ifndef NO_ELF_SYMBOL_PATCHING
54	delete fPatchGroupLocker;
55#endif // ! NO_ELF_SYMBOL_PATCHING
56}
57
58
59_EXPORT
60status_t
61BTestShell::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
85void
86BTestShell::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
94int32
95BTestShell::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
131int
132BTestShell::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
221BTestShell::VerbosityLevel
222BTestShell::Verbosity() const {
223	return fVerbosityLevel;
224}
225
226_EXPORT
227const char*
228BTestShell::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
239void
240BTestShell::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
256bool
257BTestShell::WasDebuggerCalled()
258{
259	void *var = tls_get(fTLSDebuggerCall);
260	tls_set(fTLSDebuggerCall, NULL);
261	return ((addr_t)var > 1);
262}
263
264_EXPORT
265void
266BTestShell::PrintDescription(int argc, char *argv[]) {
267	cout << endl << fDescription;
268}
269
270_EXPORT
271void
272BTestShell::PrintHelp() {
273	cout << endl;
274	cout << "VALID ARGUMENTS:     " << endl;
275	PrintValidArguments();
276	cout << endl;
277
278}
279
280_EXPORT
281void
282BTestShell::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
303void
304BTestShell::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
325bool
326BTestShell::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
345bool
346BTestShell::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
372void
373BTestShell::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
387void
388BTestShell::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
438void
439BTestShell::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
475void
476BTestShell::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
494void
495BTestShell::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
540void
541BTestShell::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
562void
563BTestShell::_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
584image_id
585BTestShell::_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
599status_t
600BTestShell::_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
617void
618BTestShell::_DebuggerHook(const char *message)
619{
620	fGlobalShell->_Debugger(message);
621}
622
623// _LoadAddOnHook
624_EXPORT
625image_id
626BTestShell::_LoadAddOnHook(const char *path)
627{
628	return fGlobalShell->_LoadAddOn(path);
629}
630
631// _UnloadAddOnHook
632_EXPORT
633status_t
634BTestShell::_UnloadAddOnHook(image_id image)
635{
636	return fGlobalShell->_UnloadAddOn(image);
637}
638
639#endif // ! NO_ELF_SYMBOL_PATCHING
640