// (C) Copyright Gennadiy Rozental 2001. // 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 : $RCSfile$ // // Version : $Revision$ // // Description : supplies offline implementation for the Test Tools // *************************************************************************** #ifndef BOOST_TEST_TEST_TOOLS_IPP_012205GER #define BOOST_TEST_TEST_TOOLS_IPP_012205GER // Boost.Test #include #include #include #include #include #include #include #include // execution_aborted #include #include // Boost #include // STL #include #include #include #include #include #include #include #include #include // !! should we use #include #include #include //____________________________________________________________________________// # ifdef BOOST_NO_STDC_NAMESPACE namespace std { using ::strcmp; using ::strlen; using ::isprint; } #if !defined( BOOST_NO_CWCHAR ) namespace std { using ::wcscmp; } #endif # endif namespace boost { namespace test_tools { namespace tt_detail { // ************************************************************************** // // ************** print_log_value ************** // // ************************************************************************** // void print_log_value::operator()( std::ostream& ostr, bool t ) { ostr << std::boolalpha << t; } void print_log_value::operator()( std::ostream& ostr, char t ) { if( (std::isprint)( static_cast(t) ) ) ostr << '\'' << t << '\''; else ostr << std::hex #if BOOST_TEST_USE_STD_LOCALE << std::showbase #else << "0x" #endif << static_cast(t); } //____________________________________________________________________________// void print_log_value::operator()( std::ostream& ostr, unsigned char t ) { ostr << std::hex // showbase is only available for new style streams: #if BOOST_TEST_USE_STD_LOCALE << std::showbase #else << "0x" #endif << static_cast(t); } //____________________________________________________________________________// void print_log_value::operator()( std::ostream& ostr, char const* t ) { ostr << ( t ? t : "null string" ); } //____________________________________________________________________________// void print_log_value::operator()( std::ostream& ostr, wchar_t const* t ) { ostr << ( t ? t : L"null string" ); } #if !defined(BOOST_NO_CXX11_NULLPTR) void print_log_value::operator()( std::ostream& ostr, std::nullptr_t ) { ostr << "nullptr"; } #endif //____________________________________________________________________________// // ************************************************************************** // // ************** TOOL BOX Implementation ************** // // ************************************************************************** // using ::boost::unit_test::lazy_ostream; static char const* check_str [] = { " == ", " != ", " < " , " <= ", " > " , " >= " }; static char const* rever_str [] = { " != ", " == ", " >= ", " > " , " <= ", " < " }; template void format_report( OutStream& os, assertion_result const& pr, unit_test::lazy_ostream const& assertion_descr, tool_level tl, check_type ct, std::size_t num_args, va_list args, char const* prefix, char const* suffix ) { using namespace unit_test; switch( ct ) { case CHECK_PRED: os << prefix << assertion_descr << suffix; if( !pr.has_empty_message() ) os << ". " << pr.message(); break; case CHECK_BUILT_ASSERTION: { os << prefix << assertion_descr << suffix; if( tl != PASS ) { const_string details_message = pr.message(); if( !details_message.is_empty() ) { os << details_message; } } break; } case CHECK_MSG: if( tl == PASS ) os << prefix << "'" << assertion_descr << "'" << suffix; else os << assertion_descr; if( !pr.has_empty_message() ) os << ". " << pr.message(); break; case CHECK_EQUAL: case CHECK_NE: case CHECK_LT: case CHECK_LE: case CHECK_GT: case CHECK_GE: { char const* arg1_descr = va_arg( args, char const* ); lazy_ostream const* arg1_val = va_arg( args, lazy_ostream const* ); char const* arg2_descr = va_arg( args, char const* ); lazy_ostream const* arg2_val = va_arg( args, lazy_ostream const* ); os << prefix << arg1_descr << check_str[ct-CHECK_EQUAL] << arg2_descr << suffix; if( tl != PASS ) os << " [" << *arg1_val << rever_str[ct-CHECK_EQUAL] << *arg2_val << "]" ; if( !pr.has_empty_message() ) os << ". " << pr.message(); break; } case CHECK_CLOSE: case CHECK_CLOSE_FRACTION: { char const* arg1_descr = va_arg( args, char const* ); lazy_ostream const* arg1_val = va_arg( args, lazy_ostream const* ); char const* arg2_descr = va_arg( args, char const* ); lazy_ostream const* arg2_val = va_arg( args, lazy_ostream const* ); /* toler_descr = */ va_arg( args, char const* ); lazy_ostream const* toler_val = va_arg( args, lazy_ostream const* ); os << "difference{" << pr.message() << "} between " << arg1_descr << "{" << *arg1_val << "} and " << arg2_descr << "{" << *arg2_val << ( tl == PASS ? "} doesn't exceed " : "} exceeds " ) << *toler_val; if( ct == CHECK_CLOSE ) os << "%"; break; } case CHECK_SMALL: { char const* arg1_descr = va_arg( args, char const* ); lazy_ostream const* arg1_val = va_arg( args, lazy_ostream const* ); /* toler_descr = */ va_arg( args, char const* ); lazy_ostream const* toler_val = va_arg( args, lazy_ostream const* ); os << "absolute value of " << arg1_descr << "{" << *arg1_val << "}" << ( tl == PASS ? " doesn't exceed " : " exceeds " ) << *toler_val; if( !pr.has_empty_message() ) os << ". " << pr.message(); break; } case CHECK_PRED_WITH_ARGS: { std::vector< std::pair > args_copy; args_copy.reserve( num_args ); for( std::size_t i = 0; i < num_args; ++i ) { char const* desc = va_arg( args, char const* ); lazy_ostream const* value = va_arg( args, lazy_ostream const* ); args_copy.push_back( std::make_pair( desc, value ) ); } os << prefix << assertion_descr; // print predicate call description os << "( "; for( std::size_t i = 0; i < num_args; ++i ) { os << args_copy[i].first; if( i != num_args-1 ) os << ", "; } os << " )" << suffix; if( tl != PASS ) { os << " for ( "; for( std::size_t i = 0; i < num_args; ++i ) { os << *args_copy[i].second; if( i != num_args-1 ) os << ", "; } os << " )"; } if( !pr.has_empty_message() ) os << ". " << pr.message(); break; } case CHECK_EQUAL_COLL: { char const* left_begin_descr = va_arg( args, char const* ); char const* left_end_descr = va_arg( args, char const* ); char const* right_begin_descr = va_arg( args, char const* ); char const* right_end_descr = va_arg( args, char const* ); os << prefix << "{ " << left_begin_descr << ", " << left_end_descr << " } == { " << right_begin_descr << ", " << right_end_descr << " }" << suffix; if( !pr.has_empty_message() ) os << ". " << pr.message(); break; } case CHECK_BITWISE_EQUAL: { char const* left_descr = va_arg( args, char const* ); char const* right_descr = va_arg( args, char const* ); os << prefix << left_descr << " =.= " << right_descr << suffix; if( !pr.has_empty_message() ) os << ". " << pr.message(); break; } } } //____________________________________________________________________________// bool report_assertion( assertion_result const& ar, lazy_ostream const& assertion_descr, const_string file_name, std::size_t line_num, tool_level tl, check_type ct, std::size_t num_args, ... ) { using namespace unit_test; if( !framework::test_in_progress() ) { // in case no test is in progress, we do not throw anything: // raising an exception here may result in raising an exception in a destructor of a global fixture // which will abort the process // We flag this as aborted instead //BOOST_TEST_I_ASSRT( framework::current_test_case_id() != INV_TEST_UNIT_ID, // std::runtime_error( "Can't use testing tools outside of test case implementation." ) ); framework::test_aborted(); return false; } if( !!ar ) tl = PASS; log_level ll; char const* prefix; char const* suffix; switch( tl ) { case PASS: ll = log_successful_tests; prefix = "check "; suffix = " has passed"; break; case WARN: ll = log_warnings; prefix = "condition "; suffix = " is not satisfied"; break; case CHECK: ll = log_all_errors; prefix = "check "; suffix = " has failed"; break; case REQUIRE: ll = log_fatal_errors; prefix = "critical check "; suffix = " has failed"; break; default: return true; } unit_test_log << unit_test::log::begin( file_name, line_num ) << ll; va_list args; va_start( args, num_args ); format_report( unit_test_log, ar, assertion_descr, tl, ct, num_args, args, prefix, suffix ); va_end( args ); unit_test_log << unit_test::log::end(); switch( tl ) { case PASS: framework::assertion_result( AR_PASSED ); return true; case WARN: framework::assertion_result( AR_TRIGGERED ); return false; case CHECK: framework::assertion_result( AR_FAILED ); return false; case REQUIRE: framework::assertion_result( AR_FAILED ); framework::test_unit_aborted( framework::current_test_unit() ); BOOST_TEST_I_THROW( execution_aborted() ); return false; } return true; } //____________________________________________________________________________// assertion_result format_assertion_result( const_string expr_val, const_string details ) { assertion_result res(false); bool starts_new_line = first_char( expr_val ) == '\n'; if( !starts_new_line && !expr_val.is_empty() ) res.message().stream() << " [" << expr_val << "]"; if( !details.is_empty() ) { if( first_char(details) != '[' ) res.message().stream() << ". "; else res.message().stream() << " "; res.message().stream() << details; } if( starts_new_line ) res.message().stream() << "." << expr_val; return res; } //____________________________________________________________________________// BOOST_TEST_DECL std::string prod_report_format( assertion_result const& ar, unit_test::lazy_ostream const& assertion_descr, check_type ct, std::size_t num_args, ... ) { std::ostringstream msg_buff; va_list args; va_start( args, num_args ); format_report( msg_buff, ar, assertion_descr, CHECK, ct, num_args, args, "assertion ", " failed" ); va_end( args ); return msg_buff.str(); } //____________________________________________________________________________// assertion_result equal_impl( char const* left, char const* right ) { return (left && right) ? std::strcmp( left, right ) == 0 : (left == right); } //____________________________________________________________________________// #if !defined( BOOST_NO_CWCHAR ) assertion_result equal_impl( wchar_t const* left, wchar_t const* right ) { return (left && right) ? std::wcscmp( left, right ) == 0 : (left == right); } #endif // !defined( BOOST_NO_CWCHAR ) //____________________________________________________________________________// bool is_defined_impl( const_string symbol_name, const_string symbol_value ) { symbol_value.trim_left( 2 ); return symbol_name != symbol_value; } //____________________________________________________________________________// // ************************************************************************** // // ************** context_frame ************** // // ************************************************************************** // context_frame::context_frame( ::boost::unit_test::lazy_ostream const& context_descr ) : m_frame_id( unit_test::framework::add_context( context_descr, true ) ) { } //____________________________________________________________________________// context_frame::~context_frame() { unit_test::framework::clear_context( m_frame_id ); } //____________________________________________________________________________// context_frame::operator bool() { return true; } //____________________________________________________________________________// } // namespace tt_detail // ************************************************************************** // // ************** output_test_stream ************** // // ************************************************************************** // struct output_test_stream::Impl { std::fstream m_pattern; bool m_match_or_save; bool m_text_or_binary; std::string m_synced_string; char get_char() { char res = 0; do { m_pattern.get( res ); } while( m_text_or_binary && res == '\r' && !m_pattern.fail() && !m_pattern.eof() ); return res; } void check_and_fill( assertion_result& res ) { if( !res.p_predicate_value ) res.message() << "Output content: \"" << m_synced_string << '\"'; } }; //____________________________________________________________________________// output_test_stream::output_test_stream( const_string pattern_file_name, bool match_or_save, bool text_or_binary ) : m_pimpl( new Impl ) { if( !pattern_file_name.is_empty() ) { std::ios::openmode m = match_or_save ? std::ios::in : std::ios::out; if( !text_or_binary ) m |= std::ios::binary; m_pimpl->m_pattern.open( pattern_file_name.begin(), m ); if( !m_pimpl->m_pattern.is_open() ) 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; m_pimpl->m_text_or_binary = text_or_binary; } //____________________________________________________________________________// output_test_stream::~output_test_stream() { delete m_pimpl; } //____________________________________________________________________________// assertion_result output_test_stream::is_empty( bool flush_stream ) { sync(); assertion_result res( m_pimpl->m_synced_string.empty() ); m_pimpl->check_and_fill( res ); if( flush_stream ) flush(); return res; } //____________________________________________________________________________// assertion_result output_test_stream::check_length( std::size_t length_, bool flush_stream ) { sync(); assertion_result res( m_pimpl->m_synced_string.length() == length_ ); m_pimpl->check_and_fill( res ); if( flush_stream ) flush(); return res; } //____________________________________________________________________________// assertion_result output_test_stream::is_equal( const_string arg, bool flush_stream ) { sync(); assertion_result res( const_string( m_pimpl->m_synced_string ) == arg ); m_pimpl->check_and_fill( res ); if( flush_stream ) flush(); return res; } //____________________________________________________________________________// 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 ) { int offset = 0; std::vector last_elements; for ( std::string::size_type i = 0; static_cast(i + offset) < static_cast(stream_string_repr.length()); ++i ) { char c = m_pimpl->get_char(); 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() && (stream_string_repr[i+offset] == c); if( !is_same ) { result = false; 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 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() << 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 matching 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() << ' '; } result.message() << '~'; // places the first tilde at the current char that mismatches for( std::size_t k = 1; 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() << "~"; } if( m_pimpl->m_pattern.eof() ) { result.message() << " (reference string shorter than current stream)"; } result.message() << "\n"; // no need to continue if the EOF is reached if( m_pimpl->m_pattern.eof() ) { break; } // 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; } } // not needed anymore /* if(offset > 0 && false) { m_pimpl->m_pattern.ignore( static_cast( offset )); } */ } else { m_pimpl->m_pattern.write( stream_string_repr.c_str(), static_cast( stream_string_repr.length() ) ); m_pimpl->m_pattern.flush(); } } if( flush_stream ) flush(); return result; } //____________________________________________________________________________// void output_test_stream::flush() { m_pimpl->m_synced_string.erase(); #ifndef BOOST_NO_STRINGSTREAM str( std::string() ); #else seekp( 0, std::ios::beg ); #endif } std::string output_test_stream::get_stream_string_representation() const { return m_pimpl->m_synced_string; } //____________________________________________________________________________// std::size_t output_test_stream::length() { sync(); return m_pimpl->m_synced_string.length(); } //____________________________________________________________________________// void output_test_stream::sync() { #ifdef BOOST_NO_STRINGSTREAM m_pimpl->m_synced_string.assign( str(), pcount() ); freeze( false ); #else m_pimpl->m_synced_string = str(); #endif } //____________________________________________________________________________// } // namespace test_tools } // namespace boost #include #endif // BOOST_TEST_TEST_TOOLS_IPP_012205GER