Chris@16: // (C) Copyright Gennadiy Rozental 2005-2008. Chris@16: // Use, modification, and distribution are subject to the Chris@16: // Boost Software License, Version 1.0. (See accompanying file Chris@16: // http://www.boost.org/LICENSE_1_0.txt) Chris@16: Chris@16: // See http://www.boost.org/libs/test for the library home page. Chris@16: // Chris@16: // File : $RCSfile$ Chris@16: // Chris@101: // Version : $Revision$ Chris@16: // Chris@16: // Description : Facilities to perform exception safety tests Chris@16: // *************************************************************************** Chris@16: Chris@16: #ifndef BOOST_TEST_EXECUTION_SAFETY_IPP_112005GER Chris@16: #define BOOST_TEST_EXECUTION_SAFETY_IPP_112005GER Chris@16: Chris@16: // Boost.Test Chris@16: #include Chris@16: Chris@16: #if BOOST_TEST_SUPPORT_INTERACTION_TESTING Chris@16: Chris@16: #include Chris@16: #include Chris@16: Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: Chris@16: #include Chris@16: Chris@16: // Boost Chris@16: #include Chris@16: Chris@16: // STL Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: Chris@16: //____________________________________________________________________________// Chris@16: Chris@16: namespace boost { Chris@16: Chris@16: using namespace ::boost::unit_test; Chris@16: Chris@16: namespace itest { Chris@16: Chris@16: // ************************************************************************** // Chris@16: // ************** execution_path_point ************** // Chris@16: // ************************************************************************** // Chris@16: Chris@16: enum exec_path_point_type { EPP_SCOPE, EPP_EXCEPT, EPP_DECISION, EPP_ALLOC }; Chris@16: Chris@16: struct execution_path_point { Chris@16: execution_path_point( exec_path_point_type t, const_string file, std::size_t line_num ) Chris@16: : m_type( t ) Chris@16: , m_file_name( file ) Chris@16: , m_line_num( line_num ) Chris@16: {} Chris@16: Chris@16: exec_path_point_type m_type; Chris@16: const_string m_file_name; Chris@16: std::size_t m_line_num; Chris@16: Chris@16: // Execution path point specific Chris@16: struct decision_data { Chris@16: bool value; Chris@16: unsigned forced_exception_point; Chris@16: }; Chris@16: struct scope_data { Chris@16: unsigned size; Chris@16: char const* name; Chris@16: }; Chris@16: struct except_data { Chris@16: char const* description; Chris@16: }; Chris@16: struct alloc_data { Chris@16: void* ptr; Chris@16: std::size_t size; Chris@16: }; Chris@16: Chris@16: union { Chris@16: struct decision_data m_decision; Chris@16: struct scope_data m_scope; Chris@16: struct except_data m_except; Chris@16: struct alloc_data m_alloc; Chris@16: }; Chris@16: }; Chris@16: Chris@16: // ************************************************************************** // Chris@16: // ************** exception safety test implementation ************** // Chris@16: // ************************************************************************** // Chris@16: Chris@16: struct exception_safety_tester : itest::manager, test_observer { Chris@16: // helpers types Chris@16: struct unique_exception {}; Chris@16: Chris@16: // Constructor Chris@16: explicit exception_safety_tester( const_string test_name ); Chris@16: ~exception_safety_tester(); Chris@16: Chris@16: // check last run and prepare for next Chris@16: bool next_execution_path(); Chris@16: Chris@16: // memory tracking Chris@16: Chris@16: // manager interface implementation Chris@16: virtual void exception_point( const_string file, std::size_t line_num, const_string description ); Chris@16: virtual bool decision_point( const_string file, std::size_t line_num ); Chris@16: virtual unsigned enter_scope( const_string file, std::size_t line_num, const_string scope_name ); Chris@16: virtual void leave_scope( unsigned enter_scope_point ); Chris@16: virtual void allocated( const_string file, std::size_t line_num, void* p, std::size_t s ); Chris@16: virtual void freed( void* p ); Chris@16: Chris@16: // test observer interface Chris@16: virtual void assertion_result( bool passed ); Chris@16: virtual int priority() { return (std::numeric_limits::max)(); } // we want this observer to run the last Chris@16: Chris@16: private: Chris@16: void failure_point(); Chris@16: void report_error(); Chris@16: Chris@16: typedef std::vector exec_path; Chris@16: typedef std::map registry; Chris@16: Chris@16: // Data members Chris@16: bool m_internal_activity; Chris@16: Chris@16: unsigned m_exception_point_counter; Chris@16: unsigned m_forced_exception_point; Chris@16: Chris@16: unsigned m_exec_path_point; Chris@16: exec_path m_execution_path; Chris@16: Chris@16: unsigned m_exec_path_counter; Chris@16: unsigned m_break_exec_path; Chris@16: Chris@16: bool m_invairant_failed; Chris@16: registry m_memory_in_use; Chris@16: }; Chris@16: Chris@16: //____________________________________________________________________________// Chris@16: Chris@16: struct activity_guard { Chris@16: bool& m_v; Chris@16: Chris@16: activity_guard( bool& v ) : m_v( v ) { m_v = true; } Chris@16: ~activity_guard() { m_v = false; } Chris@16: }; Chris@16: Chris@16: //____________________________________________________________________________// Chris@16: Chris@16: exception_safety_tester::exception_safety_tester( const_string test_name ) Chris@16: : m_internal_activity( true ) Chris@16: , m_exception_point_counter( 0 ) Chris@16: , m_forced_exception_point( 1 ) Chris@16: , m_exec_path_point( 0 ) Chris@16: , m_exec_path_counter( 1 ) Chris@16: , m_break_exec_path( static_cast(-1) ) Chris@16: , m_invairant_failed( false ) Chris@16: { Chris@16: framework::register_observer( *this ); Chris@16: Chris@16: if( !runtime_config::break_exec_path().is_empty() ) { Chris@16: using namespace unit_test; Chris@16: Chris@16: string_token_iterator tit( runtime_config::break_exec_path(), Chris@16: (dropped_delimeters = ":",kept_delimeters = " ") ); Chris@16: Chris@16: const_string test_to_break = *tit; Chris@16: Chris@16: if( test_to_break == test_name ) { Chris@16: ++tit; Chris@16: Chris@16: m_break_exec_path = lexical_cast( *tit ); Chris@16: } Chris@16: } Chris@16: Chris@16: m_internal_activity = false; Chris@16: } Chris@16: Chris@16: //____________________________________________________________________________// Chris@16: Chris@16: exception_safety_tester::~exception_safety_tester() Chris@16: { Chris@16: m_internal_activity = true; Chris@16: Chris@16: framework::deregister_observer( *this ); Chris@16: } Chris@16: Chris@16: //____________________________________________________________________________// Chris@16: Chris@16: bool Chris@16: exception_safety_tester::next_execution_path() Chris@16: { Chris@16: activity_guard ag( m_internal_activity ); Chris@16: Chris@16: // check memory usage Chris@16: if( m_execution_path.size() > 0 ) { Chris@16: bool errors_detected = m_invairant_failed || (m_memory_in_use.size() != 0); Chris@16: framework::assertion_result( !errors_detected ); Chris@16: Chris@16: if( errors_detected ) Chris@16: report_error(); Chris@16: Chris@16: m_memory_in_use.clear(); Chris@16: } Chris@16: Chris@16: m_exec_path_point = 0; Chris@16: m_exception_point_counter = 0; Chris@16: m_invairant_failed = false; Chris@16: ++m_exec_path_counter; Chris@16: Chris@16: while( m_execution_path.size() > 0 ) { Chris@16: switch( m_execution_path.back().m_type ) { Chris@16: case EPP_SCOPE: Chris@16: case EPP_ALLOC: Chris@16: m_execution_path.pop_back(); Chris@16: break; Chris@16: Chris@16: case EPP_DECISION: Chris@16: if( !m_execution_path.back().m_decision.value ) { Chris@16: m_execution_path.pop_back(); Chris@16: break; Chris@16: } Chris@16: Chris@16: m_execution_path.back().m_decision.value = false; Chris@16: m_forced_exception_point = m_execution_path.back().m_decision.forced_exception_point; Chris@16: return true; Chris@16: Chris@16: case EPP_EXCEPT: Chris@16: m_execution_path.pop_back(); Chris@16: ++m_forced_exception_point; Chris@16: return true; Chris@16: } Chris@16: } Chris@16: Chris@16: BOOST_TEST_MESSAGE( "Total tested " << --m_exec_path_counter << " execution path" ); Chris@16: Chris@16: return false; Chris@16: } Chris@16: Chris@16: //____________________________________________________________________________// Chris@16: Chris@16: void Chris@16: exception_safety_tester::exception_point( const_string file, std::size_t line_num, const_string description ) Chris@16: { Chris@16: activity_guard ag( m_internal_activity ); Chris@16: Chris@16: if( ++m_exception_point_counter == m_forced_exception_point ) { Chris@16: m_execution_path.push_back( Chris@16: execution_path_point( EPP_EXCEPT, file, line_num ) ); Chris@16: Chris@16: m_execution_path.back().m_except.description = description.begin(); Chris@16: Chris@16: ++m_exec_path_point; Chris@16: Chris@16: failure_point(); Chris@16: } Chris@16: } Chris@16: Chris@16: //____________________________________________________________________________// Chris@16: Chris@16: bool Chris@16: exception_safety_tester::decision_point( const_string file, std::size_t line_num ) Chris@16: { Chris@16: activity_guard ag( m_internal_activity ); Chris@16: Chris@16: if( m_exec_path_point < m_execution_path.size() ) { Chris@16: BOOST_REQUIRE_MESSAGE( m_execution_path[m_exec_path_point].m_type == EPP_DECISION && Chris@16: m_execution_path[m_exec_path_point].m_file_name == file && Chris@16: m_execution_path[m_exec_path_point].m_line_num == line_num, Chris@16: "Function under test exibit non-deterministic behavior" ); Chris@16: } Chris@16: else { Chris@16: m_execution_path.push_back( Chris@16: execution_path_point( EPP_DECISION, file, line_num ) ); Chris@16: Chris@16: m_execution_path.back().m_decision.value = true; Chris@16: m_execution_path.back().m_decision.forced_exception_point = m_forced_exception_point; Chris@16: } Chris@16: Chris@16: return m_execution_path[m_exec_path_point++].m_decision.value; Chris@16: } Chris@16: Chris@16: //____________________________________________________________________________// Chris@16: Chris@16: unsigned Chris@16: exception_safety_tester::enter_scope( const_string file, std::size_t line_num, const_string scope_name ) Chris@16: { Chris@16: activity_guard ag( m_internal_activity ); Chris@16: Chris@16: if( m_exec_path_point < m_execution_path.size() ) { Chris@16: BOOST_REQUIRE_MESSAGE( m_execution_path[m_exec_path_point].m_type == EPP_SCOPE && Chris@16: m_execution_path[m_exec_path_point].m_file_name == file && Chris@16: m_execution_path[m_exec_path_point].m_line_num == line_num, Chris@16: "Function under test exibit non-deterministic behavior" ); Chris@16: } Chris@16: else { Chris@16: m_execution_path.push_back( Chris@16: execution_path_point( EPP_SCOPE, file, line_num ) ); Chris@16: } Chris@16: Chris@16: m_execution_path[m_exec_path_point].m_scope.size = 0; Chris@16: m_execution_path[m_exec_path_point].m_scope.name = scope_name.begin(); Chris@16: Chris@16: return m_exec_path_point++; Chris@16: } Chris@16: Chris@16: //____________________________________________________________________________// Chris@16: Chris@16: void Chris@16: exception_safety_tester::leave_scope( unsigned enter_scope_point ) Chris@16: { Chris@16: activity_guard ag( m_internal_activity ); Chris@16: Chris@16: BOOST_REQUIRE_MESSAGE( m_execution_path[enter_scope_point].m_type == EPP_SCOPE, Chris@16: "Function under test exibit non-deterministic behavior" ); Chris@16: Chris@16: m_execution_path[enter_scope_point].m_scope.size = m_exec_path_point - enter_scope_point; Chris@16: } Chris@16: Chris@16: //____________________________________________________________________________// Chris@16: Chris@16: void Chris@16: exception_safety_tester::allocated( const_string file, std::size_t line_num, void* p, std::size_t s ) Chris@16: { Chris@16: if( m_internal_activity ) Chris@16: return; Chris@16: Chris@16: activity_guard ag( m_internal_activity ); Chris@16: Chris@16: if( m_exec_path_point < m_execution_path.size() ) Chris@16: BOOST_REQUIRE_MESSAGE( m_execution_path[m_exec_path_point].m_type == EPP_ALLOC, Chris@16: "Function under test exibit non-deterministic behavior" ); Chris@16: else Chris@16: m_execution_path.push_back( Chris@16: execution_path_point( EPP_ALLOC, file, line_num ) ); Chris@16: Chris@16: m_execution_path[m_exec_path_point].m_alloc.ptr = p; Chris@16: m_execution_path[m_exec_path_point].m_alloc.size = s; Chris@16: Chris@16: m_memory_in_use.insert( std::make_pair( p, m_exec_path_point++ ) ); Chris@16: } Chris@16: Chris@16: //____________________________________________________________________________// Chris@16: Chris@16: void Chris@16: exception_safety_tester::freed( void* p ) Chris@16: { Chris@16: if( m_internal_activity ) Chris@16: return; Chris@16: Chris@16: activity_guard ag( m_internal_activity ); Chris@16: Chris@16: registry::iterator it = m_memory_in_use.find( p ); Chris@16: if( it != m_memory_in_use.end() ) { Chris@16: m_execution_path[it->second].m_alloc.ptr = 0; Chris@16: m_memory_in_use.erase( it ); Chris@16: } Chris@16: } Chris@16: Chris@16: //____________________________________________________________________________// Chris@16: Chris@16: void Chris@16: exception_safety_tester::assertion_result( bool passed ) Chris@16: { Chris@16: if( !m_internal_activity && !passed ) { Chris@16: m_invairant_failed = true; Chris@16: Chris@16: failure_point(); Chris@16: } Chris@16: } Chris@16: Chris@16: //____________________________________________________________________________// Chris@16: Chris@16: void Chris@16: exception_safety_tester::failure_point() Chris@16: { Chris@16: if( m_exec_path_counter == m_break_exec_path ) Chris@16: debug::debugger_break(); Chris@16: Chris@16: throw unique_exception(); Chris@16: } Chris@16: Chris@16: //____________________________________________________________________________// Chris@16: Chris@16: namespace { Chris@16: Chris@16: inline void Chris@16: format_location( wrap_stringstream& formatter, execution_path_point const& /*p*/, unsigned indent ) Chris@16: { Chris@16: if( indent ) Chris@16: formatter << std::left << std::setw( indent ) << ""; Chris@16: Chris@16: // !! ?? optional if( p.m_file_name ) Chris@16: // formatter << p.m_file_name << '(' << p.m_line_num << "): "; Chris@16: } Chris@16: Chris@16: //____________________________________________________________________________// Chris@16: Chris@16: template Chris@16: inline void Chris@16: format_execution_path( wrap_stringstream& formatter, ExecPathIt it, ExecPathIt end, unsigned indent = 0 ) Chris@16: { Chris@16: while( it != end ) { Chris@16: switch( it->m_type ) { Chris@16: case EPP_SCOPE: Chris@16: format_location( formatter, *it, indent ); Chris@16: formatter << "> \"" << it->m_scope.name << "\"\n"; Chris@16: format_execution_path( formatter, it+1, it + it->m_scope.size, indent + 2 ); Chris@16: format_location( formatter, *it, indent ); Chris@16: formatter << "< \"" << it->m_scope.name << "\"\n"; Chris@16: it += it->m_scope.size; Chris@16: break; Chris@16: Chris@16: case EPP_DECISION: Chris@16: format_location( formatter, *it, indent ); Chris@16: formatter << "Decision made as " << std::boolalpha << it->m_decision.value << '\n'; Chris@16: ++it; Chris@16: break; Chris@16: Chris@16: case EPP_EXCEPT: Chris@16: format_location( formatter, *it, indent ); Chris@16: formatter << "Forced failure"; Chris@16: if( it->m_except.description ) Chris@16: formatter << ": " << it->m_except.description; Chris@16: formatter << "\n"; Chris@16: ++it; Chris@16: break; Chris@16: Chris@16: case EPP_ALLOC: Chris@16: if( it->m_alloc.ptr ) { Chris@16: format_location( formatter, *it, indent ); Chris@16: formatter << "Allocated memory block 0x" << std::uppercase << it->m_alloc.ptr Chris@16: << ", " << it->m_alloc.size << " bytes long: <"; Chris@16: Chris@16: unsigned i; Chris@16: for( i = 0; i < std::min( it->m_alloc.size, 8 ); i++ ) { Chris@16: unsigned char c = static_cast(it->m_alloc.ptr)[i]; Chris@16: if( (std::isprint)( c ) ) Chris@16: formatter << c; Chris@16: else Chris@16: formatter << '.'; Chris@16: } Chris@16: Chris@16: formatter << "> "; Chris@16: Chris@16: for( i = 0; i < std::min( it->m_alloc.size, 8 ); i++ ) { Chris@16: unsigned c = static_cast(it->m_alloc.ptr)[i]; Chris@16: formatter << std::hex << std::uppercase << c << ' '; Chris@16: } Chris@16: Chris@16: formatter << "\n"; Chris@16: } Chris@16: ++it; Chris@16: break; Chris@16: } Chris@16: } Chris@16: } Chris@16: Chris@16: //____________________________________________________________________________// Chris@16: Chris@16: } // local namespace Chris@16: Chris@16: void Chris@16: exception_safety_tester::report_error() Chris@16: { Chris@16: activity_guard ag( m_internal_activity ); Chris@16: Chris@16: unit_test_log << unit_test::log::begin( m_execution_path.back().m_file_name, Chris@16: m_execution_path.back().m_line_num ) Chris@16: << log_all_errors; Chris@16: Chris@16: wrap_stringstream formatter; Chris@16: Chris@16: if( m_invairant_failed ) Chris@16: formatter << "Failed invariant"; Chris@16: Chris@16: if( m_memory_in_use.size() != 0 ) { Chris@16: if( m_invairant_failed ) Chris@16: formatter << " and "; Chris@16: Chris@16: formatter << static_cast(m_memory_in_use.size()) << " memory leak"; Chris@16: if( m_memory_in_use.size() > 1 ) Chris@16: formatter << 's'; Chris@16: } Chris@16: formatter << " detected in the execution path " << m_exec_path_counter << ":\n"; Chris@16: Chris@16: format_execution_path( formatter, m_execution_path.begin(), m_execution_path.end() ); Chris@16: Chris@16: unit_test_log << const_string( formatter.str() ) << unit_test::log::end(); Chris@16: } Chris@16: Chris@16: //____________________________________________________________________________// Chris@16: Chris@16: // ************************************************************************** // Chris@16: // ************** exception safety test ************** // Chris@16: // ************************************************************************** // Chris@16: Chris@16: void BOOST_TEST_DECL Chris@16: exception_safety( callback0<> const& F, const_string test_name ) Chris@16: { Chris@16: exception_safety_tester est( test_name ); Chris@16: Chris@16: do { Chris@16: try { Chris@16: F(); Chris@16: } Chris@16: catch( exception_safety_tester::unique_exception const& ) {} Chris@16: Chris@16: } while( est.next_execution_path() ); Chris@16: } Chris@16: Chris@16: //____________________________________________________________________________// Chris@16: Chris@16: } // namespace itest Chris@16: Chris@16: } // namespace boost Chris@16: Chris@16: //____________________________________________________________________________// Chris@16: Chris@16: #include Chris@16: Chris@16: #endif // non-ancient compiler Chris@16: Chris@16: #endif // BOOST_TEST_EXECUTION_SAFETY_IPP_112005GER