From 67ec8709eae23dfb4fe30f789fd788d5a9c77ea8 Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Wed, 26 Sep 2012 18:38:26 +0100 Subject: [PATCH] First cut of command line support for tags --- include/internal/catch_commandline.hpp | 21 +- include/internal/catch_tags.hpp | 6 +- include/internal/catch_test_case_info.h | 2 +- include/internal/catch_test_case_info.hpp | 2 +- include/internal/catch_test_spec.h | 21 +- projects/SelfTest/MiscTests.cpp | 8 +- projects/SelfTest/TestMain.cpp | 33 +- single_include/catch.hpp | 599 ++++++++++++---------- 8 files changed, 388 insertions(+), 304 deletions(-) diff --git a/include/internal/catch_commandline.hpp b/include/internal/catch_commandline.hpp index fcb4732d..f03ae3c8 100644 --- a/include/internal/catch_commandline.hpp +++ b/include/internal/catch_commandline.hpp @@ -286,16 +286,16 @@ namespace Catch { } virtual void parseIntoConfig( const Command& cmd, ConfigData& config ) { -// std::string groupName; -// for( std::size_t i = 0; i < cmd.argsCount(); ++i ) { -// if( i != 0 ) -// groupName += " "; -// groupName += cmd[i]; -// } -// TestCaseFilters filters( groupName ); -// for( std::size_t i = 0; i < cmd.argsCount(); ++i ) -// filters.addFilter( TestCaseFilter( cmd[i] ) ); -// config.filters.push_back( filters ); + std::string groupName; + for( std::size_t i = 0; i < cmd.argsCount(); ++i ) { + if( i != 0 ) + groupName += " "; + groupName += cmd[i]; + } + TestCaseFilters filters( groupName ); + for( std::size_t i = 0; i < cmd.argsCount(); ++i ) + filters.addTags( cmd[i] ); + config.filters.push_back( filters ); } }; @@ -622,6 +622,7 @@ namespace Catch { AllOptions() { add(); // Keep this one first + add(); add(); add(); add(); diff --git a/include/internal/catch_tags.hpp b/include/internal/catch_tags.hpp index d94eaad2..566181c8 100644 --- a/include/internal/catch_tags.hpp +++ b/include/internal/catch_tags.hpp @@ -78,8 +78,7 @@ namespace Catch { std::string m_remainder; }; - class Tag - { + class Tag { public: Tag() : m_isNegated( false ) @@ -106,8 +105,7 @@ namespace Catch { bool m_isNegated; }; - class TagSet - { + class TagSet { typedef std::map TagMap; public: void add( const Tag& tag ) { diff --git a/include/internal/catch_test_case_info.h b/include/internal/catch_test_case_info.h index d09673b6..bcc87dce 100644 --- a/include/internal/catch_test_case_info.h +++ b/include/internal/catch_test_case_info.h @@ -37,7 +37,7 @@ namespace Catch { bool isHidden() const; bool hasTag( const std::string& tag ) const; bool matchesTags( const std::string& tagPattern ) const; - const std::set& tags() const; + const std::set& getTags() const; void swap( TestCaseInfo& other ); bool operator == ( const TestCaseInfo& other ) const; diff --git a/include/internal/catch_test_case_info.hpp b/include/internal/catch_test_case_info.hpp index 90693a5d..16d01d6f 100644 --- a/include/internal/catch_test_case_info.hpp +++ b/include/internal/catch_test_case_info.hpp @@ -83,7 +83,7 @@ namespace Catch { TagExpressionParser( exp ).parse( tagPattern ); return exp.matches( m_tags ); } - const std::set& TestCaseInfo::tags() const { + const std::set& TestCaseInfo::getTags() const { return m_tags; } diff --git a/include/internal/catch_test_spec.h b/include/internal/catch_test_spec.h index 3a1daeec..55e087cd 100644 --- a/include/internal/catch_test_spec.h +++ b/include/internal/catch_test_spec.h @@ -9,6 +9,7 @@ #define TWOBLUECUBES_CATCH_TESTSPEC_H_INCLUDED #include "catch_test_case_info.h" +#include "catch_tags.hpp" #include #include @@ -113,7 +114,24 @@ namespace Catch { m_inclusionFilters.push_back( filter ); } + void addTags( const std::string& tagPattern ) { + TagExpression exp; + TagExpressionParser( exp ).parse( tagPattern ); + + m_tagExpressions.push_back( exp ); + } + bool shouldInclude( const TestCaseInfo& testCase ) const { + if( !m_tagExpressions.empty() ) { + std::vector::const_iterator it = m_tagExpressions.begin(); + std::vector::const_iterator itEnd = m_tagExpressions.end(); + for(; it != itEnd; ++it ) + if( it->matches( testCase.getTags() ) ) + break; + if( it == itEnd ) + return false; + } + if( !m_inclusionFilters.empty() ) { std::vector::const_iterator it = m_inclusionFilters.begin(); std::vector::const_iterator itEnd = m_inclusionFilters.end(); @@ -123,7 +141,7 @@ namespace Catch { if( it == itEnd ) return false; } - else if( m_exclusionFilters.empty() ) { + else if( m_exclusionFilters.empty() && m_tagExpressions.empty() ) { return !testCase.isHidden(); } @@ -135,6 +153,7 @@ namespace Catch { return true; } private: + std::vector m_tagExpressions; std::vector m_inclusionFilters; std::vector m_exclusionFilters; std::string m_name; diff --git a/projects/SelfTest/MiscTests.cpp b/projects/SelfTest/MiscTests.cpp index a60428ee..5b102ffb 100644 --- a/projects/SelfTest/MiscTests.cpp +++ b/projects/SelfTest/MiscTests.cpp @@ -294,7 +294,13 @@ TEST_CASE( "empty", "An empty test with no assertions" ) { } -TEST_CASE( "Nice descriptive name", "[tag1][tag2][hide]" ) +TEST_CASE( "Nice descriptive name", "[tag1][tag2][tag3][hide]" ) { WARN( "This one ran" ); } +TEST_CASE( "first tag", "[tag1]" ) +{ +} +TEST_CASE( "second tag", "[tag2]" ) +{ +} diff --git a/projects/SelfTest/TestMain.cpp b/projects/SelfTest/TestMain.cpp index 2c0c2514..b0fe11f5 100644 --- a/projects/SelfTest/TestMain.cpp +++ b/projects/SelfTest/TestMain.cpp @@ -247,8 +247,31 @@ TEST_CASE( "selftest/parser/2", "ConfigData" ) { REQUIRE( config.allowThrows == false ); } - } + + SECTION( "streams", "" ) { + SECTION( "-o filename", "" ) { + const char* argv[] = { "test", "-o", "filename.ext" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + REQUIRE( config.outputFilename == "filename.ext" ); + REQUIRE( config.stream.empty() ); + } + SECTION( "-o %stdout", "" ) { + const char* argv[] = { "test", "-o", "%stdout" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + REQUIRE( config.stream == "stdout" ); + REQUIRE( config.outputFilename.empty() ); + } + SECTION( "--out", "" ) { + const char* argv[] = { "test", "--out", "filename.ext" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + REQUIRE( config.outputFilename == "filename.ext" ); + } + } + SECTION( "combinations", "" ) { SECTION( "-a -b", "" ) { const char* argv[] = { "test", "-a", "-b", "-nt" }; @@ -345,7 +368,7 @@ TEST_CASE( "selftest/tags", "" ) { CHECK( oneTag.getDescription() == "" ); CHECK( oneTag.hasTag( "one" ) ); - CHECK( oneTag.tags().size() == 1 ); + CHECK( oneTag.getTags().size() == 1 ); CHECK( oneTag.matchesTags( p1 ) == true ); CHECK( oneTag.matchesTags( p2 ) == true ); @@ -361,7 +384,7 @@ TEST_CASE( "selftest/tags", "" ) { CHECK( twoTags.hasTag( "one" ) ); CHECK( twoTags.hasTag( "two" ) ); CHECK( twoTags.hasTag( "three" ) == false ); - CHECK( twoTags.tags().size() == 2 ); + CHECK( twoTags.getTags().size() == 2 ); CHECK( twoTags.matchesTags( p1 ) == true ); CHECK( twoTags.matchesTags( p2 ) == true ); @@ -376,7 +399,7 @@ TEST_CASE( "selftest/tags", "" ) { CHECK( oneTagWithExtras.getDescription() == "1234" ); CHECK( oneTagWithExtras.hasTag( "one" ) ); CHECK( oneTagWithExtras.hasTag( "two" ) == false ); - CHECK( oneTagWithExtras.tags().size() == 1 ); + CHECK( oneTagWithExtras.getTags().size() == 1 ); } SECTION( "start of a tag, but not closed", "" ) { @@ -385,7 +408,7 @@ TEST_CASE( "selftest/tags", "" ) { CHECK( oneTagOpen.getDescription() == "[one" ); CHECK( oneTagOpen.hasTag( "one" ) == false ); - CHECK( oneTagOpen.tags().size() == 0 ); + CHECK( oneTagOpen.getTags().size() == 0 ); } SECTION( "hidden", "" ) { diff --git a/single_include/catch.hpp b/single_include/catch.hpp index 656b3d89..dd3fd3aa 100644 --- a/single_include/catch.hpp +++ b/single_include/catch.hpp @@ -1,5 +1,5 @@ /* - * Generated: 2012-09-24 08:29:47.663979 + * Generated: 2012-09-26 18:38:02.155253 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. @@ -222,6 +222,7 @@ namespace Catch { namespace Catch { class TestCaseInfo; + class Stream; struct IResultCapture; struct IRunner; struct IGeneratorsForTest; @@ -254,7 +255,7 @@ namespace Catch { IContext& getCurrentContext(); IMutableContext& getCurrentMutableContext(); void cleanUpContext(); - std::streambuf* createStreamBuf( const std::string& streamName ); + Stream createStream( const std::string& streamName ); } @@ -1669,7 +1670,7 @@ namespace Catch { bool isHidden() const; bool hasTag( const std::string& tag ) const; bool matchesTags( const std::string& tagPattern ) const; - const std::set& tags() const; + const std::set& getTags() const; void swap( TestCaseInfo& other ); bool operator == ( const TestCaseInfo& other ) const; @@ -1686,6 +1687,193 @@ namespace Catch { }; } +// #included from: catch_tags.hpp +#define TWOBLUECUBES_CATCH_TAGS_HPP_INCLUDED + +#include +#include +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + class TagParser { + public: + virtual ~TagParser(); + + void parse( const std::string& str ) { + std::size_t pos = 0; + while( pos < str.size() ) { + char c = str[pos]; + if( c == '[' ) { + std::size_t end = str.find_first_of( ']', pos ); + if( end != std::string::npos ) { + acceptTag( str.substr( pos+1, end-pos-1 ) ); + pos = end+1; + } + else { + acceptChar( c ); + pos++; + } + } + else { + acceptChar( c ); + pos++; + } + } + endParse(); + } + + protected: + virtual void acceptTag( const std::string& tag ) = 0; + virtual void acceptChar( char c ) = 0; + virtual void endParse() {} + + private: + }; + + class TagExtracter : public TagParser { + public: + + TagExtracter( std::set& tags ) + : m_tags( tags ) + {} + virtual ~TagExtracter(); + + void parse( std::string& description ) { + TagParser::parse( description ); + description = m_remainder; + } + + private: + virtual void acceptTag( const std::string& tag ) { + m_tags.insert( tag ); + } + virtual void acceptChar( char c ) { + m_remainder += c; + } + + std::set& m_tags; + std::string m_remainder; + }; + + class Tag { + public: + Tag() + : m_isNegated( false ) + {} + + Tag( const std::string& name, bool isNegated ) + : m_name( name ), + m_isNegated( isNegated ) + {} + + std::string getName() const { + return m_name; + } + bool isNegated() const { + return m_isNegated; + } + + bool operator ! () const { + return m_name.empty(); + } + + private: + std::string m_name; + bool m_isNegated; + }; + + class TagSet { + typedef std::map TagMap; + public: + void add( const Tag& tag ) { + m_tags.insert( std::make_pair( tag.getName(), tag ) ); + } + + // needed? + Tag find( const std::string& name ) const { + TagMap::const_iterator it = m_tags.find( name ); + if( it == m_tags.end() ) + return Tag(); + else + return it->second; + } + bool empty() const { + return m_tags.empty(); + } + + bool matches( const std::set& tags ) const { + TagMap::const_iterator it = m_tags.begin(); + TagMap::const_iterator itEnd = m_tags.end(); + for(; it != itEnd; ++it ) { + bool found = tags.find( it->first ) != tags.end(); + if( found == it->second.isNegated() ) + return false; + } + return true; + } + + private: + TagMap m_tags; + }; + + class TagExpression { + public: + bool matches( const std::set& tags ) const { + std::vector::const_iterator it = m_tagSets.begin(); + std::vector::const_iterator itEnd = m_tagSets.end(); + for(; it != itEnd; ++it ) + if( it->matches( tags ) ) + return true; + return false; + } + + private: + friend class TagExpressionParser; + + std::vector m_tagSets; + }; + + class TagExpressionParser : public TagParser { + public: + TagExpressionParser( TagExpression& exp ) + : m_isNegated( false ), + m_exp( exp ) + {} + + ~TagExpressionParser(); + + private: + virtual void acceptTag( const std::string& tag ) { + m_currentTagSet.add( Tag( tag, m_isNegated ) ); + m_isNegated = false; + } + virtual void acceptChar( char c ) { + switch( c ) { + case '~': + m_isNegated = true; + break; + case ',': + m_exp.m_tagSets.push_back( m_currentTagSet ); + break; + } + } + virtual void endParse() { + if( !m_currentTagSet.empty() ) + m_exp.m_tagSets.push_back( m_currentTagSet ); + } + + bool m_isNegated; + TagSet m_currentTagSet; + TagExpression& m_exp; + }; + +} // end namespace Catch + #include #include @@ -1789,7 +1977,24 @@ namespace Catch { m_inclusionFilters.push_back( filter ); } + void addTags( const std::string& tagPattern ) { + TagExpression exp; + TagExpressionParser( exp ).parse( tagPattern ); + + m_tagExpressions.push_back( exp ); + } + bool shouldInclude( const TestCaseInfo& testCase ) const { + if( !m_tagExpressions.empty() ) { + std::vector::const_iterator it = m_tagExpressions.begin(); + std::vector::const_iterator itEnd = m_tagExpressions.end(); + for(; it != itEnd; ++it ) + if( it->matches( testCase.getTags() ) ) + break; + if( it == itEnd ) + return false; + } + if( !m_inclusionFilters.empty() ) { std::vector::const_iterator it = m_inclusionFilters.begin(); std::vector::const_iterator itEnd = m_inclusionFilters.end(); @@ -1799,7 +2004,7 @@ namespace Catch { if( it == itEnd ) return false; } - else if( m_exclusionFilters.empty() ) { + else if( m_exclusionFilters.empty() && m_tagExpressions.empty() ) { return !testCase.isHidden(); } @@ -1811,6 +2016,7 @@ namespace Catch { return true; } private: + std::vector m_tagExpressions; std::vector m_inclusionFilters; std::vector m_exclusionFilters; std::string m_name; @@ -1831,6 +2037,84 @@ namespace Catch { }; } +// #included from: catch_stream.hpp +#define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED + +#include +#include + +namespace Catch { + + template + class StreamBufImpl : public StreamBufBase { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() { + sync(); + } + + private: + int overflow( int c ) { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast( c ) ) ); + else + sputc( static_cast( c ) ); + } + return 0; + } + + int sync() { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter { + + void operator()( const std::string &str ) { + writeToDebugConsole( str ); + } + }; + + class Stream { + public: + Stream() + : streamBuf( NULL ), isOwned( false ) + {} + + Stream( std::streambuf* _streamBuf, bool _isOwned ) + : streamBuf( _streamBuf ), isOwned( _isOwned ) + {} + + void release() { + if( isOwned ) { + delete streamBuf; + streamBuf = NULL; + isOwned = false; + } + } + + std::streambuf* streamBuf; + + private: + bool isOwned; + }; +} + #include #include #include @@ -1897,19 +2181,17 @@ namespace Catch { public: Config() - : m_streambuf( NULL ), - m_os( std::cout.rdbuf() ) + : m_os( std::cout.rdbuf() ) {} Config( const ConfigData& data ) : m_data( data ), - m_streambuf( NULL ), m_os( std::cout.rdbuf() ) {} virtual ~Config() { m_os.rdbuf( std::cout.rdbuf() ); - delete m_streambuf; + m_stream.release(); } void setFilename( const std::string& filename ) { @@ -1949,10 +2231,10 @@ namespace Catch { } void useStream( const std::string& streamName ) { - std::streambuf* newBuf = createStreamBuf( streamName ); - setStreamBuf( newBuf ); - delete m_streambuf; - m_streambuf = newBuf; + Stream stream = createStream( streamName ); + setStreamBuf( stream.streamBuf ); + m_stream.release(); + m_stream = stream; } void addTestSpec( const std::string& testSpec ) { @@ -1984,7 +2266,7 @@ namespace Catch { ConfigData m_data; // !TBD Move these out of here - std::streambuf* m_streambuf; + Stream m_stream; mutable std::ostream m_os; }; @@ -2719,10 +3001,10 @@ namespace Catch { m_optionNames.push_back( "--help" ); } virtual std::string argsSynopsis() const { - return "[