1 // -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 // vi: set et ts=4 sw=2 sts=2:
3 #ifndef DUNE_COMMON_TEST_TESTSUITE_HH
4 #define DUNE_COMMON_TEST_TESTSUITE_HH
5 
6 #include <iostream>
7 #include <sstream>
8 #include <string>
9 
10 #include <dune/common/exceptions.hh>
11 #include <dune/common/test/collectorstream.hh>
12 
13 
14 
15 namespace Dune {
16 
17 
18 
19   /**
20    * \brief A Simple helper class to organize your test suite
21    *
22    * Usage: Construct a TestSuite and call check() or require()
23    * with the condition to check and probably a name for this check.
24    * These methods return a stream such that you can pipe in an
25    * explanantion accompanied by respective data to give a reason
26    * for a test failure.
27    */
28   class TestSuite
29   {
30   public:
31     enum ThrowPolicy
32     {
33       AlwaysThrow,
34       ThrowOnRequired
35     };
36 
37     /**
38      * \brief Create TestSuite
39      *
40      * \param name A name to identify this TestSuite. Defaults to "".
41      * \param policy If AlwaysThrow any failing check will throw, otherwise only required checks will do.
42      */
TestSuite(ThrowPolicy policy,std::string name="")43     TestSuite(ThrowPolicy policy, std::string name="") :
44       name_(name),
45       checks_(0),
46       failedChecks_(0),
47       throwPolicy_(policy==AlwaysThrow)
48     {}
49 
50     /**
51      * \brief Create TestSuite
52      *
53      * \param name A name to identify this TestSuite. Defaults to "".
54      * \param policy If AlwaysThrow any failing check will throw, otherwise only required checks will do. Defaults to ThrowOnRequired
55      */
TestSuite(std::string name="",ThrowPolicy policy=ThrowOnRequired)56     TestSuite(std::string name="", ThrowPolicy policy=ThrowOnRequired) :
57       name_(name),
58       checks_(0),
59       failedChecks_(0),
60       throwPolicy_(policy==AlwaysThrow)
61     {}
62 
63     /**
64      * \brief Check condition
65      *
66      * This will throw an exception if the check fails and if the AlwaysThrow policy was used on creation.
67      *
68      * \param conditon Checks if this is true and increases the failure counter if not.
69      * \param name A name to identify this check. Defaults to ""
70      * \returns A CollectorStream that can be used to create a diagnostic message to be printed on failure.
71      */
check(bool condition,std::string name="")72     CollectorStream check(bool condition, std::string name="")
73     {
74       ++checks_;
75       if (not condition)
76         ++failedChecks_;
77 
78       return CollectorStream([condition, name, this](std::string reason) {
79           if (not condition)
80             this->announceCheckResult(throwPolicy_, "CHECK  ", name, reason);
81         });
82     }
83 
84     /**
85      * \brief Check a required condition
86      *
87      * This will always throw an exception if the check fails.
88      *
89      * \param conditon Checks if this is true and increases the failure counter if not.
90      * \param name A name to identify this check. Defaults to ""
91      * \returns A CollectorStream that can be used to create a diagnostic message to be printed on failure.
92      */
require(bool condition,std::string name="")93     CollectorStream require(bool condition, std::string name="")
94     {
95       ++checks_;
96       if (not condition)
97         ++failedChecks_;
98 
99       return CollectorStream([condition, name, this](std::string reason) {
100           if (not condition)
101             this->announceCheckResult(true, "REQUIRED CHECK", name, reason);
102         });
103     }
104 
105     /**
106      * \brief Collect data from a sub-TestSuite
107      *
108      * This will incorporate the accumulated results of the sub-TestSuite
109      * into this one. If the sub-TestSuite failed, i.e., contained failed
110      * checks, a summary will be printed.
111      */
subTest(const TestSuite & subTest)112     void subTest(const TestSuite& subTest)
113     {
114       checks_ += subTest.checks_;
115       failedChecks_ += subTest.failedChecks_;
116 
117       if (not subTest)
118         announceCheckResult(throwPolicy_, "SUBTEST", subTest.name(), std::to_string(subTest.failedChecks_)+"/"+std::to_string(subTest.checks_) + " checks failed in this subtest.");
119     }
120 
121     /**
122      * \brief Check if this TestSuite failed
123      *
124      * \returns False if any of the executed tests failed, otherwise true.
125      */
operator bool() const126     explicit operator bool () const
127     {
128       return (failedChecks_==0);
129     }
130 
131     /**
132      * \brief Query name
133      *
134      * \returns Name of this TestSuite
135      */
name() const136     std::string name() const
137     {
138       return name_;
139     }
140 
141     /**
142      * \brief Print a summary of this TestSuite
143      *
144      * \returns False if any of the executed tests failed, otherwise true.
145      */
report() const146     bool report() const
147     {
148       if (failedChecks_>0)
149         std::cout << composeMessage("TEST   ", name(), std::to_string(failedChecks_)+"/"+std::to_string(checks_) + " checks failed in this test.") << std::endl;
150       return (failedChecks_==0);
151     }
152 
153     /**
154      * \brief Exit the test.
155      *
156      * This wil print a summary of the test and return an integer
157      * to be used on program exit.
158      *
159      * \returns 1 if any of the executed tests failed, otherwise 0.
160      */
exit() const161     int exit() const
162     {
163       return (report() ? 0: 1);
164     }
165 
166   protected:
167 
168     // Compose a diagnostic message
composeMessage(std::string type,std::string name,std::string reason)169     static std::string composeMessage(std::string type, std::string name, std::string reason)
170     {
171       std::ostringstream s;
172       s << type << " FAILED";
173       if (name!="")
174         s << "(" << name << ")";
175       s << ": ";
176       if (reason!="")
177         s << reason;
178       return s.str();
179     }
180 
181     // Announce check results. To be called on failed checks
announceCheckResult(bool throwException,std::string type,std::string name,std::string reason)182     static void announceCheckResult(bool throwException, std::string type, std::string name, std::string reason)
183     {
184       std::string message = composeMessage(type, name, reason);
185       std::cout << message << std::endl;
186       if (throwException)
187       {
188         Dune::Exception ex;
189         ex.message(message);
190         throw ex;
191       }
192     }
193 
194     std::string name_;
195     std::size_t checks_;
196     std::size_t failedChecks_;
197     bool throwPolicy_;
198   };
199 
200 
201 
202 } // namespace Dune
203 
204 
205 
206 #endif // DUNE_COMMON_TEST_TESTSUITE_HH
207