123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378 |
- // 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.
- #ifndef ABSL_HASH_HASH_TESTING_H_
- #define ABSL_HASH_HASH_TESTING_H_
- #include <initializer_list>
- #include <tuple>
- #include <type_traits>
- #include <vector>
- #include "gmock/gmock.h"
- #include "gtest/gtest.h"
- #include "absl/hash/internal/spy_hash_state.h"
- #include "absl/meta/type_traits.h"
- #include "absl/strings/str_cat.h"
- #include "absl/types/variant.h"
- namespace absl {
- ABSL_NAMESPACE_BEGIN
- // Run the absl::Hash algorithm over all the elements passed in and verify that
- // their hash expansion is congruent with their `==` operator.
- //
- // It is used in conjunction with EXPECT_TRUE. Failures will output information
- // on what requirement failed and on which objects.
- //
- // Users should pass a collection of types as either an initializer list or a
- // container of cases.
- //
- // EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(
- // {v1, v2, ..., vN}));
- //
- // std::vector<MyType> cases;
- // // Fill cases...
- // EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(cases));
- //
- // Users can pass a variety of types for testing heterogeneous lookup with
- // `std::make_tuple`:
- //
- // EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(
- // std::make_tuple(v1, v2, ..., vN)));
- //
- //
- // Ideally, the values passed should provide enough coverage of the `==`
- // operator and the AbslHashValue implementations.
- // For dynamically sized types, the empty state should usually be included in
- // the values.
- //
- // The function accepts an optional comparator function, in case that `==` is
- // not enough for the values provided.
- //
- // Usage:
- //
- // EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(
- // std::make_tuple(v1, v2, ..., vN), MyCustomEq{}));
- //
- // It checks the following requirements:
- // 1. The expansion for a value is deterministic.
- // 2. For any two objects `a` and `b` in the sequence, if `a == b` evaluates
- // to true, then their hash expansion must be equal.
- // 3. If `a == b` evaluates to false their hash expansion must be unequal.
- // 4. If `a == b` evaluates to false neither hash expansion can be a
- // suffix of the other.
- // 5. AbslHashValue overloads should not be called by the user. They are only
- // meant to be called by the framework. Users should call H::combine() and
- // H::combine_contiguous().
- // 6. No moved-from instance of the hash state is used in the implementation
- // of AbslHashValue.
- //
- // The values do not have to have the same type. This can be useful for
- // equivalent types that support heterogeneous lookup.
- //
- // A possible reason for breaking (2) is combining state in the hash expansion
- // that was not used in `==`.
- // For example:
- //
- // struct Bad2 {
- // int a, b;
- // template <typename H>
- // friend H AbslHashValue(H state, Bad2 x) {
- // // Uses a and b.
- // return H::combine(std::move(state), x.a, x.b);
- // }
- // friend bool operator==(Bad2 x, Bad2 y) {
- // // Only uses a.
- // return x.a == y.a;
- // }
- // };
- //
- // As for (3), breaking this usually means that there is state being passed to
- // the `==` operator that is not used in the hash expansion.
- // For example:
- //
- // struct Bad3 {
- // int a, b;
- // template <typename H>
- // friend H AbslHashValue(H state, Bad3 x) {
- // // Only uses a.
- // return H::combine(std::move(state), x.a);
- // }
- // friend bool operator==(Bad3 x, Bad3 y) {
- // // Uses a and b.
- // return x.a == y.a && x.b == y.b;
- // }
- // };
- //
- // Finally, a common way to break 4 is by combining dynamic ranges without
- // combining the size of the range.
- // For example:
- //
- // struct Bad4 {
- // int *p, size;
- // template <typename H>
- // friend H AbslHashValue(H state, Bad4 x) {
- // return H::combine_contiguous(std::move(state), x.p, x.p + x.size);
- // }
- // friend bool operator==(Bad4 x, Bad4 y) {
- // // Compare two ranges for equality. C++14 code can instead use std::equal.
- // return absl::equal(x.p, x.p + x.size, y.p, y.p + y.size);
- // }
- // };
- //
- // An easy solution to this is to combine the size after combining the range,
- // like so:
- // template <typename H>
- // friend H AbslHashValue(H state, Bad4 x) {
- // return H::combine(
- // H::combine_contiguous(std::move(state), x.p, x.p + x.size), x.size);
- // }
- //
- template <int&... ExplicitBarrier, typename Container>
- ABSL_MUST_USE_RESULT testing::AssertionResult
- VerifyTypeImplementsAbslHashCorrectly(const Container& values);
- template <int&... ExplicitBarrier, typename Container, typename Eq>
- ABSL_MUST_USE_RESULT testing::AssertionResult
- VerifyTypeImplementsAbslHashCorrectly(const Container& values, Eq equals);
- template <int&..., typename T>
- ABSL_MUST_USE_RESULT testing::AssertionResult
- VerifyTypeImplementsAbslHashCorrectly(std::initializer_list<T> values);
- template <int&..., typename T, typename Eq>
- ABSL_MUST_USE_RESULT testing::AssertionResult
- VerifyTypeImplementsAbslHashCorrectly(std::initializer_list<T> values,
- Eq equals);
- namespace hash_internal {
- struct PrintVisitor {
- size_t index;
- template <typename T>
- std::string operator()(const T* value) const {
- return absl::StrCat("#", index, "(", testing::PrintToString(*value), ")");
- }
- };
- template <typename Eq>
- struct EqVisitor {
- Eq eq;
- template <typename T, typename U>
- bool operator()(const T* t, const U* u) const {
- return eq(*t, *u);
- }
- };
- struct ExpandVisitor {
- template <typename T>
- SpyHashState operator()(const T* value) const {
- return SpyHashState::combine(SpyHashState(), *value);
- }
- };
- template <typename Container, typename Eq>
- ABSL_MUST_USE_RESULT testing::AssertionResult
- VerifyTypeImplementsAbslHashCorrectly(const Container& values, Eq equals) {
- using V = typename Container::value_type;
- struct Info {
- const V& value;
- size_t index;
- std::string ToString() const {
- return absl::visit(PrintVisitor{index}, value);
- }
- SpyHashState expand() const { return absl::visit(ExpandVisitor{}, value); }
- };
- using EqClass = std::vector<Info>;
- std::vector<EqClass> classes;
- // Gather the values in equivalence classes.
- size_t i = 0;
- for (const auto& value : values) {
- EqClass* c = nullptr;
- for (auto& eqclass : classes) {
- if (absl::visit(EqVisitor<Eq>{equals}, value, eqclass[0].value)) {
- c = &eqclass;
- break;
- }
- }
- if (c == nullptr) {
- classes.emplace_back();
- c = &classes.back();
- }
- c->push_back({value, i});
- ++i;
- // Verify potential errors captured by SpyHashState.
- if (auto error = c->back().expand().error()) {
- return testing::AssertionFailure() << *error;
- }
- }
- if (classes.size() < 2) {
- return testing::AssertionFailure()
- << "At least two equivalence classes are expected.";
- }
- // We assume that equality is correctly implemented.
- // Now we verify that AbslHashValue is also correctly implemented.
- for (const auto& c : classes) {
- // All elements of the equivalence class must have the same hash
- // expansion.
- const SpyHashState expected = c[0].expand();
- for (const Info& v : c) {
- if (v.expand() != v.expand()) {
- return testing::AssertionFailure()
- << "Hash expansion for " << v.ToString()
- << " is non-deterministic.";
- }
- if (v.expand() != expected) {
- return testing::AssertionFailure()
- << "Values " << c[0].ToString() << " and " << v.ToString()
- << " evaluate as equal but have an unequal hash expansion.";
- }
- }
- // Elements from other classes must have different hash expansion.
- for (const auto& c2 : classes) {
- if (&c == &c2) continue;
- const SpyHashState c2_hash = c2[0].expand();
- switch (SpyHashState::Compare(expected, c2_hash)) {
- case SpyHashState::CompareResult::kEqual:
- return testing::AssertionFailure()
- << "Values " << c[0].ToString() << " and " << c2[0].ToString()
- << " evaluate as unequal but have an equal hash expansion.";
- case SpyHashState::CompareResult::kBSuffixA:
- return testing::AssertionFailure()
- << "Hash expansion of " << c2[0].ToString()
- << " is a suffix of the hash expansion of " << c[0].ToString()
- << ".";
- case SpyHashState::CompareResult::kASuffixB:
- return testing::AssertionFailure()
- << "Hash expansion of " << c[0].ToString()
- << " is a suffix of the hash expansion of " << c2[0].ToString()
- << ".";
- case SpyHashState::CompareResult::kUnequal:
- break;
- }
- }
- }
- return testing::AssertionSuccess();
- }
- template <typename... T>
- struct TypeSet {
- template <typename U, bool = disjunction<std::is_same<T, U>...>::value>
- struct Insert {
- using type = TypeSet<U, T...>;
- };
- template <typename U>
- struct Insert<U, true> {
- using type = TypeSet;
- };
- template <template <typename...> class C>
- using apply = C<T...>;
- };
- template <typename... T>
- struct MakeTypeSet : TypeSet<> {};
- template <typename T, typename... Ts>
- struct MakeTypeSet<T, Ts...> : MakeTypeSet<Ts...>::template Insert<T>::type {};
- template <typename... T>
- using VariantForTypes = typename MakeTypeSet<
- const typename std::decay<T>::type*...>::template apply<absl::variant>;
- template <typename Container>
- struct ContainerAsVector {
- using V = absl::variant<const typename Container::value_type*>;
- using Out = std::vector<V>;
- static Out Do(const Container& values) {
- Out out;
- for (const auto& v : values) out.push_back(&v);
- return out;
- }
- };
- template <typename... T>
- struct ContainerAsVector<std::tuple<T...>> {
- using V = VariantForTypes<T...>;
- using Out = std::vector<V>;
- template <size_t... I>
- static Out DoImpl(const std::tuple<T...>& tuple, absl::index_sequence<I...>) {
- return Out{&std::get<I>(tuple)...};
- }
- static Out Do(const std::tuple<T...>& values) {
- return DoImpl(values, absl::index_sequence_for<T...>());
- }
- };
- template <>
- struct ContainerAsVector<std::tuple<>> {
- static std::vector<VariantForTypes<int>> Do(std::tuple<>) { return {}; }
- };
- struct DefaultEquals {
- template <typename T, typename U>
- bool operator()(const T& t, const U& u) const {
- return t == u;
- }
- };
- } // namespace hash_internal
- template <int&..., typename Container>
- ABSL_MUST_USE_RESULT testing::AssertionResult
- VerifyTypeImplementsAbslHashCorrectly(const Container& values) {
- return hash_internal::VerifyTypeImplementsAbslHashCorrectly(
- hash_internal::ContainerAsVector<Container>::Do(values),
- hash_internal::DefaultEquals{});
- }
- template <int&..., typename Container, typename Eq>
- ABSL_MUST_USE_RESULT testing::AssertionResult
- VerifyTypeImplementsAbslHashCorrectly(const Container& values, Eq equals) {
- return hash_internal::VerifyTypeImplementsAbslHashCorrectly(
- hash_internal::ContainerAsVector<Container>::Do(values), equals);
- }
- template <int&..., typename T>
- ABSL_MUST_USE_RESULT testing::AssertionResult
- VerifyTypeImplementsAbslHashCorrectly(std::initializer_list<T> values) {
- return hash_internal::VerifyTypeImplementsAbslHashCorrectly(
- hash_internal::ContainerAsVector<std::initializer_list<T>>::Do(values),
- hash_internal::DefaultEquals{});
- }
- template <int&..., typename T, typename Eq>
- ABSL_MUST_USE_RESULT testing::AssertionResult
- VerifyTypeImplementsAbslHashCorrectly(std::initializer_list<T> values,
- Eq equals) {
- return hash_internal::VerifyTypeImplementsAbslHashCorrectly(
- hash_internal::ContainerAsVector<std::initializer_list<T>>::Do(values),
- equals);
- }
- ABSL_NAMESPACE_END
- } // namespace absl
- #endif // ABSL_HASH_HASH_TESTING_H_
|