1 // test.cpp
2 // this file is part of Context Free
3 // ---------------------
4 // Copyright (C) 2003 Mark Lentczner - markl@glyphic.com
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 //
20 // Mark Lentczner can be contacted at markl@glyphic.com or at
21 // Mark Lentczner, 1209 Villa St., Mountain View, CA 94041-1123, USA
22 //
23 //
24 
25 #include "test.h"
26 
27 #include <exception>
28 #include <iomanip>
29 #include <iostream>
30 #include <map>
31 #include <sstream>
32 #include <vector>
33 
34 
35 
36 #define SPIN 0
37     // set to 1 if you want to see spinning characters during tests
38     // only works if your output device supports backspace
39 
40 namespace Test
41 {
testlocation(const char * f,long l,int)42 	testlocation::testlocation(const char* f, long l, int)
43 		: file(f), line(l), from(0)
44 		{ }
testlocation(const char * f,long l,const testlocation & t)45 	testlocation::testlocation(const char* f, long l, const testlocation& t)
46 		: file(f), line(l), from(&t)
47 		{ }
48 
testlocation(const testlocation & o)49 	testlocation::testlocation(const testlocation& o)
50 		: file(o.file), line(o.line), from(o.from)
51 		{ }
52 
53 	testlocation&
operator =(const testlocation & o)54 	testlocation::operator=(const testlocation& o)
55 		{ file = o.file; line = o.line; from = o.from; return *this; }
56 
57 	void
put(std::ostream & out) const58 	testlocation::put(std::ostream& out) const
59 	{
60 		if (line >= 0) {
61 			out << "[" << file << ":" << line << "]";
62 		}
63 		else if (file) {
64 			out << "[" << file << ":-]";
65 		}
66 		else {
67 			out << "[-:-]";
68 		}
69 
70 		if (from) {
71 			out << "\n    from ";
72 			from->put(out);
73 		}
74 	}
75 
operator std::string() const76 	testlocation::operator std::string() const
77 	{
78 		std::ostringstream out;
79 		put(out);
80 		return out.str();
81 	}
82 
83 	class testfailure : public std::exception {
84 	public:
85 		testfailure(const testlocation& l, const std::string& m, bool fatal = true);
86 		testfailure(const std::string& m, bool fatal = true);
87 		~testfailure() throw();
88 
89 		const char* what() const throw();
90 
91 		operator std::string() const;
92 		void put(std::ostream&) const;
93 
isFatal() const94 		bool isFatal() const                { return fatal; }
95 
96 		void set_test_info(const std::string& group, const std::string& name);
97 
98 		class list : public std::vector<testfailure*> {
99 		public:
100 			void dump(std::ostream& out);
101 		};
102 
103 	private:
104 		std::string	message;
105 		std::string group;
106 		std::string name;
107 		std::string location;
108 		bool		fatal;
109 	};
110 
testfailure(const testlocation & l,const std::string & m,bool f)111 	testfailure::testfailure(const testlocation& l, const std::string& m, bool f)
112 		: message(m), location(l), fatal(f)
113 		{ }
114 
testfailure(const std::string & m,bool f)115 	testfailure::testfailure(const std::string& m, bool f)
116 		: message(m), fatal(f)
117 		{ }
118 
~testfailure()119 	testfailure::~testfailure() throw()
120 		{ }
121 
122 	const char*
what() const123 	testfailure::what() const throw()
124 		{ return message.c_str(); }
125 
126 	void
put(std::ostream & out) const127 	testfailure::put(std::ostream& out) const
128 	{
129 		out << (fatal ? "***" : "---")
130 			<< " " << group
131 			<< ", " << name
132 			<< " " << location
133 			<< std::endl
134 			<< "    " << message
135 			<< std::endl;
136 	}
137 
138 #if 0
139 	testfailure::operator std::string() const
140 	{
141 		std::ostringstream out;
142 		put(out);
143 		return out.str();
144 	}
145 #endif
146 
147 	void
set_test_info(const std::string & g,const std::string & n)148 	testfailure::set_test_info(const std::string& g, const std::string& n)
149 	{
150 		group = g;
151 		name = n;
152 	}
153 
154 	void
dump(std::ostream & out)155 	testfailure::list::dump(std::ostream& out)
156 	{
157 		if (empty())
158 			return;
159 
160 		out << std::endl;
161 		for (const_iterator i = begin(); i != end(); ++i)
162 			(*i)->put(out);
163 		out << std::endl;
164 		clear();
165 	}
166 
167 
168 	typedef std::vector<test*> TestList;
169 
170 	struct TestGroup {
171 		std::string name;
172 		TestList    list;
173 
TestGroupTest::TestGroup174 		TestGroup(const char* n) : name(n) { }
175 	};
176 
177 	typedef std::map<std::string, TestGroup*> TestMap;
178 
179 	TestMap&
registry()180 	registry()
181 	{
182 		/* This trick is to ensure that this global is constructed before
183 		   it is used.
184 		   See C++ FAQ Lite, sections 10.11 through 10.13
185 		*/
186 
187 		static TestMap* r = new TestMap;
188 		return *r;
189 	}
190 
test(const char * group,const char * n,const char * built)191 	test::test(const char* group, const char* n, const char* built)
192 		: name(n)
193 	{
194 		std::string key;
195 		struct tm timeInfo;
196 
197 		if (built  &&  strptime(built, "%b %d %Y %H:%M:%S", &timeInfo)) {
198 			char buf[20];
199 			strftime(buf, sizeof(buf), "%Y%m%d%H%M%S", &timeInfo);
200 			key = buf;
201 		}
202 		else {
203 			key = "00000000000000";
204 		}
205 
206 		key += group;
207 
208 		TestMap& r = registry();
209 		TestMap::iterator i = r.find(key);
210 		if (i == r.end()) {
211 			r[key] = new TestGroup(group);
212 		}
213 
214 		r[key]->list.push_back(this);
215 	}
216 
217 	bool anyPasses;
218 
219 	void
pass(const testlocation & loc)220 	pass(const testlocation& loc)
221 	{
222 		anyPasses = true;
223 
224 		if (SPIN) {
225 			static int          seqIndex = 0;
226 			static const char   seqChars[] = { '-', '\\', '|', '/' };
227 			static const int    seqLength = sizeof(seqChars) / sizeof(seqChars[0]);
228 
229 			std::cout << '\010'   // backspace
230 				 << seqChars[seqIndex];
231 			seqIndex = (seqIndex + 1) % seqLength;
232 		}
233 	}
234 
235 	void
fail(const testlocation & loc,const std::string & message)236 	fail(const testlocation& loc, const std::string& message)
237 		{ throw new testfailure(loc, message); }
238 
239 	namespace {
240 		bool
longstr(const std::string & s)241 		longstr(const std::string& s) {
242 			return s.size() > 20 || s.find('\n') != std::string::npos;
243 		}
244 	};
245 
246 	void
failsame(const testlocation & loc,const char * expectExpr,const std::string & expected,const char * actualExpr,const std::string & actual)247 	failsame(const testlocation& loc,
248 					 const char* expectExpr, const std::string& expected,
249 					 const char* actualExpr, const std::string& actual)
250 	{
251 		bool longmsg = longstr(expected) || longstr(actual);
252 
253 		bool noconv = expected.empty() && actual.empty();
254 			// if both value strings are empty, then there was no conversion
255 			// to string available, just show the expectedExpr
256 
257 		std::ostringstream message;
258 		message << actualExpr
259 				<< " had wrong value";
260 		if (noconv) {
261 			message << "; expected "
262 					<< expectExpr;
263 		}
264 		else {
265 			message << (longmsg ? "\nexpected:\n" : "; expected ")
266 					<< expected
267 					<< (longmsg ? "\nactual:\n" : "; actual ")
268 					<< actual
269 					<< (longmsg ? "\n" : "");
270 		}
271 
272 		fail(loc, message.str());
273 	}
274 
275 	void
fail_contains(const testlocation & loc,const std::string & needle,const std::string & haystack)276 	fail_contains(const testlocation& loc,
277 		const std::string& needle, const std::string& haystack)
278 	{
279 		if (longstr(haystack) || longstr(needle))
280 			fail(loc,
281 				"needle:\n" + needle +
282 				"\nnot found in haystack:\n" + haystack +
283 				"\n");
284 		else
285 			fail(loc, needle + " not found in " + haystack);
286 	}
287 
288 	std::string
tostring(bool b)289 	tostring(bool b)
290 		{ return b ? "true" : "false"; }
291 
292 	std::string
tostring(const std::string & s)293 	tostring(const std::string& s)
294 		{ return s; }
295 
296 	std::string
tostring(int i)297 	tostring(int i)
298 		{ std::ostringstream o; o << i; return o.str(); }
299 
300 	void
skip(const testlocation & loc)301 	skip(const testlocation& loc)
302 		{ throw new testfailure(loc, "test skipped", false); }
303 
304 
check_contains(const testlocation & loc,const char * needleExpr,const std::string & needle,const char * haystackExpr,const std::string & haystack)305 	void check_contains(const testlocation& loc,
306 		const char* needleExpr, const std::string& needle,
307 		const char* haystackExpr, const std::string& haystack)
308 	{
309 		if (haystack.find(needle) == std::string::npos)
310 			fail_contains(loc, needle, haystack);
311 		else
312 			pass(loc);
313 	}
314 
check(const testlocation & loc,const char * expr,bool actual)315 	void check(const testlocation& loc,
316 					const char* expr, bool actual)
317 		{ if (!actual) fail(loc, expr); else pass(loc); }
318 
319 	bool
runAll(bool reportPerGroup,bool stopOnFailingGroup)320 	runAll(bool reportPerGroup, bool stopOnFailingGroup)
321 	{
322 		testfailure::list failures;
323 		int testCount = 0;
324 		int passCount = 0;
325 		int failCount = 0;
326 		int skipCount = 0;
327 
328 		std::cout <<
329 			  "Running all unit tests:\n"
330 			  "--------------------------------------------------\n";
331 
332 		TestMap& r = registry();
333 		for (TestMap::const_iterator i = r.begin(); i != r.end(); ++i) {
334 			TestGroup& group = *i->second;
335 
336 			std::cout << std::setw(12) << group.name;
337 
338 			TestList& l = group.list;
339 			for (TestList::const_iterator j = l.begin(); j != l.end(); ++j) {
340 				test& t = **j;
341 
342 				testfailure* failure = 0;
343 				anyPasses = false;
344 
345 				if (SPIN) std::cout << ' ';
346 
347 
348 				try {
349 					t.run();
350 				}
351 				catch (testfailure* f) {
352 					failure = f;
353 				}
354 				catch (std::exception* e) {
355 					failure = new testfailure(e->what());
356 					delete e;
357 				}
358 				catch (const std::string& s) {
359 					failure = new testfailure(s);
360 				}
361 				catch (...) {
362 					failure = new testfailure("threw an uncaught exception");
363 				}
364 
365 				if (!failure && anyPasses == 0) {
366 					failure = new testfailure("test skipped", false);
367 				}
368 
369 
370 				if (SPIN) std::cout << '\010';
371 
372 				if (failure) {
373 					failure->set_test_info(group.name, t.name);
374 					failures.push_back(failure);
375 					if (failure->isFatal()) {
376 						std::cout << '*';
377 						failCount += 1;
378 					}
379 					else {
380 						std::cout << '-';
381 						skipCount += 1;
382 					}
383 				}
384 				else if (anyPasses) {
385 					std::cout << '.';
386 					passCount += 1;
387 				}
388 
389 				testCount += 1;
390 			}
391 			std::cout << std::endl;
392 
393 			if (reportPerGroup)
394 				failures.dump(std::cout);
395 			if (stopOnFailingGroup && failCount > 0)
396 				break;
397 		}
398 
399 		std::cout << "--------------------------------------------------\n"
400 				<< testCount << " tests: "
401 				<< passCount << " passed, "
402 				<< failCount << " failed, "
403 				<< skipCount << " skipped\n";
404 
405 		failures.dump(std::cout);
406 
407 		return failCount == 0;
408 	}
409 }
410