228 lines
		
	
	
	
		
			8.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			228 lines
		
	
	
	
		
			8.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // 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_
 |