// (C) Copyright Gennadiy Rozental 2005-2008. | |
// 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$ | |
// | |
// Version : $Revision: 54633 $ | |
// | |
// Description : Facilities to perform exception safety tests | |
// *************************************************************************** | |
#ifndef BOOST_TEST_EXECUTION_SAFETY_IPP_112005GER | |
#define BOOST_TEST_EXECUTION_SAFETY_IPP_112005GER | |
// Boost.Test | |
#include <boost/test/detail/config.hpp> | |
#if BOOST_TEST_SUPPORT_INTERACTION_TESTING | |
#include <boost/test/detail/global_typedef.hpp> | |
#include <boost/test/detail/unit_test_parameters.hpp> | |
#include <boost/test/utils/callback.hpp> | |
#include <boost/test/utils/wrap_stringstream.hpp> | |
#include <boost/test/utils/iterator/token_iterator.hpp> | |
#include <boost/test/interaction_based.hpp> | |
#include <boost/test/test_tools.hpp> | |
#include <boost/test/unit_test_log.hpp> | |
#include <boost/test/framework.hpp> | |
#include <boost/test/test_observer.hpp> | |
#include <boost/test/debug.hpp> | |
#include <boost/test/detail/suppress_warnings.hpp> | |
// Boost | |
#include <boost/lexical_cast.hpp> | |
// STL | |
#include <vector> | |
#include <cstdlib> | |
#include <map> | |
#include <iomanip> | |
#include <cctype> | |
#include <boost/limits.hpp> | |
//____________________________________________________________________________// | |
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<int>::max)(); } // we want this observer to run the last | |
private: | |
void failure_point(); | |
void report_error(); | |
typedef std::vector<execution_path_point> exec_path; | |
typedef std::map<void*,unsigned> 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( static_cast<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<unsigned>( *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 ) | |
debug::debugger_break(); | |
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<typename ExecPathIt> | |
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<std::size_t>( it->m_alloc.size, 8 ); i++ ) { | |
unsigned char c = static_cast<unsigned char*>(it->m_alloc.ptr)[i]; | |
if( (std::isprint)( c ) ) | |
formatter << c; | |
else | |
formatter << '.'; | |
} | |
formatter << "> "; | |
for( i = 0; i < std::min<std::size_t>( it->m_alloc.size, 8 ); i++ ) { | |
unsigned c = static_cast<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 << static_cast<unsigned int>(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 <boost/test/detail/enable_warnings.hpp> | |
#endif // non-ancient compiler | |
#endif // BOOST_TEST_EXECUTION_SAFETY_IPP_112005GER |