| // 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 <typeindex> |
| #include <typeinfo> |
| #include <utility> |
| |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.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/internal/mocking_bit_gen_base.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, typename> |
| struct MockSingleOverload; |
| |
| } // namespace random_internal |
| |
| // 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. |
| // |
| class MockingBitGen : public absl::random_internal::MockingBitGenBase { |
| public: |
| MockingBitGen() {} |
| |
| ~MockingBitGen() override; |
| |
| private: |
| template <typename DistrT, typename... Args> |
| using MockFnType = |
| ::testing::MockFunction<typename DistrT::result_type(Args...)>; |
| |
| // MockingBitGen::Register |
| // |
| // Register<DistrT, FormatT, ArgTupleT> is the main extension point for |
| // extending the MockingBitGen framework. It provides a mechanism to install a |
| // mock expectation for the distribution `distr_t` onto the MockingBitGen |
| // context. |
| // |
| // The returned MockFunction<...> type can be used to setup additional |
| // distribution parameters of the expectation. |
| template <typename DistrT, typename... Args, typename... Ms> |
| decltype(std::declval<MockFnType<DistrT, Args...>>().gmock_Call( |
| std::declval<Ms>()...)) |
| Register(Ms&&... matchers) { |
| auto& mock = |
| mocks_[std::type_index(GetTypeId<DistrT, std::tuple<Args...>>())]; |
| |
| if (!mock.mock_fn) { |
| auto* mock_fn = new MockFnType<DistrT, Args...>; |
| mock.mock_fn = mock_fn; |
| mock.match_impl = &MatchImpl<DistrT, Args...>; |
| deleters_.emplace_back([mock_fn] { delete mock_fn; }); |
| } |
| |
| return static_cast<MockFnType<DistrT, Args...>*>(mock.mock_fn) |
| ->gmock_Call(std::forward<Ms>(matchers)...); |
| } |
| |
| mutable std::vector<std::function<void()>> deleters_; |
| |
| using match_impl_fn = void (*)(void* mock_fn, void* t_erased_dist_args, |
| void* t_erased_result); |
| struct MockData { |
| void* mock_fn = nullptr; |
| match_impl_fn match_impl = nullptr; |
| }; |
| |
| mutable absl::flat_hash_map<std::type_index, MockData> mocks_; |
| |
| template <typename DistrT, typename... Args> |
| static void MatchImpl(void* mock_fn, void* dist_args, void* result) { |
| using result_type = typename DistrT::result_type; |
| *static_cast<result_type*>(result) = absl::apply( |
| [mock_fn](Args... args) -> result_type { |
| return (*static_cast<MockFnType<DistrT, Args...>*>(mock_fn)) |
| .Call(std::move(args)...); |
| }, |
| *static_cast<std::tuple<Args...>*>(dist_args)); |
| } |
| |
| // Looks for an appropriate mock - Returns the mocked result if one is found. |
| // Otherwise, returns a random value generated by the underlying URBG. |
| bool CallImpl(const std::type_info& key_type, void* dist_args, |
| void* result) override { |
| // Trigger a mock, if there exists one that matches `param`. |
| auto it = mocks_.find(std::type_index(key_type)); |
| if (it == mocks_.end()) return false; |
| auto* mock_data = static_cast<MockData*>(&it->second); |
| mock_data->match_impl(mock_data->mock_fn, dist_args, result); |
| return true; |
| } |
| |
| template <typename, typename> |
| friend struct ::absl::random_internal::MockSingleOverload; |
| friend struct ::absl::random_internal::DistributionCaller< |
| absl::MockingBitGen>; |
| }; |
| |
| // ----------------------------------------------------------------------------- |
| // Implementation Details Only Below |
| // ----------------------------------------------------------------------------- |
| |
| namespace random_internal { |
| |
| template <> |
| struct DistributionCaller<absl::MockingBitGen> { |
| template <typename DistrT, typename FormatT, typename... Args> |
| static typename DistrT::result_type Call(absl::MockingBitGen* gen, |
| Args&&... args) { |
| return gen->template Call<DistrT, FormatT>(std::forward<Args>(args)...); |
| } |
| }; |
| |
| } // namespace random_internal |
| ABSL_NAMESPACE_END |
| } // namespace absl |
| |
| #endif // ABSL_RANDOM_MOCKING_BIT_GEN_H_ |