1 // (C) Copyright 2016 Raffi Enficiaud.
2 // Distributed under the Boost Software License, Version 1.0.
3 // (See accompanying file LICENSE_1_0.txt or copy at
4 // http://www.boost.org/LICENSE_1_0.txt)
6 // See http://www.boost.org/libs/test for the library home page.
9 ///@brief Contains the implementatoin of the Junit log formatter (OF_JUNIT)
10 // ***************************************************************************
12 #ifndef BOOST_TEST_JUNIT_LOG_FORMATTER_IPP__
13 #define BOOST_TEST_JUNIT_LOG_FORMATTER_IPP__
16 #include <boost/test/output/junit_log_formatter.hpp>
17 #include <boost/test/execution_monitor.hpp>
18 #include <boost/test/framework.hpp>
19 #include <boost/test/tree/test_unit.hpp>
20 #include <boost/test/utils/basic_cstring/io.hpp>
21 #include <boost/test/utils/xml_printer.hpp>
22 #include <boost/test/utils/string_cast.hpp>
23 #include <boost/test/framework.hpp>
25 #include <boost/test/tree/visitor.hpp>
26 #include <boost/test/tree/test_case_counter.hpp>
27 #include <boost/test/tree/traverse.hpp>
28 #include <boost/test/results_collector.hpp>
30 #include <boost/test/utils/algorithm.hpp>
31 #include <boost/test/utils/string_cast.hpp>
33 //#include <boost/test/results_reporter.hpp>
37 #include <boost/version.hpp>
44 #include <boost/test/detail/suppress_warnings.hpp>
47 //____________________________________________________________________________//
54 struct s_replace_chars {
56 void operator()(T& to_replace)
60 else if(to_replace == ' ')
65 inline std::string tu_name_normalize(std::string full_name)
67 // maybe directly using normalize_test_case_name instead?
68 std::for_each(full_name.begin(), full_name.end(), s_replace_chars());
72 const_string file_basename(const_string filename) {
74 const_string path_sep( "\\/" );
75 const_string::iterator it = unit_test::utils::find_last_of( filename.begin(), filename.end(),
76 path_sep.begin(), path_sep.end() );
77 if( it != filename.end() )
78 filename.trim_left( it + 1 );
84 // ************************************************************************** //
85 // ************** junit_log_formatter ************** //
86 // ************************************************************************** //
89 junit_log_formatter::log_start( std::ostream& ostr, counter_t test_cases_amount)
92 list_path_to_root.clear();
93 root_id = INV_TEST_UNIT_ID;
96 //____________________________________________________________________________//
98 class junit_result_helper : public test_tree_visitor {
100 explicit junit_result_helper(
101 std::ostream& stream,
103 junit_log_formatter::map_trace_t const& mt,
104 bool display_build_info )
109 , m_display_build_info(display_build_info)
112 void add_log_entry(std::string const& entry_type,
114 junit_impl::junit_log_helper::assertion_entry const& log) const
118 << " message" << utils::attr_value() << log.logentry_message
119 << " type" << utils::attr_value() << log.logentry_type
122 if(!log.output.empty()) {
123 m_stream << utils::cdata() << "\n" + log.output;
126 m_stream << "</" << entry_type << ">";
129 void visit( test_case const& tc )
131 test_results const& tr = results_collector.results( tc.p_id );
133 junit_impl::junit_log_helper detailed_log;
134 bool need_skipping_reason = false;
135 bool skipped = false;
137 junit_log_formatter::map_trace_t::const_iterator it_element(m_map_test.find(tc.p_id));
138 if( it_element != m_map_test.end() )
140 detailed_log = it_element->second;
144 need_skipping_reason = true;
147 std::string classname;
148 test_unit_id id(tc.p_parent_id);
149 while( id != m_ts.p_id ) {
150 test_unit const& tu = boost::unit_test::framework::get( id, TUT_ANY );
152 if(need_skipping_reason)
154 test_results const& tr_parent = results_collector.results( id );
155 if( tr_parent.p_skipped )
158 detailed_log.system_out+= "- disabled: " + tu.full_name() + "\n";
160 junit_log_formatter::map_trace_t::const_iterator it_element_stack(m_map_test.find(id));
161 if( it_element_stack != m_map_test.end() )
163 detailed_log.system_out+= "- skipping decision: '" + it_element_stack->second.system_out + "'";
164 detailed_log.system_out = "SKIPPING decision stack:\n" + detailed_log.system_out;
165 need_skipping_reason = false;
169 classname = tu_name_normalize(tu.p_name) + "." + classname;
173 // removes the trailing dot
174 if(!classname.empty() && *classname.rbegin() == '.') {
175 classname.erase(classname.size()-1);
181 // total number of assertions
182 m_stream << "<testcase assertions" << utils::attr_value() << tr.p_assertions_passed + tr.p_assertions_failed;
185 if(!classname.empty())
186 m_stream << " classname" << utils::attr_value() << classname;
188 // test case name and time taken
190 << " name" << utils::attr_value() << tu_name_normalize(tc.p_name)
191 << " time" << utils::attr_value() << double(tr.p_duration_microseconds) * 1E-6
194 if( tr.p_skipped || skipped ) {
195 m_stream << "<skipped/>" << std::endl;
199 for(std::vector< junit_impl::junit_log_helper::assertion_entry >::const_iterator it(detailed_log.assertion_entries.begin());
200 it != detailed_log.assertion_entries.end();
203 if(it->log_entry == junit_impl::junit_log_helper::assertion_entry::log_entry_failure) {
204 add_log_entry("failure", tc, *it);
206 else if(it->log_entry == junit_impl::junit_log_helper::assertion_entry::log_entry_error) {
207 add_log_entry("error", tc, *it);
212 // system-out + all info/messages
213 std::string system_out = detailed_log.system_out;
214 for(std::vector< junit_impl::junit_log_helper::assertion_entry >::const_iterator it(detailed_log.assertion_entries.begin());
215 it != detailed_log.assertion_entries.end();
218 if(it->log_entry != junit_impl::junit_log_helper::assertion_entry::log_entry_info)
220 system_out += it->output;
223 if(!system_out.empty()) {
226 << utils::cdata() << system_out
231 // system-err output + test case informations
232 std::string system_err = detailed_log.system_err;
234 // test case information (redundant but useful)
235 std::ostringstream o;
236 o << "Test case:" << std::endl
237 << "- name: " << tc.full_name() << std::endl
238 << "- description: '" << tc.p_description << "'" << std::endl
239 << "- file: " << file_basename(tc.p_file_name) << std::endl
240 << "- line: " << tc.p_line_num << std::endl
242 system_err = o.str() + system_err;
246 << utils::cdata() << system_err
250 m_stream << "</testcase>" << std::endl;
253 bool test_suite_start( test_suite const& ts )
255 // unique test suite, without s, nesting not supported in CI
256 if( m_ts.p_id != ts.p_id )
259 test_results const& tr = results_collector.results( ts.p_id );
261 m_stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
262 m_stream << "<testsuite";
265 // << "disabled=\"" << tr.p_test_cases_skipped << "\" "
266 << " tests" << utils::attr_value() << tr.p_test_cases_passed
267 << " skipped" << utils::attr_value() << tr.p_test_cases_skipped
268 << " errors" << utils::attr_value() << tr.p_test_cases_aborted
269 << " failures" << utils::attr_value() << tr.p_test_cases_failed
270 << " id" << utils::attr_value() << m_id++
271 << " name" << utils::attr_value() << tu_name_normalize(ts.p_name)
272 << " time" << utils::attr_value() << (tr.p_duration_microseconds * 1E-6)
275 if(m_display_build_info)
277 m_stream << "<properties>" << std::endl;
278 m_stream << "<property name=\"platform\" value" << utils::attr_value() << BOOST_PLATFORM << std::endl;
279 m_stream << "<property name=\"compiler\" value" << utils::attr_value() << BOOST_COMPILER << std::endl;
280 m_stream << "<property name=\"stl\" value" << utils::attr_value() << BOOST_STDLIB << std::endl;
282 std::ostringstream o;
283 o << BOOST_VERSION/100000 << "." << BOOST_VERSION/100 % 1000 << "." << BOOST_VERSION % 100;
284 m_stream << "<property name=\"boost\" value" << utils::attr_value() << o.str() << std::endl;
285 m_stream << "</properties>" << std::endl;
288 return true; // indicates that the children should also be parsed
291 virtual void test_suite_finish( test_suite const& ts )
293 if( m_ts.p_id != ts.p_id )
295 m_stream << "</testsuite>";
300 std::ostream& m_stream;
301 test_unit const& m_ts;
302 junit_log_formatter::map_trace_t const& m_map_test;
304 bool m_display_build_info;
310 junit_log_formatter::log_finish( std::ostream& ostr )
312 junit_result_helper ch( ostr, boost::unit_test::framework::get( root_id, TUT_SUITE ), map_tests, m_display_build_info );
313 traverse_test_tree( root_id, ch, true ); // last is to ignore disabled suite special handling
318 //____________________________________________________________________________//
321 junit_log_formatter::log_build_info( std::ostream& ostr )
323 m_display_build_info = true;
326 //____________________________________________________________________________//
329 junit_log_formatter::test_unit_start( std::ostream& ostr, test_unit const& tu )
331 if(list_path_to_root.empty())
333 list_path_to_root.push_back( tu.p_id );
334 map_tests.insert(std::make_pair(tu.p_id, junit_impl::junit_log_helper())); // current_test_case_id not working here
339 //____________________________________________________________________________//
342 junit_log_formatter::test_unit_finish( std::ostream& ostr, test_unit const& tu, unsigned long elapsed )
344 // the time is already stored in the result_reporter
345 assert( tu.p_id == list_path_to_root.back() );
346 list_path_to_root.pop_back();
350 junit_log_formatter::test_unit_aborted( std::ostream& os, test_unit const& tu )
352 assert( tu.p_id == list_path_to_root.back() );
353 //list_path_to_root.pop_back();
356 //____________________________________________________________________________//
359 junit_log_formatter::test_unit_skipped( std::ostream& ostr, test_unit const& tu, const_string reason )
361 if(tu.p_type == TUT_CASE)
363 junit_impl::junit_log_helper& v = map_tests[tu.p_id];
364 v.system_out.assign(reason.begin(), reason.end());
368 junit_impl::junit_log_helper& v = map_tests[tu.p_id];
369 v.system_out.assign(reason.begin(), reason.end());
373 //____________________________________________________________________________//
376 junit_log_formatter::log_exception_start( std::ostream& ostr, log_checkpoint_data const& checkpoint_data, execution_exception const& ex )
378 std::ostringstream o;
379 execution_exception::location const& loc = ex.where();
381 m_is_last_assertion_or_error = false;
383 if(!list_path_to_root.empty())
385 junit_impl::junit_log_helper& last_entry = map_tests[list_path_to_root.back()];
387 junit_impl::junit_log_helper::assertion_entry entry;
389 entry.logentry_message = "unexpected exception";
390 entry.log_entry = junit_impl::junit_log_helper::assertion_entry::log_entry_error;
394 case execution_exception::cpp_exception_error:
395 entry.logentry_type = "uncaught exception";
397 case execution_exception::timeout_error:
398 entry.logentry_type = "execution timeout";
400 case execution_exception::user_error:
401 entry.logentry_type = "user, assert() or CRT error";
403 case execution_exception::user_fatal_error:
404 // Looks like never used
405 entry.logentry_type = "user fatal error";
407 case execution_exception::system_error:
408 entry.logentry_type = "system error";
410 case execution_exception::system_fatal_error:
411 entry.logentry_type = "system fatal error";
414 entry.logentry_type = "no error"; // not sure how to handle this one
418 o << "UNCAUGHT EXCEPTION:" << std::endl;
419 if( !loc.m_function.is_empty() )
420 o << "- function: \"" << loc.m_function << "\"" << std::endl;
422 o << "- file: " << file_basename(loc.m_file_name) << std::endl
423 << "- line: " << loc.m_line_num << std::endl
426 o << "\nEXCEPTION STACK TRACE: --------------\n" << ex.what()
427 << "\n-------------------------------------";
429 if( !checkpoint_data.m_file_name.is_empty() ) {
430 o << std::endl << std::endl
431 << "Last checkpoint:" << std::endl
432 << "- message: \"" << checkpoint_data.m_message << "\"" << std::endl
433 << "- file: " << file_basename(checkpoint_data.m_file_name) << std::endl
434 << "- line: " << checkpoint_data.m_line_num << std::endl
438 entry.output = o.str();
440 last_entry.assertion_entries.push_back(entry);
443 // check what to do with this one
446 //____________________________________________________________________________//
449 junit_log_formatter::log_exception_finish( std::ostream& ostr )
451 // sealing the last entry
452 assert(!map_tests[list_path_to_root.back()].assertion_entries.back().sealed);
453 map_tests[list_path_to_root.back()].assertion_entries.back().sealed = true;
456 //____________________________________________________________________________//
459 junit_log_formatter::log_entry_start( std::ostream& ostr, log_entry_data const& entry_data, log_entry_types let )
461 junit_impl::junit_log_helper& last_entry = map_tests[list_path_to_root.back()];
462 m_is_last_assertion_or_error = true;
465 case unit_test_log_formatter::BOOST_UTL_ET_INFO:
466 case unit_test_log_formatter::BOOST_UTL_ET_MESSAGE:
467 case unit_test_log_formatter::BOOST_UTL_ET_WARNING:
469 std::ostringstream o;
471 junit_impl::junit_log_helper::assertion_entry entry;
472 entry.log_entry = junit_impl::junit_log_helper::assertion_entry::log_entry_info;
473 entry.logentry_message = "info";
474 entry.logentry_type = "message";
476 o << (let == unit_test_log_formatter::BOOST_UTL_ET_WARNING ?
477 "WARNING:" : (let == unit_test_log_formatter::BOOST_UTL_ET_MESSAGE ?
478 "MESSAGE:" : "INFO:"))
480 << "- file : " << file_basename(entry_data.m_file_name) << std::endl
481 << "- line : " << entry_data.m_line_num << std::endl
482 << "- message: "; // no CR
484 entry.output += o.str();
485 last_entry.assertion_entries.push_back(entry);
489 case unit_test_log_formatter::BOOST_UTL_ET_ERROR:
490 case unit_test_log_formatter::BOOST_UTL_ET_FATAL_ERROR:
492 std::ostringstream o;
493 junit_impl::junit_log_helper::assertion_entry entry;
494 entry.log_entry = junit_impl::junit_log_helper::assertion_entry::log_entry_failure;
495 entry.logentry_message = "failure";
496 entry.logentry_type = (let == unit_test_log_formatter::BOOST_UTL_ET_ERROR ? "assertion error" : "fatal error");
498 o << "ASSERTION FAILURE:" << std::endl
499 << "- file : " << file_basename(entry_data.m_file_name) << std::endl
500 << "- line : " << entry_data.m_line_num << std::endl
501 << "- message: " ; // no CR
503 entry.output += o.str();
504 last_entry.assertion_entries.push_back(entry);
511 //____________________________________________________________________________//
515 //____________________________________________________________________________//
518 junit_log_formatter::log_entry_value( std::ostream& ostr, const_string value )
520 assert(map_tests[list_path_to_root.back()].assertion_entries.empty() || !map_tests[list_path_to_root.back()].assertion_entries.back().sealed);
521 junit_impl::junit_log_helper& last_entry = map_tests[list_path_to_root.back()];
522 std::ostringstream o;
523 utils::print_escaped_cdata( o, value );
525 if(!last_entry.assertion_entries.empty())
527 junit_impl::junit_log_helper::assertion_entry& log_entry = last_entry.assertion_entries.back();
528 log_entry.output += value;
532 // this may be a message coming from another observer
533 // the prefix is set in the log_entry_start
534 last_entry.system_out += value;
538 //____________________________________________________________________________//
541 junit_log_formatter::log_entry_finish( std::ostream& ostr )
543 assert(map_tests[list_path_to_root.back()].assertion_entries.empty() || !map_tests[list_path_to_root.back()].assertion_entries.back().sealed);
544 junit_impl::junit_log_helper& last_entry = map_tests[list_path_to_root.back()];
545 if(!last_entry.assertion_entries.empty()) {
546 junit_impl::junit_log_helper::assertion_entry& log_entry = last_entry.assertion_entries.back();
547 log_entry.output += "\n\n"; // quote end, CR
548 log_entry.sealed = true;
551 last_entry.system_out += "\n\n"; // quote end, CR
555 //____________________________________________________________________________//
558 junit_log_formatter::entry_context_start( std::ostream& ostr, log_level )
560 std::vector< junit_impl::junit_log_helper::assertion_entry > &v_failure_or_error = map_tests[list_path_to_root.back()].assertion_entries;
561 assert(!v_failure_or_error.back().sealed);
563 if(m_is_last_assertion_or_error)
565 v_failure_or_error.back().output += "\n- context:\n";
569 v_failure_or_error.back().output += "\n\nCONTEXT:\n";
573 //____________________________________________________________________________//
576 junit_log_formatter::entry_context_finish( std::ostream& ostr )
578 // no op, may be removed
579 assert(!map_tests[list_path_to_root.back()].assertion_entries.back().sealed);
582 //____________________________________________________________________________//
585 junit_log_formatter::log_entry_context( std::ostream& ostr, const_string context_descr )
587 assert(!map_tests[list_path_to_root.back()].assertion_entries.back().sealed);
588 map_tests[list_path_to_root.back()].assertion_entries.back().output += (m_is_last_assertion_or_error ? " - '": "- '") + std::string(context_descr.begin(), context_descr.end()) + "'\n"; // quote end
591 //____________________________________________________________________________//
595 junit_log_formatter::get_default_stream_description() const {
596 std::string name = framework::master_test_suite().p_name.value;
598 static const std::string to_replace[] = { " ", "\"", "/", "\\", ":"};
599 static const std::string replacement[] = { "_", "_" , "_", "_" , "_"};
601 name = unit_test::utils::replace_all_occurrences_of(
603 to_replace, to_replace + sizeof(to_replace)/sizeof(to_replace[0]),
604 replacement, replacement + sizeof(replacement)/sizeof(replacement[0]));
606 std::ifstream check_init((name + ".xml").c_str());
608 return name + ".xml";
611 for(; index < 100; index++) {
612 std::string candidate = name + "_" + utils::string_cast(index) + ".xml";
613 std::ifstream file(candidate.c_str());
618 return name + ".xml";
621 } // namespace output
622 } // namespace unit_test
625 #include <boost/test/detail/enable_warnings.hpp>
627 #endif // BOOST_TEST_junit_log_formatter_IPP_020105GER