Googletest export
Add ::testing::FieldsAre matcher for objects that support get<> and structured bindings. PiperOrigin-RevId: 337165285
This commit is contained in:
@@ -2879,6 +2879,203 @@ class PairMatcher {
|
||||
const SecondMatcher second_matcher_;
|
||||
};
|
||||
|
||||
template <typename T, size_t... I>
|
||||
auto UnpackStructImpl(const T& t, IndexSequence<I...>, int)
|
||||
-> decltype(std::tie(get<I>(t)...)) {
|
||||
static_assert(std::tuple_size<T>::value == sizeof...(I),
|
||||
"Number of arguments doesn't match the number of fields.");
|
||||
return std::tie(get<I>(t)...);
|
||||
}
|
||||
|
||||
#if defined(__cpp_structured_bindings) && __cpp_structured_bindings >= 201606
|
||||
template <typename T>
|
||||
auto UnpackStructImpl(const T& t, MakeIndexSequence<1>, char) {
|
||||
const auto& [a] = t;
|
||||
return std::tie(a);
|
||||
}
|
||||
template <typename T>
|
||||
auto UnpackStructImpl(const T& t, MakeIndexSequence<2>, char) {
|
||||
const auto& [a, b] = t;
|
||||
return std::tie(a, b);
|
||||
}
|
||||
template <typename T>
|
||||
auto UnpackStructImpl(const T& t, MakeIndexSequence<3>, char) {
|
||||
const auto& [a, b, c] = t;
|
||||
return std::tie(a, b, c);
|
||||
}
|
||||
template <typename T>
|
||||
auto UnpackStructImpl(const T& t, MakeIndexSequence<4>, char) {
|
||||
const auto& [a, b, c, d] = t;
|
||||
return std::tie(a, b, c, d);
|
||||
}
|
||||
template <typename T>
|
||||
auto UnpackStructImpl(const T& t, MakeIndexSequence<5>, char) {
|
||||
const auto& [a, b, c, d, e] = t;
|
||||
return std::tie(a, b, c, d, e);
|
||||
}
|
||||
template <typename T>
|
||||
auto UnpackStructImpl(const T& t, MakeIndexSequence<6>, char) {
|
||||
const auto& [a, b, c, d, e, f] = t;
|
||||
return std::tie(a, b, c, d, e, f);
|
||||
}
|
||||
template <typename T>
|
||||
auto UnpackStructImpl(const T& t, MakeIndexSequence<7>, char) {
|
||||
const auto& [a, b, c, d, e, f, g] = t;
|
||||
return std::tie(a, b, c, d, e, f, g);
|
||||
}
|
||||
template <typename T>
|
||||
auto UnpackStructImpl(const T& t, MakeIndexSequence<8>, char) {
|
||||
const auto& [a, b, c, d, e, f, g, h] = t;
|
||||
return std::tie(a, b, c, d, e, f, g, h);
|
||||
}
|
||||
template <typename T>
|
||||
auto UnpackStructImpl(const T& t, MakeIndexSequence<9>, char) {
|
||||
const auto& [a, b, c, d, e, f, g, h, i] = t;
|
||||
return std::tie(a, b, c, d, e, f, g, h, i);
|
||||
}
|
||||
template <typename T>
|
||||
auto UnpackStructImpl(const T& t, MakeIndexSequence<10>, char) {
|
||||
const auto& [a, b, c, d, e, f, g, h, i, j] = t;
|
||||
return std::tie(a, b, c, d, e, f, g, h, i, j);
|
||||
}
|
||||
template <typename T>
|
||||
auto UnpackStructImpl(const T& t, MakeIndexSequence<11>, char) {
|
||||
const auto& [a, b, c, d, e, f, g, h, i, j, k] = t;
|
||||
return std::tie(a, b, c, d, e, f, g, h, i, j, k);
|
||||
}
|
||||
template <typename T>
|
||||
auto UnpackStructImpl(const T& t, MakeIndexSequence<12>, char) {
|
||||
const auto& [a, b, c, d, e, f, g, h, i, j, k, l] = t;
|
||||
return std::tie(a, b, c, d, e, f, g, h, i, j, k, l);
|
||||
}
|
||||
template <typename T>
|
||||
auto UnpackStructImpl(const T& t, MakeIndexSequence<13>, char) {
|
||||
const auto& [a, b, c, d, e, f, g, h, i, j, k, l, m] = t;
|
||||
return std::tie(a, b, c, d, e, f, g, h, i, j, k, l, m);
|
||||
}
|
||||
template <typename T>
|
||||
auto UnpackStructImpl(const T& t, MakeIndexSequence<14>, char) {
|
||||
const auto& [a, b, c, d, e, f, g, h, i, j, k, l, m, n] = t;
|
||||
return std::tie(a, b, c, d, e, f, g, h, i, j, k, l, m, n);
|
||||
}
|
||||
template <typename T>
|
||||
auto UnpackStructImpl(const T& t, MakeIndexSequence<15>, char) {
|
||||
const auto& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o] = t;
|
||||
return std::tie(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o);
|
||||
}
|
||||
template <typename T>
|
||||
auto UnpackStructImpl(const T& t, MakeIndexSequence<16>, char) {
|
||||
const auto& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] = t;
|
||||
return std::tie(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p);
|
||||
}
|
||||
#endif // defined(__cpp_structured_bindings)
|
||||
|
||||
template <size_t I, typename T>
|
||||
auto UnpackStruct(const T& t)
|
||||
-> decltype((UnpackStructImpl)(t, MakeIndexSequence<I>{}, 0)) {
|
||||
return (UnpackStructImpl)(t, MakeIndexSequence<I>{}, 0);
|
||||
}
|
||||
|
||||
// Helper function to do comma folding in C++11.
|
||||
// The array ensures left-to-right order of evaluation.
|
||||
// Usage: VariadicExpand({expr...});
|
||||
template <typename T, size_t N>
|
||||
void VariadicExpand(const T (&a)[N]) {}
|
||||
|
||||
template <typename Struct, typename StructSize>
|
||||
class FieldsAreMatcherImpl;
|
||||
|
||||
template <typename Struct, size_t... I>
|
||||
class FieldsAreMatcherImpl<Struct, IndexSequence<I...>>
|
||||
: public MatcherInterface<Struct> {
|
||||
using UnpackedType =
|
||||
decltype(UnpackStruct<sizeof...(I)>(std::declval<const Struct&>()));
|
||||
using MatchersType = std::tuple<
|
||||
Matcher<const typename std::tuple_element<I, UnpackedType>::type&>...>;
|
||||
|
||||
public:
|
||||
template <typename Inner>
|
||||
explicit FieldsAreMatcherImpl(const Inner& matchers)
|
||||
: matchers_(testing::SafeMatcherCast<
|
||||
const typename std::tuple_element<I, UnpackedType>::type&>(
|
||||
std::get<I>(matchers))...) {}
|
||||
|
||||
void DescribeTo(::std::ostream* os) const override {
|
||||
const char* separator = "";
|
||||
VariadicExpand(
|
||||
{(*os << separator << "has field #" << I << " that ",
|
||||
std::get<I>(matchers_).DescribeTo(os), separator = ", and ")...});
|
||||
}
|
||||
|
||||
void DescribeNegationTo(::std::ostream* os) const override {
|
||||
const char* separator = "";
|
||||
VariadicExpand({(*os << separator << "has field #" << I << " that ",
|
||||
std::get<I>(matchers_).DescribeNegationTo(os),
|
||||
separator = ", or ")...});
|
||||
}
|
||||
|
||||
bool MatchAndExplain(Struct t, MatchResultListener* listener) const override {
|
||||
return MatchInternal((UnpackStruct<sizeof...(I)>)(t), listener);
|
||||
}
|
||||
|
||||
private:
|
||||
bool MatchInternal(UnpackedType tuple, MatchResultListener* listener) const {
|
||||
if (!listener->IsInterested()) {
|
||||
// If the listener is not interested, we don't need to construct the
|
||||
// explanation.
|
||||
bool good = true;
|
||||
VariadicExpand({good = good && std::get<I>(matchers_).Matches(
|
||||
std::get<I>(tuple))...});
|
||||
return good;
|
||||
}
|
||||
|
||||
int failed_pos = -1;
|
||||
|
||||
std::vector<StringMatchResultListener> inner_listener(sizeof...(I));
|
||||
|
||||
VariadicExpand(
|
||||
{failed_pos == -1 && !std::get<I>(matchers_).MatchAndExplain(
|
||||
std::get<I>(tuple), &inner_listener[I])
|
||||
? failed_pos = I
|
||||
: 0 ...});
|
||||
if (failed_pos != ~size_t{}) {
|
||||
*listener << "whose field #" << failed_pos << " does not match";
|
||||
PrintIfNotEmpty(inner_listener[failed_pos].str(), listener->stream());
|
||||
return false;
|
||||
}
|
||||
|
||||
*listener << "whose all elements match";
|
||||
const char* separator = ", where";
|
||||
for (size_t index = 0; index < sizeof...(I); ++index) {
|
||||
const std::string str = inner_listener[index].str();
|
||||
if (!str.empty()) {
|
||||
*listener << separator << " field #" << index << " is a value " << str;
|
||||
separator = ", and";
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MatchersType matchers_;
|
||||
};
|
||||
|
||||
template <typename... Inner>
|
||||
class FieldsAreMatcher {
|
||||
public:
|
||||
explicit FieldsAreMatcher(Inner... inner) : matchers_(std::move(inner)...) {}
|
||||
|
||||
template <typename Struct>
|
||||
operator Matcher<Struct>() const { // NOLINT
|
||||
return Matcher<Struct>(
|
||||
new FieldsAreMatcherImpl<const Struct&, IndexSequenceFor<Inner...>>(
|
||||
matchers_));
|
||||
}
|
||||
|
||||
private:
|
||||
std::tuple<Inner...> matchers_;
|
||||
};
|
||||
|
||||
// Implements ElementsAre() and ElementsAreArray().
|
||||
template <typename Container>
|
||||
class ElementsAreMatcherImpl : public MatcherInterface<Container> {
|
||||
@@ -4514,6 +4711,19 @@ Pair(FirstMatcher first_matcher, SecondMatcher second_matcher) {
|
||||
first_matcher, second_matcher);
|
||||
}
|
||||
|
||||
namespace no_adl {
|
||||
// FieldsAre(matchers...) matches piecewise the fields of compatible structs.
|
||||
// These include those that support `get<I>(obj)`, and when structured bindings
|
||||
// are enabled any class that supports them.
|
||||
// In particular, `std::tuple`, `std::pair`, `std::array` and aggregate types.
|
||||
template <typename... M>
|
||||
internal::FieldsAreMatcher<typename std::decay<M>::type...> FieldsAre(
|
||||
M&&... matchers) {
|
||||
return internal::FieldsAreMatcher<typename std::decay<M>::type...>(
|
||||
std::forward<M>(matchers)...);
|
||||
}
|
||||
} // namespace no_adl
|
||||
|
||||
// Returns a predicate that is satisfied by anything that matches the
|
||||
// given matcher.
|
||||
template <typename M>
|
||||
@@ -5053,6 +5263,9 @@ PolymorphicMatcher<internal::ExceptionMatcherImpl<Err>> ThrowsMessage(
|
||||
#define GMOCK_INTERNAL_MATCHER_ARG_USAGE(i, data_unused, arg_unused) \
|
||||
, gmock_p##i
|
||||
|
||||
// To prevent ADL on certain functions we put them on a separate namespace.
|
||||
using namespace no_adl; // NOLINT
|
||||
|
||||
} // namespace testing
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 5046
|
||||
|
||||
Reference in New Issue
Block a user