• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 ABSL_MUST_USE_RESULT testing::AssertionResult
145 VerifyTypeImplementsAbslHashCorrectly(const Container& values);
146 
147 template <int&... ExplicitBarrier, typename Container, typename Eq>
148 ABSL_MUST_USE_RESULT testing::AssertionResult
149 VerifyTypeImplementsAbslHashCorrectly(const Container& values, Eq equals);
150 
151 template <int&..., typename T>
152 ABSL_MUST_USE_RESULT testing::AssertionResult
153 VerifyTypeImplementsAbslHashCorrectly(std::initializer_list<T> values);
154 
155 template <int&..., typename T, typename Eq>
156 ABSL_MUST_USE_RESULT testing::AssertionResult
157 VerifyTypeImplementsAbslHashCorrectly(std::initializer_list<T> values,
158                                       Eq equals);
159 
160 namespace hash_internal {
161 
162 struct PrintVisitor {
163   size_t index;
164   template <typename T>
operatorPrintVisitor165   std::string operator()(const T* value) const {
166     return absl::StrCat("#", index, "(", testing::PrintToString(*value), ")");
167   }
168 };
169 
170 template <typename Eq>
171 struct EqVisitor {
172   Eq eq;
173   template <typename T, typename U>
operatorEqVisitor174   bool operator()(const T* t, const U* u) const {
175     return eq(*t, *u);
176   }
177 };
178 
179 struct ExpandVisitor {
180   template <typename T>
operatorExpandVisitor181   SpyHashState operator()(const T* value) const {
182     return SpyHashState::combine(SpyHashState(), *value);
183   }
184 };
185 
186 template <typename Container, typename Eq>
187 ABSL_MUST_USE_RESULT testing::AssertionResult
VerifyTypeImplementsAbslHashCorrectly(const Container & values,Eq equals)188 VerifyTypeImplementsAbslHashCorrectly(const Container& values, Eq equals) {
189   using V = typename Container::value_type;
190 
191   struct Info {
192     const V& value;
193     size_t index;
194     std::string ToString() const {
195       return absl::visit(PrintVisitor{index}, value);
196     }
197     SpyHashState expand() const { return absl::visit(ExpandVisitor{}, value); }
198   };
199 
200   using EqClass = std::vector<Info>;
201   std::vector<EqClass> classes;
202 
203   // Gather the values in equivalence classes.
204   size_t i = 0;
205   for (const auto& value : values) {
206     EqClass* c = nullptr;
207     for (auto& eqclass : classes) {
208       if (absl::visit(EqVisitor<Eq>{equals}, value, eqclass[0].value)) {
209         c = &eqclass;
210         break;
211       }
212     }
213     if (c == nullptr) {
214       classes.emplace_back();
215       c = &classes.back();
216     }
217     c->push_back({value, i});
218     ++i;
219 
220     // Verify potential errors captured by SpyHashState.
221     if (auto error = c->back().expand().error()) {
222       return testing::AssertionFailure() << *error;
223     }
224   }
225 
226   if (classes.size() < 2) {
227     return testing::AssertionFailure()
228            << "At least two equivalence classes are expected.";
229   }
230 
231   // We assume that equality is correctly implemented.
232   // Now we verify that AbslHashValue is also correctly implemented.
233 
234   for (const auto& c : classes) {
235     // All elements of the equivalence class must have the same hash
236     // expansion.
237     const SpyHashState expected = c[0].expand();
238     for (const Info& v : c) {
239       if (v.expand() != v.expand()) {
240         return testing::AssertionFailure()
241                << "Hash expansion for " << v.ToString()
242                << " is non-deterministic.";
243       }
244       if (v.expand() != expected) {
245         return testing::AssertionFailure()
246                << "Values " << c[0].ToString() << " and " << v.ToString()
247                << " evaluate as equal but have an unequal hash expansion.";
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         case SpyHashState::CompareResult::kBSuffixA:
261           return testing::AssertionFailure()
262                  << "Hash expansion of " << c2[0].ToString()
263                  << " is a suffix of the hash expansion of " << c[0].ToString()
264                  << ".";
265         case SpyHashState::CompareResult::kASuffixB:
266           return testing::AssertionFailure()
267                  << "Hash expansion of " << c[0].ToString()
268                  << " is a suffix of the hash expansion of " << c2[0].ToString()
269                  << ".";
270         case SpyHashState::CompareResult::kUnequal:
271           break;
272       }
273     }
274   }
275   return testing::AssertionSuccess();
276 }
277 
278 template <typename... T>
279 struct TypeSet {
280   template <typename U, bool = disjunction<std::is_same<T, U>...>::value>
281   struct Insert {
282     using type = TypeSet<U, T...>;
283   };
284   template <typename U>
285   struct Insert<U, true> {
286     using type = TypeSet;
287   };
288 
289   template <template <typename...> class C>
290   using apply = C<T...>;
291 };
292 
293 template <typename... T>
294 struct MakeTypeSet : TypeSet<> {};
295 template <typename T, typename... Ts>
296 struct MakeTypeSet<T, Ts...> : MakeTypeSet<Ts...>::template Insert<T>::type {};
297 
298 template <typename... T>
299 using VariantForTypes = typename MakeTypeSet<
300     const typename std::decay<T>::type*...>::template apply<absl::variant>;
301 
302 template <typename Container>
303 struct ContainerAsVector {
304   using V = absl::variant<const typename Container::value_type*>;
305   using Out = std::vector<V>;
306 
307   static Out Do(const Container& values) {
308     Out out;
309     for (const auto& v : values) out.push_back(&v);
310     return out;
311   }
312 };
313 
314 template <typename... T>
315 struct ContainerAsVector<std::tuple<T...>> {
316   using V = VariantForTypes<T...>;
317   using Out = std::vector<V>;
318 
319   template <size_t... I>
320   static Out DoImpl(const std::tuple<T...>& tuple, absl::index_sequence<I...>) {
321     return Out{&std::get<I>(tuple)...};
322   }
323 
324   static Out Do(const std::tuple<T...>& values) {
325     return DoImpl(values, absl::index_sequence_for<T...>());
326   }
327 };
328 
329 template <>
330 struct ContainerAsVector<std::tuple<>> {
331   static std::vector<VariantForTypes<int>> Do(std::tuple<>) { return {}; }
332 };
333 
334 struct DefaultEquals {
335   template <typename T, typename U>
336   bool operator()(const T& t, const U& u) const {
337     return t == u;
338   }
339 };
340 
341 }  // namespace hash_internal
342 
343 template <int&..., typename Container>
344 ABSL_MUST_USE_RESULT testing::AssertionResult
345 VerifyTypeImplementsAbslHashCorrectly(const Container& values) {
346   return hash_internal::VerifyTypeImplementsAbslHashCorrectly(
347       hash_internal::ContainerAsVector<Container>::Do(values),
348       hash_internal::DefaultEquals{});
349 }
350 
351 template <int&..., typename Container, typename Eq>
352 ABSL_MUST_USE_RESULT testing::AssertionResult
353 VerifyTypeImplementsAbslHashCorrectly(const Container& values, Eq equals) {
354   return hash_internal::VerifyTypeImplementsAbslHashCorrectly(
355       hash_internal::ContainerAsVector<Container>::Do(values), equals);
356 }
357 
358 template <int&..., typename T>
359 ABSL_MUST_USE_RESULT testing::AssertionResult
360 VerifyTypeImplementsAbslHashCorrectly(std::initializer_list<T> values) {
361   return hash_internal::VerifyTypeImplementsAbslHashCorrectly(
362       hash_internal::ContainerAsVector<std::initializer_list<T>>::Do(values),
363       hash_internal::DefaultEquals{});
364 }
365 
366 template <int&..., typename T, typename Eq>
367 ABSL_MUST_USE_RESULT testing::AssertionResult
368 VerifyTypeImplementsAbslHashCorrectly(std::initializer_list<T> values,
369                                       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_
379