1 /* 2 * 3 * Copyright (C) 2011-2019, OFFIS e.V. 4 * All rights reserved. See COPYRIGHT file for details. 5 * 6 * This code is inspired by quicktest. 7 * Copyright (C) 2005-2008 8 * Tyler Streeter (http://www.tylerstreeter.net) 9 * http://quicktest.sourceforge.net 10 * 11 * This software and supporting documentation were developed by 12 * 13 * OFFIS e.V. 14 * R&D Division Health 15 * Escherweg 2 16 * D-26121 Oldenburg, Germany 17 * 18 * 19 * Module: ofstd 20 * 21 * Author: Uli Schlachter 22 * 23 * Purpose: Provide a test framework for the toolkit 24 * 25 */ 26 27 28 #ifndef OFTEST_H 29 #define OFTEST_H 30 31 #include "dcmtk/config/osconfig.h" 32 #include "dcmtk/ofstd/ofconapp.h" /* for OFCommandLine */ 33 #include "dcmtk/ofstd/ofconsol.h" /* for CERR */ 34 #include "dcmtk/ofstd/oflist.h" /* for class OFList */ 35 #include "dcmtk/ofstd/ofstream.h" /* for class OFOStringStream */ 36 #include "dcmtk/ofstd/ofstring.h" /* for class OFString */ 37 #include "dcmtk/ofstd/oftypes.h" /* for OFBool */ 38 39 #ifdef OFTEST_OFSTD_ONLY 40 41 #define OFTEST_LOG_VERBOSE(msg) do { \ 42 if (verbose_) \ 43 COUT << msg << OFendl; \ 44 } while (0) 45 46 #else 47 48 #include "dcmtk/dcmdata/dcuid.h" /* for dcmtk version name */ 49 #include "dcmtk/oflog/oflog.h" 50 51 static OFLogger testLogger = OFLog::getLogger("dcmtk.test"); 52 #define OFTEST_LOG_VERBOSE(msg) OFLOG_INFO(testLogger, msg) 53 54 #endif 55 56 /** @file oftest.h 57 * A simple framework for writing and running test cases. 58 */ 59 60 /** A single test case which can be run */ 61 class OFTestTest 62 { 63 public: 64 /// This is the type used for test results. 65 typedef OFList<OFString> TestResult; 66 67 /** Special flags that a test can have. The flags for a test are the result 68 * of a bitwise or of these individual flags. 69 */ 70 enum E_Flags { 71 EF_None = 0x0, 72 /// Slow test which should only be run in exhaustive mode. 73 EF_Slow = 0x1 74 }; 75 76 /** Contructor 77 * @param testName the name of this test case 78 */ OFTestTest(const OFString & testName,int flag)79 OFTestTest(const OFString& testName, int flag) 80 : testName_(testName) 81 , results_() 82 , flags_(flag) 83 { 84 } 85 86 /// Destructor ~OFTestTest()87 virtual ~OFTestTest() 88 { 89 } 90 91 /// @return the flags of this test case flags()92 int flags() const { return flags_; } 93 94 /// @return the name of this test case getTestName()95 const OFString& getTestName() const { return testName_; } 96 97 /** Execute this test case. 98 * @return Reference to list of errors. 99 */ runAndReturn()100 const TestResult& runAndReturn() 101 { 102 results_.clear(); 103 run(); 104 return results_; 105 } 106 107 /** Execute this test case. 108 * @param result the list of error messages generated by this test. 109 * @see #OFCHECK(condition) 110 */ 111 virtual void run() = 0; 112 113 /** Add a new failure to the result set. 114 * @param result list of test failures 115 * @param file filename for this failure 116 * @param line line number for this failure 117 * @param message error description. 118 */ recordFailure(const OFString & file,unsigned long int line,const OFString & message)119 void recordFailure(const OFString& file, unsigned long int line, const OFString& message) 120 { 121 OFOStringStream oss; 122 oss << "FAILED test '" << testName_ << "' at " << file << ":" << line 123 << ": " << message << OFStringStream_ends; 124 OFSTRINGSTREAM_GETOFSTRING(oss, str) 125 results_.push_back(str); 126 } 127 128 private: 129 /// The unique name of this test. 130 OFString testName_; 131 132 /// The test results, empty for success. 133 TestResult results_; 134 135 /// Flags that this test has. 136 const int flags_; 137 }; 138 139 /** The test manager singleton manages the list of available test cases 140 * and executes them. 141 */ 142 class OFTestManager 143 { 144 public: 145 /// @return the only instance of the test manager instance()146 static OFTestManager& instance() 147 { 148 static OFTestManager manager; 149 return manager; 150 } 151 152 /// @return the currently running test case currentTest()153 OFTestTest& currentTest() 154 { 155 if (!curTest_) 156 abort(); 157 return *curTest_; 158 } 159 160 /** Register a test with this test manager. 161 * @param test the test to register 162 */ addTest(OFTestTest * test)163 void addTest(OFTestTest* test) 164 { 165 tests_.push_back(test); 166 } 167 168 /** Run a list of test cases. The results will be printed on the console. 169 * @param tests tests to execute 170 */ runTests(const OFList<OFTestTest * > & tests,const char * module)171 int runTests(const OFList<OFTestTest*>& tests, const char *module) 172 { 173 unsigned int numFailed = 0; 174 OFListConstIterator(OFTestTest*) it; 175 OFString mod_str = module ? " for module '" + OFString(module) + "'" : ""; 176 177 OFTEST_LOG_VERBOSE("Running " << tests.size() << " tests" << mod_str << ":"); 178 179 for (it = tests.begin(); it != tests.end(); ++it) 180 { 181 OFTEST_LOG_VERBOSE(" Running test '" << (*it)->getTestName() << "'..."); 182 183 curTest_ = *it; 184 const OFTestTest::TestResult& result = (*it)->runAndReturn(); 185 curTest_ = NULL; 186 187 if (!result.empty()) 188 { 189 numFailed++; 190 OFListConstIterator(OFString) rit; 191 for (rit = result.begin(); rit != result.end(); ++rit) 192 { 193 // recordFailure() already formatted the message 194 CERR << *rit << OFendl; 195 } 196 } 197 } 198 199 COUT << "Test results" << mod_str << ": " 200 << tests.size() - numFailed << " succeeded, " 201 << numFailed << " failed." << OFendl; 202 203 /* Only the lowest 8 bit of the exit code can be used! */ 204 if (numFailed > 254) 205 { 206 CERR << "WARNING: More than 254 tests failed!" << OFendl; 207 return 254; 208 } 209 210 return OFstatic_cast(int, numFailed); 211 } 212 213 /** Handle the given arguments and run the requested test case. This 214 * function should be used for implementing main(). 215 * @param argc number of arguments 216 * @param argv list of arguments 217 * @param module name of the module that we are testing for 218 * @return 0 in case of success, else an error value. 219 */ run(int argc,char * argv[],const char * module)220 int run(int argc, char* argv[], const char* module) 221 { 222 OFList<OFTestTest*> testsToRun; 223 OFBool listOnly = OFFalse; 224 225 OFString rcsid; 226 #ifdef OFTEST_OFSTD_ONLY 227 // No proper rcsid because the necessary defines are in dcmdata 228 if (module != NULL) 229 rcsid = "$dcmtk: " + OFString(module) + " $"; 230 #else 231 rcsid = "$dcmtk: "; 232 rcsid += OFSTRING_GUARD(module); 233 rcsid += " v" OFFIS_DCMTK_VERSION " " OFFIS_DCMTK_RELEASEDATE " $"; 234 #endif 235 236 OFConsoleApplication app("tests", "Run the test suite", rcsid.c_str()); 237 OFCommandLine cmd; 238 cmd.setParamColumn(13); 239 240 cmd.addParam("tests-to-run", "names of tests to run (default: all)", OFCmdParam::PM_MultiOptional); 241 242 cmd.addGroup("general options:"); 243 cmd.addOption("--help", "-h", "print this help text and exit", OFCommandLine::AF_Exclusive); 244 cmd.addOption("--list", "-l", "list available tests and exit", OFCommandLine::AF_Exclusive); 245 cmd.addOption("--exhaustive", "-x", "also run extensive and slow tests"); 246 #ifdef OFTEST_OFSTD_ONLY 247 cmd.addOption("--verbose", "-v", "verbose mode, print processing details"); 248 #else 249 OFLog::addOptions(cmd); 250 #endif 251 252 /* evaluate command line */ 253 if (app.parseCommandLine(cmd, argc, argv)) 254 { 255 /* check exclusive options first */ 256 } 257 258 #ifdef OFTEST_OFSTD_ONLY 259 if (cmd.findOption("--verbose")) verbose_ = OFTrue; 260 #else 261 /* We disable warnings and errors by default since some tests cause 262 * such messages by testing corner cases. */ 263 OFLog::configureFromCommandLine(cmd, app, OFLogger::FATAL_LOG_LEVEL); 264 #endif 265 if (cmd.findOption("--exhaustive")) exhaustive_ = OFTrue; 266 if (cmd.findOption("--list")) listOnly = OFTrue; 267 268 if (!buildTestsToRun(cmd, testsToRun)) 269 return -1; 270 271 if (testsToRun.empty()) 272 { 273 CERR << "No tests to run!" << OFendl; 274 return 0; 275 } 276 277 if (listOnly) 278 { 279 OFListIterator(OFTestTest*) it; 280 COUT << "There are " << testsToRun.size() << " tests"; 281 if (module) 282 COUT << " for module '" << module << "'"; 283 COUT << ":" << OFendl; 284 for (it = testsToRun.begin(); it != testsToRun.end(); ++it) 285 { 286 COUT << " " << (*it)->getTestName() << "\n"; 287 } 288 return 0; 289 } 290 291 return runTests(testsToRun, module); 292 } 293 294 private: 295 /// Private constructor, this is a singleton! OFTestManager()296 OFTestManager() 297 : tests_() 298 , curTest_(NULL) 299 , exhaustive_(OFFalse) 300 #ifdef OFTEST_OFSTD_ONLY 301 , verbose_(OFFalse) 302 #endif 303 { 304 } 305 306 /// Private undefined copy constructor 307 OFTestManager(const OFTestManager& obj); 308 309 /// Private undefined assignment operator 310 OFTestManager& operator=(const OFTestManager& obj); 311 312 /** Build a list of tests which should be executed from the command line. 313 * @param cmd command line arguments which should be parsed 314 * @param tests will be set to the list of tests to run 315 * @return OFFalse if the command line could not be handled. 316 */ buildTestsToRun(OFCommandLine & cmd,OFList<OFTestTest * > & tests)317 OFBool buildTestsToRun(OFCommandLine& cmd, OFList<OFTestTest*>& tests) const 318 { 319 const int paramCount = cmd.getParamCount(); 320 OFString paramString; 321 OFBool result = OFTrue; 322 323 if (paramCount == 0) 324 { 325 // If no arguments are given, run all possible tests 326 tests = tests_; 327 } 328 else 329 { 330 for (int i = 1; i <= paramCount; i++) 331 { 332 cmd.getParam(i, paramString); 333 334 // Find all tests matching this argument 335 OFBool found = OFFalse; 336 OFListConstIterator(OFTestTest*) it; 337 for (it = tests_.begin(); it != tests_.end(); ++it) 338 { 339 if (testMatches(*it, paramString)) 340 { 341 tests.push_back(*it); 342 found = OFTrue; 343 } 344 } 345 346 if (!found) 347 { 348 CERR << "Error: No test matches '" << paramString << "'" << OFendl; 349 result = OFFalse; 350 } 351 } 352 } 353 354 // If we are not in exhaustive mode, remove all slow tests 355 if (!exhaustive_) 356 { 357 OFListIterator(OFTestTest*) it = tests.begin(); 358 while (it != tests.end()) 359 { 360 if ((*it)->flags() & OFTestTest::EF_Slow) 361 it = tests.erase(it); 362 else 363 ++it; 364 } 365 } 366 367 return result; 368 } 369 370 /** Test if the test name matches the given name. This function supports '?' 371 * and '*' for wildcards. However, '*' can only be used at the end of string. 372 * @param test test to check against 373 * @param str the string describing the tests 374 * @return OFTrue if we found a match, else OFFalse 375 */ testMatches(const OFTestTest * test,const OFString & str)376 OFBool testMatches(const OFTestTest* test, const OFString& str) const 377 { 378 const char* testName = test->getTestName().c_str(); 379 const char* string = str.c_str(); 380 381 for (; *testName != '\0' && *string != '\0'; testName++, string++) 382 { 383 // Does the string still match? 384 if (string[0] != '?' && testName[0] != string[0]) 385 break; 386 } 387 388 // Is this a wildcard? 389 // So far we only support '*' at the end of the string 390 if (string[0] == '*' && string[1] == '\0') 391 return OFTrue; 392 393 // If both strings reached their end, we have a match 394 if (testName[0] == '\0' && string[0] == '\0') 395 return OFTrue; 396 return OFFalse; 397 } 398 399 /// List of tests. Statically allocated, so don't have to be freed. 400 OFList<OFTestTest*> tests_; 401 402 /// Currently running test. 403 OFTestTest* curTest_; 404 405 /// Should slow tests be run, too? 406 OFBool exhaustive_; 407 408 #ifdef OFTEST_OFSTD_ONLY 409 /// Are we running in verbose mode? Only used if oflog is not available. 410 OFBool verbose_; 411 #endif 412 }; 413 414 415 /** Implement a main() function for running tests. The main function will return 416 * the number of failed tests or -1 if an invalid test name was given. 417 * @param module the name of the module which is under test. 418 */ 419 #define OFTEST_MAIN(module) \ 420 int main(int argc, char* argv[]) \ 421 { \ 422 return OFTestManager::instance().run(argc, argv, module); \ 423 } 424 425 /// Internal macro for generating a class definition, don't use yourself! 426 #define OFTEST_CLASS(testName) \ 427 class OFTest ## testName : public OFTestTest \ 428 { \ 429 public: \ 430 OFTest ## testName(); \ 431 void run(); \ 432 } 433 434 /** Register a test to the test manager. Normally you should use 435 * OFTEST_REGISTER instead, but that macro doesn't work if OFTEST and 436 * OFTEST_REGISTER are called in the same source file. 437 * @param testName name of the test to register 438 */ 439 #define OFTEST_REGISTER_INT(testName) \ 440 OFTest ## testName OFTest ## testName ## instance 441 442 /** Register a test to the test manager. 443 * @param testName name of the test to register 444 */ 445 #define OFTEST_REGISTER(testName) \ 446 OFTEST_CLASS(testName); \ 447 OFTEST_REGISTER_INT(testName) 448 449 /** Macro to define a new test case. Internally this defines a new class 450 * inheriting from OFTest. 451 * This is equivalent to OFTEST_FLAGS(testName, EF_None). 452 * @param testName name describing the test 453 * @see OFTEST_FLAGS 454 */ 455 #define OFTEST(testName) OFTEST_FLAGS(testName, EF_None) 456 457 /** Macro to define a new test case. Internally this defines a new class 458 * inheriting from OFTest. 459 * @param flags flags that should be set for this test 460 * @param testName name describing the test 461 * @see OFTEST 462 */ 463 #define OFTEST_FLAGS(testName, flags) \ 464 OFTEST_CLASS(testName); \ 465 OFTest ## testName::OFTest ## testName() \ 466 : OFTestTest(#testName, flags) \ 467 { \ 468 OFTestManager::instance().addTest(this); \ 469 } \ 470 void OFTest ## testName ::run() 471 472 /** @name macros for checking conditions in tests 473 * These macros can be used for doing various checks in test cases. In case 474 * their check fails, they emit a descriptive message explaining the problem. 475 */ 476 //@{ 477 478 /** Check if a condition is true. Can only be used inside OFTEST(). 479 * @param condition condition to check 480 */ 481 #define OFCHECK(condition) \ 482 do { \ 483 if (!(condition)) \ 484 OFTestManager::instance().currentTest().recordFailure(__FILE__, __LINE__, #condition); \ 485 } while (0) 486 487 /** Check if two values are equal. Can only be used inside OFTEST(). Both 488 * arguments must be compatible with OFOStringStream's operator<<. 489 * @param val1 first value to compare 490 * @param val2 second value to compare 491 */ 492 #define OFCHECK_EQUAL(val1, val2) \ 493 do { \ 494 if ((val1) != (val2)) { \ 495 OFOStringStream oss___; \ 496 oss___ << "(" << (val1) << ") should equal (" << (val2) << ")" << OFStringStream_ends; \ 497 OFSTRINGSTREAM_GETOFSTRING(oss___, str___) \ 498 OFTestManager::instance().currentTest().recordFailure(__FILE__, __LINE__, str___); \ 499 } \ 500 } while (0) 501 502 /** Unconditionally add a failure 503 * @param message string describing the failure 504 */ 505 #define OFCHECK_FAIL(message) \ 506 do { \ 507 OFOStringStream oss___; \ 508 oss___ << message << OFStringStream_ends; \ 509 OFSTRINGSTREAM_GETOFSTRING(oss___, str___) \ 510 OFTestManager::instance().currentTest().recordFailure(__FILE__, __LINE__, str___); \ 511 } while (0) 512 513 //@} 514 515 #endif 516