xref: /haiku/src/tools/cppunit/TestShell.cpp (revision 7120e97489acbf17d86d3f33e3b2e68974fd4b23)
1 #include <TestShell.h>
2 
3 #include <cppunit/Exception.h>
4 #include <cppunit/Test.h>
5 #include <cppunit/TestFailure.h>
6 #include <cppunit/TestResult.h>
7 #include <cppunit/TestSuite.h>
8 #include <Directory.h>
9 #include <Entry.h>
10 #include <image.h>
11 #include <Path.h>
12 #include <TestListener.h>
13 #include <set>
14 #include <map>
15 #include <string>
16 #include <vector>
17 
18 BTestShell *BTestShell::fGlobalShell = NULL;
19 const char BTestShell::indent[] = "  ";
20 
21 BTestShell::BTestShell(const std::string &description, SyncObject *syncObject)
22 	: fVerbosityLevel(v2)
23 	, fDescription(description)
24 	, fTestResults(syncObject)
25 	, fListTestsAndExit(false)
26 	, fTestDir(NULL)
27 {
28 };
29 
30 BTestShell::~BTestShell() {
31 	delete fTestDir;
32 }
33 
34 
35 status_t
36 BTestShell::AddSuite(BTestSuite *suite) {
37 	if (suite) {
38 		if (Verbosity() >= v3)
39 			cout << "Adding suite '" << suite->getName() << "'" << endl;
40 
41 		// Add the suite
42 		fSuites[suite->getName()] = suite;
43 
44 		// Add its tests
45 		bool first = true;
46 		const TestMap &map = suite->getTests();
47 		for (TestMap::const_iterator i = map.begin();
48 			   i != map.end();
49 			      i++) {
50 			AddTest(i->first, i->second);
51 			if (Verbosity() >= v4 && i->second)
52 				cout << "  " << i->first << endl;
53 		}
54 
55 		return B_OK;
56 	} else
57 		return B_BAD_VALUE;
58 }
59 
60 void
61 BTestShell::AddTest(const std::string &name, CppUnit::Test *test) {
62 	if (test != NULL)
63 		fTests[name] = test;
64 	else
65 		fTests.erase(name);
66 }
67 
68 int32
69 BTestShell::LoadSuitesFrom(BDirectory *libDir) {
70 	if (!libDir || libDir->InitCheck() != B_OK)
71 		return 0;
72 
73 	BEntry addonEntry;
74 	BPath addonPath;
75 	image_id addonImage;
76 	int count = 0;
77 
78 	typedef BTestSuite* (*suiteFunc)(void);
79 	suiteFunc func;
80 
81 	while (libDir->GetNextEntry(&addonEntry, true) == B_OK) {
82 		status_t err;
83 		err = addonEntry.GetPath(&addonPath);
84 		if (!err) {
85 //			cout << "Checking " << addonPath.Path() << "..." << flush;
86 			addonImage = load_add_on(addonPath.Path());
87 			err = (addonImage > 0 ? B_OK : B_ERROR);
88 		}
89 		if (!err) {
90 //			cout << "..." << endl;
91 			err = get_image_symbol(addonImage,
92 				    "getTestSuite",
93 				      B_SYMBOL_TYPE_TEXT,
94 				        reinterpret_cast<void **>(&func));
95 		} else {
96 //			cout << " !!! err == " << err << endl;
97 		}
98 		if (!err)
99 			err = AddSuite(func());
100 		if (!err)
101 			count++;
102 	}
103 	return count;
104 }
105 
106 int
107 BTestShell::Run(int argc, char *argv[]) {
108 	// Make note of which directory we started in
109 	UpdateTestDir(argv);
110 
111 	// Parse the command line args
112 	if (!ProcessArguments(argc, argv))
113 		return 0;
114 
115 	// Load any dynamically loadable tests we can find
116 	LoadDynamicSuites();
117 
118 	// See if the user requested a list of tests. If so,
119 	// print and bail.
120 	if (fListTestsAndExit) {
121 		PrintInstalledTests();
122 		return 0;
123 	}
124 
125 	// Add the proper tests to our suite (or exit if there
126 	// are no tests installed).
127 	CppUnit::TestSuite suite;
128 	if (fTests.empty()) {
129 
130 		// No installed tests whatsoever, so bail
131 		cout << "ERROR: No installed tests to run!" << endl;
132 		return 0;
133 
134 	} else if (fSuitesToRun.empty() && fTestsToRun.empty()) {
135 
136 		// None specified, so run them all
137 		TestMap::iterator i;
138 		for (i = fTests.begin(); i != fTests.end(); ++i)
139 			suite.addTest( i->second );
140 
141 	} else {
142 		std::set<std::string>::const_iterator i;
143 		std::set<std::string> suitesToRemove;
144 
145 		// Add all the tests from any specified suites to the list of
146 		// tests to run (since we use a set, this eliminates the concern
147 		// of having duplicate entries).
148 		for (i = fTestsToRun.begin(); i != fTestsToRun.end(); ++i) {
149 			// See if it's a suite (since it may just be a single test)
150 			if (fSuites.find(*i) != fSuites.end()) {
151 				// Note the suite name for later removal unless the
152 				// name is also the name of an available individual test
153 				if (fTests.find(*i) == fTests.end()) {
154 					suitesToRemove.insert(*i);
155 				}
156 				const TestMap &tests = fSuites[*i]->getTests();
157 				TestMap::const_iterator j;
158 				for (j = tests.begin(); j != tests.end(); j++) {
159 					fTestsToRun.insert( j->first );
160 				}
161 			}
162 		}
163 
164 		// Remove the names of all of the suites we discovered from the
165 		// list of tests to run (unless there's also an installed individual
166 		// test of the same name).
167 		for (i = suitesToRemove.begin(); i != suitesToRemove.end(); i++) {
168 			fTestsToRun.erase(*i);
169 		}
170 
171 		// Everything still in fTestsToRun must then be an explicit test
172 		for (i = fTestsToRun.begin(); i != fTestsToRun.end(); ++i) {
173 			// Make sure it's a valid test
174 			if (fTests.find(*i) != fTests.end()) {
175 				suite.addTest( fTests[*i] );
176 			} else {
177 				cout << endl << "ERROR: Invalid argument \"" << *i << "\"" << endl;
178 				PrintHelp();
179 				return 0;
180 			}
181 		}
182 
183 	}
184 
185 	// Run all the tests
186 	InitOutput();
187 	suite.run(&fTestResults);
188 	PrintResults();
189 
190 	return 0;
191 }
192 
193 BTestShell::VerbosityLevel
194 BTestShell::Verbosity() const {
195 	return fVerbosityLevel;
196 }
197 
198 const char*
199 BTestShell::TestDir() const {
200 	return (fTestDir ? fTestDir->Path() : NULL);
201 }
202 
203 void
204 BTestShell::PrintDescription(int argc, char *argv[]) {
205 	cout << endl << fDescription;
206 }
207 
208 void
209 BTestShell::PrintHelp() {
210 	cout << endl;
211 	cout << "VALID ARGUMENTS:     " << endl;
212 	PrintValidArguments();
213 	cout << endl;
214 
215 }
216 
217 void
218 BTestShell::PrintValidArguments() {
219 	cout << indent << "--help       Displays this help text plus some other garbage" << endl;
220 	cout << indent << "--list       Lists the names of classes with installed tests" << endl;
221 	cout << indent << "-v0          Sets verbosity level to 0 (concise summary only)" << endl;
222 	cout << indent << "-v1          Sets verbosity level to 1 (complete summary only)" << endl;
223 	cout << indent << "-v2          Sets verbosity level to 2 (*default* -- per-test results plus" << endl;
224 	cout << indent << "             complete summary)" << endl;
225 	cout << indent << "-v3          Sets verbosity level to 3 (partial dynamic loading information, " << endl;
226 	cout << indent << "             per-test results and timing info, plus complete summary)" << endl;
227 	cout << indent << "-v4          Sets verbosity level to 4 (complete dynamic loading information, " << endl;
228 	cout << indent << "             per-test results and timing info, plus complete summary)" << endl;
229 	cout << indent << "NAME         Instructs the program to run the test for the given class or all" << endl;
230 	cout << indent << "             the tests for the given suite. If some bonehead adds both a class" << endl;
231 	cout << indent << "             and a suite with the same name, the suite will be run, not the class" << endl;
232 	cout << indent << "             (unless the class is part of the suite with the same name :-). If no" << endl;
233 	cout << indent << "             classes or suites are specified, all available tests are run" << endl;
234 	cout << indent << "-lPATH       Adds PATH to the search path for dynamically loadable test" << endl;
235 	cout << indent << "             libraries" << endl;
236 }
237 
238 void
239 BTestShell::PrintInstalledTests() {
240 	// Print out the list of installed suites
241 	cout << "------------------------------------------------------------------------------" << endl;
242 	cout << "Available Suites:" << endl;
243 	cout << "------------------------------------------------------------------------------" << endl;
244 	SuiteMap::const_iterator j;
245 	for (j = fSuites.begin(); j != fSuites.end(); ++j)
246 		cout << j->first << endl;
247 	cout << endl;
248 
249 	// Print out the list of installed tests
250 	cout << "------------------------------------------------------------------------------" << endl;
251 	cout << "Available Tests:" << endl;
252 	cout << "------------------------------------------------------------------------------" << endl;
253 	TestMap::const_iterator i;
254 	for (i = fTests.begin(); i != fTests.end(); ++i)
255 		cout << i->first << endl;
256 	cout << endl;
257 }
258 
259 bool
260 BTestShell::ProcessArguments(int argc, char *argv[]) {
261 	// If we're given no parameters, the default settings
262 	// will do just fine
263 	if (argc < 2)
264 		return true;
265 
266 	// Handle each command line argument (skipping the first
267 	// which is just the app name)
268 	for (int i = 1; i < argc; i++) {
269 		std::string str(argv[i]);
270 
271 		if (!ProcessArgument(str, argc, argv))
272 			return false;
273 	}
274 
275 	return true;
276 }
277 
278 bool
279 BTestShell::ProcessArgument(std::string arg, int argc, char *argv[]) {
280 	if (arg == "--help") {
281 		PrintDescription(argc, argv);
282 		PrintHelp();
283 		return false;
284 	} else if (arg == "--list") {
285 		fListTestsAndExit = true;
286 	} else if (arg == "-v0") {
287 		fVerbosityLevel = v0;
288 	} else if (arg == "-v1") {
289 		fVerbosityLevel = v1;
290 	} else if (arg == "-v2") {
291 		fVerbosityLevel = v2;
292 	} else if (arg == "-v3") {
293 		fVerbosityLevel = v3;
294 	} else if (arg == "-v4") {
295 		fVerbosityLevel = v4;
296 	} else if (arg.length() >= 2 && arg[0] == '-' && arg[1] == 'l') {
297 		fLibDirs.insert(arg.substr(2, arg.size()-2));
298 	} else {
299 		fTestsToRun.insert(arg);
300 	}
301 	return true;
302 }
303 
304 void
305 BTestShell::InitOutput() {
306 	// For vebosity level 2, we output info about each test
307 	// as we go. This involves a custom CppUnit::TestListener
308 	// class.
309 	if (fVerbosityLevel >= v2) {
310 		cout << "------------------------------------------------------------------------------" << endl;
311 		cout << "Tests" << endl;
312 		cout << "------------------------------------------------------------------------------" << endl;
313 		fTestResults.addListener(new BTestListener);
314 	}
315 	fTestResults.addListener(&fResultsCollector);
316 }
317 
318 void
319 BTestShell::PrintResults() {
320 
321 	if (fVerbosityLevel > v0) {
322 		// Print out detailed results for verbosity levels > 0
323 		cout << "------------------------------------------------------------------------------" << endl;
324 		cout << "Results " << endl;
325 		cout << "------------------------------------------------------------------------------" << endl;
326 
327 		// Print failures and errors if there are any, otherwise just say "PASSED"
328 		::CppUnit::TestResultCollector::TestFailures::const_iterator iFailure;
329 		if (fResultsCollector.testFailuresTotal() > 0) {
330 			if (fResultsCollector.testFailures() > 0) {
331 				cout << "- FAILURES: " << fResultsCollector.testFailures() << endl;
332 				for (iFailure = fResultsCollector.failures().begin();
333 				     iFailure != fResultsCollector.failures().end();
334 				     ++iFailure)
335 				{
336 					if (!(*iFailure)->isError())
337 						cout << "    " << (*iFailure)->toString() << endl;
338 				}
339 			}
340 			if (fResultsCollector.testErrors() > 0) {
341 				cout << "- ERRORS: " << fResultsCollector.testErrors() << endl;
342 				for (iFailure = fResultsCollector.failures().begin();
343 				     iFailure != fResultsCollector.failures().end();
344 				     ++iFailure)
345 				{
346 					if ((*iFailure)->isError())
347 						cout << "    " << (*iFailure)->toString() << endl;
348 				}
349 			}
350 
351 		}
352 		else
353 			cout << "+ PASSED" << endl;
354 
355 		cout << endl;
356 
357 	}
358 	else {
359 		// Print out concise results for verbosity level == 0
360 		if (fResultsCollector.testFailuresTotal() > 0)
361 			cout << "- FAILED" << endl;
362 		else
363 			cout << "+ PASSED" << endl;
364 	}
365 
366 }
367 
368 void
369 BTestShell::LoadDynamicSuites() {
370 	if (Verbosity() >= v3) {
371 		cout << "------------------------------------------------------------------------------" << endl;
372 		cout << "Loading " << endl;
373 		cout << "------------------------------------------------------------------------------" << endl;
374 	}
375 
376 	std::set<std::string>::iterator i;
377 	for (i = fLibDirs.begin(); i != fLibDirs.end(); i++) {
378 		BDirectory libDir((*i).c_str());
379 		if (Verbosity() >= v3)
380 			cout << "Checking " << *i << endl;
381 		int count = LoadSuitesFrom(&libDir);
382 		if (Verbosity() >= v3) {
383 //			cout << "Loaded " << count << " suite" << (count == 1 ? "" : "s");
384 //			cout << " from " << *i << endl;
385 		}
386 	}
387 
388 	if (Verbosity() >= v3)
389 		cout << endl;
390 
391 	// Look for suites and tests with the same name and give a
392 	// warning, as this is only asking for trouble... :-)
393 	for (SuiteMap::const_iterator i = fSuites.begin(); i != fSuites.end(); i++) {
394 		if (fTests.find(i->first) != fTests.end() && Verbosity() > v0) {
395 			cout << "WARNING: '" << i->first << "' refers to both a test suite *and* an individual" <<
396 			endl << "         test. Both will be executed, but it is reccommended you rename" <<
397 			endl << "         one of them to resolve the conflict." <<
398 			endl << endl;
399 		}
400 	}
401 
402 }
403 
404 void
405 BTestShell::UpdateTestDir(char *argv[]) {
406 	BPath path(argv[0]);
407 	if (path.InitCheck() == B_OK) {
408 		delete fTestDir;
409 		fTestDir = new BPath();
410 		if (path.GetParent(fTestDir) != B_OK)
411 			cout << "Couldn't get test dir." << endl;
412 	} else
413 		cout << "Couldn't find the path to the test app." << endl;
414 }
415 
416