From 7fed25ad1fe47289fa12bb572f25774fc09a4664 Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Wed, 8 Feb 2017 14:17:17 +0000 Subject: [PATCH] New Matchers implementation - simpler - less templates and machinery - no cloning, copying or unnecessary heap allocations - better factored --- CMakeLists.txt | 1 + include/catch.hpp | 2 +- include/internal/catch_common.h | 2 - include/internal/catch_impl.hpp | 5 - include/internal/catch_matchers.hpp | 355 ++++++--------------- include/internal/catch_matchers_string.hpp | 114 +++++++ include/internal/catch_result_builder.h | 3 +- include/internal/catch_result_builder.hpp | 5 +- 8 files changed, 218 insertions(+), 269 deletions(-) create mode 100644 include/internal/catch_matchers_string.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e9a00c2d..76d33c33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -154,6 +154,7 @@ set(INTERNAL_HEADERS ${HEADER_DIR}/internal/catch_legacy_reporter_adapter.hpp ${HEADER_DIR}/internal/catch_list.hpp ${HEADER_DIR}/internal/catch_matchers.hpp + ${HEADER_DIR}/internal/catch_matchers_string.hpp ${HEADER_DIR}/internal/catch_message.h ${HEADER_DIR}/internal/catch_message.hpp ${HEADER_DIR}/internal/catch_notimplemented_exception.h diff --git a/include/catch.hpp b/include/catch.hpp index b16f2e5f..552482bd 100644 --- a/include/catch.hpp +++ b/include/catch.hpp @@ -36,7 +36,7 @@ #include "internal/catch_generators.hpp" #include "internal/catch_interfaces_exception.h" #include "internal/catch_approx.hpp" -#include "internal/catch_matchers.hpp" +#include "internal/catch_matchers_string.hpp" #include "internal/catch_compiler_capabilities.h" #include "internal/catch_interfaces_tag_alias_registry.h" diff --git a/include/internal/catch_common.h b/include/internal/catch_common.h index 32b57ac1..d325a089 100644 --- a/include/internal/catch_common.h +++ b/include/internal/catch_common.h @@ -24,8 +24,6 @@ #include #include -#include "catch_compiler_capabilities.h" - namespace Catch { struct IConfig; diff --git a/include/internal/catch_impl.hpp b/include/internal/catch_impl.hpp index 53c89574..00101969 100644 --- a/include/internal/catch_impl.hpp +++ b/include/internal/catch_impl.hpp @@ -91,11 +91,6 @@ namespace Catch { TestSpec::TagPattern::~TagPattern() {} TestSpec::ExcludedPattern::~ExcludedPattern() {} - Matchers::Impl::StdString::Equals::~Equals() {} - Matchers::Impl::StdString::Contains::~Contains() {} - Matchers::Impl::StdString::StartsWith::~StartsWith() {} - Matchers::Impl::StdString::EndsWith::~EndsWith() {} - void Config::dummy() {} namespace TestCaseTracking { diff --git a/include/internal/catch_matchers.hpp b/include/internal/catch_matchers.hpp index 9146e39e..d241c874 100644 --- a/include/internal/catch_matchers.hpp +++ b/include/internal/catch_matchers.hpp @@ -8,313 +8,152 @@ #ifndef TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED #define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED +#include "catch_common.h" + namespace Catch { namespace Matchers { namespace Impl { - namespace Generic { - template class AllOf; - template class AnyOf; - template class Not; - } + template struct MatchAllOf; + template struct MatchAnyOf; + template struct MatchNotOf; - template - struct Matcher : SharedImpl - { - typedef ExpressionT ExpressionType; - - virtual ~Matcher() {} - virtual Ptr clone() const = 0; - virtual bool match( ExpressionT const& expr ) const = 0; - virtual std::string toString() const = 0; - - Generic::AllOf operator && ( Matcher const& other ) const; - Generic::AnyOf operator || ( Matcher const& other ) const; - Generic::Not operator ! () const; - }; - - template - struct MatcherImpl : Matcher { - - virtual Ptr > clone() const { - return Ptr >( new DerivedT( static_cast( *this ) ) ); - } - }; - - namespace Generic { - template - class Not : public MatcherImpl, ExpressionT> { + class MatcherUntypedBase { public: - explicit Not( Matcher const& matcher ) : m_matcher(matcher.clone()) {} - Not( Not const& other ) : m_matcher( other.m_matcher ) {} - - virtual bool match( ExpressionT const& expr ) const CATCH_OVERRIDE { - return !m_matcher->match( expr ); + std::string toString() const { + if( m_cachedToString.empty() ) + m_cachedToString = toStringUncached(); + return m_cachedToString; } - virtual std::string toString() const CATCH_OVERRIDE { - return "not " + m_matcher->toString(); - } - private: - Ptr< Matcher > m_matcher; + protected: + virtual std::string toStringUncached() const = 0; + mutable std::string m_cachedToString; }; - template - class AllOf : public MatcherImpl, ExpressionT> { - public: + template + struct MatcherBase : MatcherUntypedBase { - AllOf() {} - AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {} + virtual bool match( ArgT const& arg ) const = 0; - AllOf& add( Matcher const& matcher ) { - m_matchers.push_back( matcher.clone() ); - return *this; - } - virtual bool match( ExpressionT const& expr ) const - { - for( std::size_t i = 0; i < m_matchers.size(); ++i ) - if( !m_matchers[i]->match( expr ) ) + MatchAllOf operator && ( MatcherBase const& other ) const; + MatchAnyOf operator || ( MatcherBase const& other ) const; + MatchNotOf operator ! () const; + }; + + template + struct MatchAllOf : MatcherBase { + virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if (!m_matchers[i]->match(arg)) return false; + } return true; } - virtual std::string toString() const { - std::ostringstream oss; - oss << "( "; + virtual std::string toStringUncached() const CATCH_OVERRIDE { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; for( std::size_t i = 0; i < m_matchers.size(); ++i ) { if( i != 0 ) - oss << " and "; - oss << m_matchers[i]->toString(); + description += " and "; + description += m_matchers[i]->toString(); } - oss << " )"; - return oss.str(); + description += " )"; + return description; } - AllOf operator && ( Matcher const& other ) const { - AllOf allOfExpr( *this ); - allOfExpr.add( other ); - return allOfExpr; - } - - private: - std::vector > > m_matchers; - }; - - template - class AnyOf : public MatcherImpl, ExpressionT> { - public: - - AnyOf() {} - AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {} - - AnyOf& add( Matcher const& matcher ) { - m_matchers.push_back( matcher.clone() ); + MatchAllOf& operator && ( MatcherBase const& other ) { + m_matchers.push_back( &other ); return *this; } - virtual bool match( ExpressionT const& expr ) const - { - for( std::size_t i = 0; i < m_matchers.size(); ++i ) - if( m_matchers[i]->match( expr ) ) + + std::vector const*> m_matchers; + }; + template + struct MatchAnyOf : MatcherBase { + + virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if (m_matchers[i]->match(arg)) return true; + } return false; } - virtual std::string toString() const { - std::ostringstream oss; - oss << "( "; + virtual std::string toStringUncached() const CATCH_OVERRIDE { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; for( std::size_t i = 0; i < m_matchers.size(); ++i ) { if( i != 0 ) - oss << " or "; - oss << m_matchers[i]->toString(); + description += " or "; + description += m_matchers[i]->toString(); } - oss << " )"; - return oss.str(); + description += " )"; + return description; } - AnyOf operator || ( Matcher const& other ) const { - AnyOf anyOfExpr( *this ); - anyOfExpr.add( other ); - return anyOfExpr; + MatchAnyOf& operator || ( MatcherBase const& other ) { + m_matchers.push_back( &other ); + return *this; } - private: - std::vector > > m_matchers; + std::vector const*> m_matchers; }; - } // namespace Generic + template + struct MatchNotOf : MatcherBase { - template - Generic::AllOf Matcher::operator && ( Matcher const& other ) const { - Generic::AllOf allOfExpr; - allOfExpr.add( *this ); - allOfExpr.add( other ); - return allOfExpr; - } - - template - Generic::AnyOf Matcher::operator || ( Matcher const& other ) const { - Generic::AnyOf anyOfExpr; - anyOfExpr.add( *this ); - anyOfExpr.add( other ); - return anyOfExpr; - } - - template - Generic::Not Matcher::operator ! () const { - return Generic::Not( *this ); - } - - - namespace StdString { - - inline std::string makeString( std::string const& str ) { return str; } - inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); } - - struct CasedString - { - CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) - : m_caseSensitivity( caseSensitivity ), - m_str( adjustString( str ) ) - {} - std::string adjustString( std::string const& str ) const { - return m_caseSensitivity == CaseSensitive::No - ? toLower( str ) - : str; + MatchNotOf( MatcherBase const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {} + virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { + return !m_underlyingMatcher.match( arg ); } - std::string toStringSuffix() const - { - return m_caseSensitivity == CaseSensitive::No - ? " (case insensitive)" - : std::string(); + + virtual std::string toStringUncached() const CATCH_OVERRIDE { + return "not " + m_underlyingMatcher.toString(); } - CaseSensitive::Choice m_caseSensitivity; - std::string m_str; + MatcherBase const& m_underlyingMatcher; }; - struct Equals : MatcherImpl { - Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) - : m_data( str, caseSensitivity ) - {} - Equals( Equals const& other ) : m_data( other.m_data ){} + template + MatchAllOf MatcherBase::operator && ( MatcherBase const& other ) const { + return MatchAllOf() && *this && other; + } + template + MatchAnyOf MatcherBase::operator || ( MatcherBase const& other ) const { + return MatchAnyOf() || *this || other; + } + template + MatchNotOf MatcherBase::operator ! () const { + return MatchNotOf( *this ); + } - virtual ~Equals(); - - virtual bool match( std::string const& expr ) const { - return m_data.m_str == m_data.adjustString( expr );; - } - virtual std::string toString() const { - return "equals: \"" + m_data.m_str + '"' + m_data.toStringSuffix(); - } - - CasedString m_data; - }; - - struct Contains : MatcherImpl { - Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) - : m_data( substr, caseSensitivity ){} - Contains( Contains const& other ) : m_data( other.m_data ){} - - virtual ~Contains(); - - virtual bool match( std::string const& expr ) const { - return m_data.adjustString( expr ).find( m_data.m_str ) != std::string::npos; - } - virtual std::string toString() const { - return "contains: \"" + m_data.m_str + '"' + m_data.toStringSuffix(); - } - - CasedString m_data; - }; - - struct StartsWith : MatcherImpl { - StartsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) - : m_data( substr, caseSensitivity ){} - - StartsWith( StartsWith const& other ) : m_data( other.m_data ){} - - virtual ~StartsWith(); - - virtual bool match( std::string const& expr ) const { - return startsWith( m_data.adjustString( expr ), m_data.m_str ); - } - virtual std::string toString() const { - return "starts with: \"" + m_data.m_str + '"' + m_data.toStringSuffix(); - } - - CasedString m_data; - }; - - struct EndsWith : MatcherImpl { - EndsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) - : m_data( substr, caseSensitivity ){} - EndsWith( EndsWith const& other ) : m_data( other.m_data ){} - - virtual ~EndsWith(); - - virtual bool match( std::string const& expr ) const { - return endsWith( m_data.adjustString( expr ), m_data.m_str ); - } - virtual std::string toString() const { - return "ends with: \"" + m_data.m_str + '"' + m_data.toStringSuffix(); - } - - CasedString m_data; - }; - } // namespace StdString } // namespace Impl + // The following functions create the actual matcher objects. // This allows the types to be inferred - template - inline Impl::Generic::Not Not( Impl::Matcher const& m ) { - return Impl::Generic::Not( m ); + // - deprecated: prefer ||, && and ! + template + inline Impl::MatchNotOf Not( Impl::MatcherBase const& underlyingMatcher ) { + return Impl::MatchNotOf( underlyingMatcher ); } - - template - inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, - Impl::Matcher const& m2 ) { - return Impl::Generic::AllOf().add( m1 ).add( m2 ); + template + inline Impl::MatchAllOf AllOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2 ) { + return Impl::MatchAllOf() && m1 && m2; } - template - inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, - Impl::Matcher const& m2, - Impl::Matcher const& m3 ) { - return Impl::Generic::AllOf().add( m1 ).add( m2 ).add( m3 ); + template + inline Impl::MatchAllOf AllOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2, Impl::MatcherBase const& m3 ) { + return Impl::MatchAllOf() && m1 && m2 && m3; } - template - inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, - Impl::Matcher const& m2 ) { - return Impl::Generic::AnyOf().add( m1 ).add( m2 ); + template + inline Impl::MatchAnyOf AnyOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2 ) { + return Impl::MatchAnyOf() || m1 || m2; } - template - inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, - Impl::Matcher const& m2, - Impl::Matcher const& m3 ) { - return Impl::Generic::AnyOf().add( m1 ).add( m2 ).add( m3 ); - } - - inline Impl::StdString::Equals Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { - return Impl::StdString::Equals( str, caseSensitivity ); - } - inline Impl::StdString::Equals Equals( const char* str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { - return Impl::StdString::Equals( Impl::StdString::makeString( str ), caseSensitivity ); - } - inline Impl::StdString::Contains Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { - return Impl::StdString::Contains( substr, caseSensitivity ); - } - inline Impl::StdString::Contains Contains( const char* substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { - return Impl::StdString::Contains( Impl::StdString::makeString( substr ), caseSensitivity ); - } - inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) { - return Impl::StdString::StartsWith( substr ); - } - inline Impl::StdString::StartsWith StartsWith( const char* substr ) { - return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) ); - } - inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) { - return Impl::StdString::EndsWith( substr ); - } - inline Impl::StdString::EndsWith EndsWith( const char* substr ) { - return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) ); + template + inline Impl::MatchAnyOf AnyOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2, Impl::MatcherBase const& m3 ) { + return Impl::MatchAnyOf() || m1 || m2 || m3; } } // namespace Matchers diff --git a/include/internal/catch_matchers_string.hpp b/include/internal/catch_matchers_string.hpp new file mode 100644 index 00000000..07ed4c17 --- /dev/null +++ b/include/internal/catch_matchers_string.hpp @@ -0,0 +1,114 @@ +/* + * Created by Phil Nash on 08/02/2017. + * Copyright (c) 2017 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_CATCH_MATCHERS_STRING_HPP_INCLUDED +#define TWOBLUECUBES_CATCH_MATCHERS_STRING_HPP_INCLUDED + +#include "catch_matchers.hpp" + +namespace Catch { +namespace Matchers { + + namespace StdString { + + struct CasedString + { + CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_str( adjustString( str ) ) + {} + std::string adjustString( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower( str ) + : str; + } + std::string caseSensitivitySuffix() const { + return m_caseSensitivity == CaseSensitive::No + ? " (case insensitive)" + : std::string(); + } + CaseSensitive::Choice m_caseSensitivity; + std::string m_str; + }; + + // !TBD Move impl + struct StringMatcherBase : Impl::MatcherBase { + StringMatcherBase( std::string operation, CasedString const& comparator ) + : m_comparator( comparator ), + m_operation( operation ) { + } + + virtual std::string toStringUncached() const CATCH_OVERRIDE { + std::string description; + description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + + m_comparator.caseSensitivitySuffix().size()); + description += m_operation; + description += ": \""; + description += m_comparator.m_str; + description += "\""; + description += m_comparator.caseSensitivitySuffix(); + return description; + } + + CasedString m_comparator; + std::string m_operation; + }; + + // !TBD Move impl + struct EqualsMatcher : StringMatcherBase { + EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {} + + virtual bool match( std::string const& source ) const CATCH_OVERRIDE { + return m_comparator.adjustString( source ) == m_comparator.m_str; + } + }; + struct ContainsMatcher : StringMatcherBase { + ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {} + + virtual bool match( std::string const& source ) const CATCH_OVERRIDE { + return contains( m_comparator.adjustString( source ), m_comparator.m_str ); + } + }; + struct StartsWithMatcher : StringMatcherBase { + StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {} + + virtual bool match( std::string const& source ) const CATCH_OVERRIDE { + return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + }; + struct EndsWithMatcher : StringMatcherBase { + EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {} + + virtual bool match( std::string const& source ) const CATCH_OVERRIDE { + return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + }; + + } // namespace StdString + + + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + inline StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { + return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + inline StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { + return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + inline StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { + return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + inline StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { + return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + +} // namespace Matchers +} // namespace Catch + +#endif // TWOBLUECUBES_CATCH_MATCHERS_STRING_HPP_INCLUDED diff --git a/include/internal/catch_result_builder.h b/include/internal/catch_result_builder.h index dfdffd5f..305c21e6 100644 --- a/include/internal/catch_result_builder.h +++ b/include/internal/catch_result_builder.h @@ -64,7 +64,7 @@ namespace Catch { void captureResult( ResultWas::OfType resultType ); void captureExpression(); void captureExpectedException( std::string const& expectedMessage ); - void captureExpectedException( Matchers::Impl::Matcher const& matcher ); + void captureExpectedException( Matchers::Impl::MatcherBase const& matcher ); void handleResult( AssertionResult const& result ); void react(); bool shouldDebugBreak() const; @@ -106,6 +106,7 @@ namespace Catch { endExpression( expr ); } + } // namespace Catch #endif // TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED diff --git a/include/internal/catch_result_builder.hpp b/include/internal/catch_result_builder.hpp index 7bb2cdc2..f0c7b631 100644 --- a/include/internal/catch_result_builder.hpp +++ b/include/internal/catch_result_builder.hpp @@ -60,12 +60,13 @@ namespace Catch { void ResultBuilder::captureExpectedException( std::string const& expectedMessage ) { if( expectedMessage.empty() ) - captureExpectedException( Matchers::Impl::Generic::AllOf() ); + captureExpectedException( Matchers::Impl::MatchAllOf() ); else captureExpectedException( Matchers::Equals( expectedMessage ) ); } - void ResultBuilder::captureExpectedException( Matchers::Impl::Matcher const& matcher ) { + + void ResultBuilder::captureExpectedException( Matchers::Impl::MatcherBase const& matcher ) { assert( !isFalseTest( m_assertionInfo.resultDisposition ) ); AssertionResultData data = m_data;