diff options
author | Justin Bassett <jbassett@google.com> | 2024-05-20 10:44:01 -0700 |
---|---|---|
committer | Copybara-Service <copybara-worker@google.com> | 2024-05-20 10:45:19 -0700 |
commit | 254b3a5326932026fd23923fd367619d2837f0ad (patch) | |
tree | ae7b15f8c6462ed407c89ef5144fdbf5a5a91e2b /absl/random/internal/mock_overload_set.h | |
parent | 93ac3a4f9ee7792af399cebd873ee99ce15aed08 (diff) | |
download | abseil-254b3a5326932026fd23923fd367619d2837f0ad.tar.gz abseil-254b3a5326932026fd23923fd367619d2837f0ad.tar.bz2 abseil-254b3a5326932026fd23923fd367619d2837f0ad.zip |
Add (unused) validation to absl::MockingBitGen
`absl::Uniform(tag, rng, a, b)` has some restrictions on the values it can produce in that it will always be in the range specified by `a` and `b`, but these restrictions can be violated by `absl::MockingBitGen`. This makes it easier than necessary to introduce a bug in tests using a mock RNG.
We can fix this by making `MockingBitGen` emit a runtime error if the value produced is out of bounds.
Immediately fixing all the internal buggy uses of `MockingBitGen` is currently infeasible, so the plan is this:
1. Add turned-off validation to `MockingBitGen` to avoid the costs of maintaining unsubmitted code.
2. Temporarily migrate the internal buggy use cases to keep the current behavior, to be fixed later.
3. Turn on validation for `MockingBitGen`.
4. Fix the internal buggy use cases over time.
---
A few of the different categories of errors I found:
- `Call(tag, rng, a, b) -> a or b`, for open/half-open intervals (i.e. incorrect boundary condition). This case happens quite a lot, e.g. by specifying `absl::Uniform<double>(rng, 0, 1)` to return `1.0`.
- `Call(tag, rng, 0, 1) -> 42` (i.e. return an arbitrary value). These may be straightforward to fix by just returning an in-range value, or sometimes they are difficult to fix because other data structures depend on those values.
PiperOrigin-RevId: 635503223
Change-Id: I9293ab78e79450e2b7b682dcb05149f238ecc550
Diffstat (limited to 'absl/random/internal/mock_overload_set.h')
-rw-r--r-- | absl/random/internal/mock_overload_set.h | 82 |
1 files changed, 54 insertions, 28 deletions
diff --git a/absl/random/internal/mock_overload_set.h b/absl/random/internal/mock_overload_set.h index 0d9c6c12..cfaeeeef 100644 --- a/absl/random/internal/mock_overload_set.h +++ b/absl/random/internal/mock_overload_set.h @@ -16,9 +16,11 @@ #ifndef ABSL_RANDOM_INTERNAL_MOCK_OVERLOAD_SET_H_ #define ABSL_RANDOM_INTERNAL_MOCK_OVERLOAD_SET_H_ +#include <tuple> #include <type_traits> #include "gmock/gmock.h" +#include "absl/base/config.h" #include "absl/random/internal/mock_helpers.h" #include "absl/random/mocking_bit_gen.h" @@ -26,7 +28,7 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace random_internal { -template <typename DistrT, typename Fn> +template <typename DistrT, typename ValidatorT, typename Fn> struct MockSingleOverload; // MockSingleOverload @@ -38,8 +40,8 @@ struct MockSingleOverload; // arguments to MockingBitGen::Register. // // The underlying KeyT must match the KeyT constructed by DistributionCaller. -template <typename DistrT, typename Ret, typename... Args> -struct MockSingleOverload<DistrT, Ret(MockingBitGen&, Args...)> { +template <typename DistrT, typename ValidatorT, typename Ret, typename... Args> +struct MockSingleOverload<DistrT, ValidatorT, Ret(MockingBitGen&, Args...)> { static_assert(std::is_same<typename DistrT::result_type, Ret>::value, "Overload signature must have return type matching the " "distribution result_type."); @@ -47,15 +49,21 @@ struct MockSingleOverload<DistrT, Ret(MockingBitGen&, Args...)> { template <typename MockURBG> auto gmock_Call(MockURBG& gen, const ::testing::Matcher<Args>&... matchers) - -> decltype(MockHelpers::MockFor<KeyT>(gen).gmock_Call(matchers...)) { - static_assert(std::is_base_of<MockingBitGen, MockURBG>::value, - "Mocking requires an absl::MockingBitGen"); - return MockHelpers::MockFor<KeyT>(gen).gmock_Call(matchers...); + -> decltype(MockHelpers::MockFor<KeyT>(gen, ValidatorT()) + .gmock_Call(matchers...)) { + static_assert( + std::is_base_of<MockingBitGenImpl<true>, MockURBG>::value || + std::is_base_of<MockingBitGenImpl<false>, MockURBG>::value, + "Mocking requires an absl::MockingBitGen"); + return MockHelpers::MockFor<KeyT>(gen, ValidatorT()) + .gmock_Call(matchers...); } }; -template <typename DistrT, typename Ret, typename Arg, typename... Args> -struct MockSingleOverload<DistrT, Ret(Arg, MockingBitGen&, Args...)> { +template <typename DistrT, typename ValidatorT, typename Ret, typename Arg, + typename... Args> +struct MockSingleOverload<DistrT, ValidatorT, + Ret(Arg, MockingBitGen&, Args...)> { static_assert(std::is_same<typename DistrT::result_type, Ret>::value, "Overload signature must have return type matching the " "distribution result_type."); @@ -64,14 +72,44 @@ struct MockSingleOverload<DistrT, Ret(Arg, MockingBitGen&, Args...)> { template <typename MockURBG> auto gmock_Call(const ::testing::Matcher<Arg>& matcher, MockURBG& gen, const ::testing::Matcher<Args>&... matchers) - -> decltype(MockHelpers::MockFor<KeyT>(gen).gmock_Call(matcher, - matchers...)) { - static_assert(std::is_base_of<MockingBitGen, MockURBG>::value, - "Mocking requires an absl::MockingBitGen"); - return MockHelpers::MockFor<KeyT>(gen).gmock_Call(matcher, matchers...); + -> decltype(MockHelpers::MockFor<KeyT>(gen, ValidatorT()) + .gmock_Call(matcher, matchers...)) { + static_assert( + std::is_base_of<MockingBitGenImpl<true>, MockURBG>::value || + std::is_base_of<MockingBitGenImpl<false>, MockURBG>::value, + "Mocking requires an absl::MockingBitGen"); + return MockHelpers::MockFor<KeyT>(gen, ValidatorT()) + .gmock_Call(matcher, matchers...); } }; +// MockOverloadSetWithValidator +// +// MockOverloadSetWithValidator is a wrapper around MockOverloadSet which takes +// an additional Validator parameter, allowing for customization of the mock +// behavior. +// +// `ValidatorT::Validate(result, args...)` will be called after the mock +// distribution returns a value in `result`, allowing for validation against the +// args. +template <typename DistrT, typename ValidatorT, typename... Fns> +struct MockOverloadSetWithValidator; + +template <typename DistrT, typename ValidatorT, typename Sig> +struct MockOverloadSetWithValidator<DistrT, ValidatorT, Sig> + : public MockSingleOverload<DistrT, ValidatorT, Sig> { + using MockSingleOverload<DistrT, ValidatorT, Sig>::gmock_Call; +}; + +template <typename DistrT, typename ValidatorT, typename FirstSig, + typename... Rest> +struct MockOverloadSetWithValidator<DistrT, ValidatorT, FirstSig, Rest...> + : public MockSingleOverload<DistrT, ValidatorT, FirstSig>, + public MockOverloadSetWithValidator<DistrT, ValidatorT, Rest...> { + using MockSingleOverload<DistrT, ValidatorT, FirstSig>::gmock_Call; + using MockOverloadSetWithValidator<DistrT, ValidatorT, Rest...>::gmock_Call; +}; + // MockOverloadSet // // MockOverloadSet takes a distribution and a collection of signatures and @@ -79,20 +117,8 @@ struct MockSingleOverload<DistrT, Ret(Arg, MockingBitGen&, Args...)> { // `EXPECT_CALL(mock_overload_set, Call(...))` expand and do overload resolution // correctly. template <typename DistrT, typename... Signatures> -struct MockOverloadSet; - -template <typename DistrT, typename Sig> -struct MockOverloadSet<DistrT, Sig> : public MockSingleOverload<DistrT, Sig> { - using MockSingleOverload<DistrT, Sig>::gmock_Call; -}; - -template <typename DistrT, typename FirstSig, typename... Rest> -struct MockOverloadSet<DistrT, FirstSig, Rest...> - : public MockSingleOverload<DistrT, FirstSig>, - public MockOverloadSet<DistrT, Rest...> { - using MockSingleOverload<DistrT, FirstSig>::gmock_Call; - using MockOverloadSet<DistrT, Rest...>::gmock_Call; -}; +using MockOverloadSet = + MockOverloadSetWithValidator<DistrT, NoOpValidator, Signatures...>; } // namespace random_internal ABSL_NAMESPACE_END |