mirror of
https://github.com/google/googletest.git
synced 2026-06-15 00:16:13 +08:00
Introduce ContainsSubsequence matcher.
Subsequences are defined as: "a subsequence of a given sequence is a sequence that can be derived from the given sequence by deleting some or no elements without changing the order of the remaining elements." See: https://en.wikipedia.org/wiki/Subsequence This new matcher checks if a container contains elements that match a given sequence of matchers in the specified order, but not necessarily contiguously. The implementation is very similar to other matchers like ElementsAre or Contains. PiperOrigin-RevId: 918323422 Change-Id: I56d7ebbe6f81038c93546ef7585db59eea5dbd57
This commit is contained in:
parent
dc3c9eda2f
commit
add971c7cb
@ -3944,6 +3944,114 @@ class [[nodiscard]] UnorderedElementsAreMatcherImpl
|
||||
::std::vector<Matcher<const Element&>> matchers_;
|
||||
};
|
||||
|
||||
// Implements ContainsSubequence().
|
||||
template <typename Container>
|
||||
class [[nodiscard]] ContainsSubsequenceMatcherImpl
|
||||
: public MatcherInterface<Container> {
|
||||
public:
|
||||
typedef GTEST_REMOVE_REFERENCE_AND_CONST_(Container) RawContainer;
|
||||
typedef internal::StlContainerView<RawContainer> View;
|
||||
typedef typename View::type StlContainer;
|
||||
typedef typename View::const_reference StlContainerReference;
|
||||
typedef typename internal::RangeTraits<StlContainer>::value_type Element;
|
||||
|
||||
// Constructs the matcher from a sequence of element values or
|
||||
// element matchers.
|
||||
template <typename InputIter>
|
||||
ContainsSubsequenceMatcherImpl(InputIter first, InputIter last) {
|
||||
std::copy(first, last, std::back_inserter(matchers_));
|
||||
}
|
||||
|
||||
// Describes what this matcher does.
|
||||
void DescribeTo(::std::ostream* os) const override {
|
||||
if (matchers_.size() == 0) {
|
||||
*os << "contains an empty sequence";
|
||||
return;
|
||||
}
|
||||
*os << "contains in order a subsequence of elements that matches: ";
|
||||
for (size_t i = 0; i != matchers_.size(); ++i) {
|
||||
if (i > 0) {
|
||||
*os << ", then ";
|
||||
}
|
||||
matchers_[i].DescribeTo(os);
|
||||
}
|
||||
}
|
||||
|
||||
// Describes what the negation of this matcher does.
|
||||
void DescribeNegationTo(::std::ostream* os) const override {
|
||||
if (matchers_.size() == 0) {
|
||||
*os << "does not contain an empty sequence";
|
||||
return;
|
||||
}
|
||||
*os << "does not contain in order a subsequence of elements that matches ";
|
||||
for (size_t i = 0; i != matchers_.size(); ++i) {
|
||||
if (i > 0) {
|
||||
*os << ", then ";
|
||||
}
|
||||
matchers_[i].DescribeTo(os);
|
||||
}
|
||||
}
|
||||
|
||||
bool MatchAndExplain(Container container,
|
||||
MatchResultListener* listener) const override {
|
||||
StlContainerReference stl_container = View::ConstReference(container);
|
||||
|
||||
size_t num_matches = 0;
|
||||
// Track which elements match which matcher.
|
||||
size_t num_elements_examined = 0;
|
||||
std::vector<size_t> match_indices;
|
||||
for (const auto& element : stl_container) {
|
||||
if (num_matches == matchers_.size()) {
|
||||
break;
|
||||
}
|
||||
StringMatchResultListener inner_listener;
|
||||
if (matchers_[num_matches].MatchAndExplain(element, &inner_listener)) {
|
||||
++num_matches;
|
||||
match_indices.push_back(num_elements_examined);
|
||||
}
|
||||
|
||||
++num_elements_examined;
|
||||
}
|
||||
|
||||
if (num_matches < matchers_.size()) {
|
||||
// We provide an explanation of the first matcher that failed to match
|
||||
// when trying to match the subsequence greedily. A better approach would
|
||||
// be to compute the longest common subsequence (LCS) between the
|
||||
// elements and the matchers and then provide explanations for any
|
||||
// remaining matchers and elements that couldn't match each other, but
|
||||
// this introduces a fair bit of complexity.
|
||||
if (listener->IsInterested()) {
|
||||
for (size_t i = 0; i < match_indices.size(); ++i) {
|
||||
*listener << "found match for matcher #" << i
|
||||
<< " with element at position #" << match_indices[i]
|
||||
<< ", ";
|
||||
}
|
||||
|
||||
if (num_matches > 0) {
|
||||
*listener << "but ";
|
||||
}
|
||||
|
||||
*listener << "could not find a match for matcher #" << num_matches
|
||||
<< " (";
|
||||
matchers_[num_matches].DescribeTo(listener->stream());
|
||||
*listener << ")";
|
||||
|
||||
if (num_matches > 0) {
|
||||
*listener << " after the last match at position #"
|
||||
<< match_indices[num_matches - 1];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
::std::vector<Matcher<const Element&>> matchers_;
|
||||
};
|
||||
|
||||
// Functor for use in TransformTuple.
|
||||
// Performs MatcherCast<Target> on an input argument of any type.
|
||||
template <typename Target>
|
||||
@ -3954,6 +4062,33 @@ struct CastAndAppendTransform {
|
||||
}
|
||||
};
|
||||
|
||||
// Implements ContainsSubsequence.
|
||||
template <typename MatcherTuple>
|
||||
class [[nodiscard]] ContainsSubsequenceMatcher {
|
||||
public:
|
||||
explicit ContainsSubsequenceMatcher(const MatcherTuple& args)
|
||||
: matchers_(args) {}
|
||||
|
||||
template <typename Container>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
operator Matcher<Container>() const {
|
||||
typedef GTEST_REMOVE_REFERENCE_AND_CONST_(Container) RawContainer;
|
||||
typedef typename internal::StlContainerView<RawContainer>::type View;
|
||||
typedef typename internal::RangeTraits<View>::value_type Element;
|
||||
typedef ::std::vector<Matcher<const Element&>> MatcherVec;
|
||||
MatcherVec matchers;
|
||||
matchers.reserve(::std::tuple_size<MatcherTuple>::value);
|
||||
TransformTupleValues(CastAndAppendTransform<const Element&>(), matchers_,
|
||||
::std::back_inserter(matchers));
|
||||
return Matcher<Container>(
|
||||
new ContainsSubsequenceMatcherImpl<const Container&>(matchers.begin(),
|
||||
matchers.end()));
|
||||
}
|
||||
|
||||
private:
|
||||
const MatcherTuple matchers_;
|
||||
};
|
||||
|
||||
// Implements UnorderedElementsAre.
|
||||
template <typename MatcherTuple>
|
||||
class [[nodiscard]] UnorderedElementsAreMatcher {
|
||||
@ -5422,6 +5557,18 @@ UnorderedElementsAre(const Args&... matchers) {
|
||||
std::make_tuple(matchers...));
|
||||
}
|
||||
|
||||
// ContainsSubsequence(m1, m2, ..., mk) matches a container that contains
|
||||
// elements that match m1, m2, ..., mk in that order with possible gaps
|
||||
// between them.
|
||||
template <typename... Args>
|
||||
internal::ContainsSubsequenceMatcher<
|
||||
::std::tuple<typename ::std::decay<const Args&>::type...>>
|
||||
ContainsSubsequence(const Args&... matchers) {
|
||||
return internal::ContainsSubsequenceMatcher<
|
||||
::std::tuple<typename ::std::decay<const Args&>::type...>>(
|
||||
::std::make_tuple(matchers...));
|
||||
}
|
||||
|
||||
// Define variadic matcher versions.
|
||||
template <typename... Args>
|
||||
internal::AllOfMatcher<typename std::decay<const Args&>::type...> AllOf(
|
||||
|
||||
@ -3439,6 +3439,78 @@ TEST(ContainsTest, WorksForTwoDimensionalNativeArray) {
|
||||
EXPECT_THAT(a, Contains(Not(Contains(5))));
|
||||
}
|
||||
|
||||
// Tests ContainsSubsequence().
|
||||
|
||||
TEST(ContainsSubsequenceTest, WorksForNativeArray) {
|
||||
const int a[] = {1, 2, 3, 4, 5};
|
||||
EXPECT_THAT(a, ContainsSubsequence(1, 3, 4));
|
||||
EXPECT_THAT(a, Not(ContainsSubsequence(1, 3, 2)));
|
||||
}
|
||||
|
||||
TEST(ContainsSubsequenceTest, AcceptsMatcher) {
|
||||
const int a[] = {1, 2, 3, 4, 5};
|
||||
EXPECT_THAT(a, ContainsSubsequence(Eq(1), Gt(3), Gt(4)));
|
||||
EXPECT_THAT(a, Not(ContainsSubsequence(1, Gt(3), Lt(3))));
|
||||
}
|
||||
|
||||
TEST(ContainsSubsequenceTest, WorksForTwoDimensionalNativeArray) {
|
||||
int a[][3] = {{1, 2, 3}, {7, 8, 9}, {4, 5, 6}};
|
||||
EXPECT_THAT(a, ContainsSubsequence(ElementsAre(1, 2, 3), Contains(4)));
|
||||
EXPECT_THAT(a,
|
||||
Not(ContainsSubsequence(Contains(1), Contains(8), Contains(9))));
|
||||
}
|
||||
|
||||
TEST(ContainsSubsequenceTest, WorksForVector) {
|
||||
const vector<int> a = {1, 2, 3, 4, 5};
|
||||
EXPECT_THAT(a, ContainsSubsequence(1, 3, 4));
|
||||
EXPECT_THAT(a, Not(ContainsSubsequence(1, 3, 2)));
|
||||
}
|
||||
|
||||
TEST(ContainsSubsequenceTest, WorksForEmptySmallSizedSubsequences) {
|
||||
const int a[] = {1, 2, 3, 4, 5};
|
||||
EXPECT_THAT(a, ContainsSubsequence());
|
||||
EXPECT_THAT(a, ContainsSubsequence(Gt(4)));
|
||||
EXPECT_THAT(a, Not(ContainsSubsequence(Gt(6))));
|
||||
EXPECT_THAT(a, ContainsSubsequence(Lt(2), Gt(3)));
|
||||
EXPECT_THAT(a, Not(ContainsSubsequence(Lt(2), Lt(2))));
|
||||
}
|
||||
|
||||
TEST(ContainsSubsequenceTest, DescribesItselfCorrectly) {
|
||||
Matcher<const int (&)[5]> m = ContainsSubsequence(1, 3, 4);
|
||||
EXPECT_EQ(
|
||||
"contains in order a subsequence of elements that matches: is equal to "
|
||||
"1, then is equal to 3, then is equal to 4",
|
||||
Describe(m));
|
||||
m = ContainsSubsequence(Eq(1), Gt(3), Gt(4));
|
||||
EXPECT_EQ(
|
||||
"contains in order a subsequence of elements that matches: is equal to "
|
||||
"1, then is > 3, then is > 4",
|
||||
Describe(m));
|
||||
|
||||
m = Not(ContainsSubsequence(1, 3, 4));
|
||||
EXPECT_EQ(
|
||||
"does not contain in order a subsequence of elements that matches is "
|
||||
"equal to 1, then is equal to 3, then is equal to 4",
|
||||
Describe(m));
|
||||
}
|
||||
|
||||
TEST(ContainsSubsequenceTest, ExplainsMismatchCorrectlyForSingleMatcher) {
|
||||
const int a[] = {1, 2, 3, 4, 5};
|
||||
Matcher<const int (&)[5]> m = ContainsSubsequence(Eq(6));
|
||||
EXPECT_EQ(Explain(m, a),
|
||||
"could not find a match for matcher #0 (is equal to 6)");
|
||||
}
|
||||
|
||||
TEST(ContainsSubsequenceTest, ExplainsMismatchCorrectlyForMultipleMatchers) {
|
||||
const int a[] = {1, 2, 3, 4, 5};
|
||||
Matcher<const int (&)[5]> m = ContainsSubsequence(Eq(2), Gt(4), Gt(4));
|
||||
EXPECT_EQ(
|
||||
Explain(m, a),
|
||||
"found match for matcher #0 with element at position #1, found match for "
|
||||
"matcher #1 with element at position #4, but could not find a match for "
|
||||
"matcher #2 (is > 4) after the last match at position #4");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace gmock_matchers_test
|
||||
} // namespace testing
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user