// (C) Copyright Gennadiy Rozental 2005. // Use, modification, and distribution are subject to the // Boost Software License, Version 1.0. (See accompanying file // http://www.boost.org/LICENSE_1_0.txt) // See http://www.boost.org/libs/test for the library home page. // // File : $RCSfile: exception_safety.ipp,v $ // // Version : $Revision: 1.7.2.1 $ // // Description : Facilities to perform exception safety tests // *************************************************************************** #ifndef BOOST_TEST_EXECUTION_SAFETY_IPP_112005GER #define BOOST_TEST_EXECUTION_SAFETY_IPP_112005GER // Boost.Test #include #if !BOOST_WORKAROUND(__GNUC__, < 3) && \ !BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) && \ !BOOST_WORKAROUND(BOOST_MSVC, <1310) && \ !BOOST_WORKAROUND(__SUNPRO_CC, BOOST_TESTED_AT(0x530)) #include #include #include #include #include #include #include #include #include #include #include // Boost #include // STL #include #include #include #include #include #include //____________________________________________________________________________// namespace boost { using namespace ::boost::unit_test; namespace itest { // ************************************************************************** // // ************** execution_path_point ************** // // ************************************************************************** // enum exec_path_point_type { EPP_SCOPE, EPP_EXCEPT, EPP_DECISION, EPP_ALLOC }; struct execution_path_point { execution_path_point( exec_path_point_type t, const_string file, std::size_t line_num ) : m_type( t ) , m_file_name( file ) , m_line_num( line_num ) {} exec_path_point_type m_type; const_string m_file_name; std::size_t m_line_num; // Execution path point specific struct decision_data { bool value; unsigned forced_exception_point; }; struct scope_data { unsigned size; char const* name; }; struct except_data { char const* description; }; struct alloc_data { void* ptr; std::size_t size; }; union { struct decision_data m_decision; struct scope_data m_scope; struct except_data m_except; struct alloc_data m_alloc; }; }; // ************************************************************************** // // ************** exception safety test implementation ************** // // ************************************************************************** // struct exception_safety_tester : itest::manager, test_observer { // helpers types struct unique_exception {}; // Constructor explicit exception_safety_tester( const_string test_name ); ~exception_safety_tester(); // check last run and prepare for next bool next_execution_path(); // memory tracking // manager interface implementation virtual void exception_point( const_string file, std::size_t line_num, const_string description ); virtual bool decision_point( const_string file, std::size_t line_num ); virtual unsigned enter_scope( const_string file, std::size_t line_num, const_string scope_name ); virtual void leave_scope( unsigned enter_scope_point ); virtual void allocated( const_string file, std::size_t line_num, void* p, std::size_t s ); virtual void freed( void* p ); // test observer interface virtual void assertion_result( bool passed ); virtual int priority() { return (std::numeric_limits::max)(); } // we want this observer to run the last private: void failure_point(); void report_error(); typedef std::vector exec_path; typedef std::map registry; // Data members bool m_internal_activity; unsigned m_exception_point_counter; unsigned m_forced_exception_point; unsigned m_exec_path_point; exec_path m_execution_path; unsigned m_exec_path_counter; unsigned m_break_exec_path; bool m_invairant_failed; registry m_memory_in_use; }; //____________________________________________________________________________// struct activity_guard { bool& m_v; activity_guard( bool& v ) : m_v( v ) { m_v = true; } ~activity_guard() { m_v = false; } }; //____________________________________________________________________________// exception_safety_tester::exception_safety_tester( const_string test_name ) : m_internal_activity( true ) , m_exception_point_counter( 0 ) , m_forced_exception_point( 1 ) , m_exec_path_point( 0 ) , m_exec_path_counter( 1 ) , m_break_exec_path( (unsigned)-1 ) , m_invairant_failed( false ) { framework::register_observer( *this ); if( !runtime_config::break_exec_path().is_empty() ) { using namespace unit_test; string_token_iterator tit( runtime_config::break_exec_path(), (dropped_delimeters = ":",kept_delimeters = " ") ); const_string test_to_break = *tit; if( test_to_break == test_name ) { ++tit; m_break_exec_path = lexical_cast( *tit ); } } m_internal_activity = false; } //____________________________________________________________________________// exception_safety_tester::~exception_safety_tester() { m_internal_activity = true; framework::deregister_observer( *this ); } //____________________________________________________________________________// bool exception_safety_tester::next_execution_path() { activity_guard ag( m_internal_activity ); // check memory usage if( m_execution_path.size() > 0 ) { bool errors_detected = m_invairant_failed || (m_memory_in_use.size() != 0); framework::assertion_result( !errors_detected ); if( errors_detected ) report_error(); m_memory_in_use.clear(); } m_exec_path_point = 0; m_exception_point_counter = 0; m_invairant_failed = false; ++m_exec_path_counter; while( m_execution_path.size() > 0 ) { switch( m_execution_path.back().m_type ) { case EPP_SCOPE: case EPP_ALLOC: m_execution_path.pop_back(); break; case EPP_DECISION: if( !m_execution_path.back().m_decision.value ) { m_execution_path.pop_back(); break; } m_execution_path.back().m_decision.value = false; m_forced_exception_point = m_execution_path.back().m_decision.forced_exception_point; return true; case EPP_EXCEPT: m_execution_path.pop_back(); ++m_forced_exception_point; return true; } } BOOST_TEST_MESSAGE( "Total tested " << --m_exec_path_counter << " execution path" ); return false; } //____________________________________________________________________________// void exception_safety_tester::exception_point( const_string file, std::size_t line_num, const_string description ) { activity_guard ag( m_internal_activity ); if( ++m_exception_point_counter == m_forced_exception_point ) { m_execution_path.push_back( execution_path_point( EPP_EXCEPT, file, line_num ) ); m_execution_path.back().m_except.description = description.begin(); ++m_exec_path_point; failure_point(); } } //____________________________________________________________________________// bool exception_safety_tester::decision_point( const_string file, std::size_t line_num ) { activity_guard ag( m_internal_activity ); if( m_exec_path_point < m_execution_path.size() ) { BOOST_REQUIRE_MESSAGE( m_execution_path[m_exec_path_point].m_type == EPP_DECISION && m_execution_path[m_exec_path_point].m_file_name == file && m_execution_path[m_exec_path_point].m_line_num == line_num, "Function under test exibit non-deterministic behavior" ); } else { m_execution_path.push_back( execution_path_point( EPP_DECISION, file, line_num ) ); m_execution_path.back().m_decision.value = true; m_execution_path.back().m_decision.forced_exception_point = m_forced_exception_point; } return m_execution_path[m_exec_path_point++].m_decision.value; } //____________________________________________________________________________// unsigned exception_safety_tester::enter_scope( const_string file, std::size_t line_num, const_string scope_name ) { activity_guard ag( m_internal_activity ); if( m_exec_path_point < m_execution_path.size() ) { BOOST_REQUIRE_MESSAGE( m_execution_path[m_exec_path_point].m_type == EPP_SCOPE && m_execution_path[m_exec_path_point].m_file_name == file && m_execution_path[m_exec_path_point].m_line_num == line_num, "Function under test exibit non-deterministic behavior" ); } else { m_execution_path.push_back( execution_path_point( EPP_SCOPE, file, line_num ) ); } m_execution_path[m_exec_path_point].m_scope.size = 0; m_execution_path[m_exec_path_point].m_scope.name = scope_name.begin(); return m_exec_path_point++; } //____________________________________________________________________________// void exception_safety_tester::leave_scope( unsigned enter_scope_point ) { activity_guard ag( m_internal_activity ); BOOST_REQUIRE_MESSAGE( m_execution_path[enter_scope_point].m_type == EPP_SCOPE, "Function under test exibit non-deterministic behavior" ); m_execution_path[enter_scope_point].m_scope.size = m_exec_path_point - enter_scope_point; } //____________________________________________________________________________// void exception_safety_tester::allocated( const_string file, std::size_t line_num, void* p, std::size_t s ) { if( m_internal_activity ) return; activity_guard ag( m_internal_activity ); if( m_exec_path_point < m_execution_path.size() ) BOOST_REQUIRE_MESSAGE( m_execution_path[m_exec_path_point].m_type == EPP_ALLOC, "Function under test exibit non-deterministic behavior" ); else m_execution_path.push_back( execution_path_point( EPP_ALLOC, file, line_num ) ); m_execution_path[m_exec_path_point].m_alloc.ptr = p; m_execution_path[m_exec_path_point].m_alloc.size = s; m_memory_in_use.insert( std::make_pair( p, m_exec_path_point++ ) ); } //____________________________________________________________________________// void exception_safety_tester::freed( void* p ) { if( m_internal_activity ) return; activity_guard ag( m_internal_activity ); registry::iterator it = m_memory_in_use.find( p ); if( it != m_memory_in_use.end() ) { m_execution_path[it->second].m_alloc.ptr = 0; m_memory_in_use.erase( it ); } } //____________________________________________________________________________// void exception_safety_tester::assertion_result( bool passed ) { if( !m_internal_activity && !passed ) { m_invairant_failed = true; failure_point(); } } //____________________________________________________________________________// void exception_safety_tester::failure_point() { if( m_exec_path_counter == m_break_exec_path ) BOOST_ASSERT( false ); throw unique_exception(); } //____________________________________________________________________________// namespace { inline void format_location( wrap_stringstream& formatter, execution_path_point const& p, unsigned indent ) { if( indent ) formatter << std::left << std::setw( indent ) << ""; // !! ?? optional if( p.m_file_name ) // formatter << p.m_file_name << '(' << p.m_line_num << "): "; } //____________________________________________________________________________// template inline void format_execution_path( wrap_stringstream& formatter, ExecPathIt it, ExecPathIt end, unsigned indent = 0 ) { while( it != end ) { switch( it->m_type ) { case EPP_SCOPE: format_location( formatter, *it, indent ); formatter << "> \"" << it->m_scope.name << "\"\n"; format_execution_path( formatter, it+1, it + it->m_scope.size, indent + 2 ); format_location( formatter, *it, indent ); formatter << "< \"" << it->m_scope.name << "\"\n"; it += it->m_scope.size; break; case EPP_DECISION: format_location( formatter, *it, indent ); formatter << "Decision made as " << std::boolalpha << it->m_decision.value << '\n'; ++it; break; case EPP_EXCEPT: format_location( formatter, *it, indent ); formatter << "Forced failure"; if( it->m_except.description ) formatter << ": " << it->m_except.description; formatter << "\n"; ++it; break; case EPP_ALLOC: if( it->m_alloc.ptr ) { format_location( formatter, *it, indent ); formatter << "Allocated memory block 0x" << std::uppercase << it->m_alloc.ptr << ", " << it->m_alloc.size << " bytes long: <"; unsigned i; for( i = 0; i < std::min( it->m_alloc.size, 8 ); i++ ) { unsigned char c = ((unsigned char*)it->m_alloc.ptr)[i]; if( std::isprint( c ) ) formatter << c; else formatter << '.'; } formatter << "> "; for( i = 0; i < std::min( it->m_alloc.size, 8 ); i++ ) { unsigned c = ((unsigned char*)it->m_alloc.ptr)[i]; formatter << std::hex << std::uppercase << c << ' '; } formatter << "\n"; } ++it; break; } } } //____________________________________________________________________________// } // local namespace void exception_safety_tester::report_error() { activity_guard ag( m_internal_activity ); unit_test_log << unit_test::log::begin( m_execution_path.back().m_file_name, m_execution_path.back().m_line_num ) << log_all_errors; wrap_stringstream formatter; if( m_invairant_failed ) formatter << "Failed invariant"; if( m_memory_in_use.size() != 0 ) { if( m_invairant_failed ) formatter << " and "; formatter << m_memory_in_use.size() << " memory leak"; if( m_memory_in_use.size() > 1 ) formatter << 's'; } formatter << " detected in the execution path " << m_exec_path_counter << ":\n"; format_execution_path( formatter, m_execution_path.begin(), m_execution_path.end() ); unit_test_log << const_string( formatter.str() ) << unit_test::log::end(); } //____________________________________________________________________________// // ************************************************************************** // // ************** exception safety test ************** // // ************************************************************************** // void BOOST_TEST_DECL exception_safety( callback0<> const& F, const_string test_name ) { exception_safety_tester est( test_name ); do { try { F(); } catch( exception_safety_tester::unique_exception const& ) {} } while( est.next_execution_path() ); } //____________________________________________________________________________// } // namespace itest } // namespace boost //____________________________________________________________________________// #include #endif // non-ancient compiler // *************************************************************************** // Revision History : // // $Log: exception_safety.ipp,v $ // Revision 1.7.2.1 2006/07/27 11:48:49 gennaro_prota // boost guidelines (mainly from inspect tool: tabs, license reference text, etc.); more to do... // // Revision 1.7 2006/02/23 15:10:00 rogeeff // vc70 out // // Revision 1.6 2006/02/06 10:06:56 rogeeff // MSVC restored for now // // Revision 1.5 2006/01/28 08:52:35 rogeeff // operator new overloads made inline to: // 1. prevent issues with export them from DLL // 2. release link issue fixed // // Revision 1.4 2006/01/15 11:14:39 rogeeff // simpl_mock -> mock_object<>::prototype() // operator new need to be rethinked // // Revision 1.3 2005/12/22 15:49:32 rogeeff // sunpro port // made operator new conformant // // Revision 1.2 2005/12/16 02:33:17 rogeeff // portability fix // // Revision 1.1 2005/12/14 05:56:09 rogeeff // exception safety testing introduced // // *************************************************************************** #endif // BOOST_TEST_EXECUTION_SAFETY_IPP_112005GER