4 * ECOS Library. IPT CS R&D CET ECOS Copyright 2008 Nokia
5 * Siemens Networks. All right
10 #ifndef TUT_XML_REPORTER
11 #define TUT_XML_REPORTER
13 #include <tut/tut.hpp>
23 * \brief JUnit XML TUT reporter
24 * @author Lukasz Maszczynski, NSN
27 class xml_reporter : public tut::callback
30 typedef std::vector<tut::test_result> TestResults;
31 typedef std::map<std::string, TestResults> TestGroups;
33 TestGroups all_tests; /// holds all test results
34 std::string filename; /// filename base
37 * \brief Initializes object
38 * Resets counters and clears all stored test results.
45 terminations_count = 0;
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
56 virtual std::string encode(const std::string & text)
60 for (unsigned int i=0; i<text.length(); ++i) {
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
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)
100 std::ostringstream out;
102 if (tr.result == test_result::ok)
104 out << " <testcase classname=\"" << encode(tr.group) << "\" name=\"" << encode(tr.name) << "\" />";
108 string err_msg = encode(failure_msg + tr.message);
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 )
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)
126 out << " child=\"" << pid << "\"";
129 out << ">" << err_msg << "</" << tag << ">" << endl;
130 out << " </testcase>";
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
146 virtual std::string xml_build_testsuite(int errors, int failures, int total,
147 const std::string & name, const std::string & testcases)
149 std::ostringstream out;
151 out << "<testsuite errors=\"" << errors << "\" failures=\"" << failures << "\" tests=\"" << total << "\" name=\"" << encode(name) << "\">" << std::endl;
153 out << "</testsuite>";
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
166 * \brief Default constructor
167 * @param filename base filename
168 * @see setFilenameBase
170 xml_reporter(const std::string & _filename = "")
173 setFilenameBase(_filename);
177 * \brief Sets filename base for output
178 * @param _filename filename base
181 * xml_reporter reporter;
182 * reporter.setFilenameBase("my_xml");
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.
187 virtual void setFilenameBase(const std::string & _filename)
191 filename = "testResult";
195 if (_filename.length() > 200)
197 throw(std::runtime_error("Filename too long!"));
200 filename = _filename;
205 * \brief Callback function
206 * This function is called before the first test is executed. It initializes counters.
208 virtual void run_started()
214 * \brief Callback function
215 * This function is called when test completes. Counters are updated here, and test results stored.
217 virtual void test_completed(const tut::test_result& tr)
219 // update global statistics
221 case test_result::ok:
224 case test_result::fail:
225 case test_result::rethrown:
228 case test_result::ex:
229 case test_result::ex_ctor:
232 case test_result::warn:
235 case test_result::term:
236 terminations_count++;
240 // add test result to results table
241 (all_tests[tr.group]).push_back(tr);
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.
249 virtual void run_completed()
254 static int number = 1; // results file sequence number (testResult_<number>.xml)
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
267 /* generate output filename */
269 sprintf(fn, "%s_%d.xml", filename.c_str(), number++);
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"));
277 /* *********************** header ***************************** */
278 xmlfile << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << endl;
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;
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
290 switch ((*tri).result) {
291 case test_result::ok:
294 case test_result::fail:
295 failure_type = "Assertion";
299 case test_result::ex:
300 failure_type = "Assertion";
301 failure_msg = "Thrown exception: " + (*tri).exception_typeid + '\n';
304 case test_result::warn:
305 failure_type = "Assertion";
306 failure_msg = "Destructor failed.\n";
309 case test_result::term:
310 failure_type = "Error";
311 failure_msg = "Test application terminated abnormally.\n";
314 case test_result::ex_ctor:
315 failure_type = "Assertion";
316 failure_msg = "Constructor has thrown an exception: " + (*tri).exception_typeid + '\n';
319 case test_result::rethrown:
320 failure_type = "Assertion";
321 failure_msg = "Child failed";
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";
332 #if defined(TUT_USE_POSIX)
333 out << xml_build_testcase(*tri, failure_type, failure_msg, (*tri).pid) << endl;
335 out << xml_build_testcase(*tri, failure_type, failure_msg) << endl;
338 } // iterate over all test cases
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;
345 xmlfile << xml_build_testsuite(stat_errors, stat_failures, stat_all, (*tgi).first/* name */, out.str()/* testcases */) << endl;
347 } // iterate over all test groups
351 * \brief Returns true, if all tests passed
353 virtual bool all_ok() const
355 return ( (terminations_count + failures_count + warnings_count + exceptions_count) == 0);