Provide a regex matcher against std::string

Related to #1040
This commit is contained in:
Martin Hořeňovský 2017-11-13 15:35:31 +01:00
parent a06b6dc3ea
commit b0857e846f
8 changed files with 219 additions and 8 deletions

View File

@ -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.

View File

@ -8,6 +8,9 @@
#include "catch_matchers_string.h"
#include "catch_string_manip.h"
#include "catch_tostring.h"
#include <regex>
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

View File

@ -52,6 +52,16 @@ namespace Matchers {
bool match( std::string const& source ) const override;
};
struct RegexMatcher : MatcherBase<std::string> {
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

View File

@ -655,6 +655,30 @@ DecompositionTests.cpp:<line number>: FAILED:
with expansion:
Hey, its truthy!
-------------------------------------------------------------------------------
Regex string matcher
-------------------------------------------------------------------------------
MatchersTests.cpp:<line number>
...............................................................................
MatchersTests.cpp:<line number>: 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:<line number>: 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:<line number>: 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

View File

@ -4343,6 +4343,30 @@ DecompositionTests.cpp:<line number>: FAILED:
with expansion:
Hey, its truthy!
-------------------------------------------------------------------------------
Regex string matcher
-------------------------------------------------------------------------------
MatchersTests.cpp:<line number>
...............................................................................
MatchersTests.cpp:<line number>: 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:<line number>: 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:<line number>: 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:<line number>:
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:<line number>:
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:<line number>:
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:<line number>:
PASSED:
REQUIRE_THAT( testStringForMatching(), Matches("^.* 'abc' .*$") )
with expansion:
"this string contains 'abc' as a substring" matches "^.* 'abc' .*$" case
sensitively
MatchersTests.cpp:<line number>:
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:<line number>:
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

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuitesloose text artifact
>
<testsuite name="<exe-name>" errors="15" failures="85" tests="940" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
<testsuite name="<exe-name>" errors="15" failures="88" tests="948" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
<testcase classname="<exe-name>.global" name="# A test name that starts with a #" time="{duration}"/>
<testcase classname="<exe-name>.global" name="#1005: Comparing pointer to int and long (NULL can be either on various systems)" time="{duration}"/>
<testcase classname="<exe-name>.global" name="#1027" time="{duration}"/>
@ -480,6 +480,17 @@ MessageTests.cpp:<line number>
DecompositionTests.cpp:<line number>
</failure>
</testcase>
<testcase classname="<exe-name>.global" name="Regex string matcher" time="{duration}">
<failure message="&quot;this string contains 'abc' as a substring&quot; matches &quot;this STRING contains 'abc' as a substring&quot; case sensitively" type="CHECK_THAT">
MatchersTests.cpp:<line number>
</failure>
<failure message="&quot;this string contains 'abc' as a substring&quot; matches &quot;contains 'abc' as a substring&quot; case sensitively" type="CHECK_THAT">
MatchersTests.cpp:<line number>
</failure>
<failure message="&quot;this string contains 'abc' as a substring&quot; matches &quot;this string contains 'abc' as a&quot; case sensitively" type="CHECK_THAT">
MatchersTests.cpp:<line number>
</failure>
</testcase>
<testcase classname="<exe-name>.global" name="SUCCEED counts as a test pass" time="{duration}"/>
<testcase classname="<exe-name>.global" name="SUCCESS does not require an argument" time="{duration}"/>
<testcase classname="<exe-name>.Fixture" name="Scenario: BDD tests requiring Fixtures to provide commonly-accessed data or methods/Given: No operations precede me" time="{duration}"/>

View File

@ -5026,6 +5026,33 @@
</Expression>
<OverallResult success="false"/>
</TestCase>
<TestCase name="Regex string matcher" tags="[.][.failing][matchers]" filename="projects/<exe-name>/MatchersTests.cpp" >
<Expression success="false" type="CHECK_THAT" filename="projects/<exe-name>/MatchersTests.cpp" >
<Original>
testStringForMatching(), Matches("this STRING contains 'abc' as a substring")
</Original>
<Expanded>
"this string contains 'abc' as a substring" matches "this STRING contains 'abc' as a substring" case sensitively
</Expanded>
</Expression>
<Expression success="false" type="CHECK_THAT" filename="projects/<exe-name>/MatchersTests.cpp" >
<Original>
testStringForMatching(), Matches("contains 'abc' as a substring")
</Original>
<Expanded>
"this string contains 'abc' as a substring" matches "contains 'abc' as a substring" case sensitively
</Expanded>
</Expression>
<Expression success="false" type="CHECK_THAT" filename="projects/<exe-name>/MatchersTests.cpp" >
<Original>
testStringForMatching(), Matches("this string contains 'abc' as a")
</Original>
<Expanded>
"this string contains 'abc' as a substring" matches "this string contains 'abc' as a" case sensitively
</Expanded>
</Expression>
<OverallResult success="false"/>
</TestCase>
<TestCase name="SUCCEED counts as a test pass" tags="[messages]" filename="projects/<exe-name>/MessageTests.cpp" >
<OverallResult success="true"/>
</TestCase>
@ -5321,6 +5348,46 @@ Message from section two
"this string contains 'abc' as a substring" ends with: "substring"
</Expanded>
</Expression>
<Expression success="true" type="REQUIRE_THAT" filename="projects/<exe-name>/MatchersTests.cpp" >
<Original>
testStringForMatching(), Matches("this string contains 'abc' as a substring")
</Original>
<Expanded>
"this string contains 'abc' as a substring" matches "this string contains 'abc' as a substring" case sensitively
</Expanded>
</Expression>
<Expression success="true" type="REQUIRE_THAT" filename="projects/<exe-name>/MatchersTests.cpp" >
<Original>
testStringForMatching(), Matches("this string CONTAINS 'abc' as a substring", Catch::CaseSensitive::No)
</Original>
<Expanded>
"this string contains 'abc' as a substring" matches "this string CONTAINS 'abc' as a substring" case insensitively
</Expanded>
</Expression>
<Expression success="true" type="REQUIRE_THAT" filename="projects/<exe-name>/MatchersTests.cpp" >
<Original>
testStringForMatching(), Matches("^this string contains 'abc' as a substring$")
</Original>
<Expanded>
"this string contains 'abc' as a substring" matches "^this string contains 'abc' as a substring$" case sensitively
</Expanded>
</Expression>
<Expression success="true" type="REQUIRE_THAT" filename="projects/<exe-name>/MatchersTests.cpp" >
<Original>
testStringForMatching(), Matches("^.* 'abc' .*$")
</Original>
<Expanded>
"this string contains 'abc' as a substring" matches "^.* 'abc' .*$" case sensitively
</Expanded>
</Expression>
<Expression success="true" type="REQUIRE_THAT" filename="projects/<exe-name>/MatchersTests.cpp" >
<Original>
testStringForMatching(), Matches("^.* 'ABC' .*$", Catch::CaseSensitive::No)
</Original>
<Expanded>
"this string contains 'abc' as a substring" matches "^.* 'ABC' .*$" case insensitively
</Expanded>
</Expression>
<OverallResult success="true"/>
</TestCase>
<TestCase name="StringRef" tags="[Strings]" filename="projects/<exe-name>/StringRef.tests.cpp" >
@ -8764,7 +8831,7 @@ loose text artifact
</Section>
<OverallResult success="true"/>
</TestCase>
<OverallResults successes="819" failures="100" expectedFailures="21"/>
<OverallResults successes="824" failures="103" expectedFailures="21"/>
</Group>
<OverallResults successes="819" failures="99" expectedFailures="21"/>
<OverallResults successes="824" failures="102" expectedFailures="21"/>
</Catch>

View File

@ -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(),