/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include #include #include #include #include #include #include #include namespace apache::thrift { namespace test::detail { template std::string_view getFieldName() { // Thrift generates the tags in the 'apache::thrift::ident' namespace, // so we can just skip it to provide a better name for the users. // -1 because we don't want to count the terminating \0 character. constexpr std::size_t offset = sizeof("apache::thrift::ident::") - 1; // it's safe to return the string_view because pretty_name() returns a // statically allocated char * std::string_view name{folly::pretty_name()}; if (name.size() > offset) { name.remove_prefix(offset); } return name; } template class ThriftFieldMatcher { public: explicit ThriftFieldMatcher(InnerMatcher matcher) : matcher_(std::move(matcher)) {} template < typename ThriftStruct, typename = std::enable_if_t< is_thrift_class_v>>> operator testing::Matcher() const { return testing::Matcher( new Impl(matcher_)); } private: using Accessor = access_field_fn; // For field_ref, we want to forward the value pointed to the matcher // directly (it's fully transparent). // For optional_field_ref, we don't want to do it becase the mental model // is as if it was an std::optional. If we were to forward the // value, it would be impossible to check if it is empty. // Instead, we send the optional_field_ref to the matcher. The user can // provide a testing::Optional matcher to match the value contained in it. template class Impl : public testing::MatcherInterface { private: using ConstRefThriftStruct = const folly::remove_cvref_t&; using FieldRef = decltype(Accessor{}(std::declval())); using FieldReferenceType = typename FieldRef::reference_type; inline static constexpr bool IsOptionalFieldRef = ::apache::thrift::detail::is_optional_or_union_field_ref_v; // See above on why we do this switch using MatchedType = std::conditional_t; public: template explicit Impl(const MatcherOrPolymorphicMatcher& matcher) : concrete_matcher_(testing::SafeMatcherCast(matcher)) {} void DescribeTo(::std::ostream* os) const override { *os << "is an object whose field `" << getFieldName() << "` "; concrete_matcher_.DescribeTo(os); } void DescribeNegationTo(::std::ostream* os) const override { *os << "is an object whose field `" << getFieldName() << "` "; concrete_matcher_.DescribeNegationTo(os); } bool MatchAndExplain( ThriftStruct obj, testing::MatchResultListener* listener) const override { *listener << "whose field `" << getFieldName() << "` "; FieldRef val = Accessor{}(std::as_const(obj)); if constexpr (IsOptionalFieldRef) { return concrete_matcher_.MatchAndExplain(val, listener); } else { return concrete_matcher_.MatchAndExplain(*val, listener); } } private: const testing::Matcher concrete_matcher_; }; // class Impl const InnerMatcher matcher_; }; template class IsThriftUnionWithMatcher { public: explicit IsThriftUnionWithMatcher(InnerMatcher matcher) : matcher_(std::move(matcher)) {} template < typename ThriftUnion, typename = std::enable_if_t< is_thrift_union_v>>> operator testing::Matcher() const { return testing::Matcher( new Impl(matcher_)); } private: using Accessor = access_field_fn; template class Impl : public testing::MatcherInterface { private: using ThriftUnionType = folly::remove_cvref_t; using ConstRefThriftUnion = const ThriftUnionType&; using FieldRef = decltype(Accessor{}(std::declval())); // Unions do not support optional fields, so this is always a concrete // value (no optional_ref) using MatchedType = typename FieldRef::reference_type; public: template explicit Impl(const MatcherOrPolymorphicMatcher& matcher) : concrete_matcher_(testing::SafeMatcherCast(matcher)) {} void DescribeTo(::std::ostream* os) const override { *os << "is an union with `" << getFieldName() << "` active, which "; concrete_matcher_.DescribeTo(os); } void DescribeNegationTo(::std::ostream* os) const override { *os << "is an union without `" << getFieldName() << "` active, or the active value "; concrete_matcher_.DescribeNegationTo(os); } bool MatchAndExplain( ThriftUnion obj, testing::MatchResultListener* listener) const override { return op::invoke_by_field_id( static_cast(obj.getType()), [&](Id) { const auto active = op::get_name_v; if constexpr (std::is_same_v< FieldTag, op::get_ident>) { *listener << "whose active member `" << active << "` "; return concrete_matcher_.MatchAndExplain( *op::get(obj), listener); } else { *listener << "whose active member is `" << active << "`"; return false; } }, [&] { *listener << "which is unset"; return false; }); } private: const testing::Matcher concrete_matcher_; }; // class Impl const InnerMatcher matcher_; }; } // namespace test::detail } // namespace apache::thrift