Skip to content

Instantly share code, notes, and snippets.

@martinmoene
Last active January 8, 2016 09:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save martinmoene/d886a76cb38655c6395f to your computer and use it in GitHub Desktop.
Save martinmoene/d886a76cb38655c6395f to your computer and use it in GitHub Desktop.
EXPECT_ABORTS for lest test framework (C++11) - longjmp from signal handler
// Death test for abort/assert - divert std::abort
//
// Expect_aborts succeeds for std::abort() [pass]
// Expect_aborts succeeds for assert(false) [pass]
// Expect_aborts reports assert(true) [fail]
// Expect_aborts reports an unexpected standard exception [fail]
// Expect_aborts reports an unexpected non-standard exception [fail]
// Expect_no_abort succeeds for assert(true) [pass]
// Expect_no_abort reports std::abort() [fail]
// Expect_no_abort reports assert(false) [fail]
// Expect_no_abort reports an unexpected standard exception [fail]
// Expect_no_abort reports an unexpected non-standard exception [fail]
#include <lest.hpp>
// -----------------------------------------------------------------------
// Additions to lest.hpp:
#include <csetjmp>
#include <csignal>
#include <io.h>
#include <fcntl.h>
#if ! defined( lest_NO_SHORT_MACRO_NAMES )
# define EXPECT_NO_ABORT lest_EXPECT_NO_ABORT
# define EXPECT_ABORTS lest_EXPECT_ABORTS
#endif
#define lest_EXPECT_NO_ABORT( expr ) \
do \
{ \
try \
{ \
lest::scoped_abort_handler lest_UNIQUE( id ); \
if ( ! setjmp( lest_UNIQUE( id ).env() ) ) \
{ \
expr; \
} \
else \
{ \
throw lest::aborted{ "failed", lest_LOCATION, #expr }; \
} \
} \
catch (...) \
{ \
lest::inform( lest_LOCATION, #expr ); \
} \
if ( $.pass ) \
lest::report( $.os, lest::not_aborted{ "passed", lest_LOCATION, #expr }, $.testing ); \
} while ( lest::is_false() )
#define lest_EXPECT_ABORTS( expr ) \
do \
{ \
try \
{ \
lest::scoped_abort_handler lest_UNIQUE( id ); \
if ( ! setjmp( lest_UNIQUE( id ).env() ) ) \
{ \
expr; \
} \
else \
{ \
if ( $.pass ) \
lest::report( $.os, lest::aborted{ "passed", lest_LOCATION, #expr }, $.testing ); \
break; \
} \
} \
catch (...) \
{ \
lest::inform( lest_LOCATION, #expr ); \
} \
throw lest::not_aborted{ "failed", lest_LOCATION, #expr }; \
} \
while ( lest::is_false() )
#if _WIN32
# define lest_DEV_NULL "nul"
# else
# define lest_DEV_NULL "/dev/null"
#endif
namespace lest {
struct aborted : message
{
aborted( text kind, location where, text expr )
: message{ kind + ": aborted", where, expr } {}
};
struct not_aborted : message
{
not_aborted( text kind, location where, text expr )
: message{ kind + ": didn't abort", where, expr } {}
};
// install/restore signal handler for SIGABRT to intercept abort(),
// inhibit/restore output to stderr to suppress output of assert().
// non-thread-safe
class scoped_abort_handler
{
public:
scoped_abort_handler()
: previous( std::signal( SIGABRT, &scoped_abort_handler::signal_handler ) )
{
if ( previous == SIG_ERR )
{
std::cerr << "lest::scoped_abort_handler: Setup failed\n";
exit_unclean( EXIT_FAILURE );
}
inhibit_stderr();
}
~scoped_abort_handler()
{
restore_stderr();
(void) std::signal( SIGABRT, previous );
}
static jmp_buf & env()
{
static jmp_buf buf;
return buf;
}
private:
// non-C linkage function: implementation defined:
static void signal_handler( int signal )
{
if ( signal == SIGABRT )
{
std::longjmp( env(), 1 );
}
std::cerr << "lest::scoped_abort_handler: Got unexpected signal '" << signal << "'\n";
exit_unclean( EXIT_FAILURE );
}
static void exit_unclean( int status )
{
#if _MSC_VER < 1900
std::abort();
#else
std::_Exit( status );
#endif
}
void inhibit_stderr()
{
fflush( stderr );
stderr_org = dup( 2 );
stderr_new = open( lest_DEV_NULL, O_WRONLY );
dup2 ( stderr_new, 2 );
close( stderr_new );
}
void restore_stderr()
{
fflush( stdout );
dup2 ( stderr_org, 2 );
close ( stderr_org );
}
int stderr_org = 0;
int stderr_new = 0;
void (* const previous)(int);
};
} // namespace lest
// -----------------------------------------------------------------------
// Usage:
#include <cassert>
#define CASE( name ) lest_CASE( specification, name )
static lest::tests specification;
struct user_type{};
// report value of __cplusplus:
CASE( "__cplusplus" )
{
EXPECT( __cplusplus == 0 );
}
// test for abort:
CASE( "Expect_aborts succeeds for std::abort() " "[pass]" )
{
EXPECT_ABORTS( std::abort() );
}
CASE( "Expect_aborts succeeds for assert(false) " "[pass]" )
{
EXPECT_ABORTS( assert( false ) );
}
CASE( "Expect_aborts reports assert(true) " "[fail]" )
{
EXPECT_ABORTS( assert( true ) );
}
CASE( "Expect_aborts reports an unexpected standard exception " "[fail]" )
{
EXPECT_ABORTS( throw std::runtime_error("augh") );
}
CASE( "Expect_aborts reports an unexpected non-standard exception " "[fail]" )
{
EXPECT_ABORTS( throw user_type{} );
}
// test for no abort:
CASE( "Expect_no_abort succeeds for assert(true) " "[pass]" )
{
EXPECT_NO_ABORT( assert( true ) );
}
CASE( "Expect_no_abort reports std::abort() " "[fail]" )
{
EXPECT_NO_ABORT( std::abort() );
}
CASE( "Expect_no_abort reports assert(false) " "[fail]" )
{
EXPECT_NO_ABORT( assert( false ) );
}
CASE( "Expect_no_abort reports an unexpected standard exception " "[fail]" )
{
EXPECT_NO_ABORT( throw std::runtime_error("augh") );
}
CASE( "Expect_no_abort reports an unexpected non-standard exception " "[fail]" )
{
EXPECT_NO_ABORT( throw user_type{} );
}
// run tests:
int main( int argc, char * argv[] )
{
return lest::run( specification, argc, argv /*, std::cout */ );
}
// -----------------------------------------------------------------------
// Compilation:
#if 0
cl -EHsc -Dlest_FEATURE_AUTO_REGISTER=1 -I../../../lest/ main.cpp && main.exe
g++ -Wall -std=c++11 -Dlest_FEATURE_AUTO_REGISTER=1 -I../../../lest/ -o main.exe main.cpp && main.exe --pass
#endif
// Output:
// main.cpp:172: failed: __cplusplus: __cplusplus == 0 for 201103 == 0
// main.cpp:177: passed: aborted: Expect_aborts succeeds for std::abort() [pass]: std::abort()
// main.cpp:182: passed: aborted: Expect_aborts succeeds for assert(false) [pass]: assert( false )
// main.cpp:187: failed: didn't abort: Expect_aborts reports assert(true) [fail]: assert( true )
// main.cpp:192: failed: got unexpected exception with message "augh": Expect_aborts reports an unexpected standard exception [fail]: throw std::runtime_error("augh")
// main.cpp:197: failed: got unexpected exception of unknown type: Expect_aborts reports an unexpected non-standard exception [fail]: throw user_type{}
// main.cpp:204: passed: didn't abort: Expect_no_abort succeeds for assert(true) [pass]: assert( true )
// main.cpp:209: failed: aborted: Expect_no_abort reports std::abort() [fail]: std::abort()
// main.cpp:214: failed: aborted: Expect_no_abort reports assert(false) [fail]: assert( false )
// main.cpp:219: failed: got unexpected exception with message "augh": Expect_no_abort reports an unexpected standard exception [fail]: throw std::runtime_error("augh")
// main.cpp:224: failed: got unexpected exception of unknown type: Expect_no_abort reports an unexpected non-standard exception [fail]: throw user_type{}
// 8 out of 11 selected tests failed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment