mirror of
https://github.com/google/googletest.git
synced 2026-06-15 08:26:11 +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_;
|
::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.
|
// Functor for use in TransformTuple.
|
||||||
// Performs MatcherCast<Target> on an input argument of any type.
|
// Performs MatcherCast<Target> on an input argument of any type.
|
||||||
template <typename Target>
|
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.
|
// Implements UnorderedElementsAre.
|
||||||
template <typename MatcherTuple>
|
template <typename MatcherTuple>
|
||||||
class [[nodiscard]] UnorderedElementsAreMatcher {
|
class [[nodiscard]] UnorderedElementsAreMatcher {
|
||||||
@ -5422,6 +5557,18 @@ UnorderedElementsAre(const Args&... matchers) {
|
|||||||
std::make_tuple(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.
|
// Define variadic matcher versions.
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
internal::AllOfMatcher<typename std::decay<const Args&>::type...> AllOf(
|
internal::AllOfMatcher<typename std::decay<const Args&>::type...> AllOf(
|
||||||
|
|||||||
@ -3439,6 +3439,78 @@ TEST(ContainsTest, WorksForTwoDimensionalNativeArray) {
|
|||||||
EXPECT_THAT(a, Contains(Not(Contains(5))));
|
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
|
||||||
} // namespace gmock_matchers_test
|
} // namespace gmock_matchers_test
|
||||||
} // namespace testing
|
} // namespace testing
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user