Last active
January 8, 2016 09:36
-
-
Save martinmoene/d886a76cb38655c6395f to your computer and use it in GitHub Desktop.
EXPECT_ABORTS for lest test framework (C++11) - longjmp from signal handler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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