hash_testing.h (12259B)
1 // Copyright 2018 The Abseil Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #ifndef ABSL_HASH_HASH_TESTING_H_ 16 #define ABSL_HASH_HASH_TESTING_H_ 17 18 #include <initializer_list> 19 #include <tuple> 20 #include <type_traits> 21 #include <vector> 22 23 #include "gmock/gmock.h" 24 #include "gtest/gtest.h" 25 #include "absl/hash/internal/spy_hash_state.h" 26 #include "absl/meta/type_traits.h" 27 #include "absl/strings/str_cat.h" 28 #include "absl/types/variant.h" 29 30 namespace absl { 31 ABSL_NAMESPACE_BEGIN 32 33 // Run the absl::Hash algorithm over all the elements passed in and verify that 34 // their hash expansion is congruent with their `==` operator. 35 // 36 // It is used in conjunction with EXPECT_TRUE. Failures will output information 37 // on what requirement failed and on which objects. 38 // 39 // Users should pass a collection of types as either an initializer list or a 40 // container of cases. 41 // 42 // EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly( 43 // {v1, v2, ..., vN})); 44 // 45 // std::vector<MyType> cases; 46 // // Fill cases... 47 // EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(cases)); 48 // 49 // Users can pass a variety of types for testing heterogeneous lookup with 50 // `std::make_tuple`: 51 // 52 // EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly( 53 // std::make_tuple(v1, v2, ..., vN))); 54 // 55 // 56 // Ideally, the values passed should provide enough coverage of the `==` 57 // operator and the AbslHashValue implementations. 58 // For dynamically sized types, the empty state should usually be included in 59 // the values. 60 // 61 // The function accepts an optional comparator function, in case that `==` is 62 // not enough for the values provided. 63 // 64 // Usage: 65 // 66 // EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly( 67 // std::make_tuple(v1, v2, ..., vN), MyCustomEq{})); 68 // 69 // It checks the following requirements: 70 // 1. The expansion for a value is deterministic. 71 // 2. For any two objects `a` and `b` in the sequence, if `a == b` evaluates 72 // to true, then their hash expansion must be equal. 73 // 3. If `a == b` evaluates to false their hash expansion must be unequal. 74 // 4. If `a == b` evaluates to false neither hash expansion can be a 75 // suffix of the other. 76 // 5. AbslHashValue overloads should not be called by the user. They are only 77 // meant to be called by the framework. Users should call H::combine() and 78 // H::combine_contiguous(). 79 // 6. No moved-from instance of the hash state is used in the implementation 80 // of AbslHashValue. 81 // 82 // The values do not have to have the same type. This can be useful for 83 // equivalent types that support heterogeneous lookup. 84 // 85 // A possible reason for breaking (2) is combining state in the hash expansion 86 // that was not used in `==`. 87 // For example: 88 // 89 // struct Bad2 { 90 // int a, b; 91 // template <typename H> 92 // friend H AbslHashValue(H state, Bad2 x) { 93 // // Uses a and b. 94 // return H::combine(std::move(state), x.a, x.b); 95 // } 96 // friend bool operator==(Bad2 x, Bad2 y) { 97 // // Only uses a. 98 // return x.a == y.a; 99 // } 100 // }; 101 // 102 // As for (3), breaking this usually means that there is state being passed to 103 // the `==` operator that is not used in the hash expansion. 104 // For example: 105 // 106 // struct Bad3 { 107 // int a, b; 108 // template <typename H> 109 // friend H AbslHashValue(H state, Bad3 x) { 110 // // Only uses a. 111 // return H::combine(std::move(state), x.a); 112 // } 113 // friend bool operator==(Bad3 x, Bad3 y) { 114 // // Uses a and b. 115 // return x.a == y.a && x.b == y.b; 116 // } 117 // }; 118 // 119 // Finally, a common way to break 4 is by combining dynamic ranges without 120 // combining the size of the range. 121 // For example: 122 // 123 // struct Bad4 { 124 // int *p, size; 125 // template <typename H> 126 // friend H AbslHashValue(H state, Bad4 x) { 127 // return H::combine_contiguous(std::move(state), x.p, x.p + x.size); 128 // } 129 // friend bool operator==(Bad4 x, Bad4 y) { 130 // // Compare two ranges for equality. C++14 code can instead use std::equal. 131 // return absl::equal(x.p, x.p + x.size, y.p, y.p + y.size); 132 // } 133 // }; 134 // 135 // An easy solution to this is to combine the size after combining the range, 136 // like so: 137 // template <typename H> 138 // friend H AbslHashValue(H state, Bad4 x) { 139 // return H::combine( 140 // H::combine_contiguous(std::move(state), x.p, x.p + x.size), x.size); 141 // } 142 // 143 template <int&... ExplicitBarrier, typename Container> 144 testing::AssertionResult VerifyTypeImplementsAbslHashCorrectly( 145 const Container& values); 146 147 template <int&... ExplicitBarrier, typename Container, typename Eq> 148 testing::AssertionResult VerifyTypeImplementsAbslHashCorrectly( 149 const Container& values, Eq equals); 150 151 template <int&..., typename T> 152 testing::AssertionResult VerifyTypeImplementsAbslHashCorrectly( 153 std::initializer_list<T> values); 154 155 template <int&..., typename T, typename Eq> 156 testing::AssertionResult VerifyTypeImplementsAbslHashCorrectly( 157 std::initializer_list<T> values, Eq equals); 158 159 namespace hash_internal { 160 161 struct PrintVisitor { 162 size_t index; 163 template <typename T> 164 std::string operator()(const T* value) const { 165 return absl::StrCat("#", index, "(", testing::PrintToString(*value), ")"); 166 } 167 }; 168 169 template <typename Eq> 170 struct EqVisitor { 171 Eq eq; 172 template <typename T, typename U> 173 bool operator()(const T* t, const U* u) const { 174 return eq(*t, *u); 175 } 176 }; 177 178 struct ExpandVisitor { 179 template <typename T> 180 SpyHashState operator()(const T* value) const { 181 return SpyHashState::combine(SpyHashState(), *value); 182 } 183 }; 184 185 template <typename Container, typename Eq> 186 testing::AssertionResult VerifyTypeImplementsAbslHashCorrectly( 187 const Container& values, Eq equals) { 188 using V = typename Container::value_type; 189 190 struct Info { 191 const V& value; 192 size_t index; 193 std::string ToString() const { 194 return absl::visit(PrintVisitor{index}, value); 195 } 196 SpyHashState expand() const { return absl::visit(ExpandVisitor{}, value); } 197 }; 198 199 using EqClass = std::vector<Info>; 200 std::vector<EqClass> classes; 201 202 // Gather the values in equivalence classes. 203 size_t i = 0; 204 for (const auto& value : values) { 205 EqClass* c = nullptr; 206 for (auto& eqclass : classes) { 207 if (absl::visit(EqVisitor<Eq>{equals}, value, eqclass[0].value)) { 208 c = &eqclass; 209 break; 210 } 211 } 212 if (c == nullptr) { 213 classes.emplace_back(); 214 c = &classes.back(); 215 } 216 c->push_back({value, i}); 217 ++i; 218 219 // Verify potential errors captured by SpyHashState. 220 if (auto error = c->back().expand().error()) { 221 return testing::AssertionFailure() << *error; 222 } 223 } 224 225 if (classes.size() < 2) { 226 return testing::AssertionFailure() 227 << "At least two equivalence classes are expected."; 228 } 229 230 // We assume that equality is correctly implemented. 231 // Now we verify that AbslHashValue is also correctly implemented. 232 233 for (const auto& c : classes) { 234 // All elements of the equivalence class must have the same hash 235 // expansion. 236 const SpyHashState expected = c[0].expand(); 237 for (const Info& v : c) { 238 if (v.expand() != v.expand()) { 239 return testing::AssertionFailure() 240 << "Hash expansion for " << v.ToString() 241 << " is non-deterministic."; 242 } 243 if (v.expand() != expected) { 244 return testing::AssertionFailure() 245 << "Values " << c[0].ToString() << " and " << v.ToString() 246 << " evaluate as equal but have unequal hash expansions (" 247 << expected << " vs. " << v.expand() << ")."; 248 } 249 } 250 251 // Elements from other classes must have different hash expansion. 252 for (const auto& c2 : classes) { 253 if (&c == &c2) continue; 254 const SpyHashState c2_hash = c2[0].expand(); 255 switch (SpyHashState::Compare(expected, c2_hash)) { 256 case SpyHashState::CompareResult::kEqual: 257 return testing::AssertionFailure() 258 << "Values " << c[0].ToString() << " and " << c2[0].ToString() 259 << " evaluate as unequal but have an equal hash expansion:" 260 << c2_hash << "."; 261 case SpyHashState::CompareResult::kBSuffixA: 262 return testing::AssertionFailure() 263 << "Hash expansion of " << c2[0].ToString() << ";" << c2_hash 264 << " is a suffix of the hash expansion of " << c[0].ToString() 265 << ";" << expected << "."; 266 case SpyHashState::CompareResult::kASuffixB: 267 return testing::AssertionFailure() 268 << "Hash expansion of " << c[0].ToString() << ";" 269 << expected << " is a suffix of the hash expansion of " 270 << c2[0].ToString() << ";" << c2_hash << "."; 271 case SpyHashState::CompareResult::kUnequal: 272 break; 273 } 274 } 275 } 276 return testing::AssertionSuccess(); 277 } 278 279 template <typename... T> 280 struct TypeSet { 281 template <typename U, bool = disjunction<std::is_same<T, U>...>::value> 282 struct Insert { 283 using type = TypeSet<U, T...>; 284 }; 285 template <typename U> 286 struct Insert<U, true> { 287 using type = TypeSet; 288 }; 289 290 template <template <typename...> class C> 291 using apply = C<T...>; 292 }; 293 294 template <typename... T> 295 struct MakeTypeSet : TypeSet<> {}; 296 template <typename T, typename... Ts> 297 struct MakeTypeSet<T, Ts...> : MakeTypeSet<Ts...>::template Insert<T>::type {}; 298 299 template <typename... T> 300 using VariantForTypes = typename MakeTypeSet< 301 const typename std::decay<T>::type*...>::template apply<absl::variant>; 302 303 template <typename Container> 304 struct ContainerAsVector { 305 using V = absl::variant<const typename Container::value_type*>; 306 using Out = std::vector<V>; 307 308 static Out Do(const Container& values) { 309 Out out; 310 for (const auto& v : values) out.push_back(&v); 311 return out; 312 } 313 }; 314 315 template <typename... T> 316 struct ContainerAsVector<std::tuple<T...>> { 317 using V = VariantForTypes<T...>; 318 using Out = std::vector<V>; 319 320 template <size_t... I> 321 static Out DoImpl(const std::tuple<T...>& tuple, absl::index_sequence<I...>) { 322 return Out{&std::get<I>(tuple)...}; 323 } 324 325 static Out Do(const std::tuple<T...>& values) { 326 return DoImpl(values, absl::index_sequence_for<T...>()); 327 } 328 }; 329 330 template <> 331 struct ContainerAsVector<std::tuple<>> { 332 static std::vector<VariantForTypes<int>> Do(std::tuple<>) { return {}; } 333 }; 334 335 struct DefaultEquals { 336 template <typename T, typename U> 337 bool operator()(const T& t, const U& u) const { 338 return t == u; 339 } 340 }; 341 342 } // namespace hash_internal 343 344 template <int&..., typename Container> 345 testing::AssertionResult VerifyTypeImplementsAbslHashCorrectly( 346 const Container& values) { 347 return hash_internal::VerifyTypeImplementsAbslHashCorrectly( 348 hash_internal::ContainerAsVector<Container>::Do(values), 349 hash_internal::DefaultEquals{}); 350 } 351 352 template <int&..., typename Container, typename Eq> 353 testing::AssertionResult VerifyTypeImplementsAbslHashCorrectly( 354 const Container& values, Eq equals) { 355 return hash_internal::VerifyTypeImplementsAbslHashCorrectly( 356 hash_internal::ContainerAsVector<Container>::Do(values), equals); 357 } 358 359 template <int&..., typename T> 360 testing::AssertionResult VerifyTypeImplementsAbslHashCorrectly( 361 std::initializer_list<T> values) { 362 return hash_internal::VerifyTypeImplementsAbslHashCorrectly( 363 hash_internal::ContainerAsVector<std::initializer_list<T>>::Do(values), 364 hash_internal::DefaultEquals{}); 365 } 366 367 template <int&..., typename T, typename Eq> 368 testing::AssertionResult VerifyTypeImplementsAbslHashCorrectly( 369 std::initializer_list<T> values, Eq equals) { 370 return hash_internal::VerifyTypeImplementsAbslHashCorrectly( 371 hash_internal::ContainerAsVector<std::initializer_list<T>>::Do(values), 372 equals); 373 } 374 375 ABSL_NAMESPACE_END 376 } // namespace absl 377 378 #endif // ABSL_HASH_HASH_TESTING_H_