]> git.stg.codes - stg.git/blob - tests/tut/tut_xml_reporter.hpp
Merge branch 'master' into full-month-stats
[stg.git] / tests / tut / tut_xml_reporter.hpp
1 #ifndef TUT_XML_REPORTER
2 #define TUT_XML_REPORTER
3 #include <tut/tut_config.hpp>
4 #include <tut/tut.hpp>
5 #include <tut/tut_cppunit_reporter.hpp>
6 #include <cassert>
7 #include <string>
8 #include <fstream>
9 #include <vector>
10 #include <stdexcept>
11
12 namespace tut
13 {
14
15 /**
16  * \brief JUnit XML TUT reporter
17  * @author Lukasz Maszczynski, NSN
18  * @date 11/07/2008
19  */
20 class xml_reporter : public tut::callback
21 {
22     typedef std::vector<tut::test_result> TestResults;
23     typedef std::map<std::string, TestResults> TestGroups;
24
25     TestGroups all_tests_; /// holds all test results
26     const std::string filename_; /// filename base
27     std::auto_ptr<std::ostream> stream_;
28
29     /**
30      * \brief Builds "testcase" XML entity with given parameters
31      * Builds \<testcase\> entity according to given parameters. \<testcase\>-s are part of \<testsuite\>.
32      * @param tr test result to be used as source data
33      * @param failure_type type of failure to be reported ("Assertion" or "Error", empty if test passed)
34      * @param failure_msg failure message to be reported (empty, if test passed)
35      * @return string with \<testcase\> entity
36      */
37     std::string xml_build_testcase(const tut::test_result & tr, const std::string & failure_type,
38                                            const std::string & failure_msg, int pid = 0)
39     {
40         using std::endl;
41         using std::string;
42
43         std::ostringstream out;
44
45         if ( (tr.result == test_result::ok) ||
46              (tr.result == test_result::skipped) )
47         {
48             out << "    <testcase classname=\"" << cppunit_reporter::encode(tr.group) << "\" name=\"" << cppunit_reporter::encode(tr.name) << "\"/>";
49         }
50         else
51         {
52             string err_msg = cppunit_reporter::encode(failure_msg + tr.message);
53
54             string tag; // determines tag name: "failure" or "error"
55             if ( tr.result == test_result::fail || tr.result == test_result::warn ||
56                  tr.result == test_result::ex || tr.result == test_result::ex_ctor || tr.result == test_result::rethrown )
57             {
58                 tag = "failure";
59             }
60             else
61             {
62                 tag = "error";
63             }
64
65             out << "    <testcase classname=\"" << cppunit_reporter::encode(tr.group) << "\" name=\"" << cppunit_reporter::encode(tr.name) << "\">" << endl;
66             out << "      <" << tag << " message=\"" << err_msg << "\"" << " type=\"" << failure_type << "\"";
67 #if defined(TUT_USE_POSIX)
68             if(pid != getpid())
69             {
70                 out << " child=\"" << pid << "\"";
71             }
72 #else
73             (void)pid;
74 #endif
75             out << ">" << err_msg << "</" << tag << ">" << endl;
76             out << "    </testcase>";
77         }
78
79         return out.str();
80     }
81
82     /**
83      * \brief Builds "testsuite" XML entity
84      * Builds \<testsuite\> XML entity according to given parameters.
85      * @param errors number of errors to be reported
86      * @param failures number of failures to be reported
87      * @param total total number of tests to be reported
88      * @param name test suite name
89      * @param testcases cppunit_reporter::encoded XML string containing testcases
90      * @return string with \<testsuite\> entity
91      */
92     std::string xml_build_testsuite(int errors, int failures, int total,
93                                             const std::string & name, const std::string & testcases)
94     {
95         std::ostringstream out;
96
97         out << "  <testsuite errors=\"" << errors << "\" failures=\"" << failures << "\" tests=\"" << total << "\" name=\"" << cppunit_reporter::encode(name) << "\">" << std::endl;
98         out << testcases;
99         out << "  </testsuite>";
100
101         return out.str();
102     }
103
104 public:
105     int ok_count;           /// number of passed tests
106     int exceptions_count;   /// number of tests that threw exceptions
107     int failures_count;     /// number of tests that failed
108     int terminations_count; /// number of tests that would terminate
109     int warnings_count;     /// number of tests where destructors threw an exception
110
111     /**
112      * \brief Default constructor
113      * @param filename base filename
114      */
115     xml_reporter(const std::string & filename)
116         : all_tests_(),
117           filename_(filename),
118           stream_(new std::ofstream(filename_.c_str())),
119           ok_count(0),
120           exceptions_count(0),
121           failures_count(0),
122           terminations_count(0),
123           warnings_count(0)
124     {
125         if (!stream_->good()) {
126             throw tut_error("Cannot open output file `" + filename_ + "`");
127         }
128     }
129
130     xml_reporter(std::ostream & stream)
131         : all_tests_(),
132           filename_(),
133           stream_(&stream),
134           ok_count(0),
135           exceptions_count(0),
136           failures_count(0),
137           terminations_count(0),
138           warnings_count(0)
139     {
140     }
141
142     ~xml_reporter()
143     {
144         if(filename_.empty())
145         {
146             stream_.release();
147         }
148     }
149
150     /**
151      * \brief Callback function
152      * This function is called before the first test is executed. It initializes counters.
153      */
154     virtual void run_started()
155     {
156         ok_count = 0;
157         exceptions_count = 0;
158         failures_count = 0;
159         terminations_count = 0;
160         warnings_count = 0;
161         all_tests_.clear();
162     }
163
164     /**
165      * \brief Callback function
166      * This function is called when test completes. Counters are updated here, and test results stored.
167      */
168     virtual void test_completed(const tut::test_result& tr)
169     {
170         // update global statistics
171         switch (tr.result) {
172             case test_result::ok:
173             case test_result::skipped:
174                 ok_count++;
175                 break;
176             case test_result::fail:
177             case test_result::rethrown:
178                 failures_count++;
179                 break;
180             case test_result::ex:
181             case test_result::ex_ctor:
182                 exceptions_count++;
183                 break;
184             case test_result::warn:
185                 warnings_count++;
186                 break;
187             case test_result::term:
188                 terminations_count++;
189                 break;
190             case tut::test_result::dummy:
191                 assert(!"Should never be called");
192         } // switch
193
194         // add test result to results table
195         all_tests_[tr.group].push_back(tr);
196     }
197
198     /**
199      * \brief Callback function
200      * This function is called when all tests are completed. It generates XML output
201      * to file(s). File name base can be set with constructor.
202      */
203     virtual void run_completed()
204     {
205         /* *********************** header ***************************** */
206         *stream_ << "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" << std::endl;
207         *stream_ << "<testsuites>" << std::endl;
208
209         // iterate over all test groups
210         for (TestGroups::const_iterator tgi = all_tests_.begin(); tgi != all_tests_.end(); ++tgi)
211         {
212             /* per-group statistics */
213             int passed = 0;         // passed in single group
214             int exceptions = 0;     // exceptions in single group
215             int failures = 0;       // failures in single group
216             int terminations = 0;   // terminations in single group
217             int warnings = 0;       // warnings in single group
218             int errors = 0;         // errors in single group
219
220
221             // output is written to string stream buffer, because JUnit format <testsuite> tag
222             // contains statistics, which aren't known yet
223             std::ostringstream out;
224
225             // iterate over all test cases in the current test group
226             const TestResults &results = tgi->second;
227             for (TestResults::const_iterator tri = results.begin(); tri != results.end(); ++tri)
228             {
229                 std::string failure_type;    // string describing the failure type
230                 std::string failure_msg;     // a string with failure message
231
232                 switch (tri->result)
233                 {
234                     case test_result::ok:
235                     case test_result::skipped:
236                         passed++;
237                         break;
238                     case test_result::fail:
239                         failure_type = "Assertion";
240                         failure_msg  = "";
241                         failures++;
242                         break;
243                     case test_result::ex:
244                         failure_type = "Assertion";
245                         failure_msg  = "Thrown exception: " + tri->exception_typeid + '\n';
246                         exceptions++;
247                         break;
248                     case test_result::warn:
249                         failure_type = "Assertion";
250                         failure_msg  = "Destructor failed.\n";
251                         warnings++;
252                         break;
253                     case test_result::term:
254                         failure_type = "Error";
255                         failure_msg  = "Test application terminated abnormally.\n";
256                         terminations++;
257                         break;
258                     case test_result::ex_ctor:
259                         failure_type = "Assertion";
260                         failure_msg  = "Constructor has thrown an exception: " + tri->exception_typeid + ".\n";
261                         exceptions++;
262                         break;
263                     case test_result::rethrown:
264                         failure_type = "Assertion";
265                         failure_msg  = "Child failed.\n";
266                         failures++;
267                         break;
268                     default:
269                         failure_type = "Error";
270                         failure_msg  = "Unknown test status, this should have never happened. "
271                                        "You may just have found a bug in TUT, please report it immediately.\n";
272                         errors++;
273                         break;
274                 } // switch
275
276 #if defined(TUT_USE_POSIX)
277                 out << xml_build_testcase(*tri, failure_type, failure_msg, tri->pid) << std::endl;
278 #else
279                 out << xml_build_testcase(*tri, failure_type, failure_msg) << std::endl;
280 #endif
281             } // iterate over all test cases
282
283             // calculate per-group statistics
284             int stat_errors = terminations + errors;
285             int stat_failures = failures + warnings + exceptions;
286             int stat_all = stat_errors + stat_failures + passed;
287
288             *stream_ << xml_build_testsuite(stat_errors, stat_failures, stat_all, (*tgi).first/* name */, out.str()/* testcases */) << std::endl;
289         } // iterate over all test groups
290
291         *stream_ << "</testsuites>" << std::endl;
292     }
293
294     /**
295      * \brief Returns true, if all tests passed
296      */
297     virtual bool all_ok() const
298     {
299         return ( (terminations_count + failures_count + warnings_count + exceptions_count) == 0);
300     };
301 };
302
303 }
304
305 #endif