tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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_