aboutsummaryrefslogtreecommitdiff
path: root/absl/random/internal/mock_overload_set.h
diff options
context:
space:
mode:
authorJustin Bassett <jbassett@google.com>2024-05-20 10:44:01 -0700
committerCopybara-Service <copybara-worker@google.com>2024-05-20 10:45:19 -0700
commit254b3a5326932026fd23923fd367619d2837f0ad (patch)
treeae7b15f8c6462ed407c89ef5144fdbf5a5a91e2b /absl/random/internal/mock_overload_set.h
parent93ac3a4f9ee7792af399cebd873ee99ce15aed08 (diff)
downloadabseil-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.h82
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