1 #ifndef TUT_XML_REPORTER
2 #define TUT_XML_REPORTER
3 #include <tut/tut_config.hpp>
5 #include <tut/tut_cppunit_reporter.hpp>
16 * \brief JUnit XML TUT reporter
17 * @author Lukasz Maszczynski, NSN
20 class xml_reporter : public tut::callback
22 typedef std::vector<tut::test_result> TestResults;
23 typedef std::map<std::string, TestResults> TestGroups;
25 TestGroups all_tests_; /// holds all test results
26 const std::string filename_; /// filename base
27 std::auto_ptr<std::ostream> stream_;
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
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)
43 std::ostringstream out;
45 if ( (tr.result == test_result::ok) ||
46 (tr.result == test_result::skipped) )
48 out << " <testcase classname=\"" << cppunit_reporter::encode(tr.group) << "\" name=\"" << cppunit_reporter::encode(tr.name) << "\"/>";
52 string err_msg = cppunit_reporter::encode(failure_msg + tr.message);
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 )
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)
70 out << " child=\"" << pid << "\"";
75 out << ">" << err_msg << "</" << tag << ">" << endl;
76 out << " </testcase>";
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
92 std::string xml_build_testsuite(int errors, int failures, int total,
93 const std::string & name, const std::string & testcases)
95 std::ostringstream out;
97 out << " <testsuite errors=\"" << errors << "\" failures=\"" << failures << "\" tests=\"" << total << "\" name=\"" << cppunit_reporter::encode(name) << "\">" << std::endl;
99 out << " </testsuite>";
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
112 * \brief Default constructor
113 * @param filename base filename
115 xml_reporter(const std::string & filename)
118 stream_(new std::ofstream(filename_.c_str())),
122 terminations_count(0),
125 if (!stream_->good()) {
126 throw tut_error("Cannot open output file `" + filename_ + "`");
130 xml_reporter(std::ostream & stream)
137 terminations_count(0),
144 if(filename_.empty())
151 * \brief Callback function
152 * This function is called before the first test is executed. It initializes counters.
154 virtual void run_started()
157 exceptions_count = 0;
159 terminations_count = 0;
165 * \brief Callback function
166 * This function is called when test completes. Counters are updated here, and test results stored.
168 virtual void test_completed(const tut::test_result& tr)
170 // update global statistics
172 case test_result::ok:
173 case test_result::skipped:
176 case test_result::fail:
177 case test_result::rethrown:
180 case test_result::ex:
181 case test_result::ex_ctor:
184 case test_result::warn:
187 case test_result::term:
188 terminations_count++;
190 case tut::test_result::dummy:
191 assert(!"Should never be called");
194 // add test result to results table
195 all_tests_[tr.group].push_back(tr);
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.
203 virtual void run_completed()
205 /* *********************** header ***************************** */
206 *stream_ << "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" << std::endl;
207 *stream_ << "<testsuites>" << std::endl;
209 // iterate over all test groups
210 for (TestGroups::const_iterator tgi = all_tests_.begin(); tgi != all_tests_.end(); ++tgi)
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
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;
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)
229 std::string failure_type; // string describing the failure type
230 std::string failure_msg; // a string with failure message
234 case test_result::ok:
235 case test_result::skipped:
238 case test_result::fail:
239 failure_type = "Assertion";
243 case test_result::ex:
244 failure_type = "Assertion";
245 failure_msg = "Thrown exception: " + tri->exception_typeid + '\n';
248 case test_result::warn:
249 failure_type = "Assertion";
250 failure_msg = "Destructor failed.\n";
253 case test_result::term:
254 failure_type = "Error";
255 failure_msg = "Test application terminated abnormally.\n";
258 case test_result::ex_ctor:
259 failure_type = "Assertion";
260 failure_msg = "Constructor has thrown an exception: " + tri->exception_typeid + ".\n";
263 case test_result::rethrown:
264 failure_type = "Assertion";
265 failure_msg = "Child failed.\n";
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";
276 #if defined(TUT_USE_POSIX)
277 out << xml_build_testcase(*tri, failure_type, failure_msg, tri->pid) << std::endl;
279 out << xml_build_testcase(*tri, failure_type, failure_msg) << std::endl;
281 } // iterate over all test cases
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;
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
291 *stream_ << "</testsuites>" << std::endl;
295 * \brief Returns true, if all tests passed
297 virtual bool all_ok() const
299 return ( (terminations_count + failures_count + warnings_count + exceptions_count) == 0);