Reduce duplication in absl/random/internal. Consolidate the mocking requirements so that mock detection is mediated via the MockingAccess class * HasInvokeMock has been deduplicated; MockingAccess now owns it * InvokeMock is done via MockingAccess::InvokeMock * In absl::BitGenRef, if a class has a conversion operator that is used. * `friend class MockingAccess` is now the only mocking friend, except for backwards compat. - friend DistributionCaller is now unnecessary. - friend BitGenRef is now unnecessary. - friend MockHelpers now unnecessary. PiperOrigin-RevId: 889366369 Change-Id: I288cd60f6ac13b257c10ec3268d96828f1e61db6
diff --git a/absl/random/BUILD.bazel b/absl/random/BUILD.bazel index 8986211..28f6872 100644 --- a/absl/random/BUILD.bazel +++ b/absl/random/BUILD.bazel
@@ -128,13 +128,14 @@ copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ + ":mocking_access", ":random", "//absl/base:config", "//absl/base:core_headers", "//absl/base:fast_type_id", "//absl/meta:type_traits", - "//absl/random/internal:distribution_caller", "//absl/random/internal:fast_uniform_bits", + "//absl/random/internal:traits", ], ) @@ -160,6 +161,7 @@ ], linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ + ":mocking_access", ":random", "//absl/base:config", "//absl/base:fast_type_id", @@ -171,6 +173,16 @@ ], ) +cc_library( + name = "mocking_access", + hdrs = ["mocking_access.h"], + deps = [ + "//absl/base:config", + "//absl/base:fast_type_id", + "//absl/meta:type_traits", + ], +) + cc_test( name = "bernoulli_distribution_test", size = "small",
diff --git a/absl/random/CMakeLists.txt b/absl/random/CMakeLists.txt index 7382b0d..4a13e84 100644 --- a/absl/random/CMakeLists.txt +++ b/absl/random/CMakeLists.txt
@@ -44,9 +44,10 @@ DEPS absl::config absl::core_headers - absl::random_internal_distribution_caller absl::random_internal_fast_uniform_bits absl::type_traits + absl::random_mocking_access + absl::random_internal_traits ) absl_cc_test( @@ -68,6 +69,21 @@ GTest::gtest_main ) +absl_cc_library( + NAME + random_mocking_access + HDRS + "mocking_access.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::fast_type_id + absl::type_traits +) + # Internal-only target, do not depend on directly. absl_cc_library( NAME @@ -82,6 +98,7 @@ absl::config absl::fast_type_id absl::optional + absl::random_mocking_access ) # Internal-only target, do not depend on directly. @@ -118,6 +135,7 @@ absl::flat_hash_map absl::raw_logging_internal absl::random_internal_mock_helpers + absl::random_mocking_access absl::random_random absl::type_traits absl::utility @@ -536,6 +554,9 @@ ${ABSL_DEFAULT_LINKOPTS} DEPS absl::config + absl::type_traits + absl::bits + absl::int128 ) # Internal-only target, do not depend on directly. @@ -553,6 +574,7 @@ absl::utility absl::fast_type_id absl::type_traits + absl::random_mocking_access ) # Internal-only target, do not depend on directly.
diff --git a/absl/random/bit_gen_ref.h b/absl/random/bit_gen_ref.h index dfce2c4..8ac4d93 100644 --- a/absl/random/bit_gen_ref.h +++ b/absl/random/bit_gen_ref.h
@@ -27,41 +27,17 @@ #include <cstdint> #include <limits> #include <type_traits> -#include <utility> #include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/fast_type_id.h" #include "absl/meta/type_traits.h" -#include "absl/random/internal/distribution_caller.h" #include "absl/random/internal/fast_uniform_bits.h" +#include "absl/random/internal/traits.h" +#include "absl/random/mocking_access.h" namespace absl { ABSL_NAMESPACE_BEGIN -namespace random_internal { - -template <typename URBG, typename = void, typename = void, typename = void> -struct is_urbg : std::false_type {}; - -template <typename URBG> -struct is_urbg< - URBG, - absl::enable_if_t<std::is_same< - typename URBG::result_type, - typename std::decay<decltype((URBG::min)())>::type>::value>, - absl::enable_if_t<std::is_same< - typename URBG::result_type, - typename std::decay<decltype((URBG::max)())>::type>::value>, - absl::enable_if_t<std::is_same< - typename URBG::result_type, - typename std::decay<decltype(std::declval<URBG>()())>::type>::value>> - : std::true_type {}; - -template <typename> -struct DistributionCaller; -class MockHelpers; - -} // namespace random_internal // ----------------------------------------------------------------------------- // absl::BitGenRef @@ -87,24 +63,18 @@ // } // class BitGenRef { - // SFINAE to detect whether the URBG type includes a member matching - // bool InvokeMock(key_id, args_tuple*, result*). - // - // These live inside BitGenRef so that they have friend access - // to MockingBitGen. (see similar methods in DistributionCaller). template <template <class...> class Trait, class AlwaysVoid, class... Args> struct detector : std::false_type {}; template <template <class...> class Trait, class... Args> struct detector<Trait, absl::void_t<Trait<Args...>>, Args...> : std::true_type {}; - template <class T> - using invoke_mock_t = decltype(std::declval<T*>()->InvokeMock( - std::declval<FastTypeIdType>(), std::declval<void*>(), - std::declval<void*>())); + template <typename T> + using has_conversion_operator_t = + decltype(std::declval<T>().operator BitGenRef()); template <typename T> - using HasInvokeMock = typename detector<invoke_mock_t, void, T>::type; + using HasConversionOperator = detector<has_conversion_operator_t, void, T>; public: BitGenRef(const BitGenRef&) = default; @@ -112,23 +82,28 @@ BitGenRef& operator=(const BitGenRef&) = default; BitGenRef& operator=(BitGenRef&&) = default; - template < - typename URBGRef, typename URBG = absl::remove_cvref_t<URBGRef>, - typename absl::enable_if_t<(!std::is_same<URBG, BitGenRef>::value && - random_internal::is_urbg<URBG>::value && - !HasInvokeMock<URBG>::value)>* = nullptr> + template <typename URBGRef, typename URBG = absl::remove_cvref_t<URBGRef>, + typename absl::enable_if_t< + (!std::is_same<URBG, BitGenRef>::value && + !std::is_base_of<BitGenRef, URBG>::value && + !HasConversionOperator<URBG>::value && + random_internal::is_urbg<URBG>::value && + !RandomMockingAccess::HasInvokeMock<URBG>::value)>* = nullptr> BitGenRef(URBGRef&& gen ABSL_ATTRIBUTE_LIFETIME_BOUND) // NOLINT : t_erased_gen_ptr_(reinterpret_cast<uintptr_t>(&gen)), mock_call_(NotAMock), generate_impl_fn_(ImplFn<URBG>) {} template <typename URBGRef, typename URBG = absl::remove_cvref_t<URBGRef>, - typename absl::enable_if_t<(!std::is_same<URBG, BitGenRef>::value && - random_internal::is_urbg<URBG>::value && - HasInvokeMock<URBG>::value)>* = nullptr> + typename absl::enable_if_t< + (!std::is_same<URBG, BitGenRef>::value && + !std::is_base_of<BitGenRef, URBG>::value && + !HasConversionOperator<URBG>::value && + random_internal::is_urbg<URBG>::value && + RandomMockingAccess::HasInvokeMock<URBG>::value)>* = nullptr> BitGenRef(URBGRef&& gen ABSL_ATTRIBUTE_LIFETIME_BOUND) // NOLINT : t_erased_gen_ptr_(reinterpret_cast<uintptr_t>(&gen)), - mock_call_(&MockCall<URBG>), + mock_call_(MockCall<URBG>), generate_impl_fn_(ImplFn<URBG>) {} using result_type = uint64_t; @@ -157,10 +132,10 @@ // Get a type-erased InvokeMock pointer. template <typename URBG> - static bool MockCall(uintptr_t gen_ptr, FastTypeIdType key_id, void* result, - void* arg_tuple) { - return reinterpret_cast<URBG*>(gen_ptr)->InvokeMock(key_id, result, - arg_tuple); + static bool MockCall(uintptr_t gen_ptr, FastTypeIdType key_id, + void* args_tuple, void* result) { + return RandomMockingAccess::InvokeMock(reinterpret_cast<URBG*>(gen_ptr), + key_id, args_tuple, result); } static bool NotAMock(uintptr_t, FastTypeIdType, void*, void*) { return false; @@ -176,9 +151,7 @@ mock_call_fn mock_call_; impl_fn generate_impl_fn_; - template <typename> - friend struct ::absl::random_internal::DistributionCaller; // for InvokeMock - friend class ::absl::random_internal::MockHelpers; // for InvokeMock + friend class ::absl::RandomMockingAccess; // for InvokeMock }; ABSL_NAMESPACE_END
diff --git a/absl/random/bit_gen_ref_test.cc b/absl/random/bit_gen_ref_test.cc index d581352..d04ac3a 100644 --- a/absl/random/bit_gen_ref_test.cc +++ b/absl/random/bit_gen_ref_test.cc
@@ -17,6 +17,7 @@ #include <cstdint> #include <random> +#include <type_traits> #include <vector> #include "gmock/gmock.h" @@ -102,6 +103,48 @@ absl::BitGenRef gen_ref(const_gen); EXPECT_EQ(FnTest(gen_ref), 42); // Copy } + +struct MinStdRand { + // The URBG just returns 0. + using result_type = absl::BitGen::result_type; + static constexpr result_type(min)() { return (absl::BitGen::min)(); } + static constexpr result_type(max)() { return (absl::BitGen::max)(); } + result_type operator()() { return 0; } + + // Implicit conversions allow passing MinStdRand to functions taking + // absl::BitGenRef as well as explicitly constructing an absl::BitGenRef from + // a MinStdRand. + operator absl::BitGenRef() const { + conversion_count++; + return absl::BitGenRef(minstd_gen); + } + + std::minstd_rand minstd_gen; + mutable int conversion_count = 0; +}; + +TEST(BitGenRefTest, IsConvertibleTest) { + // Verify that MinStdRandBitGen is convertible to absl::BitGenRef. + EXPECT_TRUE((std::is_convertible<MinStdRand, absl::BitGenRef>::value)); + + // Explicit construction should trigger the conversion. + { + MinStdRand minstd; + absl::BitGenRef gen_ref(minstd); + EXPECT_EQ(minstd.conversion_count, 1); + (void)gen_ref; + } + + // Calling a function that accepts absl::BitGenRef should trigger the + // conversion. This tests implicit conversion. + { + MinStdRand minstd; + auto result = FnTest(minstd); + EXPECT_EQ(minstd.conversion_count, 1); + EXPECT_GE(result, 1); + } +} + } // namespace ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/random/internal/BUILD.bazel b/absl/random/internal/BUILD.bazel index c2f2c36..56377ed 100644 --- a/absl/random/internal/BUILD.bazel +++ b/absl/random/internal/BUILD.bazel
@@ -116,6 +116,7 @@ linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ "//absl/base:config", + "//absl/meta:type_traits", "//absl/numeric:bits", "//absl/numeric:int128", ], @@ -130,6 +131,7 @@ "//absl/base:config", "//absl/base:fast_type_id", "//absl/meta:type_traits", + "//absl/random:mocking_access", "//absl/utility", ], ) @@ -592,7 +594,7 @@ deps = [ "//absl/base:config", "//absl/base:fast_type_id", - "//absl/types:optional", + "//absl/random:mocking_access", ], )
diff --git a/absl/random/internal/distribution_caller.h b/absl/random/internal/distribution_caller.h index e84ec8c..d712bca 100644 --- a/absl/random/internal/distribution_caller.h +++ b/absl/random/internal/distribution_caller.h
@@ -24,6 +24,7 @@ #include "absl/base/config.h" #include "absl/base/fast_type_id.h" #include "absl/meta/type_traits.h" +#include "absl/random/mocking_access.h" #include "absl/utility/utility.h" namespace absl { @@ -37,23 +38,8 @@ struct DistributionCaller { static_assert(!std::is_pointer<URBG>::value, "You must pass a reference, not a pointer."); - // SFINAE to detect whether the URBG type includes a member matching - // bool InvokeMock(key_id, args_tuple*, result*). - // - // These live inside BitGenRef so that they have friend access - // to MockingBitGen. (see similar methods in DistributionCaller). - template <template <class...> class Trait, class AlwaysVoid, class... Args> - struct detector : std::false_type {}; - template <template <class...> class Trait, class... Args> - struct detector<Trait, absl::void_t<Trait<Args...>>, Args...> - : std::true_type {}; - template <class T> - using invoke_mock_t = decltype(std::declval<T*>()->InvokeMock( - std::declval<FastTypeIdType>(), std::declval<void*>(), - std::declval<void*>())); - - using HasInvokeMock = typename detector<invoke_mock_t, void, URBG>::type; + using RandomMockingAccess = ::absl::RandomMockingAccess; // Default implementation of distribution caller. template <typename DistrT, typename... Args> @@ -74,7 +60,8 @@ ArgTupleT arg_tuple(std::forward<Args>(args)...); ResultT result; - if (!urbg->InvokeMock(FastTypeId<KeyT>(), &arg_tuple, &result)) { + if (!RandomMockingAccess::InvokeMock<URBG>(urbg, FastTypeId<KeyT>(), + &arg_tuple, &result)) { auto dist = absl::make_from_tuple<DistrT>(arg_tuple); result = dist(*urbg); } @@ -84,8 +71,8 @@ // Default implementation of distribution caller. template <typename DistrT, typename... Args> static typename DistrT::result_type Call(URBG* urbg, Args&&... args) { - return Impl<DistrT, Args...>(HasInvokeMock{}, urbg, - std::forward<Args>(args)...); + return Impl<DistrT, Args...>(RandomMockingAccess::HasInvokeMock<URBG>{}, + urbg, std::forward<Args>(args)...); } };
diff --git a/absl/random/internal/mock_helpers.h b/absl/random/internal/mock_helpers.h index 2d66a3b..b73b9cb 100644 --- a/absl/random/internal/mock_helpers.h +++ b/absl/random/internal/mock_helpers.h
@@ -21,7 +21,7 @@ #include "absl/base/config.h" #include "absl/base/fast_type_id.h" -#include "absl/types/optional.h" +#include "absl/random/mocking_access.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -50,6 +50,7 @@ // class MockHelpers { using IdType = ::absl::FastTypeIdType; + using RandomMockingAccess = ::absl::RandomMockingAccess; // Given a key signature type used to index the mock, extract the components. // KeyT is expected to have the form: @@ -64,39 +65,7 @@ using arg_tuple_type = ArgTupleT; }; - // Detector for InvokeMock. - template <class T> - using invoke_mock_t = decltype(std::declval<T*>()->InvokeMock( - std::declval<IdType>(), std::declval<void*>(), std::declval<void*>())); - - // Empty implementation of InvokeMock. - template <typename KeyT, typename ReturnT, typename ArgTupleT, typename URBG, - typename... Args> - static std::optional<ReturnT> InvokeMockImpl(char, URBG*, Args&&...) { - return std::nullopt; - } - - // Non-empty implementation of InvokeMock. - template <typename KeyT, typename ReturnT, typename ArgTupleT, typename URBG, - typename = invoke_mock_t<URBG>, typename... Args> - static std::optional<ReturnT> InvokeMockImpl(int, URBG* urbg, - Args&&... args) { - ArgTupleT arg_tuple(std::forward<Args>(args)...); - ReturnT result; - if (urbg->InvokeMock(FastTypeId<KeyT>(), &arg_tuple, &result)) { - return result; - } - return std::nullopt; - } - public: - // InvokeMock is private; this provides access for some specialized use cases. - template <typename URBG> - static inline bool PrivateInvokeMock(URBG* urbg, IdType key_id, - void* args_tuple, void* result) { - return urbg->InvokeMock(key_id, args_tuple, result); - } - // Invoke a mock for the KeyT (may or may not be a signature). // // KeyT is used to generate a typeid-based lookup key for the mock. @@ -110,12 +79,16 @@ template <typename KeyT, typename URBG, typename... Args> static auto MaybeInvokeMock(URBG* urbg, Args&&... args) -> std::optional<typename KeySignature<KeyT>::result_type> { - // Use function overloading to dispatch to the implementation since - // more modern patterns (e.g. require + constexpr) are not supported in all - // compiler configurations. - return InvokeMockImpl<KeyT, typename KeySignature<KeyT>::result_type, - typename KeySignature<KeyT>::arg_tuple_type, URBG>( - 0, urbg, std::forward<Args>(args)...); + if constexpr (RandomMockingAccess::HasInvokeMock<URBG>::value) { + typename KeySignature<KeyT>::arg_tuple_type arg_tuple( + std::forward<Args>(args)...); + typename KeySignature<KeyT>::result_type result; + if (RandomMockingAccess::InvokeMock(urbg, FastTypeId<KeyT>(), &arg_tuple, + &result)) { + return result; + } + } + return std::nullopt; } // Acquire a mock for the KeyT (may or may not be a signature), set up to use
diff --git a/absl/random/internal/traits.h b/absl/random/internal/traits.h index f874a0f..d396329 100644 --- a/absl/random/internal/traits.h +++ b/absl/random/internal/traits.h
@@ -20,6 +20,7 @@ #include <type_traits> #include "absl/base/config.h" +#include "absl/meta/type_traits.h" #include "absl/numeric/bits.h" #include "absl/numeric/int128.h" @@ -27,6 +28,26 @@ ABSL_NAMESPACE_BEGIN namespace random_internal { +// is_urbg<URBG> +// +// Indicates whether a type URBG is a Uniform Random Bit Generator. +template <typename URBG, typename = void, typename = void, typename = void> +struct is_urbg : std::false_type {}; + +template <typename URBG> +struct is_urbg< + URBG, + absl::enable_if_t<std::is_same< + typename URBG::result_type, + typename std::decay<decltype((URBG::min)())>::type>::value>, + absl::enable_if_t<std::is_same< + typename URBG::result_type, + typename std::decay<decltype((URBG::max)())>::type>::value>, + absl::enable_if_t<std::is_same< + typename URBG::result_type, + typename std::decay<decltype(std::declval<URBG>()())>::type>::value>> + : std::true_type {}; + // random_internal::is_widening_convertible<A, B> // // Returns whether a type A is widening-convertible to a type B.
diff --git a/absl/random/internal/traits_test.cc b/absl/random/internal/traits_test.cc index 2164582..100fd9b 100644 --- a/absl/random/internal/traits_test.cc +++ b/absl/random/internal/traits_test.cc
@@ -15,14 +15,19 @@ #include "absl/random/internal/traits.h" #include <cstdint> +#include <random> #include <type_traits> #include "gtest/gtest.h" namespace { +using absl::random_internal::is_urbg; using absl::random_internal::is_widening_convertible; +static_assert(is_urbg<std::minstd_rand>::value); +static_assert(!is_urbg<uint64_t>::value); + // CheckWideningConvertsToSelf<T1, T2, ...>() // // For each type T, checks:
diff --git a/absl/random/mocking_access.h b/absl/random/mocking_access.h new file mode 100644 index 0000000..eb2f98b --- /dev/null +++ b/absl/random/mocking_access.h
@@ -0,0 +1,74 @@ +// Copyright 2019 The Abseil Authors. +// +// 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 +// +// https://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. + +#ifndef ABSL_RANDOM_MOCKING_ACCESS_H_ +#define ABSL_RANDOM_MOCKING_ACCESS_H_ + +#include <type_traits> +#include <utility> + +#include "absl/base/config.h" +#include "absl/base/fast_type_id.h" +#include "absl/meta/type_traits.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// RandomMockingAccess must be a friend of any class which exposes an InvokeMock +// method. All calls to InvokeMock should be made through RandomMockingAccess. +// +// Any URBG type which wants to participate in mocking should declare +// RandomMockingAccess as a friend and have a protected or private method with +// the signature: +// +// bool InvokeMock(absl::FastTypeIdType key_id, void* args_tuple, void* result) +// +// This method returns false when mocking is not enabled, otherwise it will +// apply the mock. Typically this will involve forwarding to an underlying +// URBG such as absl::MockingBitGen by calling RandomMockingAccess::InvokeMock +// after checking that RandomMockingAccess::HasInvokeMock<URBG> is true for the +// underlying URBG type. +class RandomMockingAccess { + template <template <class...> class Trait, class AlwaysVoid, class... Args> + struct detector : std::false_type {}; + template <template <class...> class Trait, class... Args> + struct detector<Trait, absl::void_t<Trait<Args...>>, Args...> + : std::true_type {}; + + using IdType = ::absl::FastTypeIdType; + + // Detector for `bool InvokeMock(key_id, args_tuple*, result*)` + // Lives inside RandomMockingAccess so that it has friend access to private + // members of URBG types. + template <class T> + using invoke_mock_t = decltype(std::declval<T*>()->InvokeMock( + std::declval<IdType>(), std::declval<void*>(), std::declval<void*>())); + + public: + // Returns true if the URBG type has an InvokeMock method. + template <typename T> + using HasInvokeMock = typename detector<invoke_mock_t, void, T>::type; + + // InvokeMock is private; calls to InvokeMock are proxied by MockingAccess. + template <typename URBG> + static inline bool InvokeMock(URBG* urbg, IdType key_id, void* args_tuple, + void* result) { + return urbg->InvokeMock(key_id, args_tuple, result); + } +}; + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_RANDOM_MOCKING_ACCESS_H_
diff --git a/absl/random/mocking_bit_gen.h b/absl/random/mocking_bit_gen.h index 1680ff4..249e2d6 100644 --- a/absl/random/mocking_bit_gen.h +++ b/absl/random/mocking_bit_gen.h
@@ -39,20 +39,13 @@ #include "absl/container/flat_hash_map.h" #include "absl/meta/type_traits.h" #include "absl/random/internal/mock_helpers.h" +#include "absl/random/mocking_access.h" #include "absl/random/random.h" #include "absl/utility/utility.h" namespace absl { ABSL_NAMESPACE_BEGIN -class BitGenRef; - -namespace random_internal { -template <typename> -struct DistributionCaller; -class MockHelpers; -} // namespace random_internal - // MockingBitGen // // `absl::MockingBitGen` is a mock Uniform Random Bit Generator (URBG) class @@ -224,11 +217,8 @@ absl::flat_hash_map<FastTypeIdType, std::unique_ptr<FunctionHolder>> mocks_; absl::BitGen gen_; - template <typename> - friend struct ::absl::random_internal::DistributionCaller; // for InvokeMock - friend class ::absl::BitGenRef; // for InvokeMock - friend class ::absl::random_internal::MockHelpers; // for RegisterMock, - // InvokeMock + friend class ::absl::RandomMockingAccess; // for InvokeMock + friend class ::absl::random_internal::MockHelpers; // for RegisterMock }; ABSL_NAMESPACE_END