diff options
Diffstat (limited to 'boost/test')
30 files changed, 2042 insertions, 369 deletions
diff --git a/boost/test/data/for_each_sample.hpp b/boost/test/data/for_each_sample.hpp index b3bc1ffc78..4785b038cc 100644 --- a/boost/test/data/for_each_sample.hpp +++ b/boost/test/data/for_each_sample.hpp @@ -63,7 +63,9 @@ invoke_action( Action const& action, T&& args, std::true_type /* is_tuple */ ) { invoke_action_impl( action, std::forward<T>(args), - typename make_index_sequence< 0, std::tuple_size<T>::value >::type{} ); + typename make_index_sequence< 0, + std::tuple_size<typename std::decay<T>::type>::value + >::type{} ); } diff --git a/boost/test/data/test_case.hpp b/boost/test/data/test_case.hpp index 9564816ee4..dd9de141c4 100644 --- a/boost/test/data/test_case.hpp +++ b/boost/test/data/test_case.hpp @@ -37,6 +37,7 @@ #include <boost/test/detail/suppress_warnings.hpp> #include <boost/test/tools/detail/print_helper.hpp> +#include <boost/test/utils/string_cast.hpp> #if defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) \ && !defined(BOOST_TEST_DATASET_MAX_ARITY) @@ -122,16 +123,25 @@ public: #ifndef BOOST_NO_CXX11_RVALUE_REFERENCES test_case_gen( const_string tc_name, const_string tc_file, std::size_t tc_line, DataSet&& ds ) : m_tc_name( ut_detail::normalize_test_case_name( tc_name ) ) + , m_tc_file( tc_file ) + , m_tc_line( tc_line ) + , m_tc_index( 0 ) { data::for_each_sample( std::forward<DataSet>( ds ), *this ); } test_case_gen( test_case_gen&& gen ) : m_tc_name( gen.m_tc_name ) + , m_tc_file( gen.m_tc_file ) + , m_tc_line( gen.m_tc_line ) + , m_tc_index( gen.m_tc_index ) , m_test_cases( std::move(gen.m_test_cases) ) {} #else test_case_gen( const_string tc_name, const_string tc_file, std::size_t tc_line, DataSet const& ds ) : m_tc_name( ut_detail::normalize_test_case_name( tc_name ) ) + , m_tc_file( tc_file ) + , m_tc_line( tc_line ) + , m_tc_index( 0 ) { data::for_each_sample( ds, *this ); } @@ -149,24 +159,23 @@ public: } #if !defined(BOOST_TEST_DATASET_VARIADIC) - /// make this variadic -#define TC_MAKE(z,arity,_) \ - template<BOOST_PP_ENUM_PARAMS(arity, typename Arg)> \ - void operator()( BOOST_PP_ENUM_BINARY_PARAMS(arity, Arg, const& arg) ) const \ - { \ - m_test_cases.push_back( new test_case( m_tc_name, m_tc_file, m_tc_line, \ - boost::bind( &TestCase::template test_method<BOOST_PP_ENUM_PARAMS(arity,Arg)>, \ - BOOST_PP_ENUM_PARAMS(arity, arg) ) ) ); \ - } \ + // see BOOST_TEST_DATASET_MAX_ARITY to increase the default supported arity +#define TC_MAKE(z,arity,_) \ + template<BOOST_PP_ENUM_PARAMS(arity, typename Arg)> \ + void operator()( BOOST_PP_ENUM_BINARY_PARAMS(arity, Arg, const& arg) ) const \ + { \ + m_test_cases.push_back( new test_case( genTestCaseName(), m_tc_file, m_tc_line, \ + boost::bind( &TestCase::template test_method<BOOST_PP_ENUM_PARAMS(arity,Arg)>,\ + BOOST_PP_ENUM_PARAMS(arity, arg) ) ) ); \ + } \ BOOST_PP_REPEAT_FROM_TO(1, BOOST_TEST_DATASET_MAX_ARITY, TC_MAKE, _) #else - template<typename ...Arg> void operator()(Arg&& ... arg) const { m_test_cases.push_back( - new test_case( m_tc_name, + new test_case( genTestCaseName(), m_tc_file, m_tc_line, boost::bind( &TestCase::template test_method<Arg...>, @@ -175,10 +184,16 @@ public: #endif private: + std::string genTestCaseName() const + { + return "_" + utils::string_cast(m_tc_index++); + } + // Data members std::string m_tc_name; const_string m_tc_file; std::size_t m_tc_line; + mutable std::size_t m_tc_index; mutable std::list<test_unit*> m_test_cases; }; @@ -217,16 +232,16 @@ make_test_case_gen( const_string tc_name, const_string tc_file, std::size_t tc_l /**/ #define BOOST_DATA_TEST_CASE_IMPL(arity, F, test_name, dataset, params) \ -struct test_name : public F { \ +struct BOOST_PP_CAT(test_name, case) : public F { \ template<BOOST_PP_ENUM_PARAMS(arity, typename Arg)> \ static void test_method( BOOST_DATA_TEST_CASE_PARAMS( params ) ) \ { \ BOOST_TEST_CHECKPOINT('"' << #test_name << "\" fixture entry.");\ - test_name t; \ + BOOST_PP_CAT(test_name, case) t; \ BOOST_TEST_CHECKPOINT('"' << #test_name << "\" entry."); \ BOOST_TEST_CONTEXT( "" \ BOOST_PP_SEQ_FOR_EACH(BOOST_DATA_TEST_CONTEXT, _, params)) \ - t._impl(BOOST_PP_SEQ_ENUM(params)); \ + t._impl(BOOST_PP_SEQ_ENUM(params)); \ BOOST_TEST_CHECKPOINT('"' << #test_name << "\" exit."); \ } \ private: \ @@ -234,15 +249,21 @@ private: \ void _impl(BOOST_DATA_TEST_CASE_PARAMS( params )); \ }; \ \ -BOOST_AUTO_TU_REGISTRAR( test_name )( \ - boost::unit_test::data::ds_detail::make_test_case_gen<test_name>( \ +BOOST_AUTO_TEST_SUITE( test_name ) \ + \ +BOOST_AUTO_TU_REGISTRAR( BOOST_PP_CAT(test_name, case) )( \ + boost::unit_test::data::ds_detail::make_test_case_gen< \ + BOOST_PP_CAT(test_name, case)>( \ BOOST_STRINGIZE( test_name ), \ __FILE__, __LINE__, \ boost::unit_test::data::ds_detail::seed{} ->* dataset ), \ boost::unit_test::decorator::collector::instance() ); \ \ +BOOST_AUTO_TEST_SUITE_END() \ + \ template<BOOST_PP_ENUM_PARAMS(arity, typename Arg)> \ - void test_name::_impl( BOOST_DATA_TEST_CASE_PARAMS( params ) ) \ + void BOOST_PP_CAT(test_name, case)::_impl( \ + BOOST_DATA_TEST_CASE_PARAMS( params ) ) \ /**/ #define BOOST_DATA_TEST_CASE_WITH_PARAMS( F, test_name, dataset, ... ) \ diff --git a/boost/test/detail/global_typedef.hpp b/boost/test/detail/global_typedef.hpp index 9b7653c078..b9cfeb5206 100644 --- a/boost/test/detail/global_typedef.hpp +++ b/boost/test/detail/global_typedef.hpp @@ -34,10 +34,13 @@ enum report_level { INV_REPORT_LEVEL, CONFIRMATION_REPORT, SHORT_REPORT, DETAIL //____________________________________________________________________________// +//! Indicates the output format for the loggers or the test tree printing enum output_format { OF_INVALID, - OF_CLF, ///< compiler log format - OF_XML, ///< XML format for report and log, - OF_DOT ///< dot format for output content + OF_CLF, ///< compiler log format + OF_XML, ///< XML format for report and log, + OF_JUNIT, ///< JUNIT format for report and log, + OF_CUSTOM_LOGGER, ///< User specified logger. + OF_DOT ///< dot format for output content }; //____________________________________________________________________________// diff --git a/boost/test/detail/throw_exception.hpp b/boost/test/detail/throw_exception.hpp index 2fee10c2cb..1568ec3c29 100644 --- a/boost/test/detail/throw_exception.hpp +++ b/boost/test/detail/throw_exception.hpp @@ -57,7 +57,6 @@ throw_exception(E const& e) { throw e; } //____________________________________________________________________________// #define BOOST_TEST_I_THROW( E ) unit_test::ut_detail::throw_exception( E ) -#define BOOST_TEST_I_THROW( E ) unit_test::ut_detail::throw_exception( E ) #define BOOST_TEST_I_ASSRT( cond, ex ) if( cond ) {} else BOOST_TEST_I_THROW( ex ) diff --git a/boost/test/execution_monitor.hpp b/boost/test/execution_monitor.hpp index c9036be5ba..3a203d15f9 100644 --- a/boost/test/execution_monitor.hpp +++ b/boost/test/execution_monitor.hpp @@ -66,6 +66,18 @@ #endif + +// Additional macro documentations not being generated without this hack +#ifdef BOOST_TEST_DOXYGEN_DOC__ + +//! Disables the support of the alternative stack +//! during the compilation of the Boost.test framework. This is especially useful +//! in case it is not possible to detect the lack of alternative stack support for +//! your compiler (for instance, ESXi). +#define BOOST_TEST_DISABLE_ALT_STACK + +#endif + //____________________________________________________________________________// namespace boost { @@ -276,7 +288,7 @@ private: }; // execution_exception // ************************************************************************** // -/// Function execution monitor +/// @brief Function execution monitor /// This class is used to uniformly detect and report an occurrence of several types of signals and exceptions, reducing various /// errors to a uniform execution_exception that is returned to a caller. @@ -465,6 +477,7 @@ public: char const* const p_failed_exp; }; +//!@internal #define BOOST_TEST_SYS_ASSERT( cond ) BOOST_TEST_I_ASSRT( cond, ::boost::system_error( BOOST_STRINGIZE( exp ) ) ) // ************************************************************************** // diff --git a/boost/test/framework.hpp b/boost/test/framework.hpp index 1f5189d23c..f94bcf125d 100644 --- a/boost/test/framework.hpp +++ b/boost/test/framework.hpp @@ -236,6 +236,7 @@ BOOST_TEST_DECL void test_unit_aborted( test_unit const& ); namespace impl { // exclusively for self test BOOST_TEST_DECL void setup_for_execution( test_unit const& ); +BOOST_TEST_DECL void setup_loggers( ); } // namespace impl // ************************************************************************** // diff --git a/boost/test/impl/execution_monitor.ipp b/boost/test/impl/execution_monitor.ipp index f7fc8ea4e2..ce6c6c7eb9 100644 --- a/boost/test/impl/execution_monitor.ipp +++ b/boost/test/impl/execution_monitor.ipp @@ -68,6 +68,7 @@ using std::va_list; // to use vsnprintf #if defined(__QNXNTO__) # include <stdio.h> +using std::va_list; #endif #ifdef BOOST_SEH_BASED_SIGNAL_HANDLING @@ -155,8 +156,10 @@ namespace { void _set_se_translator( void* ) {} } # include <android/api-level.h> # endif +// documentation of BOOST_TEST_DISABLE_ALT_STACK in execution_monitor.hpp # if !defined(__CYGWIN__) && !defined(__QNXNTO__) && !defined(__bgq__) && \ - (!defined(__ANDROID__) || __ANDROID_API__ >= 8) + (!defined(__ANDROID__) || __ANDROID_API__ >= 8) && \ + !defined(BOOST_TEST_DISABLE_ALT_STACK) # define BOOST_TEST_USE_ALT_STACK # endif @@ -182,8 +185,8 @@ namespace { void _set_se_translator( void* ) {} } #include <errno.h> #endif -#if defined(__GNUC__) && !defined(BOOST_NO_TYPEID) -# include <cxxabi.h> +#if !defined(BOOST_NO_TYPEID) && !defined(BOOST_NO_RTTI) +# include <boost/core/demangle.hpp> #endif #include <boost/test/detail/suppress_warnings.hpp> @@ -304,23 +307,17 @@ struct fpe_except_guard { unsigned m_previosly_enabled; }; -#ifndef BOOST_NO_TYPEID // ************************************************************************** // // ************** typeid_name ************** // // ************************************************************************** // +#if !defined(BOOST_NO_TYPEID) && !defined(BOOST_NO_RTTI) template<typename T> -char const* +std::string typeid_name( T const& t ) { -#ifdef __GNUC__ - int status; - - return abi::__cxa_demangle( typeid(t).name(), 0, 0, &status ); -#else - return typeid(t).name(); -#endif + return boost::core::demangle(typeid(t).name()); } #endif @@ -605,7 +602,7 @@ system_signal_exception::report() const break; default: - report_error( execution_exception::system_error, + report_error( execution_exception::system_error, "unrecognized signal %d", m_sig_info->si_signo ); } } @@ -1224,7 +1221,7 @@ execution_monitor::execute( boost::function<int ()> const& F ) "std::string: %s", ex.c_str() ); } // std:: exceptions -#ifdef BOOST_NO_TYPEID +#if defined(BOOST_NO_TYPEID) || defined(BOOST_NO_RTTI) #define CATCH_AND_REPORT_STD_EXCEPTION( ex_name ) \ catch( ex_name const& ex ) \ { detail::report_error( execution_exception::cpp_exception_error, \ @@ -1236,7 +1233,7 @@ execution_monitor::execute( boost::function<int ()> const& F ) catch( ex_name const& ex ) \ { detail::report_error( execution_exception::cpp_exception_error, \ current_exception_cast<boost::exception const>(), \ - "%s: %s", detail::typeid_name(ex), ex.what() ); } \ + "%s: %s", detail::typeid_name(ex).c_str(), ex.what() ); } \ /**/ #endif @@ -1266,7 +1263,7 @@ execution_monitor::execute( boost::function<int ()> const& F ) catch( boost::exception const& ex ) { detail::report_error( execution_exception::cpp_exception_error, &ex, -#ifdef BOOST_NO_TYPEID +#if defined(BOOST_NO_TYPEID) || defined(BOOST_NO_RTTI) "unknown boost::exception" ); } #else typeid(ex).name() ); } @@ -1437,4 +1434,3 @@ disable( unsigned mask ) #include <boost/test/detail/enable_warnings.hpp> #endif // BOOST_TEST_EXECUTION_MONITOR_IPP_012205GER - diff --git a/boost/test/impl/framework.ipp b/boost/test/impl/framework.ipp index 78459bac27..a513c612e1 100644 --- a/boost/test/impl/framework.ipp +++ b/boost/test/impl/framework.ipp @@ -22,6 +22,7 @@ #include <boost/test/unit_test_parameters.hpp> #include <boost/test/unit_test_log.hpp> +#include <boost/test/unit_test_log_formatter.hpp> #include <boost/test/unit_test_monitor.hpp> #include <boost/test/results_collector.hpp> #include <boost/test/progress_monitor.hpp> @@ -39,6 +40,7 @@ #include <boost/test/utils/foreach.hpp> #include <boost/test/utils/basic_cstring/io.hpp> +#include <boost/test/utils/basic_cstring/compare.hpp> #include <boost/test/detail/global_typedef.hpp> #include <boost/test/detail/throw_exception.hpp> @@ -53,6 +55,7 @@ #include <set> #include <cstdlib> #include <ctime> +#include <numeric> #ifdef BOOST_NO_STDC_NAMESPACE namespace std { using ::time; using ::srand; } @@ -98,7 +101,7 @@ tu_depth( test_unit_id tu_id, test_unit_id master_tu_id, order_info_per_tu& tuoi return 0; order_info& info = tuoi[tu_id]; - + if( info.depth == -1 ) info.depth = tu_depth( get_tu_parent( tu_id ), master_tu_id, tuoi ) + 1; @@ -236,12 +239,12 @@ public: name_filter( test_unit_id_list& targ_list, const_string filter_expr ) : m_targ_list( targ_list ), m_depth( 0 ) { #ifdef BOOST_TEST_SUPPORT_TOKEN_ITERATOR - utils::string_token_iterator tit( filter_expr, (utils::dropped_delimeters = "/", + utils::string_token_iterator tit( filter_expr, (utils::dropped_delimeters = "/", utils::kept_delimeters = utils::dt_none) ); while( tit != utils::string_token_iterator() ) { - m_components.push_back( - std::vector<component>( utils::string_token_iterator( *tit, (utils::dropped_delimeters = ",", + m_components.push_back( + std::vector<component>( utils::string_token_iterator( *tit, (utils::dropped_delimeters = ",", utils::kept_delimeters = utils::dt_none) ), utils::string_token_iterator() ) ); @@ -261,6 +264,7 @@ private: std::vector<component> const& filters = m_components[m_depth-1]; // look for match + using namespace boost::placeholders; return std::find_if( filters.begin(), filters.end(), bind( &component::pass, _1, boost::ref(tu) ) ) != filters.end(); } @@ -351,8 +355,8 @@ private: if( dep.p_run_status == tu.p_run_status ) continue; - BOOST_TEST_MESSAGE( "Including test " << dep.p_type_name << ' ' << dep.full_name() << - " as a dependency of test " << tu.p_type_name << ' ' << tu.full_name() ); + BOOST_TEST_FRAMEWORK_MESSAGE( "Including test " << dep.p_type_name << ' ' << dep.full_name() << + " as a dependency of test " << tu.p_type_name << ' ' << tu.full_name() ); m_dep_collector->push_back( dep_id ); } @@ -397,22 +401,32 @@ parse_filters( test_unit_id master_tu_id, test_unit_id_list& tu_to_enable, test_ BOOST_TEST_FOREACH( const_string, filter, filters ) { BOOST_TEST_SETUP_ASSERT( !filter.is_empty(), "Invalid filter specification" ); - enum { SELECTOR, ENABLER, DISABLER } filter_type = SELECTOR; + // each --run_test command may also be separated by a ':' (environment variable) + utils::string_token_iterator t_filter_it( filter, (utils::dropped_delimeters = ":", + utils::kept_delimeters = utils::dt_none) ); - // 11. Deduce filter type - if( filter[0] == '!' || filter[0] == '+' ) { - filter_type = filter[0] == '+' ? ENABLER : DISABLER; - filter.trim_left( 1 ); - BOOST_TEST_SETUP_ASSERT( !filter.is_empty(), "Invalid filter specification" ); - } + while( t_filter_it != utils::string_token_iterator() ) { + const_string filter_token = *t_filter_it; + + enum { SELECTOR, ENABLER, DISABLER } filter_type = SELECTOR; + + // 11. Deduce filter type + if( filter_token[0] == '!' || filter_token[0] == '+' ) { + filter_type = filter_token[0] == '+' ? ENABLER : DISABLER; + filter_token.trim_left( 1 ); + BOOST_TEST_SETUP_ASSERT( !filter_token.is_empty(), "Invalid filter specification" ); + } - had_selector_filter |= filter_type == SELECTOR; + had_selector_filter |= filter_type == SELECTOR; - // 12. Add test units to corresponding list - switch( filter_type ) { - case SELECTOR: - case ENABLER: add_filtered_test_units( master_tu_id, filter, tu_to_enable ); break; - case DISABLER: add_filtered_test_units( master_tu_id, filter, tu_to_disable ); break; + // 12. Add test units to corresponding list + switch( filter_type ) { + case SELECTOR: + case ENABLER: add_filtered_test_units( master_tu_id, filter_token, tu_to_enable ); break; + case DISABLER: add_filtered_test_units( master_tu_id, filter_token, tu_to_disable ); break; + } + + ++t_filter_it; } } @@ -437,7 +451,7 @@ public: , m_next_test_suite_id( MIN_TEST_SUITE_ID ) , m_test_in_progress( false ) , m_context_idx( 0 ) - , m_log_sink( std::cout ) + , m_log_sinks( ) , m_report_sink( std::cerr ) { } @@ -787,7 +801,7 @@ public: boost::execution_monitor m_aux_em; - runtime_config::stream_holder m_log_sink; + std::map<output_format, runtime_config::stream_holder> m_log_sinks; runtime_config::stream_holder m_report_sink; }; @@ -809,7 +823,178 @@ setup_for_execution( test_unit const& tu ) { s_frk_state().deduce_run_status( tu.p_id ); } - + +struct sum_to_first_only { + sum_to_first_only() : is_first(true) {} + template <class T, class U> + T operator()(T const& l_, U const& r_) { + if(is_first) { + is_first = false; + return l_ + r_.first; + } + return l_ + ", " + r_.first; + } + + bool is_first; +}; + +void +setup_loggers() +{ + + BOOST_TEST_I_TRY { + + + +#ifdef BOOST_TEST_SUPPORT_TOKEN_ITERATOR + bool has_combined_logger = runtime_config::has( runtime_config::COMBINED_LOGGER ) + && !runtime_config::get< std::vector<std::string> >( runtime_config::COMBINED_LOGGER ).empty(); +#else + bool has_combined_logger = false; +#endif + + if( !has_combined_logger ) { + unit_test_log.set_threshold_level( runtime_config::get<log_level>( runtime_config::LOG_LEVEL ) ); + const output_format format = runtime_config::get<output_format>( runtime_config::LOG_FORMAT ); + unit_test_log.set_format( format ); + + runtime_config::stream_holder& stream_logger = s_frk_state().m_log_sinks[format]; + if( runtime_config::has( runtime_config::LOG_SINK ) ) + stream_logger.setup( runtime_config::get<const_string>( runtime_config::LOG_SINK ) ); + unit_test_log.set_stream( stream_logger.ref() ); + } + else + { + + const std::vector<std::string>& v_output_format = runtime_config::get< std::vector<std::string> >( runtime_config::COMBINED_LOGGER ) ; + + static const std::pair<const char*, log_level> all_log_levels[] = { + std::make_pair( "all" , log_successful_tests ), + std::make_pair( "success" , log_successful_tests ), + std::make_pair( "test_suite" , log_test_units ), + std::make_pair( "unit_scope" , log_test_units ), + std::make_pair( "message" , log_messages ), + std::make_pair( "warning" , log_warnings ), + std::make_pair( "error" , log_all_errors ), + std::make_pair( "cpp_exception" , log_cpp_exception_errors ), + std::make_pair( "system_error" , log_system_errors ), + std::make_pair( "fatal_error" , log_fatal_errors ), + std::make_pair( "nothing" , log_nothing ) + }; + + static const std::pair<const char*, output_format> all_formats[] = { + std::make_pair( "HRF" , OF_CLF ), + std::make_pair( "CLF" , OF_CLF ), + std::make_pair( "XML" , OF_XML ), + std::make_pair( "JUNIT", OF_JUNIT ) + }; + + + bool is_first = true; + + BOOST_TEST_FOREACH( const_string, current_multi_config, v_output_format ) { + +#ifdef BOOST_TEST_SUPPORT_TOKEN_ITERATOR + utils::string_token_iterator current_config( current_multi_config, (utils::dropped_delimeters = ":", + utils::kept_delimeters = utils::dt_none) ); + + for( ; current_config != utils::string_token_iterator() ; ++current_config) { + + utils::string_token_iterator current_format_specs( *current_config, (utils::keep_empty_tokens, + utils::dropped_delimeters = ",", + utils::kept_delimeters = utils::dt_none) ); + + output_format format = OF_INVALID ; // default + if( current_format_specs != utils::string_token_iterator() && + current_format_specs->size() ) { + + for(size_t elem=0; elem < sizeof(all_formats)/sizeof(all_formats[0]); elem++) { + if(const_string(all_formats[elem].first) == *current_format_specs) { + format = all_formats[elem].second; + break; + } + } + } + + BOOST_TEST_I_ASSRT( format != OF_INVALID, + boost::runtime::access_to_missing_argument() + << "Unable to determine the logger type from '" + << *current_config + << "'. Possible choices are: " + << std::accumulate(all_formats, + all_formats + sizeof(all_formats)/sizeof(all_formats[0]), + std::string(""), + sum_to_first_only()) + ); + + // activates this format + if( is_first ) { + unit_test_log.set_format( format ); + } + else { + unit_test_log.add_format( format ); + } + is_first = false; + + unit_test_log_formatter * const formatter = unit_test_log.get_formatter(format); + BOOST_TEST_SETUP_ASSERT( formatter, "Logger setup error" ); + + log_level formatter_log_level = invalid_log_level; + if( !current_format_specs->size() ) { + formatter_log_level = formatter->get_log_level(); // default log level given by the formatter + } + else if( ++current_format_specs != utils::string_token_iterator() ) { + + for(size_t elem=0; elem < sizeof(all_log_levels)/sizeof(all_log_levels[0]); elem++) { + if(const_string(all_log_levels[elem].first) == *current_format_specs) { + formatter_log_level = all_log_levels[elem].second; + break; + } + } + } + + + BOOST_TEST_I_ASSRT( formatter_log_level != invalid_log_level, + boost::runtime::access_to_missing_argument() + << "Unable to determine the log level from '" + << *current_config + << "'. Possible choices are: " + << std::accumulate(all_log_levels, + all_log_levels + sizeof(all_log_levels)/sizeof(all_log_levels[0]), + std::string(""), + sum_to_first_only()) + ); + + unit_test_log.set_threshold_level( format, formatter_log_level ); + + runtime_config::stream_holder& stream_logger = s_frk_state().m_log_sinks[format]; + if( ++current_format_specs != utils::string_token_iterator() && + current_format_specs->size() ) { + stream_logger.setup( *current_format_specs ); + } + else { + stream_logger.setup( formatter->get_default_stream_description() ); + } + unit_test_log.set_stream( format, stream_logger.ref() ); + + } +#endif + } + + } + } + BOOST_TEST_I_CATCH( boost::runtime::init_error, ex ) { + BOOST_TEST_SETUP_ASSERT( false, ex.msg ); + } + BOOST_TEST_I_CATCH( boost::runtime::input_error, ex ) { + std::cerr << ex.msg << "\n\n"; + + BOOST_TEST_I_THROW( framework::nothing_to_test( boost::exit_exception_failure ) ); + } + + +} + //____________________________________________________________________________// } // namespace impl @@ -829,15 +1014,14 @@ init( init_unit_test_func init_func, int argc, char* argv[] ) runtime_config::init( argc, argv ); // 20. Set the desired log level, format and sink - unit_test_log.set_threshold_level( runtime_config::get<log_level>( runtime_config::LOG_LEVEL ) ); - unit_test_log.set_format( runtime_config::get<output_format>( runtime_config::LOG_FORMAT ) ); - s_frk_state().m_log_sink.setup( runtime_config::LOG_SINK ); - unit_test_log.set_stream( s_frk_state().m_log_sink.ref() ); + impl::setup_loggers(); // 30. Set the desired report level, format and sink results_reporter::set_level( runtime_config::get<report_level>( runtime_config::REPORT_LEVEL ) ); results_reporter::set_format( runtime_config::get<output_format>( runtime_config::REPORT_FORMAT ) ); - s_frk_state().m_report_sink.setup( runtime_config::REPORT_SINK ); + + if( runtime_config::has( runtime_config::REPORT_SINK ) ) + s_frk_state().m_report_sink.setup( runtime_config::get<const_string>( runtime_config::REPORT_SINK ) ); results_reporter::set_stream( s_frk_state().m_report_sink.ref() ); // 40. Register default test observers @@ -845,7 +1029,7 @@ init( init_unit_test_func init_func, int argc, char* argv[] ) register_observer( unit_test_log ); if( runtime_config::get<bool>( runtime_config::SHOW_PROGRESS ) ) { - progress_monitor.set_stream( s_frk_state().m_log_sink.ref() ); + progress_monitor.set_stream( std::cout ); // defaults to stdout register_observer( progress_monitor ); } @@ -1235,7 +1419,7 @@ run( test_unit_id id, bool continue_test ) case 1: seed = static_cast<unsigned>( std::rand() ^ std::time( 0 ) ); // better init using std::rand() ^ ... default: - BOOST_TEST_MESSAGE( "Test cases order is shuffled using seed: " << seed ); + BOOST_TEST_FRAMEWORK_MESSAGE( "Test cases order is shuffled using seed: " << seed ); std::srand( seed ); } diff --git a/boost/test/impl/junit_log_formatter.ipp b/boost/test/impl/junit_log_formatter.ipp new file mode 100644 index 0000000000..a07ee5e2b0 --- /dev/null +++ b/boost/test/impl/junit_log_formatter.ipp @@ -0,0 +1,627 @@ +// (C) Copyright 2016 Raffi Enficiaud. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/test for the library home page. +// +///@file +///@brief Contains the implementatoin of the Junit log formatter (OF_JUNIT) +// *************************************************************************** + +#ifndef BOOST_TEST_JUNIT_LOG_FORMATTER_IPP__ +#define BOOST_TEST_JUNIT_LOG_FORMATTER_IPP__ + +// Boost.Test +#include <boost/test/output/junit_log_formatter.hpp> +#include <boost/test/execution_monitor.hpp> +#include <boost/test/framework.hpp> +#include <boost/test/tree/test_unit.hpp> +#include <boost/test/utils/basic_cstring/io.hpp> +#include <boost/test/utils/xml_printer.hpp> +#include <boost/test/utils/string_cast.hpp> +#include <boost/test/framework.hpp> + +#include <boost/test/tree/visitor.hpp> +#include <boost/test/tree/test_case_counter.hpp> +#include <boost/test/tree/traverse.hpp> +#include <boost/test/results_collector.hpp> + +#include <boost/test/utils/algorithm.hpp> +#include <boost/test/utils/string_cast.hpp> + +//#include <boost/test/results_reporter.hpp> + + +// Boost +#include <boost/version.hpp> + +// STL +#include <iostream> +#include <fstream> +#include <set> + +#include <boost/test/detail/suppress_warnings.hpp> + + +//____________________________________________________________________________// + +namespace boost { +namespace unit_test { +namespace output { + + +struct s_replace_chars { + template <class T> + void operator()(T& to_replace) + { + if(to_replace == '/') + to_replace = '.'; + else if(to_replace == ' ') + to_replace = '_'; + } +}; + +inline std::string tu_name_normalize(std::string full_name) +{ + // maybe directly using normalize_test_case_name instead? + std::for_each(full_name.begin(), full_name.end(), s_replace_chars()); + return full_name; +} + +const_string file_basename(const_string filename) { + + const_string path_sep( "\\/" ); + const_string::iterator it = unit_test::utils::find_last_of( filename.begin(), filename.end(), + path_sep.begin(), path_sep.end() ); + if( it != filename.end() ) + filename.trim_left( it + 1 ); + + return filename; + +} + +// ************************************************************************** // +// ************** junit_log_formatter ************** // +// ************************************************************************** // + +void +junit_log_formatter::log_start( std::ostream& ostr, counter_t test_cases_amount) +{ + map_tests.clear(); + list_path_to_root.clear(); + root_id = INV_TEST_UNIT_ID; +} + +//____________________________________________________________________________// + +class junit_result_helper : public test_tree_visitor { +public: + explicit junit_result_helper( + std::ostream& stream, + test_unit const& ts, + junit_log_formatter::map_trace_t const& mt, + bool display_build_info ) + : m_stream(stream) + , m_ts( ts ) + , m_map_test( mt ) + , m_id( 0 ) + , m_display_build_info(display_build_info) + { } + + void add_log_entry(std::string const& entry_type, + test_case const& tc, + junit_impl::junit_log_helper::assertion_entry const& log) const + { + m_stream + << "<" << entry_type + << " message" << utils::attr_value() << log.logentry_message + << " type" << utils::attr_value() << log.logentry_type + << ">"; + + if(!log.output.empty()) { + m_stream << utils::cdata() << "\n" + log.output; + } + + m_stream << "</" << entry_type << ">"; + } + + void visit( test_case const& tc ) + { + test_results const& tr = results_collector.results( tc.p_id ); + + junit_impl::junit_log_helper detailed_log; + bool need_skipping_reason = false; + bool skipped = false; + + junit_log_formatter::map_trace_t::const_iterator it_element(m_map_test.find(tc.p_id)); + if( it_element != m_map_test.end() ) + { + detailed_log = it_element->second; + } + else + { + need_skipping_reason = true; + } + + std::string classname; + test_unit_id id(tc.p_parent_id); + while( id != m_ts.p_id ) { + test_unit const& tu = boost::unit_test::framework::get( id, TUT_ANY ); + + if(need_skipping_reason) + { + test_results const& tr_parent = results_collector.results( id ); + if( tr_parent.p_skipped ) + { + skipped = true; + detailed_log.system_out+= "- disabled: " + tu.full_name() + "\n"; + } + junit_log_formatter::map_trace_t::const_iterator it_element_stack(m_map_test.find(id)); + if( it_element_stack != m_map_test.end() ) + { + detailed_log.system_out+= "- skipping decision: '" + it_element_stack->second.system_out + "'"; + detailed_log.system_out = "SKIPPING decision stack:\n" + detailed_log.system_out; + need_skipping_reason = false; + } + } + + classname = tu_name_normalize(tu.p_name) + "." + classname; + id = tu.p_parent_id; + } + + // removes the trailing dot + if(!classname.empty() && *classname.rbegin() == '.') { + classname.erase(classname.size()-1); + } + + // + // test case header + + // total number of assertions + m_stream << "<testcase assertions" << utils::attr_value() << tr.p_assertions_passed + tr.p_assertions_failed; + + // class name + if(!classname.empty()) + m_stream << " classname" << utils::attr_value() << classname; + + // test case name and time taken + m_stream + << " name" << utils::attr_value() << tu_name_normalize(tc.p_name) + << " time" << utils::attr_value() << double(tr.p_duration_microseconds) * 1E-6 + << ">" << std::endl; + + if( tr.p_skipped || skipped ) { + m_stream << "<skipped/>" << std::endl; + } + else { + + for(std::vector< junit_impl::junit_log_helper::assertion_entry >::const_iterator it(detailed_log.assertion_entries.begin()); + it != detailed_log.assertion_entries.end(); + ++it) + { + if(it->log_entry == junit_impl::junit_log_helper::assertion_entry::log_entry_failure) { + add_log_entry("failure", tc, *it); + } + else if(it->log_entry == junit_impl::junit_log_helper::assertion_entry::log_entry_error) { + add_log_entry("error", tc, *it); + } + } + } + + // system-out + all info/messages + std::string system_out = detailed_log.system_out; + for(std::vector< junit_impl::junit_log_helper::assertion_entry >::const_iterator it(detailed_log.assertion_entries.begin()); + it != detailed_log.assertion_entries.end(); + ++it) + { + if(it->log_entry != junit_impl::junit_log_helper::assertion_entry::log_entry_info) + continue; + system_out += it->output; + } + + if(!system_out.empty()) { + m_stream + << "<system-out>" + << utils::cdata() << system_out + << "</system-out>" + << std::endl; + } + + // system-err output + test case informations + std::string system_err = detailed_log.system_err; + { + // test case information (redundant but useful) + std::ostringstream o; + o << "Test case:" << std::endl + << "- name: " << tc.full_name() << std::endl + << "- description: '" << tc.p_description << "'" << std::endl + << "- file: " << file_basename(tc.p_file_name) << std::endl + << "- line: " << tc.p_line_num << std::endl + ; + system_err = o.str() + system_err; + } + m_stream + << "<system-err>" + << utils::cdata() << system_err + << "</system-err>" + << std::endl; + + m_stream << "</testcase>" << std::endl; + } + + bool test_suite_start( test_suite const& ts ) + { + // unique test suite, without s, nesting not supported in CI + if( m_ts.p_id != ts.p_id ) + return true; + + test_results const& tr = results_collector.results( ts.p_id ); + + m_stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl; + m_stream << "<testsuite"; + + m_stream + // << "disabled=\"" << tr.p_test_cases_skipped << "\" " + << " tests" << utils::attr_value() << tr.p_test_cases_passed + << " skipped" << utils::attr_value() << tr.p_test_cases_skipped + << " errors" << utils::attr_value() << tr.p_test_cases_aborted + << " failures" << utils::attr_value() << tr.p_test_cases_failed + << " id" << utils::attr_value() << m_id++ + << " name" << utils::attr_value() << tu_name_normalize(ts.p_name) + << " time" << utils::attr_value() << (tr.p_duration_microseconds * 1E-6) + << ">" << std::endl; + + if(m_display_build_info) + { + m_stream << "<properties>" << std::endl; + m_stream << "<property name=\"platform\" value" << utils::attr_value() << BOOST_PLATFORM << std::endl; + m_stream << "<property name=\"compiler\" value" << utils::attr_value() << BOOST_COMPILER << std::endl; + m_stream << "<property name=\"stl\" value" << utils::attr_value() << BOOST_STDLIB << std::endl; + + std::ostringstream o; + o << BOOST_VERSION/100000 << "." << BOOST_VERSION/100 % 1000 << "." << BOOST_VERSION % 100; + m_stream << "<property name=\"boost\" value" << utils::attr_value() << o.str() << std::endl; + m_stream << "</properties>" << std::endl; + } + + return true; // indicates that the children should also be parsed + } + + virtual void test_suite_finish( test_suite const& ts ) + { + if( m_ts.p_id != ts.p_id ) + return; + m_stream << "</testsuite>"; + } + +private: + // Data members + std::ostream& m_stream; + test_unit const& m_ts; + junit_log_formatter::map_trace_t const& m_map_test; + size_t m_id; + bool m_display_build_info; +}; + + + +void +junit_log_formatter::log_finish( std::ostream& ostr ) +{ + junit_result_helper ch( ostr, boost::unit_test::framework::get( root_id, TUT_SUITE ), map_tests, m_display_build_info ); + traverse_test_tree( root_id, ch, true ); // last is to ignore disabled suite special handling + + return; +} + +//____________________________________________________________________________// + +void +junit_log_formatter::log_build_info( std::ostream& ostr ) +{ + m_display_build_info = true; +} + +//____________________________________________________________________________// + +void +junit_log_formatter::test_unit_start( std::ostream& ostr, test_unit const& tu ) +{ + if(list_path_to_root.empty()) + root_id = tu.p_id; + list_path_to_root.push_back( tu.p_id ); + map_tests.insert(std::make_pair(tu.p_id, junit_impl::junit_log_helper())); // current_test_case_id not working here +} + + + +//____________________________________________________________________________// + +void +junit_log_formatter::test_unit_finish( std::ostream& ostr, test_unit const& tu, unsigned long elapsed ) +{ + // the time is already stored in the result_reporter + assert( tu.p_id == list_path_to_root.back() ); + list_path_to_root.pop_back(); +} + +void +junit_log_formatter::test_unit_aborted( std::ostream& os, test_unit const& tu ) +{ + assert( tu.p_id == list_path_to_root.back() ); + //list_path_to_root.pop_back(); +} + +//____________________________________________________________________________// + +void +junit_log_formatter::test_unit_skipped( std::ostream& ostr, test_unit const& tu, const_string reason ) +{ + if(tu.p_type == TUT_CASE) + { + junit_impl::junit_log_helper& v = map_tests[tu.p_id]; + v.system_out.assign(reason.begin(), reason.end()); + } + else + { + junit_impl::junit_log_helper& v = map_tests[tu.p_id]; + v.system_out.assign(reason.begin(), reason.end()); + } +} + +//____________________________________________________________________________// + +void +junit_log_formatter::log_exception_start( std::ostream& ostr, log_checkpoint_data const& checkpoint_data, execution_exception const& ex ) +{ + std::ostringstream o; + execution_exception::location const& loc = ex.where(); + + m_is_last_assertion_or_error = false; + + if(!list_path_to_root.empty()) + { + junit_impl::junit_log_helper& last_entry = map_tests[list_path_to_root.back()]; + + junit_impl::junit_log_helper::assertion_entry entry; + + entry.logentry_message = "unexpected exception"; + entry.log_entry = junit_impl::junit_log_helper::assertion_entry::log_entry_error; + + switch(ex.code()) + { + case execution_exception::cpp_exception_error: + entry.logentry_type = "uncaught exception"; + break; + case execution_exception::timeout_error: + entry.logentry_type = "execution timeout"; + break; + case execution_exception::user_error: + entry.logentry_type = "user, assert() or CRT error"; + break; + case execution_exception::user_fatal_error: + // Looks like never used + entry.logentry_type = "user fatal error"; + break; + case execution_exception::system_error: + entry.logentry_type = "system error"; + break; + case execution_exception::system_fatal_error: + entry.logentry_type = "system fatal error"; + break; + default: + entry.logentry_type = "no error"; // not sure how to handle this one + break; + } + + o << "UNCAUGHT EXCEPTION:" << std::endl; + if( !loc.m_function.is_empty() ) + o << "- function: \"" << loc.m_function << "\"" << std::endl; + + o << "- file: " << file_basename(loc.m_file_name) << std::endl + << "- line: " << loc.m_line_num << std::endl + << std::endl; + + o << "\nEXCEPTION STACK TRACE: --------------\n" << ex.what() + << "\n-------------------------------------"; + + if( !checkpoint_data.m_file_name.is_empty() ) { + o << std::endl << std::endl + << "Last checkpoint:" << std::endl + << "- message: \"" << checkpoint_data.m_message << "\"" << std::endl + << "- file: " << file_basename(checkpoint_data.m_file_name) << std::endl + << "- line: " << checkpoint_data.m_line_num << std::endl + ; + } + + entry.output = o.str(); + + last_entry.assertion_entries.push_back(entry); + } + + // check what to do with this one +} + +//____________________________________________________________________________// + +void +junit_log_formatter::log_exception_finish( std::ostream& ostr ) +{ + // sealing the last entry + assert(!map_tests[list_path_to_root.back()].assertion_entries.back().sealed); + map_tests[list_path_to_root.back()].assertion_entries.back().sealed = true; +} + +//____________________________________________________________________________// + +void +junit_log_formatter::log_entry_start( std::ostream& ostr, log_entry_data const& entry_data, log_entry_types let ) +{ + junit_impl::junit_log_helper& last_entry = map_tests[list_path_to_root.back()]; + m_is_last_assertion_or_error = true; + switch(let) + { + case unit_test_log_formatter::BOOST_UTL_ET_INFO: + case unit_test_log_formatter::BOOST_UTL_ET_MESSAGE: + case unit_test_log_formatter::BOOST_UTL_ET_WARNING: + { + std::ostringstream o; + + junit_impl::junit_log_helper::assertion_entry entry; + entry.log_entry = junit_impl::junit_log_helper::assertion_entry::log_entry_info; + entry.logentry_message = "info"; + entry.logentry_type = "message"; + + o << (let == unit_test_log_formatter::BOOST_UTL_ET_WARNING ? + "WARNING:" : (let == unit_test_log_formatter::BOOST_UTL_ET_MESSAGE ? + "MESSAGE:" : "INFO:")) + << std::endl + << "- file : " << file_basename(entry_data.m_file_name) << std::endl + << "- line : " << entry_data.m_line_num << std::endl + << "- message: "; // no CR + + entry.output += o.str(); + last_entry.assertion_entries.push_back(entry); + break; + } + default: + case unit_test_log_formatter::BOOST_UTL_ET_ERROR: + case unit_test_log_formatter::BOOST_UTL_ET_FATAL_ERROR: + { + std::ostringstream o; + junit_impl::junit_log_helper::assertion_entry entry; + entry.log_entry = junit_impl::junit_log_helper::assertion_entry::log_entry_failure; + entry.logentry_message = "failure"; + entry.logentry_type = (let == unit_test_log_formatter::BOOST_UTL_ET_ERROR ? "assertion error" : "fatal error"); + + o << "ASSERTION FAILURE:" << std::endl + << "- file : " << file_basename(entry_data.m_file_name) << std::endl + << "- line : " << entry_data.m_line_num << std::endl + << "- message: " ; // no CR + + entry.output += o.str(); + last_entry.assertion_entries.push_back(entry); + break; + } + } + +} + + //____________________________________________________________________________// + + + +//____________________________________________________________________________// + +void +junit_log_formatter::log_entry_value( std::ostream& ostr, const_string value ) +{ + assert(map_tests[list_path_to_root.back()].assertion_entries.empty() || !map_tests[list_path_to_root.back()].assertion_entries.back().sealed); + junit_impl::junit_log_helper& last_entry = map_tests[list_path_to_root.back()]; + std::ostringstream o; + utils::print_escaped_cdata( o, value ); + + if(!last_entry.assertion_entries.empty()) + { + junit_impl::junit_log_helper::assertion_entry& log_entry = last_entry.assertion_entries.back(); + log_entry.output += value; + } + else + { + // this may be a message coming from another observer + // the prefix is set in the log_entry_start + last_entry.system_out += value; + } +} + +//____________________________________________________________________________// + +void +junit_log_formatter::log_entry_finish( std::ostream& ostr ) +{ + assert(map_tests[list_path_to_root.back()].assertion_entries.empty() || !map_tests[list_path_to_root.back()].assertion_entries.back().sealed); + junit_impl::junit_log_helper& last_entry = map_tests[list_path_to_root.back()]; + if(!last_entry.assertion_entries.empty()) { + junit_impl::junit_log_helper::assertion_entry& log_entry = last_entry.assertion_entries.back(); + log_entry.output += "\n\n"; // quote end, CR + log_entry.sealed = true; + } + else { + last_entry.system_out += "\n\n"; // quote end, CR + } +} + +//____________________________________________________________________________// + +void +junit_log_formatter::entry_context_start( std::ostream& ostr, log_level ) +{ + std::vector< junit_impl::junit_log_helper::assertion_entry > &v_failure_or_error = map_tests[list_path_to_root.back()].assertion_entries; + assert(!v_failure_or_error.back().sealed); + + if(m_is_last_assertion_or_error) + { + v_failure_or_error.back().output += "\n- context:\n"; + } + else + { + v_failure_or_error.back().output += "\n\nCONTEXT:\n"; + } +} + +//____________________________________________________________________________// + +void +junit_log_formatter::entry_context_finish( std::ostream& ostr ) +{ + // no op, may be removed + assert(!map_tests[list_path_to_root.back()].assertion_entries.back().sealed); +} + +//____________________________________________________________________________// + +void +junit_log_formatter::log_entry_context( std::ostream& ostr, const_string context_descr ) +{ + assert(!map_tests[list_path_to_root.back()].assertion_entries.back().sealed); + 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 +} + +//____________________________________________________________________________// + + +std::string +junit_log_formatter::get_default_stream_description() const { + std::string name = framework::master_test_suite().p_name.value; + + static const std::string to_replace[] = { " ", "\"", "/", "\\", ":"}; + static const std::string replacement[] = { "_", "_" , "_", "_" , "_"}; + + name = unit_test::utils::replace_all_occurrences_of( + name, + to_replace, to_replace + sizeof(to_replace)/sizeof(to_replace[0]), + replacement, replacement + sizeof(replacement)/sizeof(replacement[0])); + + std::ifstream check_init((name + ".xml").c_str()); + if(!check_init) + return name + ".xml"; + + int index = 0; + for(; index < 100; index++) { + std::string candidate = name + "_" + utils::string_cast(index) + ".xml"; + std::ifstream file(candidate.c_str()); + if(!file) + return candidate; + } + + return name + ".xml"; +} + +} // namespace output +} // namespace unit_test +} // namespace boost + +#include <boost/test/detail/enable_warnings.hpp> + +#endif // BOOST_TEST_junit_log_formatter_IPP_020105GER diff --git a/boost/test/impl/results_collector.ipp b/boost/test/impl/results_collector.ipp index 17a31955ea..daee8bc3bf 100644 --- a/boost/test/impl/results_collector.ipp +++ b/boost/test/impl/results_collector.ipp @@ -83,6 +83,7 @@ test_results::operator+=( test_results const& tr ) p_test_cases_failed.value += tr.p_test_cases_failed; p_test_cases_skipped.value += tr.p_test_cases_skipped; p_test_cases_aborted.value += tr.p_test_cases_aborted; + p_duration_microseconds.value += tr.p_duration_microseconds; } //____________________________________________________________________________// @@ -99,6 +100,7 @@ test_results::clear() p_test_cases_failed.value = 0; p_test_cases_skipped.value = 0; p_test_cases_aborted.value = 0; + p_duration_microseconds.value= 0; p_aborted.value = false; p_skipped.value = false; } @@ -184,7 +186,7 @@ private: //____________________________________________________________________________// void -results_collector_t::test_unit_finish( test_unit const& tu, unsigned long ) +results_collector_t::test_unit_finish( test_unit const& tu, unsigned long elapsed_in_microseconds ) { if( tu.p_type == TUT_SUITE ) { results_collect_helper ch( s_rc_impl().m_results_store[tu.p_id], tu ); @@ -192,15 +194,16 @@ results_collector_t::test_unit_finish( test_unit const& tu, unsigned long ) traverse_test_tree( tu, ch ); } else { - test_results const& tr = s_rc_impl().m_results_store[tu.p_id]; + test_results & tr = s_rc_impl().m_results_store[tu.p_id]; + tr.p_duration_microseconds.value = elapsed_in_microseconds; bool num_failures_match = tr.p_aborted || tr.p_assertions_failed >= tr.p_expected_failures; if( !num_failures_match ) - BOOST_TEST_MESSAGE( "Test case " << tu.full_name() << " has fewer failures than expected" ); + BOOST_TEST_FRAMEWORK_MESSAGE( "Test case " << tu.full_name() << " has fewer failures than expected" ); bool check_any_assertions = tr.p_aborted || (tr.p_assertions_failed != 0) || (tr.p_assertions_passed != 0); if( !check_any_assertions ) - BOOST_TEST_MESSAGE( "Test case " << tu.full_name() << " did not check any assertions" ); + BOOST_TEST_FRAMEWORK_MESSAGE( "Test case " << tu.full_name() << " did not check any assertions" ); } } diff --git a/boost/test/impl/test_tools.ipp b/boost/test/impl/test_tools.ipp index ed94da3a5b..a6b20a7729 100644 --- a/boost/test/impl/test_tools.ipp +++ b/boost/test/impl/test_tools.ipp @@ -29,6 +29,8 @@ #include <boost/test/detail/throw_exception.hpp> +#include <boost/test/utils/algorithm.hpp> + // Boost #include <boost/config.hpp> @@ -505,7 +507,7 @@ output_test_stream::output_test_stream( const_string pattern_file_name, bool mat m_pimpl->m_pattern.open( pattern_file_name.begin(), m ); if( !m_pimpl->m_pattern.is_open() ) - BOOST_TEST_MESSAGE( "Can't open pattern file " << pattern_file_name << " for " << (match_or_save ? "reading" : "writing") ); + BOOST_TEST_FRAMEWORK_MESSAGE( "Can't open pattern file " << pattern_file_name << " for " << (match_or_save ? "reading" : "writing") ); } m_pimpl->m_match_or_save = match_or_save; @@ -572,57 +574,163 @@ output_test_stream::is_equal( const_string arg, bool flush_stream ) //____________________________________________________________________________// +std::string pretty_print_log(std::string str) { + + static const std::string to_replace[] = { "\r", "\n" }; + static const std::string replacement[] = { "\\r", "\\n" }; + + return unit_test::utils::replace_all_occurrences_of( + str, + to_replace, to_replace + sizeof(to_replace)/sizeof(to_replace[0]), + replacement, replacement + sizeof(replacement)/sizeof(replacement[0])); +} + assertion_result output_test_stream::match_pattern( bool flush_stream ) { + const std::string::size_type n_chars_presuffix = 10; sync(); assertion_result result( true ); + const std::string stream_string_repr = get_stream_string_representation(); + if( !m_pimpl->m_pattern.is_open() ) { result = false; result.message() << "Pattern file can't be opened!"; } else { if( m_pimpl->m_match_or_save ) { - for ( std::string::size_type i = 0; i < m_pimpl->m_synced_string.length(); ++i ) { + + int offset = 0; + std::vector<char> last_elements; + for ( std::string::size_type i = 0; static_cast<int>(i + offset) < static_cast<int>(stream_string_repr.length()); ++i ) { char c = m_pimpl->get_char(); - result = !m_pimpl->m_pattern.fail() && + if( last_elements.size() <= n_chars_presuffix ) { + last_elements.push_back( c ); + } + else { + last_elements[ i % last_elements.size() ] = c; + } + + bool is_same = !m_pimpl->m_pattern.fail() && !m_pimpl->m_pattern.eof() && - (m_pimpl->m_synced_string[i] == c); + (stream_string_repr[i+offset] == c); - if( !result ) { - std::string::size_type suffix_size = (std::min)( m_pimpl->m_synced_string.length() - i, - static_cast<std::string::size_type>(5) ); + if( !is_same ) { - // try to log area around the mismatch - result.message() << "Mismatch at position " << i << '\n' - << "..." << m_pimpl->m_synced_string.substr( i, suffix_size ) << "..." << '\n' - << "..." << c; + result = false; - std::string::size_type counter = suffix_size; - while( --counter ) { + std::string::size_type prefix_size = (std::min)( i + offset, n_chars_presuffix ); + + std::string::size_type suffix_size = (std::min)( stream_string_repr.length() - i - offset, + n_chars_presuffix ); + + // try to log area around the mismatch + std::string substr = stream_string_repr.substr(0, i+offset); + std::size_t line = std::count(substr.begin(), substr.end(), '\n'); + std::size_t column = i + offset - substr.rfind('\n'); + + result.message() + << "Mismatch at position " << i + << " (line " << line + << ", column " << column + << "): '" << pretty_print_log(std::string(1, stream_string_repr[i+offset])) << "' != '" << pretty_print_log(std::string(1, c)) << "' :\n"; + + // we already escape this substring because we need its actual size for the pretty print + // of the difference location. + std::string sub_str_prefix(pretty_print_log(stream_string_repr.substr( i + offset - prefix_size, prefix_size ))); + + // we need this substring as is because we compute the best matching substrings on it. + std::string sub_str_suffix(stream_string_repr.substr( i + offset, suffix_size)); + result.message() << "... " << sub_str_prefix + pretty_print_log(sub_str_suffix) << " ..." << '\n'; + + result.message() << "... "; + for( std::size_t j = 0; j < last_elements.size() ; j++ ) + result.message() << pretty_print_log(std::string(1, last_elements[(i + j + 1) % last_elements.size()])); + + std::vector<char> last_elements_ordered; + last_elements_ordered.push_back(c); + for( std::string::size_type counter = 0; counter < suffix_size - 1 ; counter++ ) { char c2 = m_pimpl->get_char(); if( m_pimpl->m_pattern.fail() || m_pimpl->m_pattern.eof() ) break; - result.message() << c2; + result.message() << pretty_print_log(std::string(1, c2)); + + last_elements_ordered.push_back(c2); + } + + // tries to find the best substring matching in the remainder of the + // two strings + std::size_t max_nb_char_in_common = 0; + std::size_t best_pattern_start_index = 0; + std::size_t best_stream_start_index = 0; + for( std::size_t pattern_start_index = best_pattern_start_index; + pattern_start_index < last_elements_ordered.size(); + pattern_start_index++ ) { + for( std::size_t stream_start_index = best_stream_start_index; + stream_start_index < sub_str_suffix.size(); + stream_start_index++ ) { + + std::size_t max_size = (std::min)( last_elements_ordered.size() - pattern_start_index, sub_str_suffix.size() - stream_start_index ); + if( max_nb_char_in_common > max_size ) + break; // safely break to go to the outer loop + + std::size_t nb_char_in_common = 0; + for( std::size_t k = 0; k < max_size; k++) { + if( last_elements_ordered[pattern_start_index + k] == sub_str_suffix[stream_start_index + k] ) + nb_char_in_common ++; + else + break; // we take fully macthing substring only + } + + if( nb_char_in_common > max_nb_char_in_common ) { + max_nb_char_in_common = nb_char_in_common; + best_pattern_start_index = pattern_start_index; + best_stream_start_index = stream_start_index; + } + } + } + + // indicates with more precision the location of the mismatchs in ascii arts ... + result.message() << " ...\n... "; + for( std::string::size_type j = 0; j < sub_str_prefix.size(); j++) { + result.message() << ' '; + } + + for( std::size_t k = 0; k < (std::max)(best_pattern_start_index, best_stream_start_index); k++ ) { // 1 is for the current char c + std::string s1(pretty_print_log(std::string(1, last_elements_ordered[(std::min)(k, best_pattern_start_index)]))); + std::string s2(pretty_print_log(std::string(1, sub_str_suffix[(std::min)(k, best_stream_start_index)]))); + for( int h = (std::max)(s1.size(), s2.size()); h > 0; h--) + result.message() << "~"; } + result.message() << "\n"; - result.message() << "..."; + // first char is a replicat of c, so we do not copy it. + for(std::string::size_type counter = 0; counter < last_elements_ordered.size() - 1 ; counter++) + last_elements[ (i + 1 + counter) % last_elements.size() ] = last_elements_ordered[counter + 1]; + + i += last_elements_ordered.size()-1; + offset += best_stream_start_index - best_pattern_start_index; - // skip rest of the bytes. May help for further matching - m_pimpl->m_pattern.ignore( - static_cast<std::streamsize>( m_pimpl->m_synced_string.length() - i - suffix_size) ); - break; } + + } + + // not needed anymore + /* + if(offset > 0 && false) { + m_pimpl->m_pattern.ignore( + static_cast<std::streamsize>( offset )); } + */ } else { - m_pimpl->m_pattern.write( m_pimpl->m_synced_string.c_str(), - static_cast<std::streamsize>( m_pimpl->m_synced_string.length() ) ); + m_pimpl->m_pattern.write( stream_string_repr.c_str(), + static_cast<std::streamsize>( stream_string_repr.length() ) ); m_pimpl->m_pattern.flush(); } } @@ -647,6 +755,12 @@ output_test_stream::flush() #endif } + +std::string +output_test_stream::get_stream_string_representation() const { + return m_pimpl->m_synced_string; +} + //____________________________________________________________________________// std::size_t diff --git a/boost/test/impl/unit_test_log.ipp b/boost/test/impl/unit_test_log.ipp index 4c9ac40691..5f3fa6510d 100644 --- a/boost/test/impl/unit_test_log.ipp +++ b/boost/test/impl/unit_test_log.ipp @@ -23,12 +23,14 @@ #include <boost/test/unit_test_parameters.hpp> #include <boost/test/utils/basic_cstring/compare.hpp> +#include <boost/test/utils/foreach.hpp> #include <boost/test/output/compiler_log_formatter.hpp> #include <boost/test/output/xml_log_formatter.hpp> +#include <boost/test/output/junit_log_formatter.hpp> // Boost -#include <boost/scoped_ptr.hpp> +#include <boost/shared_ptr.hpp> #include <boost/io/ios_state.hpp> typedef ::boost::io::ios_base_all_saver io_saver_type; @@ -81,38 +83,68 @@ entry_value_collector::~entry_value_collector() namespace { +// log data +struct unit_test_log_data_helper_impl { + typedef boost::shared_ptr<unit_test_log_formatter> formatter_ptr; + typedef boost::shared_ptr<io_saver_type> saver_ptr; + + bool m_enabled; + output_format m_format; + std::ostream* m_stream; + saver_ptr m_stream_state_saver; + formatter_ptr m_log_formatter; + bool m_entry_in_progress; + + unit_test_log_data_helper_impl(unit_test_log_formatter* p_log_formatter, output_format format, bool enabled = false) + : m_enabled( enabled ) + , m_format( format ) + , m_stream( &std::cout ) + , m_stream_state_saver( new io_saver_type( std::cout ) ) + , m_log_formatter() + , m_entry_in_progress( false ) + { + m_log_formatter.reset(p_log_formatter); + m_log_formatter->set_log_level(log_all_errors); + } + + // helper functions + std::ostream& stream() + { + return *m_stream; + } + + log_level get_log_level() const + { + return m_log_formatter->get_log_level(); + } +}; + struct unit_test_log_impl { // Constructor unit_test_log_impl() - : m_stream( &std::cout ) - , m_stream_state_saver( new io_saver_type( std::cout ) ) - , m_threshold_level( log_all_errors ) - , m_log_formatter( new output::compiler_log_formatter ) { + m_log_formatter_data.push_back( unit_test_log_data_helper_impl(new output::compiler_log_formatter, OF_CLF, true) ); // only this one is active by default, + m_log_formatter_data.push_back( unit_test_log_data_helper_impl(new output::xml_log_formatter, OF_XML, false) ); + m_log_formatter_data.push_back( unit_test_log_data_helper_impl(new output::junit_log_formatter, OF_JUNIT, false) ); } - // log data - typedef scoped_ptr<unit_test_log_formatter> formatter_ptr; - typedef scoped_ptr<io_saver_type> saver_ptr; - - std::ostream* m_stream; - saver_ptr m_stream_state_saver; - log_level m_threshold_level; - formatter_ptr m_log_formatter; + typedef std::vector<unit_test_log_data_helper_impl> v_formatter_data_t; + v_formatter_data_t m_log_formatter_data; // entry data - bool m_entry_in_progress; - bool m_entry_started; log_entry_data m_entry_data; + bool has_entry_in_progress() const { + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl const&, current_logger_data, m_log_formatter_data ) { + if( current_logger_data.m_entry_in_progress ) + return true; + } + return false; + } + // check point data log_checkpoint_data m_checkpoint_data; - // helper functions - std::ostream& stream() - { - return *m_stream; - } void set_checkpoint( const_string file, std::size_t line_num, const_string msg ) { assign_op( m_checkpoint_data.m_message, msg, 0 ); @@ -130,15 +162,17 @@ unit_test_log_impl& s_log_impl() { static unit_test_log_impl the_inst; return th void unit_test_log_t::test_start( counter_t test_cases_amount ) { - if( s_log_impl().m_threshold_level == log_nothing ) - return; + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + if( !current_logger_data.m_enabled || current_logger_data.get_log_level() == log_nothing ) + continue; - s_log_impl().m_log_formatter->log_start( s_log_impl().stream(), test_cases_amount ); + current_logger_data.m_log_formatter->log_start( current_logger_data.stream(), test_cases_amount ); - if( runtime_config::get<bool>( runtime_config::BUILD_INFO ) ) - s_log_impl().m_log_formatter->log_build_info( s_log_impl().stream() ); + if( runtime_config::get<bool>( runtime_config::BUILD_INFO ) ) + current_logger_data.m_log_formatter->log_build_info( current_logger_data.stream() ); - s_log_impl().m_entry_in_progress = false; + current_logger_data.m_entry_in_progress = false; + } } //____________________________________________________________________________// @@ -146,12 +180,14 @@ unit_test_log_t::test_start( counter_t test_cases_amount ) void unit_test_log_t::test_finish() { - if( s_log_impl().m_threshold_level == log_nothing ) - return; + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + if( !current_logger_data.m_enabled || current_logger_data.get_log_level() == log_nothing ) + continue; - s_log_impl().m_log_formatter->log_finish( s_log_impl().stream() ); + current_logger_data.m_log_formatter->log_finish( current_logger_data.stream() ); - s_log_impl().stream().flush(); + current_logger_data.stream().flush(); + } } //____________________________________________________________________________// @@ -167,13 +203,13 @@ unit_test_log_t::test_aborted() void unit_test_log_t::test_unit_start( test_unit const& tu ) { - if( s_log_impl().m_threshold_level > log_test_units ) - return; - - if( s_log_impl().m_entry_in_progress ) + if( s_log_impl().has_entry_in_progress() ) *this << log::end(); - - s_log_impl().m_log_formatter->test_unit_start( s_log_impl().stream(), tu ); + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + if( !current_logger_data.m_enabled || current_logger_data.get_log_level() > log_test_units ) + continue; + current_logger_data.m_log_formatter->test_unit_start( current_logger_data.stream(), tu ); + } } //____________________________________________________________________________// @@ -181,15 +217,18 @@ unit_test_log_t::test_unit_start( test_unit const& tu ) void unit_test_log_t::test_unit_finish( test_unit const& tu, unsigned long elapsed ) { - if( s_log_impl().m_threshold_level > log_test_units ) - return; - s_log_impl().m_checkpoint_data.clear(); - if( s_log_impl().m_entry_in_progress ) + if( s_log_impl().has_entry_in_progress() ) *this << log::end(); - s_log_impl().m_log_formatter->test_unit_finish( s_log_impl().stream(), tu, elapsed ); + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + + if( !current_logger_data.m_enabled || current_logger_data.get_log_level() > log_test_units ) + continue; + + current_logger_data.m_log_formatter->test_unit_finish( current_logger_data.stream(), tu, elapsed ); + } } //____________________________________________________________________________// @@ -197,13 +236,29 @@ unit_test_log_t::test_unit_finish( test_unit const& tu, unsigned long elapsed ) void unit_test_log_t::test_unit_skipped( test_unit const& tu, const_string reason ) { - if( s_log_impl().m_threshold_level > log_test_units ) - return; + if( s_log_impl().has_entry_in_progress() ) + *this << log::end(); + + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + if( !current_logger_data.m_enabled || current_logger_data.get_log_level() > log_test_units ) + continue; - if( s_log_impl().m_entry_in_progress ) + current_logger_data.m_log_formatter->test_unit_skipped( current_logger_data.stream(), tu, reason ); + } +} + +void +unit_test_log_t::test_unit_aborted( test_unit const& tu ) +{ + if( s_log_impl().has_entry_in_progress() ) *this << log::end(); - s_log_impl().m_log_formatter->test_unit_skipped( s_log_impl().stream(), tu, reason ); + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + if( !current_logger_data.m_enabled || current_logger_data.get_log_level() > log_test_units ) + continue; + + current_logger_data.m_log_formatter->test_unit_aborted(current_logger_data.stream(), tu ); + } } //____________________________________________________________________________// @@ -216,17 +271,20 @@ unit_test_log_t::exception_caught( execution_exception const& ex ) (ex.code() <= execution_exception::timeout_error ? log_system_errors : log_fatal_errors ); - if( l >= s_log_impl().m_threshold_level ) { - if( s_log_impl().m_entry_in_progress ) - *this << log::end(); + if( s_log_impl().has_entry_in_progress() ) + *this << log::end(); - s_log_impl().m_log_formatter->log_exception_start( s_log_impl().stream(), s_log_impl().m_checkpoint_data, ex ); + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { - log_entry_context( l ); + if( current_logger_data.m_enabled && l >= current_logger_data.get_log_level() ) { - s_log_impl().m_log_formatter->log_exception_finish( s_log_impl().stream() ); - } + current_logger_data.m_log_formatter->log_exception_start( current_logger_data.stream(), s_log_impl().m_checkpoint_data, ex ); + + log_entry_context( l ); + current_logger_data.m_log_formatter->log_exception_finish( current_logger_data.stream() ); + } + } clear_entry_context(); } @@ -249,10 +307,14 @@ set_unix_slash( char in ) unit_test_log_t& unit_test_log_t::operator<<( log::begin const& b ) { - if( s_log_impl().m_entry_in_progress ) + if( s_log_impl().has_entry_in_progress() ) *this << log::end(); - s_log_impl().m_stream_state_saver->restore(); + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + if( current_logger_data.m_enabled ) { + current_logger_data.m_stream_state_saver->restore(); + } + } s_log_impl().m_entry_data.clear(); @@ -273,12 +335,15 @@ unit_test_log_t::operator<<( log::begin const& b ) unit_test_log_t& unit_test_log_t::operator<<( log::end const& ) { - if( s_log_impl().m_entry_in_progress ) { + if( s_log_impl().has_entry_in_progress() ) { log_entry_context( s_log_impl().m_entry_data.m_level ); - s_log_impl().m_log_formatter->log_entry_finish( s_log_impl().stream() ); - - s_log_impl().m_entry_in_progress = false; + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + if( current_logger_data.m_enabled && current_logger_data.m_entry_in_progress ) { + current_logger_data.m_log_formatter->log_entry_finish( current_logger_data.stream() ); + } + current_logger_data.m_entry_in_progress = false; + } } clear_entry_context(); @@ -309,43 +374,53 @@ unit_test_log_t::operator()( log_level l ) //____________________________________________________________________________// bool -unit_test_log_t::log_entry_start() +unit_test_log_t::log_entry_start(output_format log_format) { - if( s_log_impl().m_entry_in_progress ) + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + + if( current_logger_data.m_format != log_format ) + continue; + + if( current_logger_data.m_entry_in_progress ) + return true; + + if( !current_logger_data.m_enabled ) + return false; + + switch( s_log_impl().m_entry_data.m_level ) { + case log_successful_tests: + current_logger_data.m_log_formatter->log_entry_start( current_logger_data.stream(), s_log_impl().m_entry_data, + unit_test_log_formatter::BOOST_UTL_ET_INFO ); + break; + case log_messages: + current_logger_data.m_log_formatter->log_entry_start( current_logger_data.stream(), s_log_impl().m_entry_data, + unit_test_log_formatter::BOOST_UTL_ET_MESSAGE ); + break; + case log_warnings: + current_logger_data.m_log_formatter->log_entry_start( current_logger_data.stream(), s_log_impl().m_entry_data, + unit_test_log_formatter::BOOST_UTL_ET_WARNING ); + break; + case log_all_errors: + case log_cpp_exception_errors: + case log_system_errors: + current_logger_data.m_log_formatter->log_entry_start( current_logger_data.stream(), s_log_impl().m_entry_data, + unit_test_log_formatter::BOOST_UTL_ET_ERROR ); + break; + case log_fatal_errors: + current_logger_data.m_log_formatter->log_entry_start( current_logger_data.stream(), s_log_impl().m_entry_data, + unit_test_log_formatter::BOOST_UTL_ET_FATAL_ERROR ); + break; + case log_nothing: + case log_test_units: + case invalid_log_level: + return false; + } + + current_logger_data.m_entry_in_progress = true; return true; - - switch( s_log_impl().m_entry_data.m_level ) { - case log_successful_tests: - s_log_impl().m_log_formatter->log_entry_start( s_log_impl().stream(), s_log_impl().m_entry_data, - unit_test_log_formatter::BOOST_UTL_ET_INFO ); - break; - case log_messages: - s_log_impl().m_log_formatter->log_entry_start( s_log_impl().stream(), s_log_impl().m_entry_data, - unit_test_log_formatter::BOOST_UTL_ET_MESSAGE ); - break; - case log_warnings: - s_log_impl().m_log_formatter->log_entry_start( s_log_impl().stream(), s_log_impl().m_entry_data, - unit_test_log_formatter::BOOST_UTL_ET_WARNING ); - break; - case log_all_errors: - case log_cpp_exception_errors: - case log_system_errors: - s_log_impl().m_log_formatter->log_entry_start( s_log_impl().stream(), s_log_impl().m_entry_data, - unit_test_log_formatter::BOOST_UTL_ET_ERROR ); - break; - case log_fatal_errors: - s_log_impl().m_log_formatter->log_entry_start( s_log_impl().stream(), s_log_impl().m_entry_data, - unit_test_log_formatter::BOOST_UTL_ET_FATAL_ERROR ); - break; - case log_nothing: - case log_test_units: - case invalid_log_level: - return false; } - s_log_impl().m_entry_in_progress = true; - - return true; + return false; } //____________________________________________________________________________// @@ -353,9 +428,11 @@ unit_test_log_t::log_entry_start() unit_test_log_t& unit_test_log_t::operator<<( const_string value ) { - if( s_log_impl().m_entry_data.m_level >= s_log_impl().m_threshold_level && !value.empty() && log_entry_start() ) - s_log_impl().m_log_formatter->log_entry_value( s_log_impl().stream(), value ); + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + if( current_logger_data.m_enabled && s_log_impl().m_entry_data.m_level >= current_logger_data.get_log_level() && !value.empty() && log_entry_start(current_logger_data.m_format) ) + current_logger_data.m_log_formatter->log_entry_value( current_logger_data.stream(), value ); + } return *this; } @@ -364,9 +441,10 @@ unit_test_log_t::operator<<( const_string value ) unit_test_log_t& unit_test_log_t::operator<<( lazy_ostream const& value ) { - if( s_log_impl().m_entry_data.m_level >= s_log_impl().m_threshold_level && !value.empty() && log_entry_start() ) - s_log_impl().m_log_formatter->log_entry_value( s_log_impl().stream(), value ); - + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + if( current_logger_data.m_enabled && s_log_impl().m_entry_data.m_level >= current_logger_data.get_log_level() && !value.empty() && log_entry_start(current_logger_data.m_format) ) + current_logger_data.m_log_formatter->log_entry_value( current_logger_data.stream(), value ); + } return *this; } @@ -381,12 +459,26 @@ unit_test_log_t::log_entry_context( log_level l ) const_string frame; - s_log_impl().m_log_formatter->entry_context_start( s_log_impl().stream(), l ); + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + if( current_logger_data.m_enabled ) { + current_logger_data.m_log_formatter->entry_context_start( current_logger_data.stream(), l ); + } + } while( !(frame=context.next()).is_empty() ) - s_log_impl().m_log_formatter->log_entry_context( s_log_impl().stream(), frame ); + { + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + if( current_logger_data.m_enabled ) { + current_logger_data.m_log_formatter->log_entry_context( current_logger_data.stream(), frame ); + } + } + } - s_log_impl().m_log_formatter->entry_context_finish( s_log_impl().stream() ); + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + if( current_logger_data.m_enabled ) { + current_logger_data.m_log_formatter->entry_context_finish( current_logger_data.stream() ); + } + } } //____________________________________________________________________________// @@ -402,11 +494,30 @@ unit_test_log_t::clear_entry_context() void unit_test_log_t::set_stream( std::ostream& str ) { - if( s_log_impl().m_entry_in_progress ) + if( s_log_impl().has_entry_in_progress() ) + return; + + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + current_logger_data.m_stream = &str; + current_logger_data.m_stream_state_saver.reset( new io_saver_type( str ) ); + } +} + +//____________________________________________________________________________// + +void +unit_test_log_t::set_stream( output_format log_format, std::ostream& str ) +{ + if( s_log_impl().has_entry_in_progress() ) return; - s_log_impl().m_stream = &str; - s_log_impl().m_stream_state_saver.reset( new io_saver_type( str ) ); + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + if( current_logger_data.m_format == log_format) { + current_logger_data.m_stream = &str; + current_logger_data.m_stream_state_saver.reset( new io_saver_type( str ) ); + break; + } + } } //____________________________________________________________________________// @@ -414,10 +525,28 @@ unit_test_log_t::set_stream( std::ostream& str ) void unit_test_log_t::set_threshold_level( log_level lev ) { - if( s_log_impl().m_entry_in_progress || lev == invalid_log_level ) + if( s_log_impl().has_entry_in_progress() || lev == invalid_log_level ) + return; + + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + current_logger_data.m_log_formatter->set_log_level( lev ); + } +} + +//____________________________________________________________________________// + +void +unit_test_log_t::set_threshold_level( output_format log_format, log_level lev ) +{ + if( s_log_impl().has_entry_in_progress() || lev == invalid_log_level ) return; - s_log_impl().m_threshold_level = lev; + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + if( current_logger_data.m_format == log_format) { + current_logger_data.m_log_formatter->set_log_level( lev ); + break; + } + } } //____________________________________________________________________________// @@ -425,26 +554,90 @@ unit_test_log_t::set_threshold_level( log_level lev ) void unit_test_log_t::set_format( output_format log_format ) { - if( s_log_impl().m_entry_in_progress ) + if( s_log_impl().has_entry_in_progress() ) return; - switch( log_format ) { - default: - case OF_CLF: - set_formatter( new output::compiler_log_formatter ); - break; - case OF_XML: - set_formatter( new output::xml_log_formatter ); - break; + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + current_logger_data.m_enabled = current_logger_data.m_format == log_format; } } //____________________________________________________________________________// void +unit_test_log_t::add_format( output_format log_format ) +{ + if( s_log_impl().has_entry_in_progress() ) + return; + + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + if( current_logger_data.m_format == log_format) { + current_logger_data.m_enabled = true; + break; + } + } +} + +//____________________________________________________________________________// + +unit_test_log_formatter* +unit_test_log_t::get_formatter( output_format log_format ) { + BOOST_TEST_FOREACH( unit_test_log_data_helper_impl&, current_logger_data, s_log_impl().m_log_formatter_data ) { + if( current_logger_data.m_format == log_format) { + return current_logger_data.m_log_formatter.get(); + } + } + return 0; +} + + +void +unit_test_log_t::add_formatter( unit_test_log_formatter* the_formatter ) +{ + // remove only user defined logger + for(unit_test_log_impl::v_formatter_data_t::iterator it(s_log_impl().m_log_formatter_data.begin()), + ite(s_log_impl().m_log_formatter_data.end()); + it != ite; + ++it) + { + if( it->m_format == OF_CUSTOM_LOGGER) { + s_log_impl().m_log_formatter_data.erase(it); + break; + } + } + + if( the_formatter ) { + s_log_impl().m_log_formatter_data.push_back( unit_test_log_data_helper_impl(the_formatter, OF_CUSTOM_LOGGER, true) ); + } +} + +void unit_test_log_t::set_formatter( unit_test_log_formatter* the_formatter ) { - s_log_impl().m_log_formatter.reset( the_formatter ); + // remove only user defined logger + log_level current_level = invalid_log_level; + std::ostream *current_stream = 0; + output_format previous_format = OF_INVALID; + for(unit_test_log_impl::v_formatter_data_t::iterator it(s_log_impl().m_log_formatter_data.begin()), + ite(s_log_impl().m_log_formatter_data.end()); + it != ite; + ++it) + { + if( it->m_enabled ) { + if( current_level == invalid_log_level || it->m_format < previous_format || it->m_format == OF_CUSTOM_LOGGER) { + current_level = it->get_log_level(); + current_stream = &(it->stream()); + previous_format = it->m_format; + } + } + } + + if( the_formatter ) { + add_formatter(the_formatter); + set_format(OF_CUSTOM_LOGGER); + set_threshold_level(OF_CUSTOM_LOGGER, current_level); + set_stream(OF_CUSTOM_LOGGER, *current_stream); + } } //____________________________________________________________________________// @@ -459,6 +652,18 @@ unit_test_log_formatter::log_entry_value( std::ostream& ostr, lazy_ostream const log_entry_value( ostr, (wrap_stringstream().ref() << value).str() ); } +void +unit_test_log_formatter::set_log_level(log_level new_log_level) +{ + m_log_level = new_log_level; +} + +log_level +unit_test_log_formatter::get_log_level() const +{ + return m_log_level; +} + //____________________________________________________________________________// } // namespace unit_test @@ -467,3 +672,4 @@ unit_test_log_formatter::log_entry_value( std::ostream& ostr, lazy_ostream const #include <boost/test/detail/enable_warnings.hpp> #endif // BOOST_TEST_UNIT_TEST_LOG_IPP_012205GER + diff --git a/boost/test/impl/unit_test_main.ipp b/boost/test/impl/unit_test_main.ipp index 1f30c0213e..db61930652 100644 --- a/boost/test/impl/unit_test_main.ipp +++ b/boost/test/impl/unit_test_main.ipp @@ -191,7 +191,9 @@ unit_test_main( init_unit_test_func init_func, int argc, char* argv[] ) if( runtime_config::get<bool>( runtime_config::WAIT_FOR_DEBUGGER ) ) { results_reporter::get_stream() << "Press any key to continue..." << std::endl; - std::getchar(); + // getchar is defined as a macro in uClibc. Use parenthesis to fix + // gcc bug 58952 for gcc <= 4.8.2. + (std::getchar)(); results_reporter::get_stream() << "Continuing..." << std::endl; } diff --git a/boost/test/impl/unit_test_parameters.ipp b/boost/test/impl/unit_test_parameters.ipp index 3ca183e400..f49079ea3e 100644 --- a/boost/test/impl/unit_test_parameters.ipp +++ b/boost/test/impl/unit_test_parameters.ipp @@ -24,6 +24,7 @@ #include <boost/test/utils/basic_cstring/basic_cstring.hpp> #include <boost/test/utils/basic_cstring/compare.hpp> #include <boost/test/utils/basic_cstring/io.hpp> +#include <boost/test/utils/iterator/token_iterator.hpp> #include <boost/test/debug.hpp> #include <boost/test/framework.hpp> @@ -69,7 +70,7 @@ namespace rt = boost::runtime; namespace runtime_config { -// UTF parameters +// UTF parameters std::string AUTO_START_DBG = "auto_start_dbg"; std::string BREAK_EXEC_PATH = "break_exec_path"; std::string BUILD_INFO = "build_info"; @@ -82,6 +83,7 @@ std::string LIST_LABELS = "list_labels"; std::string LOG_FORMAT = "log_format"; std::string LOG_LEVEL = "log_level"; std::string LOG_SINK = "log_sink"; +std::string COMBINED_LOGGER = "logger"; std::string OUTPUT_FORMAT = "output_format"; std::string RANDOM_SEED = "random"; std::string REPORT_FORMAT = "report_format"; @@ -134,9 +136,11 @@ register_parameters( rt::parameters_store& store ) #endif )); - break_exec_path.add_cla_id( "--", BREAK_EXEC_PATH, "=" ); + break_exec_path.add_cla_id( "--", BREAK_EXEC_PATH, "=" ); store.add( break_exec_path ); + /////////////////////////////////////////////// + rt::option build_info( BUILD_INFO, ( rt::description = "Displays library build information.", rt::env_var = "BOOST_TEST_BUILD_INFO", @@ -162,7 +166,7 @@ register_parameters( rt::parameters_store& store ) #endif rt::help = "If option " + CATCH_SYS_ERRORS + " has value no the frameworks does not attempt to catch " "asynchronous system failure events (signals on *NIX platforms or structured exceptions on Windows). " - " Default value is " + " Default value is " #ifdef BOOST_TEST_DEFAULTS_TO_CORE_DUMP "no." #else @@ -226,8 +230,8 @@ register_parameters( rt::parameters_store& store ) rt::env_var = "BOOST_TEST_LIST_CONTENT", rt::default_value = OF_INVALID, rt::optional_value = OF_CLF, - rt::enum_values<unit_test::output_format>::value = -#if !defined(BOOST_NO_CXX11_HDR_INITIALIZER_LIST) && !defined(BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX) + rt::enum_values<unit_test::output_format>::value = +#if defined(BOOST_TEST_CLA_NEW_API) { { "HRF", OF_CLF }, { "DOT", OF_DOT } @@ -266,17 +270,19 @@ register_parameters( rt::parameters_store& store ) rt::env_var = "BOOST_TEST_LOG_FORMAT", rt::default_value = OF_CLF, rt::enum_values<unit_test::output_format>::value = -#if !defined(BOOST_NO_CXX11_HDR_INITIALIZER_LIST) && !defined(BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX) +#if defined(BOOST_TEST_CLA_NEW_API) { { "HRF", OF_CLF }, { "CLF", OF_CLF }, - { "XML", OF_XML } + { "XML", OF_XML }, + { "JUNIT", OF_JUNIT }, }, #else rt::enum_values_list<unit_test::output_format>() ( "HRF", OF_CLF ) ( "CLF", OF_CLF ) ( "XML", OF_XML ) + ( "JUNIT", OF_JUNIT ) , #endif rt::help = "Parameter " + LOG_FORMAT + " allows to set the frameowrk's log format to one " @@ -284,7 +290,7 @@ register_parameters( rt::parameters_store& store ) "parameter are the names of the output formats supplied by the framework. By " "default the framework uses human readable format (HRF) for testing log. This " "format is similar to compiler error format. Alternatively you can specify XML " - "as log format. This format is easier to process by testing automation tools." + "or JUNIT as log format, which are easier to process by testing automation tools." )); log_format.add_cla_id( "--", LOG_FORMAT, "=" ); @@ -298,7 +304,7 @@ register_parameters( rt::parameters_store& store ) rt::env_var = "BOOST_TEST_LOG_LEVEL", rt::default_value = log_all_errors, rt::enum_values<unit_test::log_level>::value = -#if !defined(BOOST_NO_CXX11_HDR_INITIALIZER_LIST) && !defined(BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX) +#if defined(BOOST_TEST_CLA_NEW_API) { { "all" , log_successful_tests }, { "success" , log_successful_tests }, @@ -361,7 +367,7 @@ register_parameters( rt::parameters_store& store ) rt::description = "Specifies output format (both log and report).", rt::env_var = "BOOST_TEST_OUTPUT_FORMAT", rt::enum_values<unit_test::output_format>::value = -#if !defined(BOOST_NO_CXX11_HDR_INITIALIZER_LIST) && !defined(BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX) +#if defined(BOOST_TEST_CLA_NEW_API) { { "HRF", OF_CLF }, { "CLF", OF_CLF }, @@ -374,7 +380,7 @@ register_parameters( rt::parameters_store& store ) ( "XML", OF_XML ) , #endif - rt::help = "Parameter " + OUTPUT_FORMAT + " combines an effect of " + REPORT_FORMAT + + rt::help = "Parameter " + OUTPUT_FORMAT + " combines an effect of " + REPORT_FORMAT + " and " + LOG_FORMAT + " parameters. This parameter has higher priority " "than either one of them. In other words if this parameter is specified " "it overrides the value of other two parameters. This parameter does not " @@ -387,6 +393,19 @@ register_parameters( rt::parameters_store& store ) output_format.add_cla_id( "-", "o", " " ); store.add( output_format ); + /////////////////////////////////////////////// combined logger option + + rt::parameter<std::string,rt::REPEATABLE_PARAM> combined_logger( COMBINED_LOGGER, ( + rt::description = "Specifies log level and sink for one or several log format", + rt::env_var = "BOOST_TEST_LOGGER", + rt::value_hint = "log_format:log_level:log_sink", + rt::help = "Parameter " + COMBINED_LOGGER + " allows to specify the logger type, level and sink\n" + "in one command." + )); + + combined_logger.add_cla_id( "--", COMBINED_LOGGER, "=" ); + store.add( combined_logger ); + /////////////////////////////////////////////// rt::parameter<unsigned> random_seed( RANDOM_SEED, ( @@ -416,7 +435,7 @@ register_parameters( rt::parameters_store& store ) rt::env_var = "BOOST_TEST_REPORT_FORMAT", rt::default_value = OF_CLF, rt::enum_values<unit_test::output_format>::value = -#if !defined(BOOST_NO_CXX11_HDR_INITIALIZER_LIST) && !defined(BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX) +#if defined(BOOST_TEST_CLA_NEW_API) { { "HRF", OF_CLF }, { "CLF", OF_CLF }, @@ -448,7 +467,7 @@ register_parameters( rt::parameters_store& store ) rt::env_var = "BOOST_TEST_REPORT_LEVEL", rt::default_value = CONFIRMATION_REPORT, rt::enum_values<unit_test::report_level>::value = -#if !defined(BOOST_NO_CXX11_HDR_INITIALIZER_LIST) && !defined(BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX) +#if defined(BOOST_TEST_CLA_NEW_API) { { "confirm", CONFIRMATION_REPORT }, { "short", SHORT_REPORT }, @@ -531,7 +550,7 @@ register_parameters( rt::parameters_store& store ) "filters', which allow to disable some test units. The __UTF__ also supports " "enabling/disabling test units at compile time. These settings identify the default " "set of test units to run. Parameter " + RUN_FILTERS + " is used to change this default. " - "This parameter is repeatable, so you can specify more than one filter if necessary." + "This parameter is repeatable, so you can specify more than one filter if necessary." )); tests_to_run.add_cla_id( "--", RUN_FILTERS, "=" ); @@ -545,8 +564,8 @@ register_parameters( rt::parameters_store& store ) rt::env_var = "BOOST_TEST_SAVE_PATTERN", rt::help = "Parameter " + SAVE_TEST_PATTERN + " facilitates switching mode of operation for " "testing output streams.\n\nThis parameter serves no particular purpose within the " - "framework itself. It can be used by test modules relying on output_test_stream to " - "implement testing logic. Default mode is 'match' (false)." + "framework itself. It can be used by test modules relying on output_test_stream to " + "implement testing logic. Default mode is 'match' (false)." )); save_test_pattern.add_cla_id( "--", SAVE_TEST_PATTERN, "=" ); @@ -664,6 +683,7 @@ init( int& argc, char** argv ) s_arguments_store.set( REPORT_FORMAT, of ); s_arguments_store.set( LOG_FORMAT, of ); } + } BOOST_TEST_I_CATCH( rt::init_error, ex ) { BOOST_TEST_SETUP_ASSERT( false, ex.msg ); @@ -689,7 +709,7 @@ init( int& argc, char** argv ) std::cerr << "\n"; parser->usage( std::cerr ); } - + BOOST_TEST_I_THROW( framework::nothing_to_test( boost::exit_exception_failure ) ); } BOOST_TEST_I_CATCH( rt::input_error, ex ) { @@ -697,7 +717,7 @@ init( int& argc, char** argv ) if( parser ) parser->usage( std::cerr, ex.param_name ); - + BOOST_TEST_I_THROW( framework::nothing_to_test( boost::exit_exception_failure ) ); } } diff --git a/boost/test/included/test_exec_monitor.hpp b/boost/test/included/test_exec_monitor.hpp index cedbcb0ad8..34b6ef44dd 100644 --- a/boost/test/included/test_exec_monitor.hpp +++ b/boost/test/included/test_exec_monitor.hpp @@ -14,6 +14,7 @@ #define BOOST_INCLUDED_TEST_EXEC_MONITOR_HPP_071894GER #include <boost/test/impl/compiler_log_formatter.ipp> +#include <boost/test/impl/junit_log_formatter.ipp> #include <boost/test/impl/debug.ipp> #include <boost/test/impl/decorator.ipp> #include <boost/test/impl/execution_monitor.ipp> diff --git a/boost/test/included/unit_test.hpp b/boost/test/included/unit_test.hpp index 03c0277d6d..8835acd455 100644 --- a/boost/test/included/unit_test.hpp +++ b/boost/test/included/unit_test.hpp @@ -13,6 +13,7 @@ #define BOOST_INCLUDED_UNIT_TEST_FRAMEWORK_HPP_071894GER #include <boost/test/impl/compiler_log_formatter.ipp> +#include <boost/test/impl/junit_log_formatter.ipp> #include <boost/test/impl/debug.ipp> #include <boost/test/impl/decorator.ipp> #include <boost/test/impl/framework.ipp> @@ -28,6 +29,7 @@ #include <boost/test/impl/unit_test_monitor.ipp> #include <boost/test/impl/unit_test_parameters.ipp> #include <boost/test/impl/xml_log_formatter.ipp> +#include <boost/test/impl/junit_log_formatter.ipp> #include <boost/test/impl/xml_report_formatter.ipp> #define BOOST_TEST_INCLUDED diff --git a/boost/test/output/compiler_log_formatter.hpp b/boost/test/output/compiler_log_formatter.hpp index e3f98d7567..cb6172aab6 100644 --- a/boost/test/output/compiler_log_formatter.hpp +++ b/boost/test/output/compiler_log_formatter.hpp @@ -5,11 +5,8 @@ // See http://www.boost.org/libs/test for the library home page. // -// File : $RCSfile$ -// -// Version : $Revision$ -// -// Description : contains compiler like Log formatter definition +/// @file +/// @brief Contains the formatter for the Human Readable Format (HRF) // *************************************************************************** #ifndef BOOST_TEST_COMPILER_LOG_FORMATTER_HPP_020105GER @@ -31,6 +28,7 @@ namespace output { // ************** compiler_log_formatter ************** // // ************************************************************************** // +//!@brief Log formatter for the Human Readable Format (HRF) log format class BOOST_TEST_DECL compiler_log_formatter : public unit_test_log_formatter { public: compiler_log_formatter() : m_color_output( false ) {} diff --git a/boost/test/output/junit_log_formatter.hpp b/boost/test/output/junit_log_formatter.hpp new file mode 100644 index 0000000000..b6e818e37f --- /dev/null +++ b/boost/test/output/junit_log_formatter.hpp @@ -0,0 +1,135 @@ +// (C) Copyright 2016 Raffi Enficiaud. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/test for the library home page. +// +///@file +///@brief Contains the definition of the Junit log formatter (OF_JUNIT) +// *************************************************************************** + +#ifndef BOOST_TEST_JUNIT_LOG_FORMATTER__ +#define BOOST_TEST_JUNIT_LOG_FORMATTER__ + +// Boost.Test +#include <boost/test/detail/global_typedef.hpp> +#include <boost/test/unit_test_log_formatter.hpp> +#include <boost/test/tree/test_unit.hpp> + +//#include <boost/test/results_collector.hpp> + +// STL +#include <cstddef> // std::size_t +#include <map> +#include <list> + +#include <boost/test/detail/suppress_warnings.hpp> + +//____________________________________________________________________________// + +namespace boost { +namespace unit_test { +namespace output { + + + namespace junit_impl { + + // helper for the JUnit logger + struct junit_log_helper + { + struct assertion_entry { + + enum log_entry_t { + log_entry_info, + log_entry_error, + log_entry_failure + }; + + assertion_entry() : sealed(false) + {} + + std::string logentry_message; + std::string logentry_type; // the one that will get expanded in the final junit (failure, error) + std::string output; // additional information/message generated by the assertion + + log_entry_t log_entry; // the type associated to the assertion (or error) + + bool sealed; // indicates if the entry can accept additional information + }; + + std::string system_out; // sysout: additional information + std::string system_err; // syserr: additional information + + // list of failure, errors and messages (assertions message and the full log) + std::vector< assertion_entry > assertion_entries; + + }; + } + +// ************************************************************************** // +// ************** junit_log_formatter ************** // +// ************************************************************************** // + +/// JUnit logger class +class junit_log_formatter : public unit_test_log_formatter { +public: + + junit_log_formatter() : m_display_build_info(false) + { + this->m_log_level = log_successful_tests; + } + + // Formatter interface + void log_start( std::ostream&, counter_t test_cases_amount ); + void log_finish( std::ostream& ); + void log_build_info( std::ostream& ); + + void test_unit_start( std::ostream&, test_unit const& tu ); + void test_unit_finish( std::ostream&, test_unit const& tu, unsigned long elapsed ); + void test_unit_skipped( std::ostream&, test_unit const& tu, const_string reason ); + void test_unit_aborted( std::ostream& os, test_unit const& tu ); + + void log_exception_start( std::ostream&, log_checkpoint_data const&, execution_exception const& ex ); + void log_exception_finish( std::ostream& ); + + void log_entry_start( std::ostream&, log_entry_data const&, log_entry_types let ); + + using unit_test_log_formatter::log_entry_value; // bring base class functions into overload set + void log_entry_value( std::ostream&, const_string value ); + void log_entry_finish( std::ostream& ); + + void entry_context_start( std::ostream&, log_level ); + void log_entry_context( std::ostream&, const_string ); + void entry_context_finish( std::ostream& ); + + //! Discards changes in the log level + virtual void set_log_level(log_level ) + { + } + + //! Instead of a regular stream, returns a file name corresponding to + //! the current master test suite. If the file already exists, adds an index + //! to it. + virtual std::string get_default_stream_description() const; + + +private: + typedef std::map<test_unit_id, junit_impl::junit_log_helper> map_trace_t; + map_trace_t map_tests; + + std::list<test_unit_id> list_path_to_root; + test_unit_id root_id; + bool m_display_build_info; + bool m_is_last_assertion_or_error; // true if failure, false if error + + friend class junit_result_helper; +}; + +} // namespace output +} // namespace unit_test +} // namespace boost + +#include <boost/test/detail/enable_warnings.hpp> + +#endif // BOOST_TEST_JUNIT_LOG_FORMATTER__ diff --git a/boost/test/results_collector.hpp b/boost/test/results_collector.hpp index d12fefb39c..7d2c2bee58 100644 --- a/boost/test/results_collector.hpp +++ b/boost/test/results_collector.hpp @@ -5,10 +5,11 @@ // See http://www.boost.org/libs/test for the library home page. // -/// @file results_collector.hpp @brief defines testing result collector components +/// @file +/// @brief Defines testing result collector components /// -/// Defines class results_collector_t that is responsible for -/// gathering test results and class test_results for presenting this information to end-user +/// Defines classes for keeping track (@ref test_results) and collecting +/// (@ref results_collector_t) the states of the test units. // *************************************************************************** #ifndef BOOST_TEST_RESULTS_COLLECTOR_HPP_071894GER @@ -44,59 +45,71 @@ inline void first_failed_assertion() {} // ************************************************************************** // /// @brief Collection of attributes constituting test unit results /// -/// This class is a collection of attributes describing testing results. The atributes presented as public properties on +/// This class is a collection of attributes describing a test result. +/// +/// The attributes presented as public properties on /// an instance of the class. In addition summary conclusion methods are presented to generate simple answer to pass/fail question -// ************************************************************************** // class BOOST_TEST_DECL test_results { public: test_results(); /// Type representing counter like public property - typedef BOOST_READONLY_PROPERTY( counter_t, (results_collector_t)(test_results)(results_collect_helper) ) counter_prop; + typedef BOOST_READONLY_PROPERTY( counter_t, (results_collector_t) + (test_results) + (results_collect_helper) ) counter_prop; /// Type representing boolean like public property - typedef BOOST_READONLY_PROPERTY( bool, (results_collector_t)(test_results)(results_collect_helper) ) bool_prop; + typedef BOOST_READONLY_PROPERTY( bool, (results_collector_t) + (test_results) + (results_collect_helper) ) bool_prop; - /// @name Public properties - counter_prop p_assertions_passed; - counter_prop p_assertions_failed; - counter_prop p_warnings_failed; + counter_prop p_assertions_passed; //!< Number of successful assertions + counter_prop p_assertions_failed; //!< Number of failing assertions + counter_prop p_warnings_failed; //!< Number of warnings counter_prop p_expected_failures; - counter_prop p_test_cases_passed; - counter_prop p_test_cases_warned; - counter_prop p_test_cases_failed; - counter_prop p_test_cases_skipped; - counter_prop p_test_cases_aborted; - bool_prop p_aborted; - bool_prop p_skipped; - /// @} - - /// @name Summary conclusion + counter_prop p_test_cases_passed; //!< Number of successfull test cases + counter_prop p_test_cases_warned; //!< Number of warnings in test cases + counter_prop p_test_cases_failed; //!< Number of failing test cases + counter_prop p_test_cases_skipped; //!< Number of skipped test cases + counter_prop p_test_cases_aborted; //!< Number of aborted test cases + counter_prop p_duration_microseconds; //!< Duration of the test in microseconds + bool_prop p_aborted; //!< Indicates that the test unit execution has been aborted + bool_prop p_skipped; //!< Indicates that the test unit execution has been skipped /// Returns true if test unit passed bool passed() const; - /// Produces result code for the test unit execution - /// This methhod return one of the result codes defined in boost/cstdlib.hpp - /// @returns boost::exit_success on success, boost::exit_exception_failure in case test unit was aborted for any reason - /// (incuding uncausght exception) and boost::exit_test_failure otherwise + /// Produces result code for the test unit execution + /// + /// This methhod return one of the result codes defined in @c boost/cstdlib.hpp + /// @returns + /// - @c boost::exit_success on success, + /// - @c boost::exit_exception_failure in case test unit + /// was aborted for any reason (incuding uncaught exception) + /// - and @c boost::exit_test_failure otherwise int result_code() const; - /// @} - // collection helper + //! Combines the results of the current instance with another + //! + //! Only the counters are updated and the @c p_aborted and @c p_skipped are left unchanged. void operator+=( test_results const& ); + //! Resets the current state of the result void clear(); }; // ************************************************************************** // -/// This class implements test observer interface to collect the result of test unit execution -// ************************************************************************** // - +/// @brief Collects and combines the test results +/// +/// This class collects and combines the results of the test unit during the execution of the +/// test tree. The results_collector_t::results() function combines the test results on a subtree +/// of the test tree. +/// +/// @see boost::unit_test::test_observer class BOOST_TEST_DECL results_collector_t : public test_observer, public singleton<results_collector_t> { public: - virtual void test_start( counter_t test_cases_amount ); + virtual void test_start( counter_t ); virtual void test_unit_start( test_unit const& ); virtual void test_unit_finish( test_unit const&, unsigned long ); @@ -109,7 +122,7 @@ public: virtual int priority() { return 2; } /// Results access per test unit - + /// /// @param[in] tu_id id of a test unit test_results const& results( test_unit_id tu_id ) const; @@ -125,4 +138,3 @@ BOOST_TEST_SINGLETON_INST( results_collector ) #include <boost/test/detail/enable_warnings.hpp> #endif // BOOST_TEST_RESULTS_COLLECTOR_HPP_071894GER - diff --git a/boost/test/tools/output_test_stream.hpp b/boost/test/tools/output_test_stream.hpp index 02d3715e9d..2abbf7b521 100644 --- a/boost/test/tools/output_test_stream.hpp +++ b/boost/test/tools/output_test_stream.hpp @@ -39,10 +39,10 @@ class BOOST_TEST_DECL output_test_stream : public wrap_stringstream::wrapped_str public: //! Constructor //! - //!@param[in] pattern_file_name indicates the name of the file for matching. If the + //!@param[in] pattern_file_name indicates the name of the file for matching. If the //! string is empty, the standard input or output streams are used instead //! (depending on match_or_save) - //!@param[in] match_or_save if true, the pattern file will be read, otherwise it will be + //!@param[in] match_or_save if true, the pattern file will be read, otherwise it will be //! written //!@param[in] text_or_binary if false, opens the stream in binary mode. Otherwise the stream //! is opened with default flags and the carriage returns are ignored. @@ -51,38 +51,49 @@ public: bool text_or_binary = true ); // Destructor - ~output_test_stream(); + virtual ~output_test_stream(); //! Checks if the stream is empty //! //!@param[in] flush_stream if true, flushes the stream after the call - assertion_result is_empty( bool flush_stream = true ); - + virtual assertion_result is_empty( bool flush_stream = true ); + //! Checks the length of the stream //! //!@param[in] length target length //!@param[in] flush_stream if true, flushes the stream after the call. Set to false to call //! additional checks on the same content. - assertion_result check_length( std::size_t length, bool flush_stream = true ); - + virtual assertion_result check_length( std::size_t length, bool flush_stream = true ); + //! Checks the content of the stream against a string //! //!@param[in] arg_ the target stream //!@param[in] flush_stream if true, flushes the stream after the call. - assertion_result is_equal( const_string arg_, bool flush_stream = true ); + virtual assertion_result is_equal( const_string arg_, bool flush_stream = true ); //! Checks the content of the stream against a pattern file //! - //!@param[in] flush_stream if true, flushes the stream after the call. - assertion_result match_pattern( bool flush_stream = true ); + //!@param[in] flush_stream if true, flushes/resets the stream after the call. + virtual assertion_result match_pattern( bool flush_stream = true ); //! Flushes the stream void flush(); +protected: + + //! Returns the string representation of the stream + //! + //! May be overriden in order to mutate the string before the matching operations. + virtual std::string get_stream_string_representation() const; + private: // helper functions + + //! Length of the stream std::size_t length(); - void sync(); + + //! Synching the stream into an internal string representation + virtual void sync(); struct Impl; Impl* m_pimpl; diff --git a/boost/test/tree/global_fixture.hpp b/boost/test/tree/global_fixture.hpp index 89ee61eb0c..2114595929 100644 --- a/boost/test/tree/global_fixture.hpp +++ b/boost/test/tree/global_fixture.hpp @@ -5,11 +5,8 @@ // See http://www.boost.org/libs/test for the library home page. // -// File : $RCSfile$ -// -// Version : $Revision: 74640 $ -// -// Description : defines global_fixture +/// @file +/// Defines global_fixture // *************************************************************************** #ifndef BOOST_TEST_TREE_GLOBAL_FIXTURE_HPP_091911GER diff --git a/boost/test/tree/observer.hpp b/boost/test/tree/observer.hpp index d878949f67..4db930fb07 100644 --- a/boost/test/tree/observer.hpp +++ b/boost/test/tree/observer.hpp @@ -28,17 +28,57 @@ namespace unit_test { // ************** test_observer ************** // // ************************************************************************** // +/// @brief Generic test observer interface +/// +/// This interface is used by observers in order to receive notifications from the +/// Boost.Test framework on the current execution state. +/// +/// Several observers can be running at the same time, and it is not unusual to +/// have interactions among them. The test_observer#priority member function allows the specification +/// of a particular order among them (lowest priority executed first, except specified otherwise). +/// class BOOST_TEST_DECL test_observer { public: - // test observer interface - virtual void test_start( counter_t /* test_cases_amount */ ) {} + + //! Called before the framework starts executing the test cases + //! + //! @param[in] number_of_test_cases indicates the number of test cases. Only active + //! test cases are taken into account. + //! + virtual void test_start( counter_t /* number_of_test_cases */ ) {} + + + //! Called after the framework ends executing the test cases + //! + //! @note The call is made with a reversed priority order. virtual void test_finish() {} + + //! Called when a critical error is detected + //! + //! The critical errors are mainly the signals sent by the system and caught by the Boost.Test framework. + //! Since the running binary may be in incoherent/instable state, the test execution is aborted and all remaining + //! tests are discarded. + //! + //! @note may be called before test_observer::test_unit_finish() virtual void test_aborted() {} - virtual void test_unit_start( test_unit const& ) {} - virtual void test_unit_finish( test_unit const&, unsigned long /* elapsed */ ) {} + //! Called before the framework starts executing a test unit + //! + //! @param[in] test_unit the test being executed + virtual void test_unit_start( test_unit const& /* test */) {} + + //! Called at each end of a test unit. + //! + //! @param elapsed duration of the test unit in microseconds. + virtual void test_unit_finish( test_unit const& /* test */, unsigned long /* elapsed */ ) {} virtual void test_unit_skipped( test_unit const& tu, const_string ) { test_unit_skipped( tu ); } virtual void test_unit_skipped( test_unit const& ) {} ///< backward compatibility + + //! Called when a test unit indicates a fatal error. + //! + //! A fatal error happens when + //! - a strong assertion (with @c REQUIRE) fails, which indicates that the test case cannot continue + //! - an unexpected exception is caught by the Boost.Test framework virtual void test_unit_aborted( test_unit const& ) {} virtual void assertion_result( unit_test::assertion_result ar ) @@ -50,12 +90,18 @@ public: default: break; } } + + //! Called when an exception is intercepted + //! + //! In case an exception is intercepted, this call happens before the call + //! to @ref test_unit_aborted in order to log + //! additional data about the exception. virtual void exception_caught( execution_exception const& ) {} virtual int priority() { return 0; } protected: - // depracated now + //! Deprecated virtual void assertion_result( bool /* passed */ ) {} BOOST_TEST_PROTECTED_VIRTUAL ~test_observer() {} diff --git a/boost/test/tree/test_case_counter.hpp b/boost/test/tree/test_case_counter.hpp index 6feaddcb0e..a74f37f152 100644 --- a/boost/test/tree/test_case_counter.hpp +++ b/boost/test/tree/test_case_counter.hpp @@ -5,11 +5,8 @@ // See http://www.boost.org/libs/test for the library home page. // -// File : $RCSfile$ -// -// Version : $Revision: 74640 $ -// -// Description : defines test_case_counter +/// @file +/// Defines @ref test_case_counter // *************************************************************************** #ifndef BOOST_TEST_TREE_TEST_CASE_COUNTER_HPP_100211GER @@ -33,6 +30,7 @@ namespace unit_test { // ************** test_case_counter ************** // // ************************************************************************** // +///! Counts the number of enabled test cases class test_case_counter : public test_tree_visitor { public: // Constructor diff --git a/boost/test/unit_test_log.hpp b/boost/test/unit_test_log.hpp index 4126953d02..ba998b0ca4 100644 --- a/boost/test/unit_test_log.hpp +++ b/boost/test/unit_test_log.hpp @@ -85,6 +85,30 @@ private: // ************** unit_test_log ************** // // ************************************************************************** // +/// @brief Manages the sets of loggers, their streams and log levels +/// +/// The Boost.Test framework allows for having several formatters/loggers at the same time, each of which +/// having their own log level and output stream. +/// +/// This class serves the purpose of +/// - exposing an interface to the test framework (as a boost::unit_test::test_observer) +/// - exposing an interface to the testing tools +/// - managing several loggers +/// +/// @note Accesses to the functions exposed by this class are made through the singleton +/// @c boost::unit_test::unit_test_log. +/// +/// Users/developers willing to implement their own formatter need to: +/// - implement a boost::unit_test::unit_test_log_formatter that will output the desired format +/// - register the formatter during a eg. global fixture using the method @c set_formatter (though the framework singleton). +/// +/// @warning this observer has a higher priority than the @ref boost::unit_test::results_collector_t. This means +/// that the various @ref boost::unit_test::test_results associated to each test unit may not be available at the time +/// the @c test_unit_start, @c test_unit_finish ... are called. +/// +/// @see +/// - boost::unit_test::test_observer +/// - boost::unit_test::unit_test_log_formatter class BOOST_TEST_DECL unit_test_log_t : public test_observer, public singleton<unit_test_log_t> { public: // test_observer interface implementation @@ -95,17 +119,83 @@ public: virtual void test_unit_start( test_unit const& ); virtual void test_unit_finish( test_unit const&, unsigned long elapsed ); virtual void test_unit_skipped( test_unit const&, const_string ); + virtual void test_unit_aborted( test_unit const& ); virtual void exception_caught( execution_exception const& ex ); virtual int priority() { return 1; } // log configuration methods + //! Sets the stream for all loggers + //! + //! This will override the log sink/stream of all loggers, whether enabled or not. void set_stream( std::ostream& ); + + //! Sets the stream for specific logger + //! + //! @note Has no effect if the specified format is not found + //! @par Since Boost 1.62 + void set_stream( output_format, std::ostream& ); + + //! Sets the threshold level for all loggers/formatters. + //! + //! This will override the log level of all loggers, whether enabled or not. void set_threshold_level( log_level ); + + //! Sets the threshold/log level of a specific format + //! + //! @note Has no effect if the specified format is not found + //! @par Since Boost 1.62 + void set_threshold_level( output_format, log_level ); + + //! Add a format to the set of loggers + //! + //! Adding a logger means that the specified logger is enabled. The log level is managed by the formatter itself + //! and specifies what events are forwarded to the underlying formatter. + //! @par Since Boost 1.62 + void add_format( output_format ); + + //! Sets the format of the logger + //! + //! This will become the only active format of the logs. void set_format( output_format ); + + //! Returns the logger instance for a specific format. + //! + //! @returns the logger/formatter instance, or @c (unit_test_log_formatter*)0 if the format is not found. + //! @par Since Boost 1.62 + unit_test_log_formatter* get_formatter( output_format ); + + //! Sets the logger instance + //! + //! The specified logger becomes the unique active one. The custom log formatter has the + //! format @c OF_CUSTOM_LOGGER. If such a format exists already, its formatter gets replaced by the one + //! given in argument. + //! + //! The log level and output stream of the new formatter are taken from the currently active logger. In case + //! several loggers are active, the order of priority is CUSTOM, HRF, XML, and JUNIT. + //! If (unit_test_log_formatter*)0 is given as argument, the custom logger (if any) is removed. + //! + //! @note The ownership of the pointer is transfered to the Boost.Test framework. This call is equivalent to + //! - a call to @c add_formatter + //! - a call to @c set_format(OF_CUSTOM_LOGGER) + //! - a configuration of the newly added logger with a previously configured stream and log level. void set_formatter( unit_test_log_formatter* ); + //! Adds a custom log formatter to the set of formatters + //! + //! The specified logger is added with the format @c OF_CUSTOM_LOGGER, such that it can + //! be futher selected or its stream/log level can be specified. + //! If there is already a custom logger (with @c OF_CUSTOM_LOGGER), then + //! the existing one gets replaced by the one given in argument. + //! The provided logger is added with an enabled state. + //! If (unit_test_log_formatter*)0 is given as argument, the custom logger (if any) is removed and + //! no other action is performed. + //! + //! @note The ownership of the pointer is transfered to the Boost.Test framework. + //! @par Since Boost 1.62 + void add_formatter( unit_test_log_formatter* the_formatter ); + // test progress logging void set_checkpoint( const_string file, std::size_t line_num, const_string msg = const_string() ); @@ -120,7 +210,7 @@ public: private: // Implementation helpers - bool log_entry_start(); + bool log_entry_start(output_format log_format); void log_entry_context( log_level l ); void clear_entry_context(); @@ -142,6 +232,17 @@ BOOST_TEST_SINGLETON_INST( unit_test_log ) // ************** Unit test log interface helpers ************** // // ************************************************************************** // +// messages sent by the framework +#define BOOST_TEST_FRAMEWORK_MESSAGE( M ) \ + (::boost::unit_test::unit_test_log \ + << ::boost::unit_test::log::begin( \ + "boost.test framework", \ + __LINE__ )) \ + ( ::boost::unit_test::log_messages ) \ + << BOOST_TEST_LAZY_MSG( M ) \ +/**/ + + #define BOOST_TEST_MESSAGE( M ) \ BOOST_TEST_LOG_ENTRY( ::boost::unit_test::log_messages ) \ << BOOST_TEST_LAZY_MSG( M ) \ diff --git a/boost/test/unit_test_log_formatter.hpp b/boost/test/unit_test_log_formatter.hpp index 1e3d64322f..5a9d6bada5 100644 --- a/boost/test/unit_test_log_formatter.hpp +++ b/boost/test/unit_test_log_formatter.hpp @@ -23,6 +23,7 @@ // STL #include <iosfwd> #include <string> // for std::string +#include <iostream> #include <boost/test/detail/suppress_warnings.hpp> @@ -72,19 +73,37 @@ struct BOOST_TEST_DECL log_checkpoint_data }; // ************************************************************************** // -/// Abstract Unit Test Framework log formatter interface - -/// During the test module execution Unit Test Framework can report messages about success or failure of assertions, -/// which test suites are being run and more (specifically which messages are reported depends on log level threshold selected by the user). -/// All these messages constitute Unit Test Framework log. There are many ways (formats) to present these messages to the user. Boost.Test comes with -/// two formats: "Compiler-like log format" and "XML based log format". Former is intended for human consumption and later is intended for processing -/// by automated regression test systems. If you want to produce some other format you need to implement class with specific interface and use -/// method unit_test_log_t::set_formatter during a test module initialization to set an active formatter. The class unit_test_log_formatter defines this -/// interface. +/// @brief Abstract Unit Test Framework log formatter interface /// -/// This interface requires you to format all possible messages being produced in the log. These includes error messages about failed assertions, messages -/// about caught exceptions and information messages about test units being started/ended. All the methods in this interface takes a reference to standard -/// stream as a first argument. This is where final messages needs to be directed to. Also you are given all the information necessary to produce a message. +/// During the test module execution Unit Test Framework can report messages about success +/// or failure of assertions, which test suites are being run and more (specifically which +/// messages are reported depends on log level threshold selected by the user). +/// +/// All these messages constitute Unit Test Framework log. There are many ways (formats) to present +/// these messages to the user. +/// +/// Boost.Test comes with three formats: +/// - Compiler-like log format: intended for human consumption/diagnostic +/// - XML based log format: intended for processing by automated regression test systems. +/// - JUNIT based log format: intended for processing by automated regression test systems. +/// +/// If you want to produce some other format you need to implement class with specific interface and use +/// method @c unit_test_log_t::set_formatter during a test module initialization to set an active formatter. +/// The class unit_test_log_formatter defines this interface. +/// +/// This interface requires you to format all possible messages being produced in the log. +/// These includes error messages about failed assertions, messages about caught exceptions and +/// information messages about test units being started/ended. All the methods in this interface takes +/// a reference to standard stream as a first argument. This is where final messages needs to be directed +/// to. Also you are given all the information necessary to produce a message. +/// +/// @par Since Boost 1.62: +/// - Each formatter may indicate the default output stream. This is convenient for instance for streams intended +/// for automated processing that indicate a file. See @c get_default_stream_description for more details. +/// - Each formatter may manage its own log level through the getter/setter @c get_log_level and @c set_log_level . +/// +/// @see +/// - boost::unit_test::test_observer for an indication of the calls of the test observer interface class BOOST_TEST_DECL unit_test_log_formatter { public: /// Types of log entries (messages written into a log) @@ -95,26 +114,31 @@ public: BOOST_UTL_ET_FATAL_ERROR ///< Fatal error notification message }; + //! Constructor + unit_test_log_formatter() + : m_log_level(log_all_errors) + {} + // Destructor virtual ~unit_test_log_formatter() {} // @name Test start/finish /// Invoked at the beginning of test module execution - + /// /// @param[in] os output stream to write a messages to /// @param[in] test_cases_amount total test case amount to be run /// @see log_finish virtual void log_start( std::ostream& os, counter_t test_cases_amount ) = 0; /// Invoked at the end of test module execution - + /// /// @param[in] os output stream to write a messages into /// @see log_start virtual void log_finish( std::ostream& os ) = 0; /// Invoked when Unit Test Framework build information is requested - + /// /// @param[in] os output stream to write a messages into virtual void log_build_info( std::ostream& os ) = 0; // @} @@ -122,22 +146,22 @@ public: // @name Test unit start/finish /// Invoked when test unit starts (either test suite or test case) - + /// /// @param[in] os output stream to write a messages into /// @param[in] tu test unit being started /// @see test_unit_finish virtual void test_unit_start( std::ostream& os, test_unit const& tu ) = 0; /// Invoked when test unit finishes - + /// /// @param[in] os output stream to write a messages into /// @param[in] tu test unit being finished - /// @param[in] elapsed time in milliseconds spend executing this test unit + /// @param[in] elapsed time in microseconds spend executing this test unit /// @see test_unit_start virtual void test_unit_finish( std::ostream& os, test_unit const& tu, unsigned long elapsed ) = 0; /// Invoked if test unit skipped for any reason - + /// /// @param[in] os output stream to write a messages into /// @param[in] tu skipped test unit /// @param[in] reason explanation why was it skipped @@ -149,14 +173,23 @@ public: /// Deprecated version of this interface virtual void test_unit_skipped( std::ostream& os, test_unit const& tu ) {} + /// Invoked when a test unit is aborted + virtual void test_unit_aborted( std::ostream& os, test_unit const& tu ) {} + // @} // @name Uncaught exception report /// Invoked when Unit Test Framework detects uncaught exception - - /// Call to this function starts uncaught exception report. It is going to followed by context information. Report is finalized by call to - /// log_exception_finish. + /// + /// The framwork calls this function when an uncaught exception it detected. + /// This call is followed by context information: + /// - one call to @c entry_context_start, + /// - as many calls to @c log_entry_context as there are context entries + /// - one call to @c entry_context_finish + /// + /// The logging of the exception information is finilized by a call to @c log_exception_finish. + /// /// @param[in] os output stream to write a messages into /// @param[in] lcd information about the last checkpoint before the exception was triggered /// @param[in] ex information about the caught exception @@ -164,7 +197,7 @@ public: virtual void log_exception_start( std::ostream& os, log_checkpoint_data const& lcd, execution_exception const& ex ) = 0; /// Invoked when Unit Test Framework detects uncaught exception - + /// /// Call to this function finishes uncaught exception report. /// @param[in] os output stream to write a messages into /// @see log_exception_start @@ -182,10 +215,13 @@ public: /// @param[in] led log entry attributes /// @param[in] let log entry type log_entry_finish /// @see log_entry_value, log_entry_finish + /// + /// @note call to this function may happen before any call to test_unit_start or all calls to test_unit_finish as the + /// framework might log errors raised during global initialization/shutdown. virtual void log_entry_start( std::ostream& os, log_entry_data const& led, log_entry_types let ) = 0; /// Invoked by Unit Test Framework to report a log entry content - + /// /// This is one of two overloaded methods to report log entry content. This one is used to report plain string value. /// @param[in] os output stream to write a messages into. /// @param[in] value log entry string value @@ -217,7 +253,7 @@ public: /// Context consists of multiple "scopes" identified by description messages assigned by the test module using /// BOOST_TEST_INFO/BOOST_TEST_CONTEXT statements. /// @param[in] os output stream to write a messages into - /// @param[in] l entry log_leveg, to be used to fine tune the message + /// @param[in] l entry log_level, to be used to fine tune the message /// @see log_entry_context, entry_context_finish virtual void entry_context_start( std::ostream& os, log_level l ) = 0; @@ -235,6 +271,42 @@ public: /// @see log_entry_start, entry_context_context virtual void entry_context_finish( std::ostream& os ) = 0; // @} + + // @name Log level management + + /// Sets the log level of the logger/formatter + /// + /// Some loggers need to manage the log level by their own. This + /// member function let the implementation decide of that. + /// @par Since Boost 1.62 + virtual void set_log_level(log_level new_log_level); + + /// Returns the log level of the logger/formatter + /// @par Since Boost 1.62 + virtual log_level get_log_level() const; + // @} + + + // @name Stream management + + /// Returns a default stream for this logger. + /// + /// The returned string describes the stream as if it was passed from + /// the command line @c "--log_sink" parameter. With that regards, @b stdout and @b stderr + /// have special meaning indicating the standard output or error stream respectively. + /// + /// @par Since Boost 1.62 + virtual std::string get_default_stream_description() const + { + return "stdout"; + } + + // @} + + +protected: + log_level m_log_level; + }; } // namespace unit_test diff --git a/boost/test/unit_test_parameters.hpp b/boost/test/unit_test_parameters.hpp index ad69c1732e..d6fecf0b8a 100644 --- a/boost/test/unit_test_parameters.hpp +++ b/boost/test/unit_test_parameters.hpp @@ -17,6 +17,7 @@ // Boost.Test #include <boost/test/detail/global_typedef.hpp> #include <boost/test/utils/runtime/argument.hpp> +#include <boost/make_shared.hpp> // STL #include <iostream> @@ -34,7 +35,7 @@ namespace runtime_config { // ************** runtime_config ************** // // ************************************************************************** // -// UTF parameters +// UTF parameters BOOST_TEST_DECL extern std::string AUTO_START_DBG; BOOST_TEST_DECL extern std::string BREAK_EXEC_PATH; BOOST_TEST_DECL extern std::string BUILD_INFO; @@ -44,6 +45,7 @@ BOOST_TEST_DECL extern std::string DETECT_FP_EXCEPT; BOOST_TEST_DECL extern std::string DETECT_MEM_LEAKS; BOOST_TEST_DECL extern std::string LIST_CONTENT; BOOST_TEST_DECL extern std::string LIST_LABELS; +BOOST_TEST_DECL extern std::string COMBINED_LOGGER; BOOST_TEST_DECL extern std::string LOG_FORMAT; BOOST_TEST_DECL extern std::string LOG_LEVEL; BOOST_TEST_DECL extern std::string LOG_SINK; @@ -76,6 +78,11 @@ get( runtime::cstring parameter_name ) return argument_store().get<T>( parameter_name ); } +inline bool has( runtime::cstring parameter_name ) +{ + return argument_store().has( parameter_name ); +} + /// For public access BOOST_TEST_DECL bool save_pattern(); @@ -86,35 +93,34 @@ BOOST_TEST_DECL bool save_pattern(); class stream_holder { public: // Constructor - explicit stream_holder( std::ostream& default_stream ) + explicit stream_holder( std::ostream& default_stream = std::cout) : m_stream( &default_stream ) { } - void setup( runtime::cstring param_name ) + void setup( const const_string& stream_name ) { - if( !runtime_config::argument_store().has( param_name ) ) + if(stream_name.empty()) return; - std::string const& file_name = runtime_config::get<std::string>( param_name ); - - if( file_name == "stderr" ) + if( stream_name == "stderr" ) m_stream = &std::cerr; - else if( file_name == "stdout" ) + else if( stream_name == "stdout" ) m_stream = &std::cout; else { - m_file.open( file_name.c_str() ); - m_stream = &m_file; + m_file = boost::make_shared<std::ofstream>(); + m_file->open( std::string(stream_name.begin(), stream_name.end()).c_str() ); + m_stream = m_file.get(); } } // Access methods - std::ostream& ref() const { return *m_stream; } + std::ostream& ref() const { return *m_stream; } private: // Data members - std::ofstream m_file; - std::ostream* m_stream; + boost::shared_ptr<std::ofstream> m_file; + std::ostream* m_stream; }; } // namespace runtime_config diff --git a/boost/test/utils/algorithm.hpp b/boost/test/utils/algorithm.hpp index 76625cbd91..a5491816eb 100644 --- a/boost/test/utils/algorithm.hpp +++ b/boost/test/utils/algorithm.hpp @@ -213,6 +213,104 @@ find_last_not_of( BidirectionalIterator1 first1, BidirectionalIterator1 last1, //____________________________________________________________________________// + +/// @brief This algorithm replaces all occurrences of a set of substrings by another substrings +/// +/// @param str - string of operation +/// @param first1 - iterator to the beginning of the substrings to replace +/// @param last1 - iterator to the end of the substrings to replace +/// @param first2 - iterator to the beginning of the substrings to replace with +/// @param last2 - iterator to the end of the substrings to replace with +template<class StringClass, class ForwardIterator> +inline StringClass +replace_all_occurrences_of( StringClass str, + ForwardIterator first1, ForwardIterator last1, + ForwardIterator first2, ForwardIterator last2) +{ + for(; first1 != last1 && first2 != last2; ++first1, ++first2) { + std::size_t found = str.find( *first1 ); + while( found != StringClass::npos ) { + str.replace(found, first1->size(), *first2 ); + found = str.find( *first1, found + first2->size() ); + } + } + + return str; +} + +/// @brief This algorithm replaces all occurrences of a string with basic wildcards +/// with another (optionally containing wildcards as well). +/// +/// @param str - string to transform +/// @param it_string_to_find - iterator to the beginning of the substrings to replace +/// @param it_string_to_find_end - iterator to the end of the substrings to replace +/// @param it_string_to_replace - iterator to the beginning of the substrings to replace with +/// @param it_string_to_replace_end - iterator to the end of the substrings to replace with +/// +/// The wildcard is the symbol '*'. Only a unique wildcard per string is supported. The replacement +/// string may also contain a wildcard, in which case it is considered as a placeholder to the content +/// of the wildcard in the source string. +/// Example: +/// - In order to replace the occurrences of @c 'time=\"some-variable-value\"' to a constant string, +/// one may use @c 'time=\"*\"' as the string to search for, and 'time=\"0.0\"' as the replacement string. +/// - In order to replace the occurrences of 'file.cpp(XX)' per 'file.cpp:XX', where XX is a variable to keep, +/// on may use @c 'file.cpp(*)' as the string to search for, and 'file.cpp:*' as the replacement string. +template<class StringClass, class ForwardIterator> +inline StringClass +replace_all_occurrences_with_wildcards( + StringClass str, + ForwardIterator it_string_to_find, ForwardIterator it_string_to_find_end, + ForwardIterator it_string_to_replace, ForwardIterator it_string_to_replace_end) +{ + for(; it_string_to_find != it_string_to_find_end && it_string_to_replace != it_string_to_replace_end; + ++it_string_to_find, ++ it_string_to_replace) { + + std::size_t wildcard_pos = it_string_to_find->find("*"); + if(wildcard_pos == StringClass::npos) { + ForwardIterator it_to_find_current_end(it_string_to_find); + ForwardIterator it_to_replace_current_end(it_string_to_replace); + str = replace_all_occurrences_of( + str, + it_string_to_find, ++it_to_find_current_end, + it_string_to_replace, ++it_to_replace_current_end); + continue; + } + + std::size_t wildcard_pos_replace = it_string_to_replace->find("*"); + + std::size_t found_begin = str.find( it_string_to_find->substr(0, wildcard_pos) ); + while( found_begin != StringClass::npos ) { + std::size_t found_end = str.find(it_string_to_find->substr(wildcard_pos+1), found_begin + wildcard_pos + 1); // to simplify + if( found_end != StringClass::npos ) { + + if( wildcard_pos_replace == StringClass::npos ) { + StringClass replace_content = *it_string_to_replace; + str.replace( + found_begin, + found_end + (it_string_to_find->size() - wildcard_pos - 1 ) - found_begin, + replace_content); + } else { + StringClass replace_content = + it_string_to_replace->substr(0, wildcard_pos_replace) + + str.substr(found_begin + wildcard_pos, + found_end - found_begin - wildcard_pos) + + it_string_to_replace->substr(wildcard_pos_replace+1) ; + str.replace( + found_begin, + found_end + (it_string_to_find->size() - wildcard_pos - 1 ) - found_begin, + replace_content); + + } + } + + // may adapt the restart to the replacement and be more efficient + found_begin = str.find( it_string_to_find->substr(0, wildcard_pos), found_begin + 1 ); + } + } + + return str; +} + } // namespace utils } // namespace unit_test } // namespace boost diff --git a/boost/test/utils/runtime/argument_factory.hpp b/boost/test/utils/runtime/argument_factory.hpp index a163deb9a2..f3448f8cc4 100644 --- a/boost/test/utils/runtime/argument_factory.hpp +++ b/boost/test/utils/runtime/argument_factory.hpp @@ -128,7 +128,7 @@ template<typename EnumType> struct value_interpreter<EnumType, true> { template<typename Modifiers> explicit value_interpreter( Modifiers const& m ) -#if !defined(BOOST_NO_CXX11_HDR_INITIALIZER_LIST) && !defined(BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX) +#if defined(BOOST_TEST_CLA_NEW_API) : m_name_to_value( m[enum_values<EnumType>::value] ) { } diff --git a/boost/test/utils/runtime/errors.hpp b/boost/test/utils/runtime/errors.hpp index c11686132c..37f8d9371b 100644 --- a/boost/test/utils/runtime/errors.hpp +++ b/boost/test/utils/runtime/errors.hpp @@ -74,60 +74,60 @@ class specific_param_error : public Base { protected: explicit specific_param_error( cstring param_name ) : Base( param_name ) {} ~specific_param_error() BOOST_NOEXCEPT_OR_NOTHROW {} -}; + +public: //____________________________________________________________________________// -#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES +#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) && \ + !defined(BOOST_NO_CXX11_REF_QUALIFIERS) -template<typename Derived, typename Base> -inline Derived -operator<<(specific_param_error<Derived, Base>&& ex, char const* val) -{ - ex.msg.append( val ); + Derived operator<<(char const* val) && + { + this->msg.append( val ); - return reinterpret_cast<Derived&&>(ex); -} + return reinterpret_cast<Derived&&>(*this); + } -//____________________________________________________________________________// + //____________________________________________________________________________// -template<typename Derived, typename Base, typename T> -inline Derived -operator<<(specific_param_error<Derived, Base>&& ex, T const& val) -{ - ex.msg.append( unit_test::utils::string_cast( val ) ); + template<typename T> + Derived operator<<(T const& val) && + { + this->msg.append( unit_test::utils::string_cast( val ) ); - return reinterpret_cast<Derived&&>(ex); -} + return reinterpret_cast<Derived&&>(*this); + } -//____________________________________________________________________________// + //____________________________________________________________________________// #else -template<typename Derived, typename Base> -inline Derived -operator<<(specific_param_error<Derived, Base> const& ex, char const* val) -{ - const_cast<specific_param_error<Derived, Base>&>(ex).msg.append( val ); + Derived const& operator<<(char const* val) const + { + const_cast<specific_param_error<Derived, Base>&>(*this).msg.append( val ); - return static_cast<Derived const&>(ex); -} + return static_cast<Derived const&>(*this); + } -//____________________________________________________________________________// + //____________________________________________________________________________// -template<typename Derived, typename Base, typename T> -inline Derived -operator<<(specific_param_error<Derived, Base> const& ex, T const& val) -{ - const_cast<specific_param_error<Derived, Base>&>(ex).msg.append( unit_test::utils::string_cast( val ) ); + template<typename T> + Derived const& operator<<(T const& val) const + { + const_cast<specific_param_error<Derived, Base>&>(*this).msg.append( unit_test::utils::string_cast( val ) ); - return static_cast<Derived const&>(ex); -} + return static_cast<Derived const&>(*this); + } -//____________________________________________________________________________// + //____________________________________________________________________________// #endif +}; + + + // ************************************************************************** // // ************** specific exception types ************** // // ************************************************************************** // diff --git a/boost/test/utils/runtime/modifier.hpp b/boost/test/utils/runtime/modifier.hpp index ed77ca0afe..f4f5a42baa 100644 --- a/boost/test/utils/runtime/modifier.hpp +++ b/boost/test/utils/runtime/modifier.hpp @@ -23,6 +23,15 @@ #include <boost/test/detail/suppress_warnings.hpp> + +// New CLA API available only for some C++11 compilers +#if !defined(BOOST_NO_CXX11_AUTO_DECLARATIONS) \ + && !defined(BOOST_NO_CXX11_TEMPLATE_ALIASES) \ + && !defined(BOOST_NO_CXX11_HDR_INITIALIZER_LIST) \ + && !defined(BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX) +#define BOOST_TEST_CLA_NEW_API +#endif + namespace boost { namespace runtime { @@ -32,10 +41,6 @@ namespace runtime { namespace { -#if !defined(BOOST_NO_CXX11_AUTO_DECLARATIONS) && !defined(BOOST_NO_CXX11_TEMPLATE_ALIASES) -#define BOOST_TEST_CLA_NEW_API -#endif - #ifdef BOOST_TEST_CLA_NEW_API auto const& description = unit_test::static_constant<nfp::typed_keyword<cstring,struct description_t>>::value; auto const& help = unit_test::static_constant<nfp::typed_keyword<cstring,struct help_t>>::value; |