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