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