1// (C) Copyright Gennadiy Rozental 2005-2008. 2// Use, modification, and distribution are subject to the 3// Boost Software License, Version 1.0. (See accompanying file 4// http://www.boost.org/LICENSE_1_0.txt) 5 6// See http://www.boost.org/libs/test for the library home page. 7// 8// File : $RCSfile$ 9// 10// Version : $Revision: 54633 $ 11// 12// Description : Facilities to perform exception safety tests 13// *************************************************************************** 14 15#ifndef BOOST_TEST_EXECUTION_SAFETY_IPP_112005GER 16#define BOOST_TEST_EXECUTION_SAFETY_IPP_112005GER 17 18// Boost.Test 19#include <boost/test/detail/config.hpp> 20 21#if BOOST_TEST_SUPPORT_INTERACTION_TESTING 22 23#include <boost/test/detail/global_typedef.hpp> 24#include <boost/test/detail/unit_test_parameters.hpp> 25 26#include <boost/test/utils/callback.hpp> 27#include <boost/test/utils/wrap_stringstream.hpp> 28#include <boost/test/utils/iterator/token_iterator.hpp> 29 30#include <boost/test/interaction_based.hpp> 31#include <boost/test/test_tools.hpp> 32#include <boost/test/unit_test_log.hpp> 33#include <boost/test/framework.hpp> 34#include <boost/test/test_observer.hpp> 35#include <boost/test/debug.hpp> 36 37#include <boost/test/detail/suppress_warnings.hpp> 38 39// Boost 40#include <boost/lexical_cast.hpp> 41 42// STL 43#include <vector> 44#include <cstdlib> 45#include <map> 46#include <iomanip> 47#include <cctype> 48#include <boost/limits.hpp> 49 50//____________________________________________________________________________// 51 52namespace boost { 53 54using namespace ::boost::unit_test; 55 56namespace itest { 57 58// ************************************************************************** // 59// ************** execution_path_point ************** // 60// ************************************************************************** // 61 62enum exec_path_point_type { EPP_SCOPE, EPP_EXCEPT, EPP_DECISION, EPP_ALLOC }; 63 64struct execution_path_point { 65 execution_path_point( exec_path_point_type t, const_string file, std::size_t line_num ) 66 : m_type( t ) 67 , m_file_name( file ) 68 , m_line_num( line_num ) 69 {} 70 71 exec_path_point_type m_type; 72 const_string m_file_name; 73 std::size_t m_line_num; 74 75 // Execution path point specific 76 struct decision_data { 77 bool value; 78 unsigned forced_exception_point; 79 }; 80 struct scope_data { 81 unsigned size; 82 char const* name; 83 }; 84 struct except_data { 85 char const* description; 86 }; 87 struct alloc_data { 88 void* ptr; 89 std::size_t size; 90 }; 91 92 union { 93 struct decision_data m_decision; 94 struct scope_data m_scope; 95 struct except_data m_except; 96 struct alloc_data m_alloc; 97 }; 98}; 99 100// ************************************************************************** // 101// ************** exception safety test implementation ************** // 102// ************************************************************************** // 103 104struct exception_safety_tester : itest::manager, test_observer { 105 // helpers types 106 struct unique_exception {}; 107 108 // Constructor 109 explicit exception_safety_tester( const_string test_name ); 110 ~exception_safety_tester(); 111 112 // check last run and prepare for next 113 bool next_execution_path(); 114 115 // memory tracking 116 117 // manager interface implementation 118 virtual void exception_point( const_string file, std::size_t line_num, const_string description ); 119 virtual bool decision_point( const_string file, std::size_t line_num ); 120 virtual unsigned enter_scope( const_string file, std::size_t line_num, const_string scope_name ); 121 virtual void leave_scope( unsigned enter_scope_point ); 122 virtual void allocated( const_string file, std::size_t line_num, void* p, std::size_t s ); 123 virtual void freed( void* p ); 124 125 // test observer interface 126 virtual void assertion_result( bool passed ); 127 virtual int priority() { return (std::numeric_limits<int>::max)(); } // we want this observer to run the last 128 129private: 130 void failure_point(); 131 void report_error(); 132 133 typedef std::vector<execution_path_point> exec_path; 134 typedef std::map<void*,unsigned> registry; 135 136 // Data members 137 bool m_internal_activity; 138 139 unsigned m_exception_point_counter; 140 unsigned m_forced_exception_point; 141 142 unsigned m_exec_path_point; 143 exec_path m_execution_path; 144 145 unsigned m_exec_path_counter; 146 unsigned m_break_exec_path; 147 148 bool m_invairant_failed; 149 registry m_memory_in_use; 150}; 151 152//____________________________________________________________________________// 153 154struct activity_guard { 155 bool& m_v; 156 157 activity_guard( bool& v ) : m_v( v ) { m_v = true; } 158 ~activity_guard() { m_v = false; } 159}; 160 161//____________________________________________________________________________// 162 163exception_safety_tester::exception_safety_tester( const_string test_name ) 164: m_internal_activity( true ) 165, m_exception_point_counter( 0 ) 166, m_forced_exception_point( 1 ) 167, m_exec_path_point( 0 ) 168, m_exec_path_counter( 1 ) 169, m_break_exec_path( static_cast<unsigned>(-1) ) 170, m_invairant_failed( false ) 171{ 172 framework::register_observer( *this ); 173 174 if( !runtime_config::break_exec_path().is_empty() ) { 175 using namespace unit_test; 176 177 string_token_iterator tit( runtime_config::break_exec_path(), 178 (dropped_delimeters = ":",kept_delimeters = " ") ); 179 180 const_string test_to_break = *tit; 181 182 if( test_to_break == test_name ) { 183 ++tit; 184 185 m_break_exec_path = lexical_cast<unsigned>( *tit ); 186 } 187 } 188 189 m_internal_activity = false; 190} 191 192//____________________________________________________________________________// 193 194exception_safety_tester::~exception_safety_tester() 195{ 196 m_internal_activity = true; 197 198 framework::deregister_observer( *this ); 199} 200 201//____________________________________________________________________________// 202 203bool 204exception_safety_tester::next_execution_path() 205{ 206 activity_guard ag( m_internal_activity ); 207 208 // check memory usage 209 if( m_execution_path.size() > 0 ) { 210 bool errors_detected = m_invairant_failed || (m_memory_in_use.size() != 0); 211 framework::assertion_result( !errors_detected ); 212 213 if( errors_detected ) 214 report_error(); 215 216 m_memory_in_use.clear(); 217 } 218 219 m_exec_path_point = 0; 220 m_exception_point_counter = 0; 221 m_invairant_failed = false; 222 ++m_exec_path_counter; 223 224 while( m_execution_path.size() > 0 ) { 225 switch( m_execution_path.back().m_type ) { 226 case EPP_SCOPE: 227 case EPP_ALLOC: 228 m_execution_path.pop_back(); 229 break; 230 231 case EPP_DECISION: 232 if( !m_execution_path.back().m_decision.value ) { 233 m_execution_path.pop_back(); 234 break; 235 } 236 237 m_execution_path.back().m_decision.value = false; 238 m_forced_exception_point = m_execution_path.back().m_decision.forced_exception_point; 239 return true; 240 241 case EPP_EXCEPT: 242 m_execution_path.pop_back(); 243 ++m_forced_exception_point; 244 return true; 245 } 246 } 247 248 BOOST_TEST_MESSAGE( "Total tested " << --m_exec_path_counter << " execution path" ); 249 250 return false; 251} 252 253//____________________________________________________________________________// 254 255void 256exception_safety_tester::exception_point( const_string file, std::size_t line_num, const_string description ) 257{ 258 activity_guard ag( m_internal_activity ); 259 260 if( ++m_exception_point_counter == m_forced_exception_point ) { 261 m_execution_path.push_back( 262 execution_path_point( EPP_EXCEPT, file, line_num ) ); 263 264 m_execution_path.back().m_except.description = description.begin(); 265 266 ++m_exec_path_point; 267 268 failure_point(); 269 } 270} 271 272//____________________________________________________________________________// 273 274bool 275exception_safety_tester::decision_point( const_string file, std::size_t line_num ) 276{ 277 activity_guard ag( m_internal_activity ); 278 279 if( m_exec_path_point < m_execution_path.size() ) { 280 BOOST_REQUIRE_MESSAGE( m_execution_path[m_exec_path_point].m_type == EPP_DECISION && 281 m_execution_path[m_exec_path_point].m_file_name == file && 282 m_execution_path[m_exec_path_point].m_line_num == line_num, 283 "Function under test exibit non-deterministic behavior" ); 284 } 285 else { 286 m_execution_path.push_back( 287 execution_path_point( EPP_DECISION, file, line_num ) ); 288 289 m_execution_path.back().m_decision.value = true; 290 m_execution_path.back().m_decision.forced_exception_point = m_forced_exception_point; 291 } 292 293 return m_execution_path[m_exec_path_point++].m_decision.value; 294} 295 296//____________________________________________________________________________// 297 298unsigned 299exception_safety_tester::enter_scope( const_string file, std::size_t line_num, const_string scope_name ) 300{ 301 activity_guard ag( m_internal_activity ); 302 303 if( m_exec_path_point < m_execution_path.size() ) { 304 BOOST_REQUIRE_MESSAGE( m_execution_path[m_exec_path_point].m_type == EPP_SCOPE && 305 m_execution_path[m_exec_path_point].m_file_name == file && 306 m_execution_path[m_exec_path_point].m_line_num == line_num, 307 "Function under test exibit non-deterministic behavior" ); 308 } 309 else { 310 m_execution_path.push_back( 311 execution_path_point( EPP_SCOPE, file, line_num ) ); 312 } 313 314 m_execution_path[m_exec_path_point].m_scope.size = 0; 315 m_execution_path[m_exec_path_point].m_scope.name = scope_name.begin(); 316 317 return m_exec_path_point++; 318} 319 320//____________________________________________________________________________// 321 322void 323exception_safety_tester::leave_scope( unsigned enter_scope_point ) 324{ 325 activity_guard ag( m_internal_activity ); 326 327 BOOST_REQUIRE_MESSAGE( m_execution_path[enter_scope_point].m_type == EPP_SCOPE, 328 "Function under test exibit non-deterministic behavior" ); 329 330 m_execution_path[enter_scope_point].m_scope.size = m_exec_path_point - enter_scope_point; 331} 332 333//____________________________________________________________________________// 334 335void 336exception_safety_tester::allocated( const_string file, std::size_t line_num, void* p, std::size_t s ) 337{ 338 if( m_internal_activity ) 339 return; 340 341 activity_guard ag( m_internal_activity ); 342 343 if( m_exec_path_point < m_execution_path.size() ) 344 BOOST_REQUIRE_MESSAGE( m_execution_path[m_exec_path_point].m_type == EPP_ALLOC, 345 "Function under test exibit non-deterministic behavior" ); 346 else 347 m_execution_path.push_back( 348 execution_path_point( EPP_ALLOC, file, line_num ) ); 349 350 m_execution_path[m_exec_path_point].m_alloc.ptr = p; 351 m_execution_path[m_exec_path_point].m_alloc.size = s; 352 353 m_memory_in_use.insert( std::make_pair( p, m_exec_path_point++ ) ); 354} 355 356//____________________________________________________________________________// 357 358void 359exception_safety_tester::freed( void* p ) 360{ 361 if( m_internal_activity ) 362 return; 363 364 activity_guard ag( m_internal_activity ); 365 366 registry::iterator it = m_memory_in_use.find( p ); 367 if( it != m_memory_in_use.end() ) { 368 m_execution_path[it->second].m_alloc.ptr = 0; 369 m_memory_in_use.erase( it ); 370 } 371} 372 373//____________________________________________________________________________// 374 375void 376exception_safety_tester::assertion_result( bool passed ) 377{ 378 if( !m_internal_activity && !passed ) { 379 m_invairant_failed = true; 380 381 failure_point(); 382 } 383} 384 385//____________________________________________________________________________// 386 387void 388exception_safety_tester::failure_point() 389{ 390 if( m_exec_path_counter == m_break_exec_path ) 391 debug::debugger_break(); 392 393 throw unique_exception(); 394} 395 396//____________________________________________________________________________// 397 398namespace { 399 400inline void 401format_location( wrap_stringstream& formatter, execution_path_point const& /*p*/, unsigned indent ) 402{ 403 if( indent ) 404 formatter << std::left << std::setw( indent ) << ""; 405 406// !! ?? optional if( p.m_file_name ) 407// formatter << p.m_file_name << '(' << p.m_line_num << "): "; 408} 409 410//____________________________________________________________________________// 411 412template<typename ExecPathIt> 413inline void 414format_execution_path( wrap_stringstream& formatter, ExecPathIt it, ExecPathIt end, unsigned indent = 0 ) 415{ 416 while( it != end ) { 417 switch( it->m_type ) { 418 case EPP_SCOPE: 419 format_location( formatter, *it, indent ); 420 formatter << "> \"" << it->m_scope.name << "\"\n"; 421 format_execution_path( formatter, it+1, it + it->m_scope.size, indent + 2 ); 422 format_location( formatter, *it, indent ); 423 formatter << "< \"" << it->m_scope.name << "\"\n"; 424 it += it->m_scope.size; 425 break; 426 427 case EPP_DECISION: 428 format_location( formatter, *it, indent ); 429 formatter << "Decision made as " << std::boolalpha << it->m_decision.value << '\n'; 430 ++it; 431 break; 432 433 case EPP_EXCEPT: 434 format_location( formatter, *it, indent ); 435 formatter << "Forced failure"; 436 if( it->m_except.description ) 437 formatter << ": " << it->m_except.description; 438 formatter << "\n"; 439 ++it; 440 break; 441 442 case EPP_ALLOC: 443 if( it->m_alloc.ptr ) { 444 format_location( formatter, *it, indent ); 445 formatter << "Allocated memory block 0x" << std::uppercase << it->m_alloc.ptr 446 << ", " << it->m_alloc.size << " bytes long: <"; 447 448 unsigned i; 449 for( i = 0; i < std::min<std::size_t>( it->m_alloc.size, 8 ); i++ ) { 450 unsigned char c = static_cast<unsigned char*>(it->m_alloc.ptr)[i]; 451 if( (std::isprint)( c ) ) 452 formatter << c; 453 else 454 formatter << '.'; 455 } 456 457 formatter << "> "; 458 459 for( i = 0; i < std::min<std::size_t>( it->m_alloc.size, 8 ); i++ ) { 460 unsigned c = static_cast<unsigned char*>(it->m_alloc.ptr)[i]; 461 formatter << std::hex << std::uppercase << c << ' '; 462 } 463 464 formatter << "\n"; 465 } 466 ++it; 467 break; 468 } 469 } 470} 471 472//____________________________________________________________________________// 473 474} // local namespace 475 476void 477exception_safety_tester::report_error() 478{ 479 activity_guard ag( m_internal_activity ); 480 481 unit_test_log << unit_test::log::begin( m_execution_path.back().m_file_name, 482 m_execution_path.back().m_line_num ) 483 << log_all_errors; 484 485 wrap_stringstream formatter; 486 487 if( m_invairant_failed ) 488 formatter << "Failed invariant"; 489 490 if( m_memory_in_use.size() != 0 ) { 491 if( m_invairant_failed ) 492 formatter << " and "; 493 494 formatter << static_cast<unsigned int>(m_memory_in_use.size()) << " memory leak"; 495 if( m_memory_in_use.size() > 1 ) 496 formatter << 's'; 497 } 498 formatter << " detected in the execution path " << m_exec_path_counter << ":\n"; 499 500 format_execution_path( formatter, m_execution_path.begin(), m_execution_path.end() ); 501 502 unit_test_log << const_string( formatter.str() ) << unit_test::log::end(); 503} 504 505//____________________________________________________________________________// 506 507// ************************************************************************** // 508// ************** exception safety test ************** // 509// ************************************************************************** // 510 511void BOOST_TEST_DECL 512exception_safety( callback0<> const& F, const_string test_name ) 513{ 514 exception_safety_tester est( test_name ); 515 516 do { 517 try { 518 F(); 519 } 520 catch( exception_safety_tester::unique_exception const& ) {} 521 522 } while( est.next_execution_path() ); 523} 524 525//____________________________________________________________________________// 526 527} // namespace itest 528 529} // namespace boost 530 531//____________________________________________________________________________// 532 533#include <boost/test/detail/enable_warnings.hpp> 534 535#endif // non-ancient compiler 536 537#endif // BOOST_TEST_EXECUTION_SAFETY_IPP_112005GER 538