]> 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 /*
2  * tut_xml_reporter.hpp
3  *
4  * ECOS Library. IPT CS R&D CET ECOS Copyright 2008 Nokia
5  * Siemens Networks. All right
6  *
7  *
8  */
9
10 #ifndef TUT_XML_REPORTER
11 #define TUT_XML_REPORTER
12
13 #include <tut/tut.hpp>
14 #include <string>
15 #include <fstream>
16 #include <vector>
17 #include <stdexcept>
18
19 namespace tut
20 {
21
22 /**
23  * \brief JUnit XML TUT reporter
24  * @author Lukasz Maszczynski, NSN
25  * @date 11/07/2008
26  */
27 class xml_reporter : public tut::callback
28 {
29 protected:
30     typedef std::vector<tut::test_result> TestResults;
31     typedef std::map<std::string, TestResults> TestGroups;
32
33     TestGroups all_tests; /// holds all test results
34     std::string filename; /// filename base
35
36     /**
37      * \brief Initializes object
38      * Resets counters and clears all stored test results.
39      */
40     virtual void init()
41     {
42         ok_count = 0;
43         exceptions_count = 0;
44         failures_count = 0;
45         terminations_count = 0;
46         warnings_count = 0;
47         all_tests.clear();
48     }
49
50     /**
51      * \brief Encodes text to XML
52      * XML-reserved characters (e.g. "<") are encoded according to specification
53      * @param text text to be encoded
54      * @return encoded string
55      */
56     virtual std::string encode(const std::string & text)
57     {
58         std::string out;
59
60         for (unsigned int i=0; i<text.length(); ++i) {
61             char c = text[i];
62             switch (c) {
63                 case '<':
64                     out += "&lt;";
65                     break;
66                 case '>':
67                     out += "&gt;";
68                     break;
69                 case '&':
70                     out += "&amp;";
71                     break;
72                 case '\'':
73                     out += "&apos;";
74                     break;
75                 case '"':
76                     out += "&quot;";
77                     break;
78                 default:
79                     out += c;
80             }
81         }
82
83         return out;
84     }
85
86     /**
87      * \brief Builds "testcase" XML entity with given parameters
88      * Builds \<testcase\> entity according to given parameters. \<testcase\>-s are part of \<testsuite\>.
89      * @param tr test result to be used as source data
90      * @param failure_type type of failure to be reported ("Assertion" or "Error", empty if test passed)
91      * @param failure_msg failure message to be reported (empty, if test passed)
92      * @return string with \<testcase\> entity
93      */
94     virtual std::string xml_build_testcase(const tut::test_result & tr, const std::string & failure_type,
95                                            const std::string & failure_msg, int pid = 0)
96     {
97         using std::endl;
98         using std::string;
99
100         std::ostringstream out;
101
102         if (tr.result == test_result::ok)
103         {
104             out << "  <testcase classname=\"" << encode(tr.group) << "\" name=\"" << encode(tr.name) << "\" />";
105         }
106         else
107         {
108             string err_msg = encode(failure_msg + tr.message);
109
110             string tag; // determines tag name: "failure" or "error"
111             if ( tr.result == test_result::fail || tr.result == test_result::warn ||
112                  tr.result == test_result::ex || tr.result == test_result::ex_ctor )
113             {
114                 tag = "failure";
115             }
116             else
117             {
118                 tag = "error";
119             }
120
121             out << "  <testcase classname=\"" << encode(tr.group) << "\" name=\"" << encode(tr.name) << "\">" << endl;
122             out << "    <" << tag << " message=\"" << err_msg << "\"" << " type=\"" << failure_type << "\"";
123 #if defined(TUT_USE_POSIX)
124             if(pid != getpid())
125             {
126                 out << " child=\"" << pid << "\"";
127             }
128 #endif
129             out << ">" << err_msg << "</" << tag << ">" << endl;
130             out << "  </testcase>";
131         }
132
133         return out.str();
134     }
135
136     /**
137      * \brief Builds "testsuite" XML entity
138      * Builds \<testsuite\> XML entity according to given parameters.
139      * @param errors number of errors to be reported
140      * @param failures number of failures to be reported
141      * @param total total number of tests to be reported
142      * @param name test suite name
143      * @param testcases encoded XML string containing testcases
144      * @return string with \<testsuite\> entity
145      */
146     virtual std::string xml_build_testsuite(int errors, int failures, int total,
147                                             const std::string & name, const std::string & testcases)
148     {
149         std::ostringstream out;
150
151         out << "<testsuite errors=\"" << errors << "\" failures=\"" << failures << "\" tests=\"" << total << "\" name=\"" << encode(name) << "\">" << std::endl;
152         out << testcases;
153         out << "</testsuite>";
154
155         return out.str();
156     }
157
158 public:
159     int ok_count;           /// number of passed tests
160     int exceptions_count;   /// number of tests that threw exceptions
161     int failures_count;     /// number of tests that failed
162     int terminations_count; /// number of tests that would terminate
163     int warnings_count;     /// number of tests where destructors threw an exception
164
165     /**
166      * \brief Default constructor
167      * @param filename base filename
168      * @see setFilenameBase
169      */
170     xml_reporter(const std::string & _filename = "")
171     {
172         init();
173         setFilenameBase(_filename);
174     }
175
176     /**
177      * \brief Sets filename base for output
178      * @param _filename filename base
179      * Example usage:
180      * @code
181      * xml_reporter reporter;
182      * reporter.setFilenameBase("my_xml");
183      * @endcode
184      * The above code will instruct reporter to create my_xml_1.xml file for the first test group,
185      * my_xml_2.xml file for the second, and so on.
186      */
187     virtual void setFilenameBase(const std::string & _filename)
188     {
189         if (_filename == "")
190         {
191             filename = "testResult";
192         }
193         else
194         {
195             if (_filename.length() > 200)
196             {
197                 throw(std::runtime_error("Filename too long!"));
198             }
199
200             filename = _filename;
201         }
202     }
203
204     /**
205      * \brief Callback function
206      * This function is called before the first test is executed. It initializes counters.
207      */
208     virtual void run_started()
209     {
210         init();
211     }
212
213     /**
214      * \brief Callback function
215      * This function is called when test completes. Counters are updated here, and test results stored.
216      */
217     virtual void test_completed(const tut::test_result& tr)
218     {
219         // update global statistics
220         switch (tr.result) {
221             case test_result::ok:
222                 ok_count++;
223                 break;
224             case test_result::fail:
225             case test_result::rethrown:
226                 failures_count++;
227                 break;
228             case test_result::ex:
229             case test_result::ex_ctor:
230                 exceptions_count++;
231                 break;
232             case test_result::warn:
233                 warnings_count++;
234                 break;
235             case test_result::term:
236                 terminations_count++;
237                 break;
238         } // switch
239
240         // add test result to results table
241         (all_tests[tr.group]).push_back(tr);
242     }
243
244     /**
245      * \brief Callback function
246      * This function is called when all tests are completed. It generates XML output
247      * to file(s). File name base can be set with \ref setFilenameBase.
248      */
249     virtual void run_completed()
250     {
251         using std::endl;
252         using std::string;
253
254         static int number = 1;  // results file sequence number (testResult_<number>.xml)
255
256         // iterate over all test groups
257         TestGroups::const_iterator tgi;
258         for (tgi = all_tests.begin(); tgi != all_tests.end(); ++tgi) {
259             /* per-group statistics */
260             int passed = 0;         // passed in single group
261             int exceptions = 0;     // exceptions in single group
262             int failures = 0;       // failures in single group
263             int terminations = 0;   // terminations in single group
264             int warnings = 0;       // warnings in single group
265             int errors = 0;     // errors in single group
266
267             /* generate output filename */
268             char fn[256];
269             sprintf(fn, "%s_%d.xml", filename.c_str(), number++);
270
271             std::ofstream xmlfile;
272             xmlfile.open(fn, std::ios::in | std::ios::trunc);
273             if (!xmlfile.is_open()) {
274                 throw (std::runtime_error("Cannot open file for output"));
275             }
276
277             /* *********************** header ***************************** */
278             xmlfile << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << endl;
279
280             // output is written to string stream buffer, because JUnit format <testsuite> tag
281             // contains statistics, which aren't known yet
282             std::ostringstream out;
283
284             // iterate over all test cases in the current test group
285             TestResults::const_iterator tri;
286             for (tri = (*tgi).second.begin(); tri != (*tgi).second.end(); ++tri) {
287                 string failure_type;    // string describing the failure type
288                 string failure_msg;     // a string with failure message
289
290                 switch ((*tri).result) {
291                     case test_result::ok:
292                         passed++;
293                         break;
294                     case test_result::fail:
295                         failure_type = "Assertion";
296                         failure_msg  = "";
297                         failures++;
298                         break;
299                     case test_result::ex:
300                         failure_type = "Assertion";
301                         failure_msg  = "Thrown exception: " + (*tri).exception_typeid + '\n';
302                         exceptions++;
303                         break;
304                     case test_result::warn:
305                         failure_type = "Assertion";
306                         failure_msg  = "Destructor failed.\n";
307                         warnings++;
308                         break;
309                     case test_result::term:
310                         failure_type = "Error";
311                         failure_msg  = "Test application terminated abnormally.\n";
312                         terminations++;
313                         break;
314                     case test_result::ex_ctor:
315                         failure_type = "Assertion";
316                         failure_msg  = "Constructor has thrown an exception: " + (*tri).exception_typeid + '\n';
317                         exceptions++;
318                         break;
319                     case test_result::rethrown:
320                         failure_type = "Assertion";
321                         failure_msg  = "Child failed";
322                         failures++;
323                         break;
324                     default:
325                         failure_type = "Error";
326                         failure_msg  = "Unknown test status, this should have never happened. "
327                                 "You may just have found a BUG in TUT XML reporter, please report it immediately.\n";
328                         errors++;
329                         break;
330                 } // switch
331
332 #if defined(TUT_USE_POSIX)
333                 out << xml_build_testcase(*tri, failure_type, failure_msg, (*tri).pid) << endl;
334 #else
335                 out << xml_build_testcase(*tri, failure_type, failure_msg) << endl;
336 #endif
337
338             } // iterate over all test cases
339
340             // calculate per-group statistics
341             int stat_errors = terminations + errors;
342             int stat_failures = failures + warnings + exceptions;
343             int stat_all = stat_errors + stat_failures + passed;
344
345             xmlfile << xml_build_testsuite(stat_errors, stat_failures, stat_all, (*tgi).first/* name */, out.str()/* testcases */) << endl;
346             xmlfile.close();
347         } // iterate over all test groups
348     }
349
350     /**
351      * \brief Returns true, if all tests passed
352      */
353     virtual bool all_ok() const
354     {
355         return ( (terminations_count + failures_count + warnings_count + exceptions_count) == 0);
356     };
357 };
358
359 }
360
361 #endif