// Copyright 2013-2018 by Martin Moene // // lest is based on ideas by Kevlin Henney, see video at // http://skillsmatter.com/podcast/agile-testing/kevlin-henney-rethinking-unit-testing-in-c-plus-plus // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) #ifndef LEST_LEST_HPP_INCLUDED #define LEST_LEST_HPP_INCLUDED #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define lest_MAJOR 1 #define lest_MINOR 35 #define lest_PATCH 1 #define lest_VERSION lest_STRINGIFY(lest_MAJOR) "." lest_STRINGIFY(lest_MINOR) "." lest_STRINGIFY(lest_PATCH) #ifndef lest_FEATURE_COLOURISE # define lest_FEATURE_COLOURISE 0 #endif #ifndef lest_FEATURE_LITERAL_SUFFIX # define lest_FEATURE_LITERAL_SUFFIX 0 #endif #ifndef lest_FEATURE_REGEX_SEARCH # define lest_FEATURE_REGEX_SEARCH 0 #endif #ifndef lest_FEATURE_TIME # define lest_FEATURE_TIME 1 #endif #ifndef lest_FEATURE_TIME_PRECISION #define lest_FEATURE_TIME_PRECISION 0 #endif #ifdef _WIN32 # define lest_PLATFORM_IS_WINDOWS 1 #else # define lest_PLATFORM_IS_WINDOWS 0 #endif #if lest_FEATURE_REGEX_SEARCH # include #endif #if lest_FEATURE_TIME # if lest_PLATFORM_IS_WINDOWS # include # include # else # include # include # endif #endif // Compiler warning suppression: #if defined (__clang__) # pragma clang diagnostic ignored "-Waggregate-return" # pragma clang diagnostic ignored "-Woverloaded-shift-op-parentheses" # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdate-time" #elif defined (__GNUC__) # pragma GCC diagnostic ignored "-Waggregate-return" # pragma GCC diagnostic push #endif // Suppress shadow and unused-value warning for sections: #if defined (__clang__) # define lest_SUPPRESS_WSHADOW _Pragma( "clang diagnostic push" ) \ _Pragma( "clang diagnostic ignored \"-Wshadow\"" ) # define lest_SUPPRESS_WUNUSED _Pragma( "clang diagnostic push" ) \ _Pragma( "clang diagnostic ignored \"-Wunused-value\"" ) # define lest_RESTORE_WARNINGS _Pragma( "clang diagnostic pop" ) #elif defined (__GNUC__) # define lest_SUPPRESS_WSHADOW _Pragma( "GCC diagnostic push" ) \ _Pragma( "GCC diagnostic ignored \"-Wshadow\"" ) # define lest_SUPPRESS_WUNUSED _Pragma( "GCC diagnostic push" ) \ _Pragma( "GCC diagnostic ignored \"-Wunused-value\"" ) # define lest_RESTORE_WARNINGS _Pragma( "GCC diagnostic pop" ) #else # define lest_SUPPRESS_WSHADOW /*empty*/ # define lest_SUPPRESS_WUNUSED /*empty*/ # define lest_RESTORE_WARNINGS /*empty*/ #endif // Stringify: #define lest_STRINGIFY( x ) lest_STRINGIFY_( x ) #define lest_STRINGIFY_( x ) #x // Compiler versions: #if defined( _MSC_VER ) && !defined( __clang__ ) # define lest_COMPILER_MSVC_VERSION ( _MSC_VER / 10 - 10 * ( 5 + ( _MSC_VER < 1900 ) ) ) #else # define lest_COMPILER_MSVC_VERSION 0 #endif #define lest_COMPILER_VERSION( major, minor, patch ) ( 10 * ( 10 * major + minor ) + patch ) #if defined (__clang__) # define lest_COMPILER_CLANG_VERSION lest_COMPILER_VERSION( __clang_major__, __clang_minor__, __clang_patchlevel__ ) #else # define lest_COMPILER_CLANG_VERSION 0 #endif #if defined (__GNUC__) # define lest_COMPILER_GNUC_VERSION lest_COMPILER_VERSION( __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__ ) #else # define lest_COMPILER_GNUC_VERSION 0 #endif // C++ language version detection (C++20 is speculative): // Note: VC14.0/1900 (VS2015) lacks too much from C++14. #ifndef lest_CPLUSPLUS # if defined(_MSVC_LANG ) && !defined(__clang__) # define lest_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG ) # else # define lest_CPLUSPLUS __cplusplus # endif #endif #define lest_CPP98_OR_GREATER ( lest_CPLUSPLUS >= 199711L ) #define lest_CPP11_OR_GREATER ( lest_CPLUSPLUS >= 201103L || lest_COMPILER_MSVC_VERSION >= 120 ) #define lest_CPP14_OR_GREATER ( lest_CPLUSPLUS >= 201402L ) #define lest_CPP17_OR_GREATER ( lest_CPLUSPLUS >= 201703L ) #define lest_CPP20_OR_GREATER ( lest_CPLUSPLUS >= 202000L ) #define lest_CPP11_100 (lest_CPP11_OR_GREATER || lest_COMPILER_MSVC_VERSION >= 100) #ifndef __has_cpp_attribute # define __has_cpp_attribute(name) 0 #endif // Indicate argument as possibly unused, if possible: #if __has_cpp_attribute(maybe_unused) && lest_CPP17_OR_GREATER # define lest_MAYBE_UNUSED(ARG) [[maybe_unused]] ARG #elif defined (__GNUC__) # define lest_MAYBE_UNUSED(ARG) ARG __attribute((unused)) #else # define lest_MAYBE_UNUSED(ARG) ARG #endif // Presence of language and library features: #define lest_HAVE(FEATURE) ( lest_HAVE_##FEATURE ) // Presence of C++11 language features: #define lest_HAVE_NOEXCEPT ( lest_CPP11_100 ) #define lest_HAVE_NULLPTR ( lest_CPP11_100 ) // C++ feature usage: #if lest_HAVE( NULLPTR ) # define lest_nullptr nullptr #else # define lest_nullptr NULL #endif // Additional includes and tie: #if lest_CPP11_100 # include # include # include namespace lest { using std::tie; } #else # if !defined(__clang__) && defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Weffc++" # endif namespace lest { // tie: template< typename T1, typename T2 > struct Tie { Tie( T1 & first_, T2 & second_) : first( first_), second( second_) {} std::pair const & operator=( std::pair const & rhs ) { first = rhs.first; second = rhs.second; return rhs; } private: void operator=( Tie const & ); T1 & first; T2 & second; }; template< typename T1, typename T2 > inline Tie tie( T1 & first, T2 & second ) { return Tie( first, second ); } } # if !defined(__clang__) && defined(__GNUC__) # pragma GCC diagnostic pop # endif #endif // lest_CPP11_100 namespace lest { using std::abs; using std::min; using std::strtol; using std::rand; using std::srand; } #if ! defined( lest_NO_SHORT_MACRO_NAMES ) && ! defined( lest_NO_SHORT_ASSERTION_NAMES ) # define SETUP lest_SETUP # define SECTION lest_SECTION # define EXPECT lest_EXPECT # define EXPECT_NOT lest_EXPECT_NOT # define EXPECT_NO_THROW lest_EXPECT_NO_THROW # define EXPECT_THROWS lest_EXPECT_THROWS # define EXPECT_THROWS_AS lest_EXPECT_THROWS_AS # define SCENARIO lest_SCENARIO # define GIVEN lest_GIVEN # define WHEN lest_WHEN # define THEN lest_THEN # define AND_WHEN lest_AND_WHEN # define AND_THEN lest_AND_THEN #endif #define lest_SCENARIO( specification, sketch ) \ lest_CASE( specification, lest::text("Scenario: ") + sketch ) #define lest_GIVEN( context ) lest_SETUP( lest::text(" Given: ") + context ) #define lest_WHEN( story ) lest_SECTION( lest::text(" When: ") + story ) #define lest_THEN( story ) lest_SECTION( lest::text(" Then: ") + story ) #define lest_AND_WHEN( story ) lest_SECTION( lest::text("And then: ") + story ) #define lest_AND_THEN( story ) lest_SECTION( lest::text("And then: ") + story ) #define lest_CASE( specification, proposition ) \ static void lest_FUNCTION( lest::env & ); \ namespace { lest::add_test lest_REGISTRAR( specification, lest::test( proposition, lest_FUNCTION ) ); } \ static void lest_FUNCTION( lest_MAYBE_UNUSED( lest::env & lest_env ) ) #define lest_ADD_TEST( specification, test ) \ specification.push_back( test ) #define lest_SETUP( context ) \ for ( int lest__section = 0, lest__count = 1; lest__section < lest__count; lest__count -= 0==lest__section++ ) \ for ( lest::ctx lest__ctx_setup( lest_env, context ); lest__ctx_setup; ) #define lest_SECTION( proposition ) \ lest_SUPPRESS_WSHADOW \ static int lest_UNIQUE( id ) = 0; \ if ( lest::guard( lest_UNIQUE( id ), lest__section, lest__count ) ) \ for ( int lest__section = 0, lest__count = 1; lest__section < lest__count; lest__count -= 0==lest__section++ ) \ for ( lest::ctx lest__ctx_section( lest_env, proposition ); lest__ctx_section; ) \ lest_RESTORE_WARNINGS #define lest_EXPECT( expr ) \ do { \ try \ { \ if ( lest::result score = lest_DECOMPOSE( expr ) ) \ throw lest::failure( lest_LOCATION, #expr, score.decomposition ); \ else if ( lest_env.pass() ) \ lest::report( lest_env.os, lest::passing( lest_LOCATION, #expr, score.decomposition, lest_env.zen() ), lest_env.context() ); \ } \ catch(...) \ { \ lest::inform( lest_LOCATION, #expr ); \ } \ } while ( lest::is_false() ) #define lest_EXPECT_NOT( expr ) \ do { \ try \ { \ if ( lest::result score = lest_DECOMPOSE( expr ) ) \ { \ if ( lest_env.pass() ) \ lest::report( lest_env.os, lest::passing( lest_LOCATION, lest::not_expr( #expr ), lest::not_expr( score.decomposition ), lest_env.zen() ), lest_env.context() ); \ } \ else \ throw lest::failure( lest_LOCATION, lest::not_expr( #expr ), lest::not_expr( score.decomposition ) ); \ } \ catch(...) \ { \ lest::inform( lest_LOCATION, lest::not_expr( #expr ) ); \ } \ } while ( lest::is_false() ) #define lest_EXPECT_NO_THROW( expr ) \ do \ { \ try \ { \ lest_SUPPRESS_WUNUSED \ expr; \ lest_RESTORE_WARNINGS \ } \ catch (...) { lest::inform( lest_LOCATION, #expr ); } \ if ( lest_env.pass() ) \ lest::report( lest_env.os, lest::got_none( lest_LOCATION, #expr ), lest_env.context() ); \ } while ( lest::is_false() ) #define lest_EXPECT_THROWS( expr ) \ do \ { \ try \ { \ lest_SUPPRESS_WUNUSED \ expr; \ lest_RESTORE_WARNINGS \ } \ catch (...) \ { \ if ( lest_env.pass() ) \ lest::report( lest_env.os, lest::got( lest_LOCATION, #expr ), lest_env.context() ); \ break; \ } \ throw lest::expected( lest_LOCATION, #expr ); \ } \ while ( lest::is_false() ) #define lest_EXPECT_THROWS_AS( expr, excpt ) \ do \ { \ try \ { \ lest_SUPPRESS_WUNUSED \ expr; \ lest_RESTORE_WARNINGS \ } \ catch ( excpt & ) \ { \ if ( lest_env.pass() ) \ lest::report( lest_env.os, lest::got( lest_LOCATION, #expr, lest::of_type( #excpt ) ), lest_env.context() ); \ break; \ } \ catch (...) {} \ throw lest::expected( lest_LOCATION, #expr, lest::of_type( #excpt ) ); \ } \ while ( lest::is_false() ) #define lest_DECOMPOSE( expr ) ( lest::expression_decomposer() << expr ) #define lest_STRING( name ) lest_STRING2( name ) #define lest_STRING2( name ) #name #define lest_UNIQUE( name ) lest_UNIQUE2( name, __LINE__ ) #define lest_UNIQUE2( name, line ) lest_UNIQUE3( name, line ) #define lest_UNIQUE3( name, line ) name ## line #define lest_LOCATION lest::location(__FILE__, __LINE__) #define lest_FUNCTION lest_UNIQUE(__lest_function__ ) #define lest_REGISTRAR lest_UNIQUE(__lest_registrar__ ) #define lest_DIMENSION_OF( a ) ( sizeof(a) / sizeof(0[a]) ) namespace lest { const int exit_max_value = 255; typedef std::string text; typedef std::vector texts; struct env; struct test { text name; void (* behaviour)( env & ); test( text name_, void (* behaviour_)( env & ) ) : name( name_), behaviour( behaviour_) {} }; typedef std::vector tests; typedef tests test_specification; struct add_test { add_test( tests & specification, test const & test_case ) { specification.push_back( test_case ); } }; struct result { const bool passed; const text decomposition; template< typename T > result( T const & passed_, text decomposition_) : passed( !!passed_), decomposition( decomposition_) {} operator bool() { return ! passed; } }; struct location { const text file; const int line; location( text file_, int line_) : file( file_), line( line_) {} }; struct comment { const text info; comment( text info_) : info( info_) {} operator bool() { return ! info.empty(); } }; struct message : std::runtime_error { const text kind; const location where; const comment note; #if ! lest_CPP11_OR_GREATER ~message() throw() {} #endif message( text kind_, location where_, text expr_, text note_ = "" ) : std::runtime_error( expr_), kind( kind_), where( where_), note( note_) {} }; struct failure : message { failure( location where_, text expr_, text decomposition_) : message( "failed", where_, expr_ + " for " + decomposition_) {} }; struct success : message { success( text kind_, location where_, text expr_, text note_ = "" ) : message( kind_, where_, expr_, note_) {} }; struct passing : success { passing( location where_, text expr_, text decomposition_, bool zen ) : success( "passed", where_, expr_ + (zen ? "":" for " + decomposition_) ) {} }; struct got_none : success { got_none( location where_, text expr_) : success( "passed: got no exception", where_, expr_) {} }; struct got : success { got( location where_, text expr_) : success( "passed: got exception", where_, expr_) {} got( location where_, text expr_, text excpt_) : success( "passed: got exception " + excpt_, where_, expr_) {} }; struct expected : message { expected( location where_, text expr_, text excpt_ = "" ) : message( "failed: didn't get exception", where_, expr_, excpt_) {} }; struct unexpected : message { unexpected( location where_, text expr_, text note_ = "" ) : message( "failed: got unexpected exception", where_, expr_, note_) {} }; struct guard { int & id; int const & section; guard( int & id_, int const & section_, int & count ) : id( id_ ), section( section_ ) { if ( section == 0 ) id = count++ - 1; } operator bool() { return id == section; } }; class approx { public: explicit approx ( double magnitude ) : epsilon_ ( 100.0 * static_cast( std::numeric_limits::epsilon() ) ) , scale_ ( 1.0 ) , magnitude_( magnitude ) {} static approx custom() { return approx( 0 ); } approx operator()( double new_magnitude ) { approx appr( new_magnitude ); appr.epsilon( epsilon_ ); appr.scale ( scale_ ); return appr; } double magnitude() const { return magnitude_; } approx & epsilon( double epsilon ) { epsilon_ = epsilon; return *this; } approx & scale ( double scale ) { scale_ = scale; return *this; } friend bool operator == ( double lhs, approx const & rhs ) { // Thanks to Richard Harris for his help refining this formula. return lest::abs( lhs - rhs.magnitude_ ) < rhs.epsilon_ * ( rhs.scale_ + (lest::min)( lest::abs( lhs ), lest::abs( rhs.magnitude_ ) ) ); } friend bool operator == ( approx const & lhs, double rhs ) { return operator==( rhs, lhs ); } friend bool operator != ( double lhs, approx const & rhs ) { return !operator==( lhs, rhs ); } friend bool operator != ( approx const & lhs, double rhs ) { return !operator==( rhs, lhs ); } friend bool operator <= ( double lhs, approx const & rhs ) { return lhs < rhs.magnitude_ || lhs == rhs; } friend bool operator <= ( approx const & lhs, double rhs ) { return lhs.magnitude_ < rhs || lhs == rhs; } friend bool operator >= ( double lhs, approx const & rhs ) { return lhs > rhs.magnitude_ || lhs == rhs; } friend bool operator >= ( approx const & lhs, double rhs ) { return lhs.magnitude_ > rhs || lhs == rhs; } private: double epsilon_; double scale_; double magnitude_; }; inline bool is_false( ) { return false; } inline bool is_true ( bool flag ) { return flag; } inline text not_expr( text message ) { return "! ( " + message + " )"; } inline text with_message( text message ) { return "with message \"" + message + "\""; } inline text of_type( text type ) { return "of type " + type; } inline void inform( location where, text expr ) { try { throw; } catch( failure const & ) { throw; } catch( std::exception const & e ) { throw unexpected( where, expr, with_message( e.what() ) ); \ } catch(...) { throw unexpected( where, expr, "of unknown type" ); \ } } // Expression decomposition: inline bool unprintable( char c ) { return 0 <= c && c < ' '; } inline std::string to_hex_string(char c) { std::ostringstream os; os << "\\x" << std::hex << std::setw(2) << std::setfill('0') << static_cast( static_cast(c) ); return os.str(); } inline std::string transformed( char chr ) { struct Tr { char chr; char const * str; } table[] = { {'\\', "\\\\" }, {'\r', "\\r" }, {'\f', "\\f" }, {'\n', "\\n" }, {'\t', "\\t" }, }; for ( Tr * pos = table; pos != table + lest_DIMENSION_OF( table ); ++pos ) { if ( chr == pos->chr ) return pos->str; } return unprintable( chr ) ? to_hex_string( chr ) : std::string( 1, chr ); } inline std::string make_tran_string( std::string const & txt ) { std::ostringstream os; for( std::string::const_iterator pos = txt.begin(); pos != txt.end(); ++pos ) os << transformed( *pos ); return os.str(); } template< typename T > inline std::string to_string( T const & value ); #if lest_CPP11_OR_GREATER || lest_COMPILER_MSVC_VERSION >= 100 inline std::string to_string( std::nullptr_t const & ) { return "nullptr"; } #endif inline std::string to_string( std::string const & txt ) { return "\"" + make_tran_string( txt ) + "\""; } inline std::string to_string( char const * const & txt ) { return "\"" + make_tran_string( txt ) + "\""; } inline std::string to_string( char const & chr ) { return "'" + make_tran_string( std::string( 1, chr ) ) + "'"; } inline std::string to_string( signed char const & chr ) { return to_string( static_cast( chr ) ); } inline std::string to_string( unsigned char const & chr ) { return to_string( static_cast( chr ) ); } inline std::ostream & operator<<( std::ostream & os, approx const & appr ) { return os << appr.magnitude(); } template< typename T > inline std::string make_string( T const * ptr ) { // Note showbase affects the behavior of /integer/ output; std::ostringstream os; os << std::internal << std::hex << std::showbase << std::setw( 2 + 2 * sizeof(T*) ) << std::setfill('0') << reinterpret_cast( ptr ); return os.str(); } template< typename C, typename R > inline std::string make_string( R C::* ptr ) { std::ostringstream os; os << std::internal << std::hex << std::showbase << std::setw( 2 + 2 * sizeof(R C::* ) ) << std::setfill('0') << ptr; return os.str(); } template< typename T > struct string_maker { static std::string to_string( T const & value ) { std::ostringstream os; os << std::boolalpha << value; return os.str(); } }; template< typename T > struct string_maker< T* > { static std::string to_string( T const * ptr ) { return ! ptr ? lest_STRING( lest_nullptr ) : make_string( ptr ); } }; template< typename C, typename R > struct string_maker< R C::* > { static std::string to_string( R C::* ptr ) { return ! ptr ? lest_STRING( lest_nullptr ) : make_string( ptr ); } }; template< typename T > inline std::string to_string( T const & value ) { return string_maker::to_string( value ); } template< typename T1, typename T2 > std::string to_string( std::pair const & pair ) { std::ostringstream oss; oss << "{ " << to_string( pair.first ) << ", " << to_string( pair.second ) << " }"; return oss.str(); } #if lest_CPP11_OR_GREATER template< typename TU, std::size_t N > struct make_tuple_string { static std::string make( TU const & tuple ) { std::ostringstream os; os << to_string( std::get( tuple ) ) << ( N < std::tuple_size::value ? ", ": " "); return make_tuple_string::make( tuple ) + os.str(); } }; template< typename TU > struct make_tuple_string { static std::string make( TU const & ) { return ""; } }; template< typename ...TS > auto to_string( std::tuple const & tuple ) -> std::string { return "{ " + make_tuple_string, sizeof...(TS)>::make( tuple ) + "}"; } #endif // lest_CPP11_OR_GREATER template< typename L, typename R > std::string to_string( L const & lhs, std::string op, R const & rhs ) { std::ostringstream os; os << to_string( lhs ) << " " << op << " " << to_string( rhs ); return os.str(); } template< typename L > struct expression_lhs { L lhs; expression_lhs( L lhs_) : lhs( lhs_) {} operator result() { return result( !!lhs, to_string( lhs ) ); } template< typename R > result operator==( R const & rhs ) { return result( lhs == rhs, to_string( lhs, "==", rhs ) ); } template< typename R > result operator!=( R const & rhs ) { return result( lhs != rhs, to_string( lhs, "!=", rhs ) ); } template< typename R > result operator< ( R const & rhs ) { return result( lhs < rhs, to_string( lhs, "<" , rhs ) ); } template< typename R > result operator<=( R const & rhs ) { return result( lhs <= rhs, to_string( lhs, "<=", rhs ) ); } template< typename R > result operator> ( R const & rhs ) { return result( lhs > rhs, to_string( lhs, ">" , rhs ) ); } template< typename R > result operator>=( R const & rhs ) { return result( lhs >= rhs, to_string( lhs, ">=", rhs ) ); } }; struct expression_decomposer { template< typename L > expression_lhs operator<< ( L const & operand ) { return expression_lhs( operand ); } }; // Reporter: #if lest_FEATURE_COLOURISE inline text red ( text words ) { return "\033[1;31m" + words + "\033[0m"; } inline text green( text words ) { return "\033[1;32m" + words + "\033[0m"; } inline text gray ( text words ) { return "\033[1;30m" + words + "\033[0m"; } inline bool starts_with( text words, text with ) { return 0 == words.find( with ); } inline text replace( text words, text from, text to ) { size_t pos = words.find( from ); return pos == std::string::npos ? words : words.replace( pos, from.length(), to ); } inline text colour( text words ) { if ( starts_with( words, "failed" ) ) return replace( words, "failed", red ( "failed" ) ); else if ( starts_with( words, "passed" ) ) return replace( words, "passed", green( "passed" ) ); return replace( words, "for", gray( "for" ) ); } inline bool is_cout( std::ostream & os ) { return &os == &std::cout; } struct colourise { const text words; colourise( text words ) : words( words ) {} // only colourise for std::cout, not for a stringstream as used in tests: std::ostream & operator()( std::ostream & os ) const { return is_cout( os ) ? os << colour( words ) : os << words; } }; inline std::ostream & operator<<( std::ostream & os, colourise words ) { return words( os ); } #else inline text colourise( text words ) { return words; } #endif inline text pluralise( text word,int n ) { return n == 1 ? word : word + "s"; } inline std::ostream & operator<<( std::ostream & os, comment note ) { return os << (note ? " " + note.info : "" ); } inline std::ostream & operator<<( std::ostream & os, location where ) { #ifdef __GNUG__ return os << where.file << ":" << where.line; #else return os << where.file << "(" << where.line << ")"; #endif } inline void report( std::ostream & os, message const & e, text test ) { os << e.where << ": " << colourise( e.kind ) << e.note << ": " << test << ": " << colourise( e.what() ) << std::endl; } // Test runner: #if lest_FEATURE_REGEX_SEARCH inline bool search( text re, text line ) { return std::regex_search( line, std::regex( re ) ); } #else inline bool case_insensitive_equal( char a, char b ) { return tolower( a ) == tolower( b ); } inline bool search( text part, text line ) { return std::search( line.begin(), line.end(), part.begin(), part.end(), case_insensitive_equal ) != line.end(); } #endif inline bool match( texts whats, text line ) { for ( texts::iterator what = whats.begin(); what != whats.end() ; ++what ) { if ( search( *what, line ) ) return true; } return false; } inline bool hidden( text name ) { #if lest_FEATURE_REGEX_SEARCH texts skipped; skipped.push_back( "\\[\\.\\]" ); skipped.push_back( "\\[hide\\]" ); #else texts skipped; skipped.push_back( "[." ); skipped.push_back( "[hide]" ); #endif return match( skipped, name ); } inline bool none( texts args ) { return args.size() == 0; } inline bool select( text name, texts include ) { if ( none( include ) ) { return ! hidden( name ); } bool any = false; for ( texts::reverse_iterator pos = include.rbegin(); pos != include.rend(); ++pos ) { text & part = *pos; if ( part == "@" || part == "*" ) return true; if ( search( part, name ) ) return true; if ( '!' == part[0] ) { any = true; if ( search( part.substr(1), name ) ) return false; } else { any = false; } } return any && ! hidden( name ); } inline int indefinite( int repeat ) { return repeat == -1; } #if lest_CPP11_OR_GREATER typedef std::mt19937::result_type seed_t; #else typedef unsigned int seed_t; #endif struct options { options() : help(false), abort(false), count(false), list(false), tags(false), time(false) , pass(false), zen(false), lexical(false), random(false), verbose(false), version(false), repeat(1), seed(0) {} bool help; bool abort; bool count; bool list; bool tags; bool time; bool pass; bool zen; bool lexical; bool random; bool verbose; bool version; int repeat; seed_t seed; }; struct env { std::ostream & os; options opt; text testing; std::vector< text > ctx; env( std::ostream & out, options option ) : os( out ), opt( option ), testing(), ctx() {} env & operator()( text test ) { clear(); testing = test; return *this; } bool abort() { return opt.abort; } bool pass() { return opt.pass; } bool zen() { return opt.zen; } void clear() { ctx.clear(); } void pop() { ctx.pop_back(); } void push( text proposition ) { ctx.push_back( proposition ); } text context() { return testing + sections(); } text sections() { if ( ! opt.verbose ) return ""; text msg; for( size_t i = 0; i != ctx.size(); ++i ) { msg += "\n " + ctx[i]; } return msg; } }; struct ctx { env & environment; bool once; ctx( env & environment_, text proposition_ ) : environment( environment_), once( true ) { environment.push( proposition_); } ~ctx() { #if lest_CPP17_OR_GREATER if ( std::uncaught_exceptions() == 0 ) #else if ( ! std::uncaught_exception() ) #endif { environment.pop(); } } operator bool() { bool result = once; once = false; return result; } }; struct action { std::ostream & os; action( std::ostream & out ) : os( out ) {} operator int() { return 0; } bool abort() { return false; } action & operator()( test ) { return *this; } private: action( action const & ); void operator=( action const & ); }; struct print : action { print( std::ostream & out ) : action( out ) {} print & operator()( test testing ) { os << testing.name << "\n"; return *this; } }; inline texts tags( text name, texts result = texts() ) { size_t none = std::string::npos; size_t lb = name.find_first_of( "[" ); size_t rb = name.find_first_of( "]" ); if ( lb == none || rb == none ) return result; result.push_back( name.substr( lb, rb - lb + 1 ) ); return tags( name.substr( rb + 1 ), result ); } struct ptags : action { std::set result; ptags( std::ostream & out ) : action( out ), result() {} ptags & operator()( test testing ) { texts tags_( tags( testing.name ) ); for ( texts::iterator pos = tags_.begin(); pos != tags_.end() ; ++pos ) result.insert( *pos ); return *this; } ~ptags() { std::copy( result.begin(), result.end(), std::ostream_iterator( os, "\n" ) ); } }; struct count : action { int n; count( std::ostream & out ) : action( out ), n( 0 ) {} count & operator()( test ) { ++n; return *this; } ~count() { os << n << " selected " << pluralise("test", n) << "\n"; } }; #if lest_FEATURE_TIME #if lest_PLATFORM_IS_WINDOWS # if ! lest_CPP11_OR_GREATER && ! lest_COMPILER_MSVC_VERSION typedef unsigned long uint64_t; # elif lest_COMPILER_MSVC_VERSION >= 60 && lest_COMPILER_MSVC_VERSION < 100 typedef /*un*/signed __int64 uint64_t; # else using ::uint64_t; # endif #else # if ! lest_CPP11_OR_GREATER typedef unsigned long long uint64_t; # endif #endif #if lest_PLATFORM_IS_WINDOWS inline uint64_t current_ticks() { static LARGE_INTEGER hz = {{ 0,0 }}, hzo = {{ 0,0 }}; if ( ! hz.QuadPart ) { QueryPerformanceFrequency( &hz ); QueryPerformanceCounter ( &hzo ); } LARGE_INTEGER t = {{ 0,0 }}; QueryPerformanceCounter( &t ); return uint64_t( ( ( t.QuadPart - hzo.QuadPart ) * 1000000 ) / hz.QuadPart ); } #else inline uint64_t current_ticks() { timeval t; gettimeofday( &t, lest_nullptr ); return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); } #endif struct timer { const uint64_t start_ticks; timer() : start_ticks( current_ticks() ) {} double elapsed_seconds() const { return static_cast( current_ticks() - start_ticks ) / 1e6; } }; struct times : action { env output; int selected; int failures; timer total; times( std::ostream & out, options option ) : action( out ), output( out, option ), selected( 0 ), failures( 0 ), total() { os << std::setfill(' ') << std::fixed << std::setprecision( lest_FEATURE_TIME_PRECISION ); } operator int() { return failures; } bool abort() { return output.abort() && failures > 0; } times & operator()( test testing ) { timer t; try { testing.behaviour( output( testing.name ) ); } catch( message const & ) { ++failures; } os << std::setw(5) << ( 1000 * t.elapsed_seconds() ) << " ms: " << testing.name << "\n"; return *this; } ~times() { os << "Elapsed time: " << std::setprecision(1) << total.elapsed_seconds() << " s\n"; } }; #else struct times : action { times( std::ostream & out, options ) : action( out ) {} }; #endif struct confirm : action { env output; int selected; int failures; confirm( std::ostream & out, options option ) : action( out ), output( out, option ), selected( 0 ), failures( 0 ) {} operator int() { return failures; } bool abort() { return output.abort() && failures > 0; } confirm & operator()( test testing ) { try { ++selected; testing.behaviour( output( testing.name ) ); } catch( message const & e ) { ++failures; report( os, e, output.context() ); } return *this; } ~confirm() { if ( failures > 0 ) { os << failures << " out of " << selected << " selected " << pluralise("test", selected) << " " << colourise( "failed.\n" ); } else if ( output.pass() ) { os << "All " << selected << " selected " << pluralise("test", selected) << " " << colourise( "passed.\n" ); } } }; template< typename Action > bool abort( Action & perform ) { return perform.abort(); } template< typename Action > Action & for_test( tests specification, texts in, Action & perform, int n = 1 ) { for ( int i = 0; indefinite( n ) || i < n; ++i ) { for ( tests::iterator pos = specification.begin(); pos != specification.end() ; ++pos ) { test & testing = *pos; if ( select( testing.name, in ) ) if ( abort( perform( testing ) ) ) return perform; } } return perform; } inline bool test_less( test const & a, test const & b ) { return a.name < b.name; } inline void sort( tests & specification ) { std::sort( specification.begin(), specification.end(), test_less ); } // Use struct to avoid VC6 error C2664 when using free function: struct rng { int operator()( int n ) { return lest::rand() % n; } }; inline void shuffle( tests & specification, options option ) { #if lest_CPP11_OR_GREATER std::shuffle( specification.begin(), specification.end(), std::mt19937( option.seed ) ); #else lest::srand( option.seed ); rng generator; std::random_shuffle( specification.begin(), specification.end(), generator ); #endif } inline int stoi( text num ) { return static_cast( lest::strtol( num.c_str(), lest_nullptr, 10 ) ); } inline bool is_number( text arg ) { const text digits = "0123456789"; return text::npos != arg.find_first_of ( digits ) && text::npos == arg.find_first_not_of( digits ); } inline seed_t seed( text opt, text arg ) { // std::time_t: implementation dependent if ( arg == "time" ) return static_cast( time( lest_nullptr ) ); if ( is_number( arg ) ) return static_cast( lest::stoi( arg ) ); throw std::runtime_error( "expecting 'time' or positive number with option '" + opt + "', got '" + arg + "' (try option --help)" ); } inline int repeat( text opt, text arg ) { const int num = lest::stoi( arg ); if ( indefinite( num ) || num >= 0 ) return num; throw std::runtime_error( "expecting '-1' or positive number with option '" + opt + "', got '" + arg + "' (try option --help)" ); } inline std::pair split_option( text arg ) { text::size_type pos = arg.rfind( '=' ); return pos == text::npos ? std::make_pair( arg, text() ) : std::make_pair( arg.substr( 0, pos ), arg.substr( pos + 1 ) ); } inline std::pair split_arguments( texts args ) { options option; texts in; bool in_options = true; for ( texts::iterator pos = args.begin(); pos != args.end() ; ++pos ) { text opt, val, arg = *pos; tie( opt, val ) = split_option( arg ); if ( in_options ) { if ( opt[0] != '-' ) { in_options = false; } else if ( opt == "--" ) { in_options = false; continue; } else if ( opt == "-h" || "--help" == opt ) { option.help = true; continue; } else if ( opt == "-a" || "--abort" == opt ) { option.abort = true; continue; } else if ( opt == "-c" || "--count" == opt ) { option.count = true; continue; } else if ( opt == "-g" || "--list-tags" == opt ) { option.tags = true; continue; } else if ( opt == "-l" || "--list-tests" == opt ) { option.list = true; continue; } else if ( opt == "-t" || "--time" == opt ) { option.time = true; continue; } else if ( opt == "-p" || "--pass" == opt ) { option.pass = true; continue; } else if ( opt == "-z" || "--pass-zen" == opt ) { option.zen = true; continue; } else if ( opt == "-v" || "--verbose" == opt ) { option.verbose = true; continue; } else if ( "--version" == opt ) { option.version = true; continue; } else if ( opt == "--order" && "declared" == val ) { /* by definition */ ; continue; } else if ( opt == "--order" && "lexical" == val ) { option.lexical = true; continue; } else if ( opt == "--order" && "random" == val ) { option.random = true; continue; } else if ( opt == "--random-seed" ) { option.seed = seed ( "--random-seed", val ); continue; } else if ( opt == "--repeat" ) { option.repeat = repeat( "--repeat" , val ); continue; } else throw std::runtime_error( "unrecognised option '" + opt + "' (try option --help)" ); } in.push_back( arg ); } option.pass = option.pass || option.zen; return std::make_pair( option, in ); } inline int usage( std::ostream & os ) { os << "\nUsage: test [options] [test-spec ...]\n" "\n" "Options:\n" " -h, --help this help message\n" " -a, --abort abort at first failure\n" " -c, --count count selected tests\n" " -g, --list-tags list tags of selected tests\n" " -l, --list-tests list selected tests\n" " -p, --pass also report passing tests\n" " -z, --pass-zen ... without expansion\n" #if lest_FEATURE_TIME " -t, --time list duration of selected tests\n" #endif " -v, --verbose also report passing or failing sections\n" " --order=declared use source code test order (default)\n" " --order=lexical use lexical sort test order\n" " --order=random use random test order\n" " --random-seed=n use n for random generator seed\n" " --random-seed=time use time for random generator seed\n" " --repeat=n repeat selected tests n times (-1: indefinite)\n" " --version report lest version and compiler used\n" " -- end options\n" "\n" "Test specification:\n" " \"@\", \"*\" all tests, unless excluded\n" " empty all tests, unless tagged [hide] or [.optional-name]\n" #if lest_FEATURE_REGEX_SEARCH " \"re\" select tests that match regular expression\n" " \"!re\" omit tests that match regular expression\n" #else " \"text\" select tests that contain text (case insensitive)\n" " \"!text\" omit tests that contain text (case insensitive)\n" #endif ; return 0; } inline text compiler() { std::ostringstream os; #if defined (__clang__ ) os << "clang " << __clang_version__; #elif defined (__GNUC__ ) os << "gcc " << __GNUC__ << "." << __GNUC_MINOR__ << "." << __GNUC_PATCHLEVEL__; #elif defined ( _MSC_VER ) os << "MSVC " << lest_COMPILER_MSVC_VERSION << " (" << _MSC_VER << ")"; #else os << "[compiler]"; #endif return os.str(); } inline int version( std::ostream & os ) { os << "lest version " << lest_VERSION << "\n" << "Compiled with " << compiler() << " on " << __DATE__ << " at " << __TIME__ << ".\n" << "For more information, see https://github.com/martinmoene/lest.\n"; return 0; } inline int run( tests specification, texts arguments, std::ostream & os = std::cout ) { try { options option; texts in; tie( option, in ) = split_arguments( arguments ); if ( option.lexical ) { sort( specification ); } if ( option.random ) { shuffle( specification, option ); } if ( option.help ) { return usage ( os ); } if ( option.version ) { return version( os ); } if ( option.count ) { count count_( os ); return for_test( specification, in, count_ ); } if ( option.list ) { print print_( os ); return for_test( specification, in, print_ ); } if ( option.tags ) { ptags ptags_( os ); return for_test( specification, in, ptags_ ); } if ( option.time ) { times times_( os, option ); return for_test( specification, in, times_ ); } { confirm confirm_( os, option ); return for_test( specification, in, confirm_, option.repeat ); } } catch ( std::exception const & e ) { os << "Error: " << e.what() << "\n"; return 1; } } // VC6: make(first,last) replaces cont(first,last) template< typename C, typename T > C make( T const * first, T const * const last ) { C result; for ( ; first != last; ++first ) { result.push_back( *first ); } return result; } inline tests make_tests( test const * first, test const * const last ) { return make( first, last ); } inline texts make_texts( char const * const * first, char const * const * last ) { return make( first, last ); } // Traversal of test[N] (test_specification[N]) set up to also work with MSVC6: template< typename C > test const * test_begin( C const & c ) { return &*c; } template< typename C > test const * test_end( C const & c ) { return test_begin( c ) + lest_DIMENSION_OF( c ); } template< typename C > char const * const * text_begin( C const & c ) { return &*c; } template< typename C > char const * const * text_end( C const & c ) { return text_begin( c ) + lest_DIMENSION_OF( c ); } template< typename C > tests make_tests( C const & c ) { return make_tests( test_begin( c ), test_end( c ) ); } template< typename C > texts make_texts( C const & c ) { return make_texts( text_begin( c ), text_end( c ) ); } inline int run( tests const & specification, int argc, char ** argv, std::ostream & os = std::cout ) { return run( specification, make_texts( argv + 1, argv + argc ), os ); } inline int run( tests const & specification, std::ostream & os = std::cout ) { std::cout.sync_with_stdio( false ); return (min)( run( specification, texts(), os ), exit_max_value ); } template< typename C > int run( C const & specification, texts args, std::ostream & os = std::cout ) { return run( make_tests( specification ), args, os ); } template< typename C > int run( C const & specification, int argc, char ** argv, std::ostream & os = std::cout ) { return run( make_tests( specification ), argv, argc, os ); } template< typename C > int run( C const & specification, std::ostream & os = std::cout ) { return run( make_tests( specification ), os ); } } // namespace lest #if defined (__clang__) # pragma clang diagnostic pop #elif defined (__GNUC__) # pragma GCC diagnostic pop #endif #endif // LEST_LEST_HPP_INCLUDED