// Copyright 2018 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. // // ----------------------------------------------------------------------------- // mocking_bit_gen.h // ----------------------------------------------------------------------------- // // This file includes an `absl::MockingBitGen` class to use as a mock within the // Googletest testing framework. Such a mock is useful to provide deterministic // values as return values within (otherwise random) Abseil distribution // functions. Such determinism within a mock is useful within testing frameworks // to test otherwise indeterminate APIs. // // More information about the Googletest testing framework is available at // https://github.com/google/googletest #ifndef ABSL_RANDOM_MOCKING_BIT_GEN_H_ #define ABSL_RANDOM_MOCKING_BIT_GEN_H_ #include <iterator> #include <limits> #include <memory> #include <tuple> #include <type_traits> #include <utility> #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/internal/fast_type_id.h" #include "absl/container/flat_hash_map.h" #include "absl/meta/type_traits.h" #include "absl/random/distributions.h" #include "absl/random/internal/distribution_caller.h" #include "absl/random/random.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" #include "absl/types/span.h" #include "absl/types/variant.h" #include "absl/utility/utility.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace random_internal { template <typename> struct DistributionCaller; class MockHelpers; } // namespace random_internal class BitGenRef; // MockingBitGen // // `absl::MockingBitGen` is a mock Uniform Random Bit Generator (URBG) class // which can act in place of an `absl::BitGen` URBG within tests using the // Googletest testing framework. // // Usage: // // Use an `absl::MockingBitGen` along with a mock distribution object (within // mock_distributions.h) inside Googletest constructs such as ON_CALL(), // EXPECT_TRUE(), etc. to produce deterministic results conforming to the // distribution's API contract. // // Example: // // // Mock a call to an `absl::Bernoulli` distribution using Googletest // absl::MockingBitGen bitgen; // // ON_CALL(absl::MockBernoulli(), Call(bitgen, 0.5)) // .WillByDefault(testing::Return(true)); // EXPECT_TRUE(absl::Bernoulli(bitgen, 0.5)); // // // Mock a call to an `absl::Uniform` distribution within Googletest // absl::MockingBitGen bitgen; // // ON_CALL(absl::MockUniform<int>(), Call(bitgen, testing::_, testing::_)) // .WillByDefault([] (int low, int high) { // return (low + high) / 2; // }); // // EXPECT_EQ(absl::Uniform<int>(gen, 0, 10), 5); // EXPECT_EQ(absl::Uniform<int>(gen, 30, 40), 35); // // At this time, only mock distributions supplied within the Abseil random // library are officially supported. // // EXPECT_CALL and ON_CALL need to be made within the same DLL component as // the call to absl::Uniform and related methods, otherwise mocking will fail // since the underlying implementation creates a type-specific pointer which // will be distinct across different DLL boundaries. // class MockingBitGen { public: MockingBitGen() = default; ~MockingBitGen() { for (const auto& del : deleters_) del(); } // URBG interface 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 gen_(); } private: using match_impl_fn = void (*)(void* mock_fn, void* t_erased_arg_tuple, void* t_erased_result); struct MockData { void* mock_fn = nullptr; match_impl_fn match_impl = nullptr; }; // GetMockFnType returns the testing::MockFunction for a result and tuple. // This method only exists for type deduction and is otherwise unimplemented. template <typename ResultT, typename... Args> static auto GetMockFnType(ResultT, std::tuple<Args...>) -> ::testing::MockFunction<ResultT(Args...)>; // MockFnCaller is a helper method for use with absl::apply to // apply an ArgTupleT to a compatible MockFunction. // NOTE: MockFnCaller is essentially equivalent to the lambda: // [fn](auto... args) { return fn->Call(std::move(args)...)} // however that fails to build on some supported platforms. template <typename ResultT, typename MockFnType, typename Tuple> struct MockFnCaller; // specialization for std::tuple. template <typename ResultT, typename MockFnType, typename... Args> struct MockFnCaller<ResultT, MockFnType, std::tuple<Args...>> { MockFnType* fn; inline ResultT operator()(Args... args) { return fn->Call(std::move(args)...); } }; // MockingBitGen::RegisterMock // // RegisterMock<ResultT, ArgTupleT>(FastTypeIdType) is the main extension // point for extending the MockingBitGen framework. It provides a mechanism to // install a mock expectation for a function like ResultT(Args...) keyed by // type_idex onto the MockingBitGen context. The key is that the type_index // used to register must match the type index used to call the mock. // // The returned MockFunction<...> type can be used to setup additional // distribution parameters of the expectation. template <typename ResultT, typename ArgTupleT> auto RegisterMock(base_internal::FastTypeIdType type) -> decltype(GetMockFnType(std::declval<ResultT>(), std::declval<ArgTupleT>()))& { using MockFnType = decltype( GetMockFnType(std::declval<ResultT>(), std::declval<ArgTupleT>())); auto& mock = mocks_[type]; if (!mock.mock_fn) { auto* mock_fn = new MockFnType; mock.mock_fn = mock_fn; mock.match_impl = &MatchImpl<ResultT, ArgTupleT>; deleters_.emplace_back([mock_fn] { delete mock_fn; }); } return *static_cast<MockFnType*>(mock.mock_fn); } // MockingBitGen::MatchImpl<> is a dispatch function which converts the // generic type-erased parameters into a specific mock invocation call. // Requires tuple_args to point to a ArgTupleT, which is a std::tuple<Args...> // used to invoke the mock function. // Requires result to point to a ResultT, which is the result of the call. template <typename ResultT, typename ArgTupleT> static void MatchImpl(/*MockFnType<ResultT, Args...>*/ void* mock_fn, /*ArgTupleT*/ void* args_tuple, /*ResultT*/ void* result) { using MockFnType = decltype( GetMockFnType(std::declval<ResultT>(), std::declval<ArgTupleT>())); *static_cast<ResultT*>(result) = absl::apply( MockFnCaller<ResultT, MockFnType, ArgTupleT>{ static_cast<MockFnType*>(mock_fn)}, *static_cast<ArgTupleT*>(args_tuple)); } // MockingBitGen::InvokeMock // // InvokeMock(FastTypeIdType, args, result) is the entrypoint for invoking // mocks registered on MockingBitGen. // // When no mocks are registered on the provided FastTypeIdType, returns false. // Otherwise attempts to invoke the mock function ResultT(Args...) that // was previously registered via the type_index. // Requires tuple_args to point to a ArgTupleT, which is a std::tuple<Args...> // used to invoke the mock function. // Requires result to point to a ResultT, which is the result of the call. inline bool InvokeMock(base_internal::FastTypeIdType type, void* args_tuple, void* result) { // Trigger a mock, if there exists one that matches `param`. auto it = mocks_.find(type); if (it == mocks_.end()) return false; auto* mock_data = static_cast<MockData*>(&it->second); mock_data->match_impl(mock_data->mock_fn, args_tuple, result); return true; } absl::flat_hash_map<base_internal::FastTypeIdType, MockData> mocks_; std::vector<std::function<void()>> deleters_; 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 }; ABSL_NAMESPACE_END } // namespace absl #endif // ABSL_RANDOM_MOCKING_BIT_GEN_H_