/* * tut_xml_reporter.hpp * * ECOS Library. IPT CS R&D CET ECOS Copyright 2008 Nokia * Siemens Networks. All right * * */ #ifndef TUT_XML_REPORTER #define TUT_XML_REPORTER #include #include #include #include #include namespace tut { /** * \brief JUnit XML TUT reporter * @author Lukasz Maszczynski, NSN * @date 11/07/2008 */ class xml_reporter : public tut::callback { protected: typedef std::vector TestResults; typedef std::map TestGroups; TestGroups all_tests; /// holds all test results std::string filename; /// filename base /** * \brief Initializes object * Resets counters and clears all stored test results. */ virtual void init() { ok_count = 0; exceptions_count = 0; failures_count = 0; terminations_count = 0; warnings_count = 0; all_tests.clear(); } /** * \brief Encodes text to XML * XML-reserved characters (e.g. "<") are encoded according to specification * @param text text to be encoded * @return encoded string */ virtual std::string encode(const std::string & text) { std::string out; for (unsigned int i=0; i': out += ">"; break; case '&': out += "&"; break; case '\'': out += "'"; break; case '"': out += """; break; default: out += c; } } return out; } /** * \brief Builds "testcase" XML entity with given parameters * Builds \ entity according to given parameters. \-s are part of \. * @param tr test result to be used as source data * @param failure_type type of failure to be reported ("Assertion" or "Error", empty if test passed) * @param failure_msg failure message to be reported (empty, if test passed) * @return string with \ entity */ virtual std::string xml_build_testcase(const tut::test_result & tr, const std::string & failure_type, const std::string & failure_msg, int pid = 0) { using std::endl; using std::string; std::ostringstream out; if (tr.result == test_result::ok) { out << " "; } else { string err_msg = encode(failure_msg + tr.message); string tag; // determines tag name: "failure" or "error" if ( tr.result == test_result::fail || tr.result == test_result::warn || tr.result == test_result::ex || tr.result == test_result::ex_ctor ) { tag = "failure"; } else { tag = "error"; } out << " " << endl; out << " <" << tag << " message=\"" << err_msg << "\"" << " type=\"" << failure_type << "\""; #if defined(TUT_USE_POSIX) if(pid != getpid()) { out << " child=\"" << pid << "\""; } #endif out << ">" << err_msg << "" << endl; out << " "; } return out.str(); } /** * \brief Builds "testsuite" XML entity * Builds \ XML entity according to given parameters. * @param errors number of errors to be reported * @param failures number of failures to be reported * @param total total number of tests to be reported * @param name test suite name * @param testcases encoded XML string containing testcases * @return string with \ entity */ virtual std::string xml_build_testsuite(int errors, int failures, int total, const std::string & name, const std::string & testcases) { std::ostringstream out; out << "" << std::endl; out << testcases; out << ""; return out.str(); } public: int ok_count; /// number of passed tests int exceptions_count; /// number of tests that threw exceptions int failures_count; /// number of tests that failed int terminations_count; /// number of tests that would terminate int warnings_count; /// number of tests where destructors threw an exception /** * \brief Default constructor * @param filename base filename * @see setFilenameBase */ xml_reporter(const std::string & _filename = "") { init(); setFilenameBase(_filename); } /** * \brief Sets filename base for output * @param _filename filename base * Example usage: * @code * xml_reporter reporter; * reporter.setFilenameBase("my_xml"); * @endcode * The above code will instruct reporter to create my_xml_1.xml file for the first test group, * my_xml_2.xml file for the second, and so on. */ virtual void setFilenameBase(const std::string & _filename) { if (_filename == "") { filename = "testResult"; } else { if (_filename.length() > 200) { throw(std::runtime_error("Filename too long!")); } filename = _filename; } } /** * \brief Callback function * This function is called before the first test is executed. It initializes counters. */ virtual void run_started() { init(); } /** * \brief Callback function * This function is called when test completes. Counters are updated here, and test results stored. */ virtual void test_completed(const tut::test_result& tr) { // update global statistics switch (tr.result) { case test_result::ok: ok_count++; break; case test_result::fail: case test_result::rethrown: failures_count++; break; case test_result::ex: case test_result::ex_ctor: exceptions_count++; break; case test_result::warn: warnings_count++; break; case test_result::term: terminations_count++; break; } // switch // add test result to results table (all_tests[tr.group]).push_back(tr); } /** * \brief Callback function * This function is called when all tests are completed. It generates XML output * to file(s). File name base can be set with \ref setFilenameBase. */ virtual void run_completed() { using std::endl; using std::string; static int number = 1; // results file sequence number (testResult_.xml) // iterate over all test groups TestGroups::const_iterator tgi; for (tgi = all_tests.begin(); tgi != all_tests.end(); ++tgi) { /* per-group statistics */ int passed = 0; // passed in single group int exceptions = 0; // exceptions in single group int failures = 0; // failures in single group int terminations = 0; // terminations in single group int warnings = 0; // warnings in single group int errors = 0; // errors in single group /* generate output filename */ char fn[256]; sprintf(fn, "%s_%d.xml", filename.c_str(), number++); std::ofstream xmlfile; xmlfile.open(fn, std::ios::in | std::ios::trunc); if (!xmlfile.is_open()) { throw (std::runtime_error("Cannot open file for output")); } /* *********************** header ***************************** */ xmlfile << "" << endl; // output is written to string stream buffer, because JUnit format tag // contains statistics, which aren't known yet std::ostringstream out; // iterate over all test cases in the current test group TestResults::const_iterator tri; for (tri = (*tgi).second.begin(); tri != (*tgi).second.end(); ++tri) { string failure_type; // string describing the failure type string failure_msg; // a string with failure message switch ((*tri).result) { case test_result::ok: passed++; break; case test_result::fail: failure_type = "Assertion"; failure_msg = ""; failures++; break; case test_result::ex: failure_type = "Assertion"; failure_msg = "Thrown exception: " + (*tri).exception_typeid + '\n'; exceptions++; break; case test_result::warn: failure_type = "Assertion"; failure_msg = "Destructor failed.\n"; warnings++; break; case test_result::term: failure_type = "Error"; failure_msg = "Test application terminated abnormally.\n"; terminations++; break; case test_result::ex_ctor: failure_type = "Assertion"; failure_msg = "Constructor has thrown an exception: " + (*tri).exception_typeid + '\n'; exceptions++; break; case test_result::rethrown: failure_type = "Assertion"; failure_msg = "Child failed"; failures++; break; default: failure_type = "Error"; failure_msg = "Unknown test status, this should have never happened. " "You may just have found a BUG in TUT XML reporter, please report it immediately.\n"; errors++; break; } // switch #if defined(TUT_USE_POSIX) out << xml_build_testcase(*tri, failure_type, failure_msg, (*tri).pid) << endl; #else out << xml_build_testcase(*tri, failure_type, failure_msg) << endl; #endif } // iterate over all test cases // calculate per-group statistics int stat_errors = terminations + errors; int stat_failures = failures + warnings + exceptions; int stat_all = stat_errors + stat_failures + passed; xmlfile << xml_build_testsuite(stat_errors, stat_failures, stat_all, (*tgi).first/* name */, out.str()/* testcases */) << endl; xmlfile.close(); } // iterate over all test groups } /** * \brief Returns true, if all tests passed */ virtual bool all_ok() const { return ( (terminations_count + failures_count + warnings_count + exceptions_count) == 0); }; }; } #endif