diff --git a/docs/matchers.md b/docs/matchers.md index 9717cae8..fb95ce26 100644 --- a/docs/matchers.md +++ b/docs/matchers.md @@ -39,7 +39,10 @@ REQUIRE_THAT( str, Catch currently provides some matchers, they are in the `Catch::Matchers` and `Catch` namespaces. ### String matchers -The string matchers are `StartsWith`, `EndsWith`, `Contains` and `Equals`. Each of them also takes an optional second argument, that decides case sensitivity (by-default, they are case sensitive). +The string matchers are `StartsWith`, `EndsWith`, `Contains`, `Equals` and `Matches`. The first four match a literal (sub)string against a result, while `Matches` takes and matches an ECMAScript regex. Do note that `Matches` matches the string as a whole, meaning that "abc" will not match against "abcd", but "abc.*" will. + +Each of the provided `std::string` matchers also takes an optional second argument, that decides case sensitivity (by-default, they are case sensitive). + ### Vector matchers The vector matchers are `Contains`, `VectorContains` and `Equals`. `VectorContains` looks for a single element in the matched vector, `Contains` looks for a set (vector) of elements inside the matched vector. diff --git a/include/internal/catch_matchers_string.cpp b/include/internal/catch_matchers_string.cpp index 2f8ffdb8..1e3e72fd 100644 --- a/include/internal/catch_matchers_string.cpp +++ b/include/internal/catch_matchers_string.cpp @@ -8,6 +8,9 @@ #include "catch_matchers_string.h" #include "catch_string_manip.h" +#include "catch_tostring.h" + +#include namespace Catch { namespace Matchers { @@ -74,6 +77,23 @@ namespace Matchers { return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); } + + + RegexMatcher::RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity): m_regex(std::move(regex)), m_caseSensitivity(caseSensitivity) {} + + bool RegexMatcher::match(std::string const& matchee) const { + auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway + if (m_caseSensitivity == CaseSensitive::Choice::No) { + flags |= std::regex::icase; + } + auto reg = std::regex(m_regex, flags); + return std::regex_match(matchee, reg); + } + + std::string RegexMatcher::describe() const { + return "matches " + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Choice::Yes)? " case sensitively" : " case insensitively"); + } + } // namespace StdString @@ -90,5 +110,9 @@ namespace Matchers { return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) ); } + StdString::RegexMatcher Matches(std::string const& regex, CaseSensitive::Choice caseSensitivity) { + return StdString::RegexMatcher(regex, caseSensitivity); + } + } // namespace Matchers } // namespace Catch diff --git a/include/internal/catch_matchers_string.h b/include/internal/catch_matchers_string.h index 3306951b..fdbc03ce 100644 --- a/include/internal/catch_matchers_string.h +++ b/include/internal/catch_matchers_string.h @@ -52,6 +52,16 @@ namespace Matchers { bool match( std::string const& source ) const override; }; + struct RegexMatcher : MatcherBase { + RegexMatcher( std::string regex, CaseSensitive::Choice caseSensitivity ); + bool match( std::string const& matchee ) const override; + std::string describe() const override; + + private: + std::string m_regex; + CaseSensitive::Choice m_caseSensitivity; + }; + } // namespace StdString @@ -62,6 +72,7 @@ namespace Matchers { StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::RegexMatcher Matches( std::string const& regex, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); } // namespace Matchers } // namespace Catch diff --git a/projects/SelfTest/Baselines/console.std.approved.txt b/projects/SelfTest/Baselines/console.std.approved.txt index 38d1f12a..fcc7a7c5 100644 --- a/projects/SelfTest/Baselines/console.std.approved.txt +++ b/projects/SelfTest/Baselines/console.std.approved.txt @@ -655,6 +655,30 @@ DecompositionTests.cpp:: FAILED: with expansion: Hey, its truthy! +------------------------------------------------------------------------------- +Regex string matcher +------------------------------------------------------------------------------- +MatchersTests.cpp: +............................................................................... + +MatchersTests.cpp:: FAILED: + CHECK_THAT( testStringForMatching(), Matches("this STRING contains 'abc' as a substring") ) +with expansion: + "this string contains 'abc' as a substring" matches "this STRING contains + 'abc' as a substring" case sensitively + +MatchersTests.cpp:: FAILED: + CHECK_THAT( testStringForMatching(), Matches("contains 'abc' as a substring") ) +with expansion: + "this string contains 'abc' as a substring" matches "contains 'abc' as a + substring" case sensitively + +MatchersTests.cpp:: FAILED: + CHECK_THAT( testStringForMatching(), Matches("this string contains 'abc' as a") ) +with expansion: + "this string contains 'abc' as a substring" matches "this string contains + 'abc' as a" case sensitively + A string sent directly to stdout A string sent directly to stderr Message from section one @@ -1003,6 +1027,6 @@ with expansion: "{?}" == "1" =============================================================================== -test cases: 188 | 137 passed | 47 failed | 4 failed as expected -assertions: 940 | 823 passed | 96 failed | 21 failed as expected +test cases: 189 | 137 passed | 48 failed | 4 failed as expected +assertions: 948 | 828 passed | 99 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/console.sw.approved.txt b/projects/SelfTest/Baselines/console.sw.approved.txt index 9b4d9abc..5fbc73f1 100644 --- a/projects/SelfTest/Baselines/console.sw.approved.txt +++ b/projects/SelfTest/Baselines/console.sw.approved.txt @@ -4343,6 +4343,30 @@ DecompositionTests.cpp:: FAILED: with expansion: Hey, its truthy! +------------------------------------------------------------------------------- +Regex string matcher +------------------------------------------------------------------------------- +MatchersTests.cpp: +............................................................................... + +MatchersTests.cpp:: FAILED: + CHECK_THAT( testStringForMatching(), Matches("this STRING contains 'abc' as a substring") ) +with expansion: + "this string contains 'abc' as a substring" matches "this STRING contains + 'abc' as a substring" case sensitively + +MatchersTests.cpp:: FAILED: + CHECK_THAT( testStringForMatching(), Matches("contains 'abc' as a substring") ) +with expansion: + "this string contains 'abc' as a substring" matches "contains 'abc' as a + substring" case sensitively + +MatchersTests.cpp:: FAILED: + CHECK_THAT( testStringForMatching(), Matches("this string contains 'abc' as a") ) +with expansion: + "this string contains 'abc' as a substring" matches "this string contains + 'abc' as a" case sensitively + ------------------------------------------------------------------------------- SUCCEED counts as a test pass ------------------------------------------------------------------------------- @@ -4646,6 +4670,41 @@ PASSED: with expansion: "this string contains 'abc' as a substring" ends with: "substring" +MatchersTests.cpp:: +PASSED: + REQUIRE_THAT( testStringForMatching(), Matches("this string contains 'abc' as a substring") ) +with expansion: + "this string contains 'abc' as a substring" matches "this string contains + 'abc' as a substring" case sensitively + +MatchersTests.cpp:: +PASSED: + REQUIRE_THAT( testStringForMatching(), Matches("this string CONTAINS 'abc' as a substring", Catch::CaseSensitive::No) ) +with expansion: + "this string contains 'abc' as a substring" matches "this string CONTAINS + 'abc' as a substring" case insensitively + +MatchersTests.cpp:: +PASSED: + REQUIRE_THAT( testStringForMatching(), Matches("^this string contains 'abc' as a substring$") ) +with expansion: + "this string contains 'abc' as a substring" matches "^this string contains + 'abc' as a substring$" case sensitively + +MatchersTests.cpp:: +PASSED: + REQUIRE_THAT( testStringForMatching(), Matches("^.* 'abc' .*$") ) +with expansion: + "this string contains 'abc' as a substring" matches "^.* 'abc' .*$" case + sensitively + +MatchersTests.cpp:: +PASSED: + REQUIRE_THAT( testStringForMatching(), Matches("^.* 'ABC' .*$", Catch::CaseSensitive::No) ) +with expansion: + "this string contains 'abc' as a substring" matches "^.* 'ABC' .*$" case + insensitively + ------------------------------------------------------------------------------- StringRef Empty string @@ -7903,6 +7962,6 @@ MiscTests.cpp:: PASSED: =============================================================================== -test cases: 188 | 135 passed | 49 failed | 4 failed as expected -assertions: 939 | 819 passed | 99 failed | 21 failed as expected +test cases: 189 | 135 passed | 50 failed | 4 failed as expected +assertions: 947 | 824 passed | 102 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/junit.sw.approved.txt b/projects/SelfTest/Baselines/junit.sw.approved.txt index fff51dd5..8bad0b8f 100644 --- a/projects/SelfTest/Baselines/junit.sw.approved.txt +++ b/projects/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -480,6 +480,17 @@ MessageTests.cpp: DecompositionTests.cpp: + + +MatchersTests.cpp: + + +MatchersTests.cpp: + + +MatchersTests.cpp: + + diff --git a/projects/SelfTest/Baselines/xml.sw.approved.txt b/projects/SelfTest/Baselines/xml.sw.approved.txt index 67d6eff0..cdcaedf7 100644 --- a/projects/SelfTest/Baselines/xml.sw.approved.txt +++ b/projects/SelfTest/Baselines/xml.sw.approved.txt @@ -5026,6 +5026,33 @@ + + + + testStringForMatching(), Matches("this STRING contains 'abc' as a substring") + + + "this string contains 'abc' as a substring" matches "this STRING contains 'abc' as a substring" case sensitively + + + + + testStringForMatching(), Matches("contains 'abc' as a substring") + + + "this string contains 'abc' as a substring" matches "contains 'abc' as a substring" case sensitively + + + + + testStringForMatching(), Matches("this string contains 'abc' as a") + + + "this string contains 'abc' as a substring" matches "this string contains 'abc' as a" case sensitively + + + + @@ -5321,6 +5348,46 @@ Message from section two "this string contains 'abc' as a substring" ends with: "substring" + + + testStringForMatching(), Matches("this string contains 'abc' as a substring") + + + "this string contains 'abc' as a substring" matches "this string contains 'abc' as a substring" case sensitively + + + + + testStringForMatching(), Matches("this string CONTAINS 'abc' as a substring", Catch::CaseSensitive::No) + + + "this string contains 'abc' as a substring" matches "this string CONTAINS 'abc' as a substring" case insensitively + + + + + testStringForMatching(), Matches("^this string contains 'abc' as a substring$") + + + "this string contains 'abc' as a substring" matches "^this string contains 'abc' as a substring$" case sensitively + + + + + testStringForMatching(), Matches("^.* 'abc' .*$") + + + "this string contains 'abc' as a substring" matches "^.* 'abc' .*$" case sensitively + + + + + testStringForMatching(), Matches("^.* 'ABC' .*$", Catch::CaseSensitive::No) + + + "this string contains 'abc' as a substring" matches "^.* 'ABC' .*$" case insensitively + + @@ -8764,7 +8831,7 @@ loose text artifact - + - + diff --git a/projects/SelfTest/MatchersTests.cpp b/projects/SelfTest/MatchersTests.cpp index f1da1b36..9a992b73 100644 --- a/projects/SelfTest/MatchersTests.cpp +++ b/projects/SelfTest/MatchersTests.cpp @@ -36,6 +36,12 @@ TEST_CASE("String matchers", "[matchers]" ) CHECK_THAT( testStringForMatching(), StartsWith( "this" ) ); CHECK_THAT( testStringForMatching(), EndsWith( "substring" ) ); + + REQUIRE_THAT(testStringForMatching(), Matches("this string contains 'abc' as a substring")); + REQUIRE_THAT(testStringForMatching(), Matches("this string CONTAINS 'abc' as a substring", Catch::CaseSensitive::No)); + REQUIRE_THAT(testStringForMatching(), Matches("^this string contains 'abc' as a substring$")); + REQUIRE_THAT(testStringForMatching(), Matches("^.* 'abc' .*$")); + REQUIRE_THAT(testStringForMatching(), Matches("^.* 'ABC' .*$", Catch::CaseSensitive::No)); } TEST_CASE("Contains string matcher", "[.][failing][matchers]") @@ -63,6 +69,12 @@ TEST_CASE("Equals", "[matchers]") CHECK_THAT( testStringForMatching(), Equals( "this string contains 'abc' as a substring" ) ); } +TEST_CASE("Regex string matcher", "[matchers][.failing]") { + CHECK_THAT( testStringForMatching(), Matches("this STRING contains 'abc' as a substring")); + CHECK_THAT( testStringForMatching(), Matches("contains 'abc' as a substring")); + CHECK_THAT( testStringForMatching(), Matches("this string contains 'abc' as a")); +} + TEST_CASE("Matchers can be (AllOf) composed with the && operator", "[matchers][operators][operator&&]") { CHECK_THAT( testStringForMatching(),