about summary refs log tree commit diff
path: root/third_party/abseil_cpp/absl/container/internal/raw_hash_set_test.cc
diff options
context:
space:
mode:
authorVincent Ambo <tazjin@google.com>2020-05-20T01·32+0100
committerVincent Ambo <tazjin@google.com>2020-05-20T01·32+0100
commitfc8dc48020ac5b52731d0828a96ea4d2526c77ba (patch)
tree353204eea3268095a9ad3f5345720f32c2615c69 /third_party/abseil_cpp/absl/container/internal/raw_hash_set_test.cc
parentffb2ae54beb5796cd408fbe15d2d2da09ff37adf (diff)
parent768eb2ca2857342673fcd462792ce04b8bac3fa3 (diff)
Add 'third_party/abseil_cpp/' from commit '768eb2ca2857342673fcd462792ce04b8bac3fa3' r/781
git-subtree-dir: third_party/abseil_cpp
git-subtree-mainline: ffb2ae54beb5796cd408fbe15d2d2da09ff37adf
git-subtree-split: 768eb2ca2857342673fcd462792ce04b8bac3fa3
Diffstat (limited to 'third_party/abseil_cpp/absl/container/internal/raw_hash_set_test.cc')
-rw-r--r--third_party/abseil_cpp/absl/container/internal/raw_hash_set_test.cc1871
1 files changed, 1871 insertions, 0 deletions
diff --git a/third_party/abseil_cpp/absl/container/internal/raw_hash_set_test.cc b/third_party/abseil_cpp/absl/container/internal/raw_hash_set_test.cc
new file mode 100644
index 0000000000..2fc85591ca
--- /dev/null
+++ b/third_party/abseil_cpp/absl/container/internal/raw_hash_set_test.cc
@@ -0,0 +1,1871 @@
+// 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.
+
+#include "absl/container/internal/raw_hash_set.h"
+
+#include <cmath>
+#include <cstdint>
+#include <deque>
+#include <functional>
+#include <memory>
+#include <numeric>
+#include <random>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/attributes.h"
+#include "absl/base/internal/cycleclock.h"
+#include "absl/base/internal/raw_logging.h"
+#include "absl/container/internal/container_memory.h"
+#include "absl/container/internal/hash_function_defaults.h"
+#include "absl/container/internal/hash_policy_testing.h"
+#include "absl/container/internal/hashtable_debug.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace container_internal {
+
+struct RawHashSetTestOnlyAccess {
+  template <typename C>
+  static auto GetSlots(const C& c) -> decltype(c.slots_) {
+    return c.slots_;
+  }
+};
+
+namespace {
+
+using ::testing::DoubleNear;
+using ::testing::ElementsAre;
+using ::testing::Ge;
+using ::testing::Lt;
+using ::testing::Optional;
+using ::testing::Pair;
+using ::testing::UnorderedElementsAre;
+
+TEST(Util, NormalizeCapacity) {
+  EXPECT_EQ(1, NormalizeCapacity(0));
+  EXPECT_EQ(1, NormalizeCapacity(1));
+  EXPECT_EQ(3, NormalizeCapacity(2));
+  EXPECT_EQ(3, NormalizeCapacity(3));
+  EXPECT_EQ(7, NormalizeCapacity(4));
+  EXPECT_EQ(7, NormalizeCapacity(7));
+  EXPECT_EQ(15, NormalizeCapacity(8));
+  EXPECT_EQ(15, NormalizeCapacity(15));
+  EXPECT_EQ(15 * 2 + 1, NormalizeCapacity(15 + 1));
+  EXPECT_EQ(15 * 2 + 1, NormalizeCapacity(15 + 2));
+}
+
+TEST(Util, GrowthAndCapacity) {
+  // Verify that GrowthToCapacity gives the minimum capacity that has enough
+  // growth.
+  for (size_t growth = 0; growth < 10000; ++growth) {
+    SCOPED_TRACE(growth);
+    size_t capacity = NormalizeCapacity(GrowthToLowerboundCapacity(growth));
+    // The capacity is large enough for `growth`
+    EXPECT_THAT(CapacityToGrowth(capacity), Ge(growth));
+    if (growth != 0 && capacity > 1) {
+      // There is no smaller capacity that works.
+      EXPECT_THAT(CapacityToGrowth(capacity / 2), Lt(growth));
+    }
+  }
+
+  for (size_t capacity = Group::kWidth - 1; capacity < 10000;
+       capacity = 2 * capacity + 1) {
+    SCOPED_TRACE(capacity);
+    size_t growth = CapacityToGrowth(capacity);
+    EXPECT_THAT(growth, Lt(capacity));
+    EXPECT_LE(GrowthToLowerboundCapacity(growth), capacity);
+    EXPECT_EQ(NormalizeCapacity(GrowthToLowerboundCapacity(growth)), capacity);
+  }
+}
+
+TEST(Util, probe_seq) {
+  probe_seq<16> seq(0, 127);
+  auto gen = [&]() {
+    size_t res = seq.offset();
+    seq.next();
+    return res;
+  };
+  std::vector<size_t> offsets(8);
+  std::generate_n(offsets.begin(), 8, gen);
+  EXPECT_THAT(offsets, ElementsAre(0, 16, 48, 96, 32, 112, 80, 64));
+  seq = probe_seq<16>(128, 127);
+  std::generate_n(offsets.begin(), 8, gen);
+  EXPECT_THAT(offsets, ElementsAre(0, 16, 48, 96, 32, 112, 80, 64));
+}
+
+TEST(BitMask, Smoke) {
+  EXPECT_FALSE((BitMask<uint8_t, 8>(0)));
+  EXPECT_TRUE((BitMask<uint8_t, 8>(5)));
+
+  EXPECT_THAT((BitMask<uint8_t, 8>(0)), ElementsAre());
+  EXPECT_THAT((BitMask<uint8_t, 8>(0x1)), ElementsAre(0));
+  EXPECT_THAT((BitMask<uint8_t, 8>(0x2)), ElementsAre(1));
+  EXPECT_THAT((BitMask<uint8_t, 8>(0x3)), ElementsAre(0, 1));
+  EXPECT_THAT((BitMask<uint8_t, 8>(0x4)), ElementsAre(2));
+  EXPECT_THAT((BitMask<uint8_t, 8>(0x5)), ElementsAre(0, 2));
+  EXPECT_THAT((BitMask<uint8_t, 8>(0x55)), ElementsAre(0, 2, 4, 6));
+  EXPECT_THAT((BitMask<uint8_t, 8>(0xAA)), ElementsAre(1, 3, 5, 7));
+}
+
+TEST(BitMask, WithShift) {
+  // See the non-SSE version of Group for details on what this math is for.
+  uint64_t ctrl = 0x1716151413121110;
+  uint64_t hash = 0x12;
+  constexpr uint64_t msbs = 0x8080808080808080ULL;
+  constexpr uint64_t lsbs = 0x0101010101010101ULL;
+  auto x = ctrl ^ (lsbs * hash);
+  uint64_t mask = (x - lsbs) & ~x & msbs;
+  EXPECT_EQ(0x0000000080800000, mask);
+
+  BitMask<uint64_t, 8, 3> b(mask);
+  EXPECT_EQ(*b, 2);
+}
+
+TEST(BitMask, LeadingTrailing) {
+  EXPECT_EQ((BitMask<uint32_t, 16>(0x00001a40).LeadingZeros()), 3);
+  EXPECT_EQ((BitMask<uint32_t, 16>(0x00001a40).TrailingZeros()), 6);
+
+  EXPECT_EQ((BitMask<uint32_t, 16>(0x00000001).LeadingZeros()), 15);
+  EXPECT_EQ((BitMask<uint32_t, 16>(0x00000001).TrailingZeros()), 0);
+
+  EXPECT_EQ((BitMask<uint32_t, 16>(0x00008000).LeadingZeros()), 0);
+  EXPECT_EQ((BitMask<uint32_t, 16>(0x00008000).TrailingZeros()), 15);
+
+  EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x0000008080808000).LeadingZeros()), 3);
+  EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x0000008080808000).TrailingZeros()), 1);
+
+  EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x0000000000000080).LeadingZeros()), 7);
+  EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x0000000000000080).TrailingZeros()), 0);
+
+  EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x8000000000000000).LeadingZeros()), 0);
+  EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x8000000000000000).TrailingZeros()), 7);
+}
+
+TEST(Group, EmptyGroup) {
+  for (h2_t h = 0; h != 128; ++h) EXPECT_FALSE(Group{EmptyGroup()}.Match(h));
+}
+
+TEST(Group, Match) {
+  if (Group::kWidth == 16) {
+    ctrl_t group[] = {kEmpty, 1, kDeleted, 3, kEmpty, 5, kSentinel, 7,
+                      7,      5, 3,        1, 1,      1, 1,         1};
+    EXPECT_THAT(Group{group}.Match(0), ElementsAre());
+    EXPECT_THAT(Group{group}.Match(1), ElementsAre(1, 11, 12, 13, 14, 15));
+    EXPECT_THAT(Group{group}.Match(3), ElementsAre(3, 10));
+    EXPECT_THAT(Group{group}.Match(5), ElementsAre(5, 9));
+    EXPECT_THAT(Group{group}.Match(7), ElementsAre(7, 8));
+  } else if (Group::kWidth == 8) {
+    ctrl_t group[] = {kEmpty, 1, 2, kDeleted, 2, 1, kSentinel, 1};
+    EXPECT_THAT(Group{group}.Match(0), ElementsAre());
+    EXPECT_THAT(Group{group}.Match(1), ElementsAre(1, 5, 7));
+    EXPECT_THAT(Group{group}.Match(2), ElementsAre(2, 4));
+  } else {
+    FAIL() << "No test coverage for Group::kWidth==" << Group::kWidth;
+  }
+}
+
+TEST(Group, MatchEmpty) {
+  if (Group::kWidth == 16) {
+    ctrl_t group[] = {kEmpty, 1, kDeleted, 3, kEmpty, 5, kSentinel, 7,
+                      7,      5, 3,        1, 1,      1, 1,         1};
+    EXPECT_THAT(Group{group}.MatchEmpty(), ElementsAre(0, 4));
+  } else if (Group::kWidth == 8) {
+    ctrl_t group[] = {kEmpty, 1, 2, kDeleted, 2, 1, kSentinel, 1};
+    EXPECT_THAT(Group{group}.MatchEmpty(), ElementsAre(0));
+  } else {
+    FAIL() << "No test coverage for Group::kWidth==" << Group::kWidth;
+  }
+}
+
+TEST(Group, MatchEmptyOrDeleted) {
+  if (Group::kWidth == 16) {
+    ctrl_t group[] = {kEmpty, 1, kDeleted, 3, kEmpty, 5, kSentinel, 7,
+                      7,      5, 3,        1, 1,      1, 1,         1};
+    EXPECT_THAT(Group{group}.MatchEmptyOrDeleted(), ElementsAre(0, 2, 4));
+  } else if (Group::kWidth == 8) {
+    ctrl_t group[] = {kEmpty, 1, 2, kDeleted, 2, 1, kSentinel, 1};
+    EXPECT_THAT(Group{group}.MatchEmptyOrDeleted(), ElementsAre(0, 3));
+  } else {
+    FAIL() << "No test coverage for Group::kWidth==" << Group::kWidth;
+  }
+}
+
+TEST(Batch, DropDeletes) {
+  constexpr size_t kCapacity = 63;
+  constexpr size_t kGroupWidth = container_internal::Group::kWidth;
+  std::vector<ctrl_t> ctrl(kCapacity + 1 + kGroupWidth);
+  ctrl[kCapacity] = kSentinel;
+  std::vector<ctrl_t> pattern = {kEmpty, 2, kDeleted, 2, kEmpty, 1, kDeleted};
+  for (size_t i = 0; i != kCapacity; ++i) {
+    ctrl[i] = pattern[i % pattern.size()];
+    if (i < kGroupWidth - 1)
+      ctrl[i + kCapacity + 1] = pattern[i % pattern.size()];
+  }
+  ConvertDeletedToEmptyAndFullToDeleted(ctrl.data(), kCapacity);
+  ASSERT_EQ(ctrl[kCapacity], kSentinel);
+  for (size_t i = 0; i < kCapacity + 1 + kGroupWidth; ++i) {
+    ctrl_t expected = pattern[i % (kCapacity + 1) % pattern.size()];
+    if (i == kCapacity) expected = kSentinel;
+    if (expected == kDeleted) expected = kEmpty;
+    if (IsFull(expected)) expected = kDeleted;
+    EXPECT_EQ(ctrl[i], expected)
+        << i << " " << int{pattern[i % pattern.size()]};
+  }
+}
+
+TEST(Group, CountLeadingEmptyOrDeleted) {
+  const std::vector<ctrl_t> empty_examples = {kEmpty, kDeleted};
+  const std::vector<ctrl_t> full_examples = {0, 1, 2, 3, 5, 9, 127, kSentinel};
+
+  for (ctrl_t empty : empty_examples) {
+    std::vector<ctrl_t> e(Group::kWidth, empty);
+    EXPECT_EQ(Group::kWidth, Group{e.data()}.CountLeadingEmptyOrDeleted());
+    for (ctrl_t full : full_examples) {
+      for (size_t i = 0; i != Group::kWidth; ++i) {
+        std::vector<ctrl_t> f(Group::kWidth, empty);
+        f[i] = full;
+        EXPECT_EQ(i, Group{f.data()}.CountLeadingEmptyOrDeleted());
+      }
+      std::vector<ctrl_t> f(Group::kWidth, empty);
+      f[Group::kWidth * 2 / 3] = full;
+      f[Group::kWidth / 2] = full;
+      EXPECT_EQ(
+          Group::kWidth / 2, Group{f.data()}.CountLeadingEmptyOrDeleted());
+    }
+  }
+}
+
+struct IntPolicy {
+  using slot_type = int64_t;
+  using key_type = int64_t;
+  using init_type = int64_t;
+
+  static void construct(void*, int64_t* slot, int64_t v) { *slot = v; }
+  static void destroy(void*, int64_t*) {}
+  static void transfer(void*, int64_t* new_slot, int64_t* old_slot) {
+    *new_slot = *old_slot;
+  }
+
+  static int64_t& element(slot_type* slot) { return *slot; }
+
+  template <class F>
+  static auto apply(F&& f, int64_t x) -> decltype(std::forward<F>(f)(x, x)) {
+    return std::forward<F>(f)(x, x);
+  }
+};
+
+class StringPolicy {
+  template <class F, class K, class V,
+            class = typename std::enable_if<
+                std::is_convertible<const K&, absl::string_view>::value>::type>
+  decltype(std::declval<F>()(
+      std::declval<const absl::string_view&>(), std::piecewise_construct,
+      std::declval<std::tuple<K>>(),
+      std::declval<V>())) static apply_impl(F&& f,
+                                            std::pair<std::tuple<K>, V> p) {
+    const absl::string_view& key = std::get<0>(p.first);
+    return std::forward<F>(f)(key, std::piecewise_construct, std::move(p.first),
+                              std::move(p.second));
+  }
+
+ public:
+  struct slot_type {
+    struct ctor {};
+
+    template <class... Ts>
+    slot_type(ctor, Ts&&... ts) : pair(std::forward<Ts>(ts)...) {}
+
+    std::pair<std::string, std::string> pair;
+  };
+
+  using key_type = std::string;
+  using init_type = std::pair<std::string, std::string>;
+
+  template <class allocator_type, class... Args>
+  static void construct(allocator_type* alloc, slot_type* slot, Args... args) {
+    std::allocator_traits<allocator_type>::construct(
+        *alloc, slot, typename slot_type::ctor(), std::forward<Args>(args)...);
+  }
+
+  template <class allocator_type>
+  static void destroy(allocator_type* alloc, slot_type* slot) {
+    std::allocator_traits<allocator_type>::destroy(*alloc, slot);
+  }
+
+  template <class allocator_type>
+  static void transfer(allocator_type* alloc, slot_type* new_slot,
+                       slot_type* old_slot) {
+    construct(alloc, new_slot, std::move(old_slot->pair));
+    destroy(alloc, old_slot);
+  }
+
+  static std::pair<std::string, std::string>& element(slot_type* slot) {
+    return slot->pair;
+  }
+
+  template <class F, class... Args>
+  static auto apply(F&& f, Args&&... args)
+      -> decltype(apply_impl(std::forward<F>(f),
+                             PairArgs(std::forward<Args>(args)...))) {
+    return apply_impl(std::forward<F>(f),
+                      PairArgs(std::forward<Args>(args)...));
+  }
+};
+
+struct StringHash : absl::Hash<absl::string_view> {
+  using is_transparent = void;
+};
+struct StringEq : std::equal_to<absl::string_view> {
+  using is_transparent = void;
+};
+
+struct StringTable
+    : raw_hash_set<StringPolicy, StringHash, StringEq, std::allocator<int>> {
+  using Base = typename StringTable::raw_hash_set;
+  StringTable() {}
+  using Base::Base;
+};
+
+struct IntTable
+    : raw_hash_set<IntPolicy, container_internal::hash_default_hash<int64_t>,
+                   std::equal_to<int64_t>, std::allocator<int64_t>> {
+  using Base = typename IntTable::raw_hash_set;
+  using Base::Base;
+};
+
+template <typename T>
+struct CustomAlloc : std::allocator<T> {
+  CustomAlloc() {}
+
+  template <typename U>
+  CustomAlloc(const CustomAlloc<U>& other) {}
+
+  template<class U> struct rebind {
+    using other = CustomAlloc<U>;
+  };
+};
+
+struct CustomAllocIntTable
+    : raw_hash_set<IntPolicy, container_internal::hash_default_hash<int64_t>,
+                   std::equal_to<int64_t>, CustomAlloc<int64_t>> {
+  using Base = typename CustomAllocIntTable::raw_hash_set;
+  using Base::Base;
+};
+
+struct BadFastHash {
+  template <class T>
+  size_t operator()(const T&) const {
+    return 0;
+  }
+};
+
+struct BadTable : raw_hash_set<IntPolicy, BadFastHash, std::equal_to<int>,
+                               std::allocator<int>> {
+  using Base = typename BadTable::raw_hash_set;
+  BadTable() {}
+  using Base::Base;
+};
+
+TEST(Table, EmptyFunctorOptimization) {
+  static_assert(std::is_empty<std::equal_to<absl::string_view>>::value, "");
+  static_assert(std::is_empty<std::allocator<int>>::value, "");
+
+  struct MockTable {
+    void* ctrl;
+    void* slots;
+    size_t size;
+    size_t capacity;
+    size_t growth_left;
+    void* infoz;
+  };
+  struct StatelessHash {
+    size_t operator()(absl::string_view) const { return 0; }
+  };
+  struct StatefulHash : StatelessHash {
+    size_t dummy;
+  };
+
+  EXPECT_EQ(
+      sizeof(MockTable),
+      sizeof(
+          raw_hash_set<StringPolicy, StatelessHash,
+                       std::equal_to<absl::string_view>, std::allocator<int>>));
+
+  EXPECT_EQ(
+      sizeof(MockTable) + sizeof(StatefulHash),
+      sizeof(
+          raw_hash_set<StringPolicy, StatefulHash,
+                       std::equal_to<absl::string_view>, std::allocator<int>>));
+}
+
+TEST(Table, Empty) {
+  IntTable t;
+  EXPECT_EQ(0, t.size());
+  EXPECT_TRUE(t.empty());
+}
+
+TEST(Table, LookupEmpty) {
+  IntTable t;
+  auto it = t.find(0);
+  EXPECT_TRUE(it == t.end());
+}
+
+TEST(Table, Insert1) {
+  IntTable t;
+  EXPECT_TRUE(t.find(0) == t.end());
+  auto res = t.emplace(0);
+  EXPECT_TRUE(res.second);
+  EXPECT_THAT(*res.first, 0);
+  EXPECT_EQ(1, t.size());
+  EXPECT_THAT(*t.find(0), 0);
+}
+
+TEST(Table, Insert2) {
+  IntTable t;
+  EXPECT_TRUE(t.find(0) == t.end());
+  auto res = t.emplace(0);
+  EXPECT_TRUE(res.second);
+  EXPECT_THAT(*res.first, 0);
+  EXPECT_EQ(1, t.size());
+  EXPECT_TRUE(t.find(1) == t.end());
+  res = t.emplace(1);
+  EXPECT_TRUE(res.second);
+  EXPECT_THAT(*res.first, 1);
+  EXPECT_EQ(2, t.size());
+  EXPECT_THAT(*t.find(0), 0);
+  EXPECT_THAT(*t.find(1), 1);
+}
+
+TEST(Table, InsertCollision) {
+  BadTable t;
+  EXPECT_TRUE(t.find(1) == t.end());
+  auto res = t.emplace(1);
+  EXPECT_TRUE(res.second);
+  EXPECT_THAT(*res.first, 1);
+  EXPECT_EQ(1, t.size());
+
+  EXPECT_TRUE(t.find(2) == t.end());
+  res = t.emplace(2);
+  EXPECT_THAT(*res.first, 2);
+  EXPECT_TRUE(res.second);
+  EXPECT_EQ(2, t.size());
+
+  EXPECT_THAT(*t.find(1), 1);
+  EXPECT_THAT(*t.find(2), 2);
+}
+
+// Test that we do not add existent element in case we need to search through
+// many groups with deleted elements
+TEST(Table, InsertCollisionAndFindAfterDelete) {
+  BadTable t;  // all elements go to the same group.
+  // Have at least 2 groups with Group::kWidth collisions
+  // plus some extra collisions in the last group.
+  constexpr size_t kNumInserts = Group::kWidth * 2 + 5;
+  for (size_t i = 0; i < kNumInserts; ++i) {
+    auto res = t.emplace(i);
+    EXPECT_TRUE(res.second);
+    EXPECT_THAT(*res.first, i);
+    EXPECT_EQ(i + 1, t.size());
+  }
+
+  // Remove elements one by one and check
+  // that we still can find all other elements.
+  for (size_t i = 0; i < kNumInserts; ++i) {
+    EXPECT_EQ(1, t.erase(i)) << i;
+    for (size_t j = i + 1; j < kNumInserts; ++j) {
+      EXPECT_THAT(*t.find(j), j);
+      auto res = t.emplace(j);
+      EXPECT_FALSE(res.second) << i << " " << j;
+      EXPECT_THAT(*res.first, j);
+      EXPECT_EQ(kNumInserts - i - 1, t.size());
+    }
+  }
+  EXPECT_TRUE(t.empty());
+}
+
+TEST(Table, LazyEmplace) {
+  StringTable t;
+  bool called = false;
+  auto it = t.lazy_emplace("abc", [&](const StringTable::constructor& f) {
+    called = true;
+    f("abc", "ABC");
+  });
+  EXPECT_TRUE(called);
+  EXPECT_THAT(*it, Pair("abc", "ABC"));
+  called = false;
+  it = t.lazy_emplace("abc", [&](const StringTable::constructor& f) {
+    called = true;
+    f("abc", "DEF");
+  });
+  EXPECT_FALSE(called);
+  EXPECT_THAT(*it, Pair("abc", "ABC"));
+}
+
+TEST(Table, ContainsEmpty) {
+  IntTable t;
+
+  EXPECT_FALSE(t.contains(0));
+}
+
+TEST(Table, Contains1) {
+  IntTable t;
+
+  EXPECT_TRUE(t.insert(0).second);
+  EXPECT_TRUE(t.contains(0));
+  EXPECT_FALSE(t.contains(1));
+
+  EXPECT_EQ(1, t.erase(0));
+  EXPECT_FALSE(t.contains(0));
+}
+
+TEST(Table, Contains2) {
+  IntTable t;
+
+  EXPECT_TRUE(t.insert(0).second);
+  EXPECT_TRUE(t.contains(0));
+  EXPECT_FALSE(t.contains(1));
+
+  t.clear();
+  EXPECT_FALSE(t.contains(0));
+}
+
+int decompose_constructed;
+struct DecomposeType {
+  DecomposeType(int i) : i(i) {  // NOLINT
+    ++decompose_constructed;
+  }
+
+  explicit DecomposeType(const char* d) : DecomposeType(*d) {}
+
+  int i;
+};
+
+struct DecomposeHash {
+  using is_transparent = void;
+  size_t operator()(DecomposeType a) const { return a.i; }
+  size_t operator()(int a) const { return a; }
+  size_t operator()(const char* a) const { return *a; }
+};
+
+struct DecomposeEq {
+  using is_transparent = void;
+  bool operator()(DecomposeType a, DecomposeType b) const { return a.i == b.i; }
+  bool operator()(DecomposeType a, int b) const { return a.i == b; }
+  bool operator()(DecomposeType a, const char* b) const { return a.i == *b; }
+};
+
+struct DecomposePolicy {
+  using slot_type = DecomposeType;
+  using key_type = DecomposeType;
+  using init_type = DecomposeType;
+
+  template <typename T>
+  static void construct(void*, DecomposeType* slot, T&& v) {
+    *slot = DecomposeType(std::forward<T>(v));
+  }
+  static void destroy(void*, DecomposeType*) {}
+  static DecomposeType& element(slot_type* slot) { return *slot; }
+
+  template <class F, class T>
+  static auto apply(F&& f, const T& x) -> decltype(std::forward<F>(f)(x, x)) {
+    return std::forward<F>(f)(x, x);
+  }
+};
+
+template <typename Hash, typename Eq>
+void TestDecompose(bool construct_three) {
+  DecomposeType elem{0};
+  const int one = 1;
+  const char* three_p = "3";
+  const auto& three = three_p;
+
+  raw_hash_set<DecomposePolicy, Hash, Eq, std::allocator<int>> set1;
+
+  decompose_constructed = 0;
+  int expected_constructed = 0;
+  EXPECT_EQ(expected_constructed, decompose_constructed);
+  set1.insert(elem);
+  EXPECT_EQ(expected_constructed, decompose_constructed);
+  set1.insert(1);
+  EXPECT_EQ(++expected_constructed, decompose_constructed);
+  set1.emplace("3");
+  EXPECT_EQ(++expected_constructed, decompose_constructed);
+  EXPECT_EQ(expected_constructed, decompose_constructed);
+
+  {  // insert(T&&)
+    set1.insert(1);
+    EXPECT_EQ(expected_constructed, decompose_constructed);
+  }
+
+  {  // insert(const T&)
+    set1.insert(one);
+    EXPECT_EQ(expected_constructed, decompose_constructed);
+  }
+
+  {  // insert(hint, T&&)
+    set1.insert(set1.begin(), 1);
+    EXPECT_EQ(expected_constructed, decompose_constructed);
+  }
+
+  {  // insert(hint, const T&)
+    set1.insert(set1.begin(), one);
+    EXPECT_EQ(expected_constructed, decompose_constructed);
+  }
+
+  {  // emplace(...)
+    set1.emplace(1);
+    EXPECT_EQ(expected_constructed, decompose_constructed);
+    set1.emplace("3");
+    expected_constructed += construct_three;
+    EXPECT_EQ(expected_constructed, decompose_constructed);
+    set1.emplace(one);
+    EXPECT_EQ(expected_constructed, decompose_constructed);
+    set1.emplace(three);
+    expected_constructed += construct_three;
+    EXPECT_EQ(expected_constructed, decompose_constructed);
+  }
+
+  {  // emplace_hint(...)
+    set1.emplace_hint(set1.begin(), 1);
+    EXPECT_EQ(expected_constructed, decompose_constructed);
+    set1.emplace_hint(set1.begin(), "3");
+    expected_constructed += construct_three;
+    EXPECT_EQ(expected_constructed, decompose_constructed);
+    set1.emplace_hint(set1.begin(), one);
+    EXPECT_EQ(expected_constructed, decompose_constructed);
+    set1.emplace_hint(set1.begin(), three);
+    expected_constructed += construct_three;
+    EXPECT_EQ(expected_constructed, decompose_constructed);
+  }
+}
+
+TEST(Table, Decompose) {
+  TestDecompose<DecomposeHash, DecomposeEq>(false);
+
+  struct TransparentHashIntOverload {
+    size_t operator()(DecomposeType a) const { return a.i; }
+    size_t operator()(int a) const { return a; }
+  };
+  struct TransparentEqIntOverload {
+    bool operator()(DecomposeType a, DecomposeType b) const {
+      return a.i == b.i;
+    }
+    bool operator()(DecomposeType a, int b) const { return a.i == b; }
+  };
+  TestDecompose<TransparentHashIntOverload, DecomposeEq>(true);
+  TestDecompose<TransparentHashIntOverload, TransparentEqIntOverload>(true);
+  TestDecompose<DecomposeHash, TransparentEqIntOverload>(true);
+}
+
+// Returns the largest m such that a table with m elements has the same number
+// of buckets as a table with n elements.
+size_t MaxDensitySize(size_t n) {
+  IntTable t;
+  t.reserve(n);
+  for (size_t i = 0; i != n; ++i) t.emplace(i);
+  const size_t c = t.bucket_count();
+  while (c == t.bucket_count()) t.emplace(n++);
+  return t.size() - 1;
+}
+
+struct Modulo1000Hash {
+  size_t operator()(int x) const { return x % 1000; }
+};
+
+struct Modulo1000HashTable
+    : public raw_hash_set<IntPolicy, Modulo1000Hash, std::equal_to<int>,
+                          std::allocator<int>> {};
+
+// Test that rehash with no resize happen in case of many deleted slots.
+TEST(Table, RehashWithNoResize) {
+  Modulo1000HashTable t;
+  // Adding the same length (and the same hash) strings
+  // to have at least kMinFullGroups groups
+  // with Group::kWidth collisions. Then fill up to MaxDensitySize;
+  const size_t kMinFullGroups = 7;
+  std::vector<int> keys;
+  for (size_t i = 0; i < MaxDensitySize(Group::kWidth * kMinFullGroups); ++i) {
+    int k = i * 1000;
+    t.emplace(k);
+    keys.push_back(k);
+  }
+  const size_t capacity = t.capacity();
+
+  // Remove elements from all groups except the first and the last one.
+  // All elements removed from full groups will be marked as kDeleted.
+  const size_t erase_begin = Group::kWidth / 2;
+  const size_t erase_end = (t.size() / Group::kWidth - 1) * Group::kWidth;
+  for (size_t i = erase_begin; i < erase_end; ++i) {
+    EXPECT_EQ(1, t.erase(keys[i])) << i;
+  }
+  keys.erase(keys.begin() + erase_begin, keys.begin() + erase_end);
+
+  auto last_key = keys.back();
+  size_t last_key_num_probes = GetHashtableDebugNumProbes(t, last_key);
+
+  // Make sure that we have to make a lot of probes for last key.
+  ASSERT_GT(last_key_num_probes, kMinFullGroups);
+
+  int x = 1;
+  // Insert and erase one element, before inplace rehash happen.
+  while (last_key_num_probes == GetHashtableDebugNumProbes(t, last_key)) {
+    t.emplace(x);
+    ASSERT_EQ(capacity, t.capacity());
+    // All elements should be there.
+    ASSERT_TRUE(t.find(x) != t.end()) << x;
+    for (const auto& k : keys) {
+      ASSERT_TRUE(t.find(k) != t.end()) << k;
+    }
+    t.erase(x);
+    ++x;
+  }
+}
+
+TEST(Table, InsertEraseStressTest) {
+  IntTable t;
+  const size_t kMinElementCount = 250;
+  std::deque<int> keys;
+  size_t i = 0;
+  for (; i < MaxDensitySize(kMinElementCount); ++i) {
+    t.emplace(i);
+    keys.push_back(i);
+  }
+  const size_t kNumIterations = 1000000;
+  for (; i < kNumIterations; ++i) {
+    ASSERT_EQ(1, t.erase(keys.front()));
+    keys.pop_front();
+    t.emplace(i);
+    keys.push_back(i);
+  }
+}
+
+TEST(Table, InsertOverloads) {
+  StringTable t;
+  // These should all trigger the insert(init_type) overload.
+  t.insert({{}, {}});
+  t.insert({"ABC", {}});
+  t.insert({"DEF", "!!!"});
+
+  EXPECT_THAT(t, UnorderedElementsAre(Pair("", ""), Pair("ABC", ""),
+                                      Pair("DEF", "!!!")));
+}
+
+TEST(Table, LargeTable) {
+  IntTable t;
+  for (int64_t i = 0; i != 100000; ++i) t.emplace(i << 40);
+  for (int64_t i = 0; i != 100000; ++i) ASSERT_EQ(i << 40, *t.find(i << 40));
+}
+
+// Timeout if copy is quadratic as it was in Rust.
+TEST(Table, EnsureNonQuadraticAsInRust) {
+  static const size_t kLargeSize = 1 << 15;
+
+  IntTable t;
+  for (size_t i = 0; i != kLargeSize; ++i) {
+    t.insert(i);
+  }
+
+  // If this is quadratic, the test will timeout.
+  IntTable t2;
+  for (const auto& entry : t) t2.insert(entry);
+}
+
+TEST(Table, ClearBug) {
+  IntTable t;
+  constexpr size_t capacity = container_internal::Group::kWidth - 1;
+  constexpr size_t max_size = capacity / 2 + 1;
+  for (size_t i = 0; i < max_size; ++i) {
+    t.insert(i);
+  }
+  ASSERT_EQ(capacity, t.capacity());
+  intptr_t original = reinterpret_cast<intptr_t>(&*t.find(2));
+  t.clear();
+  ASSERT_EQ(capacity, t.capacity());
+  for (size_t i = 0; i < max_size; ++i) {
+    t.insert(i);
+  }
+  ASSERT_EQ(capacity, t.capacity());
+  intptr_t second = reinterpret_cast<intptr_t>(&*t.find(2));
+  // We are checking that original and second are close enough to each other
+  // that they are probably still in the same group.  This is not strictly
+  // guaranteed.
+  EXPECT_LT(std::abs(original - second),
+            capacity * sizeof(IntTable::value_type));
+}
+
+TEST(Table, Erase) {
+  IntTable t;
+  EXPECT_TRUE(t.find(0) == t.end());
+  auto res = t.emplace(0);
+  EXPECT_TRUE(res.second);
+  EXPECT_EQ(1, t.size());
+  t.erase(res.first);
+  EXPECT_EQ(0, t.size());
+  EXPECT_TRUE(t.find(0) == t.end());
+}
+
+TEST(Table, EraseMaintainsValidIterator) {
+  IntTable t;
+  const int kNumElements = 100;
+  for (int i = 0; i < kNumElements; i ++) {
+    EXPECT_TRUE(t.emplace(i).second);
+  }
+  EXPECT_EQ(t.size(), kNumElements);
+
+  int num_erase_calls = 0;
+  auto it = t.begin();
+  while (it != t.end()) {
+    t.erase(it++);
+    num_erase_calls++;
+  }
+
+  EXPECT_TRUE(t.empty());
+  EXPECT_EQ(num_erase_calls, kNumElements);
+}
+
+// Collect N bad keys by following algorithm:
+// 1. Create an empty table and reserve it to 2 * N.
+// 2. Insert N random elements.
+// 3. Take first Group::kWidth - 1 to bad_keys array.
+// 4. Clear the table without resize.
+// 5. Go to point 2 while N keys not collected
+std::vector<int64_t> CollectBadMergeKeys(size_t N) {
+  static constexpr int kGroupSize = Group::kWidth - 1;
+
+  auto topk_range = [](size_t b, size_t e, IntTable* t) -> std::vector<int64_t> {
+    for (size_t i = b; i != e; ++i) {
+      t->emplace(i);
+    }
+    std::vector<int64_t> res;
+    res.reserve(kGroupSize);
+    auto it = t->begin();
+    for (size_t i = b; i != e && i != b + kGroupSize; ++i, ++it) {
+      res.push_back(*it);
+    }
+    return res;
+  };
+
+  std::vector<int64_t> bad_keys;
+  bad_keys.reserve(N);
+  IntTable t;
+  t.reserve(N * 2);
+
+  for (size_t b = 0; bad_keys.size() < N; b += N) {
+    auto keys = topk_range(b, b + N, &t);
+    bad_keys.insert(bad_keys.end(), keys.begin(), keys.end());
+    t.erase(t.begin(), t.end());
+    EXPECT_TRUE(t.empty());
+  }
+  return bad_keys;
+}
+
+struct ProbeStats {
+  // Number of elements with specific probe length over all tested tables.
+  std::vector<size_t> all_probes_histogram;
+  // Ratios total_probe_length/size for every tested table.
+  std::vector<double> single_table_ratios;
+
+  friend ProbeStats operator+(const ProbeStats& a, const ProbeStats& b) {
+    ProbeStats res = a;
+    res.all_probes_histogram.resize(std::max(res.all_probes_histogram.size(),
+                                             b.all_probes_histogram.size()));
+    std::transform(b.all_probes_histogram.begin(), b.all_probes_histogram.end(),
+                   res.all_probes_histogram.begin(),
+                   res.all_probes_histogram.begin(), std::plus<size_t>());
+    res.single_table_ratios.insert(res.single_table_ratios.end(),
+                                   b.single_table_ratios.begin(),
+                                   b.single_table_ratios.end());
+    return res;
+  }
+
+  // Average ratio total_probe_length/size over tables.
+  double AvgRatio() const {
+    return std::accumulate(single_table_ratios.begin(),
+                           single_table_ratios.end(), 0.0) /
+           single_table_ratios.size();
+  }
+
+  // Maximum ratio total_probe_length/size over tables.
+  double MaxRatio() const {
+    return *std::max_element(single_table_ratios.begin(),
+                             single_table_ratios.end());
+  }
+
+  // Percentile ratio total_probe_length/size over tables.
+  double PercentileRatio(double Percentile = 0.95) const {
+    auto r = single_table_ratios;
+    auto mid = r.begin() + static_cast<size_t>(r.size() * Percentile);
+    if (mid != r.end()) {
+      std::nth_element(r.begin(), mid, r.end());
+      return *mid;
+    } else {
+      return MaxRatio();
+    }
+  }
+
+  // Maximum probe length over all elements and all tables.
+  size_t MaxProbe() const { return all_probes_histogram.size(); }
+
+  // Fraction of elements with specified probe length.
+  std::vector<double> ProbeNormalizedHistogram() const {
+    double total_elements = std::accumulate(all_probes_histogram.begin(),
+                                            all_probes_histogram.end(), 0ull);
+    std::vector<double> res;
+    for (size_t p : all_probes_histogram) {
+      res.push_back(p / total_elements);
+    }
+    return res;
+  }
+
+  size_t PercentileProbe(double Percentile = 0.99) const {
+    size_t idx = 0;
+    for (double p : ProbeNormalizedHistogram()) {
+      if (Percentile > p) {
+        Percentile -= p;
+        ++idx;
+      } else {
+        return idx;
+      }
+    }
+    return idx;
+  }
+
+  friend std::ostream& operator<<(std::ostream& out, const ProbeStats& s) {
+    out << "{AvgRatio:" << s.AvgRatio() << ", MaxRatio:" << s.MaxRatio()
+        << ", PercentileRatio:" << s.PercentileRatio()
+        << ", MaxProbe:" << s.MaxProbe() << ", Probes=[";
+    for (double p : s.ProbeNormalizedHistogram()) {
+      out << p << ",";
+    }
+    out << "]}";
+
+    return out;
+  }
+};
+
+struct ExpectedStats {
+  double avg_ratio;
+  double max_ratio;
+  std::vector<std::pair<double, double>> pecentile_ratios;
+  std::vector<std::pair<double, double>> pecentile_probes;
+
+  friend std::ostream& operator<<(std::ostream& out, const ExpectedStats& s) {
+    out << "{AvgRatio:" << s.avg_ratio << ", MaxRatio:" << s.max_ratio
+        << ", PercentileRatios: [";
+    for (auto el : s.pecentile_ratios) {
+      out << el.first << ":" << el.second << ", ";
+    }
+    out << "], PercentileProbes: [";
+    for (auto el : s.pecentile_probes) {
+      out << el.first << ":" << el.second << ", ";
+    }
+    out << "]}";
+
+    return out;
+  }
+};
+
+void VerifyStats(size_t size, const ExpectedStats& exp,
+                 const ProbeStats& stats) {
+  EXPECT_LT(stats.AvgRatio(), exp.avg_ratio) << size << " " << stats;
+  EXPECT_LT(stats.MaxRatio(), exp.max_ratio) << size << " " << stats;
+  for (auto pr : exp.pecentile_ratios) {
+    EXPECT_LE(stats.PercentileRatio(pr.first), pr.second)
+        << size << " " << pr.first << " " << stats;
+  }
+
+  for (auto pr : exp.pecentile_probes) {
+    EXPECT_LE(stats.PercentileProbe(pr.first), pr.second)
+        << size << " " << pr.first << " " << stats;
+  }
+}
+
+using ProbeStatsPerSize = std::map<size_t, ProbeStats>;
+
+// Collect total ProbeStats on num_iters iterations of the following algorithm:
+// 1. Create new table and reserve it to keys.size() * 2
+// 2. Insert all keys xored with seed
+// 3. Collect ProbeStats from final table.
+ProbeStats CollectProbeStatsOnKeysXoredWithSeed(const std::vector<int64_t>& keys,
+                                                size_t num_iters) {
+  const size_t reserve_size = keys.size() * 2;
+
+  ProbeStats stats;
+
+  int64_t seed = 0x71b1a19b907d6e33;
+  while (num_iters--) {
+    seed = static_cast<int64_t>(static_cast<uint64_t>(seed) * 17 + 13);
+    IntTable t1;
+    t1.reserve(reserve_size);
+    for (const auto& key : keys) {
+      t1.emplace(key ^ seed);
+    }
+
+    auto probe_histogram = GetHashtableDebugNumProbesHistogram(t1);
+    stats.all_probes_histogram.resize(
+        std::max(stats.all_probes_histogram.size(), probe_histogram.size()));
+    std::transform(probe_histogram.begin(), probe_histogram.end(),
+                   stats.all_probes_histogram.begin(),
+                   stats.all_probes_histogram.begin(), std::plus<size_t>());
+
+    size_t total_probe_seq_length = 0;
+    for (size_t i = 0; i < probe_histogram.size(); ++i) {
+      total_probe_seq_length += i * probe_histogram[i];
+    }
+    stats.single_table_ratios.push_back(total_probe_seq_length * 1.0 /
+                                        keys.size());
+    t1.erase(t1.begin(), t1.end());
+  }
+  return stats;
+}
+
+ExpectedStats XorSeedExpectedStats() {
+  constexpr bool kRandomizesInserts =
+#ifdef NDEBUG
+      false;
+#else   // NDEBUG
+      true;
+#endif  // NDEBUG
+
+  // The effective load factor is larger in non-opt mode because we insert
+  // elements out of order.
+  switch (container_internal::Group::kWidth) {
+    case 8:
+      if (kRandomizesInserts) {
+  return {0.05,
+          1.0,
+          {{0.95, 0.5}},
+          {{0.95, 0}, {0.99, 2}, {0.999, 4}, {0.9999, 10}}};
+      } else {
+  return {0.05,
+          2.0,
+          {{0.95, 0.1}},
+          {{0.95, 0}, {0.99, 2}, {0.999, 4}, {0.9999, 10}}};
+      }
+    case 16:
+      if (kRandomizesInserts) {
+        return {0.1,
+                1.0,
+                {{0.95, 0.1}},
+                {{0.95, 0}, {0.99, 1}, {0.999, 8}, {0.9999, 15}}};
+      } else {
+        return {0.05,
+                1.0,
+                {{0.95, 0.05}},
+                {{0.95, 0}, {0.99, 1}, {0.999, 4}, {0.9999, 10}}};
+      }
+  }
+  ABSL_RAW_LOG(FATAL, "%s", "Unknown Group width");
+  return {};
+}
+
+TEST(Table, DISABLED_EnsureNonQuadraticTopNXorSeedByProbeSeqLength) {
+  ProbeStatsPerSize stats;
+  std::vector<size_t> sizes = {Group::kWidth << 5, Group::kWidth << 10};
+  for (size_t size : sizes) {
+    stats[size] =
+        CollectProbeStatsOnKeysXoredWithSeed(CollectBadMergeKeys(size), 200);
+  }
+  auto expected = XorSeedExpectedStats();
+  for (size_t size : sizes) {
+    auto& stat = stats[size];
+    VerifyStats(size, expected, stat);
+  }
+}
+
+// Collect total ProbeStats on num_iters iterations of the following algorithm:
+// 1. Create new table
+// 2. Select 10% of keys and insert 10 elements key * 17 + j * 13
+// 3. Collect ProbeStats from final table
+ProbeStats CollectProbeStatsOnLinearlyTransformedKeys(
+    const std::vector<int64_t>& keys, size_t num_iters) {
+  ProbeStats stats;
+
+  std::random_device rd;
+  std::mt19937 rng(rd());
+  auto linear_transform = [](size_t x, size_t y) { return x * 17 + y * 13; };
+  std::uniform_int_distribution<size_t> dist(0, keys.size()-1);
+  while (num_iters--) {
+    IntTable t1;
+    size_t num_keys = keys.size() / 10;
+    size_t start = dist(rng);
+    for (size_t i = 0; i != num_keys; ++i) {
+      for (size_t j = 0; j != 10; ++j) {
+        t1.emplace(linear_transform(keys[(i + start) % keys.size()], j));
+      }
+    }
+
+    auto probe_histogram = GetHashtableDebugNumProbesHistogram(t1);
+    stats.all_probes_histogram.resize(
+        std::max(stats.all_probes_histogram.size(), probe_histogram.size()));
+    std::transform(probe_histogram.begin(), probe_histogram.end(),
+                   stats.all_probes_histogram.begin(),
+                   stats.all_probes_histogram.begin(), std::plus<size_t>());
+
+    size_t total_probe_seq_length = 0;
+    for (size_t i = 0; i < probe_histogram.size(); ++i) {
+      total_probe_seq_length += i * probe_histogram[i];
+    }
+    stats.single_table_ratios.push_back(total_probe_seq_length * 1.0 /
+                                        t1.size());
+    t1.erase(t1.begin(), t1.end());
+  }
+  return stats;
+}
+
+ExpectedStats LinearTransformExpectedStats() {
+  constexpr bool kRandomizesInserts =
+#ifdef NDEBUG
+      false;
+#else   // NDEBUG
+      true;
+#endif  // NDEBUG
+
+  // The effective load factor is larger in non-opt mode because we insert
+  // elements out of order.
+  switch (container_internal::Group::kWidth) {
+    case 8:
+      if (kRandomizesInserts) {
+        return {0.1,
+                0.5,
+                {{0.95, 0.3}},
+                {{0.95, 0}, {0.99, 1}, {0.999, 8}, {0.9999, 15}}};
+      } else {
+        return {0.15,
+                0.5,
+                {{0.95, 0.3}},
+                {{0.95, 0}, {0.99, 3}, {0.999, 15}, {0.9999, 25}}};
+      }
+    case 16:
+      if (kRandomizesInserts) {
+        return {0.1,
+                0.4,
+                {{0.95, 0.3}},
+                {{0.95, 0}, {0.99, 1}, {0.999, 8}, {0.9999, 15}}};
+      } else {
+        return {0.05,
+                0.2,
+                {{0.95, 0.1}},
+                {{0.95, 0}, {0.99, 1}, {0.999, 6}, {0.9999, 10}}};
+      }
+  }
+  ABSL_RAW_LOG(FATAL, "%s", "Unknown Group width");
+  return {};
+}
+
+TEST(Table, DISABLED_EnsureNonQuadraticTopNLinearTransformByProbeSeqLength) {
+  ProbeStatsPerSize stats;
+  std::vector<size_t> sizes = {Group::kWidth << 5, Group::kWidth << 10};
+  for (size_t size : sizes) {
+    stats[size] = CollectProbeStatsOnLinearlyTransformedKeys(
+        CollectBadMergeKeys(size), 300);
+  }
+  auto expected = LinearTransformExpectedStats();
+  for (size_t size : sizes) {
+    auto& stat = stats[size];
+    VerifyStats(size, expected, stat);
+  }
+}
+
+TEST(Table, EraseCollision) {
+  BadTable t;
+
+  // 1 2 3
+  t.emplace(1);
+  t.emplace(2);
+  t.emplace(3);
+  EXPECT_THAT(*t.find(1), 1);
+  EXPECT_THAT(*t.find(2), 2);
+  EXPECT_THAT(*t.find(3), 3);
+  EXPECT_EQ(3, t.size());
+
+  // 1 DELETED 3
+  t.erase(t.find(2));
+  EXPECT_THAT(*t.find(1), 1);
+  EXPECT_TRUE(t.find(2) == t.end());
+  EXPECT_THAT(*t.find(3), 3);
+  EXPECT_EQ(2, t.size());
+
+  // DELETED DELETED 3
+  t.erase(t.find(1));
+  EXPECT_TRUE(t.find(1) == t.end());
+  EXPECT_TRUE(t.find(2) == t.end());
+  EXPECT_THAT(*t.find(3), 3);
+  EXPECT_EQ(1, t.size());
+
+  // DELETED DELETED DELETED
+  t.erase(t.find(3));
+  EXPECT_TRUE(t.find(1) == t.end());
+  EXPECT_TRUE(t.find(2) == t.end());
+  EXPECT_TRUE(t.find(3) == t.end());
+  EXPECT_EQ(0, t.size());
+}
+
+TEST(Table, EraseInsertProbing) {
+  BadTable t(100);
+
+  // 1 2 3 4
+  t.emplace(1);
+  t.emplace(2);
+  t.emplace(3);
+  t.emplace(4);
+
+  // 1 DELETED 3 DELETED
+  t.erase(t.find(2));
+  t.erase(t.find(4));
+
+  // 1 10 3 11 12
+  t.emplace(10);
+  t.emplace(11);
+  t.emplace(12);
+
+  EXPECT_EQ(5, t.size());
+  EXPECT_THAT(t, UnorderedElementsAre(1, 10, 3, 11, 12));
+}
+
+TEST(Table, Clear) {
+  IntTable t;
+  EXPECT_TRUE(t.find(0) == t.end());
+  t.clear();
+  EXPECT_TRUE(t.find(0) == t.end());
+  auto res = t.emplace(0);
+  EXPECT_TRUE(res.second);
+  EXPECT_EQ(1, t.size());
+  t.clear();
+  EXPECT_EQ(0, t.size());
+  EXPECT_TRUE(t.find(0) == t.end());
+}
+
+TEST(Table, Swap) {
+  IntTable t;
+  EXPECT_TRUE(t.find(0) == t.end());
+  auto res = t.emplace(0);
+  EXPECT_TRUE(res.second);
+  EXPECT_EQ(1, t.size());
+  IntTable u;
+  t.swap(u);
+  EXPECT_EQ(0, t.size());
+  EXPECT_EQ(1, u.size());
+  EXPECT_TRUE(t.find(0) == t.end());
+  EXPECT_THAT(*u.find(0), 0);
+}
+
+TEST(Table, Rehash) {
+  IntTable t;
+  EXPECT_TRUE(t.find(0) == t.end());
+  t.emplace(0);
+  t.emplace(1);
+  EXPECT_EQ(2, t.size());
+  t.rehash(128);
+  EXPECT_EQ(2, t.size());
+  EXPECT_THAT(*t.find(0), 0);
+  EXPECT_THAT(*t.find(1), 1);
+}
+
+TEST(Table, RehashDoesNotRehashWhenNotNecessary) {
+  IntTable t;
+  t.emplace(0);
+  t.emplace(1);
+  auto* p = &*t.find(0);
+  t.rehash(1);
+  EXPECT_EQ(p, &*t.find(0));
+}
+
+TEST(Table, RehashZeroDoesNotAllocateOnEmptyTable) {
+  IntTable t;
+  t.rehash(0);
+  EXPECT_EQ(0, t.bucket_count());
+}
+
+TEST(Table, RehashZeroDeallocatesEmptyTable) {
+  IntTable t;
+  t.emplace(0);
+  t.clear();
+  EXPECT_NE(0, t.bucket_count());
+  t.rehash(0);
+  EXPECT_EQ(0, t.bucket_count());
+}
+
+TEST(Table, RehashZeroForcesRehash) {
+  IntTable t;
+  t.emplace(0);
+  t.emplace(1);
+  auto* p = &*t.find(0);
+  t.rehash(0);
+  EXPECT_NE(p, &*t.find(0));
+}
+
+TEST(Table, ConstructFromInitList) {
+  using P = std::pair<std::string, std::string>;
+  struct Q {
+    operator P() const { return {}; }
+  };
+  StringTable t = {P(), Q(), {}, {{}, {}}};
+}
+
+TEST(Table, CopyConstruct) {
+  IntTable t;
+  t.emplace(0);
+  EXPECT_EQ(1, t.size());
+  {
+    IntTable u(t);
+    EXPECT_EQ(1, u.size());
+    EXPECT_THAT(*u.find(0), 0);
+  }
+  {
+    IntTable u{t};
+    EXPECT_EQ(1, u.size());
+    EXPECT_THAT(*u.find(0), 0);
+  }
+  {
+    IntTable u = t;
+    EXPECT_EQ(1, u.size());
+    EXPECT_THAT(*u.find(0), 0);
+  }
+}
+
+TEST(Table, CopyConstructWithAlloc) {
+  StringTable t;
+  t.emplace("a", "b");
+  EXPECT_EQ(1, t.size());
+  StringTable u(t, Alloc<std::pair<std::string, std::string>>());
+  EXPECT_EQ(1, u.size());
+  EXPECT_THAT(*u.find("a"), Pair("a", "b"));
+}
+
+struct ExplicitAllocIntTable
+    : raw_hash_set<IntPolicy, container_internal::hash_default_hash<int64_t>,
+                   std::equal_to<int64_t>, Alloc<int64_t>> {
+  ExplicitAllocIntTable() {}
+};
+
+TEST(Table, AllocWithExplicitCtor) {
+  ExplicitAllocIntTable t;
+  EXPECT_EQ(0, t.size());
+}
+
+TEST(Table, MoveConstruct) {
+  {
+    StringTable t;
+    t.emplace("a", "b");
+    EXPECT_EQ(1, t.size());
+
+    StringTable u(std::move(t));
+    EXPECT_EQ(1, u.size());
+    EXPECT_THAT(*u.find("a"), Pair("a", "b"));
+  }
+  {
+    StringTable t;
+    t.emplace("a", "b");
+    EXPECT_EQ(1, t.size());
+
+    StringTable u{std::move(t)};
+    EXPECT_EQ(1, u.size());
+    EXPECT_THAT(*u.find("a"), Pair("a", "b"));
+  }
+  {
+    StringTable t;
+    t.emplace("a", "b");
+    EXPECT_EQ(1, t.size());
+
+    StringTable u = std::move(t);
+    EXPECT_EQ(1, u.size());
+    EXPECT_THAT(*u.find("a"), Pair("a", "b"));
+  }
+}
+
+TEST(Table, MoveConstructWithAlloc) {
+  StringTable t;
+  t.emplace("a", "b");
+  EXPECT_EQ(1, t.size());
+  StringTable u(std::move(t), Alloc<std::pair<std::string, std::string>>());
+  EXPECT_EQ(1, u.size());
+  EXPECT_THAT(*u.find("a"), Pair("a", "b"));
+}
+
+TEST(Table, CopyAssign) {
+  StringTable t;
+  t.emplace("a", "b");
+  EXPECT_EQ(1, t.size());
+  StringTable u;
+  u = t;
+  EXPECT_EQ(1, u.size());
+  EXPECT_THAT(*u.find("a"), Pair("a", "b"));
+}
+
+TEST(Table, CopySelfAssign) {
+  StringTable t;
+  t.emplace("a", "b");
+  EXPECT_EQ(1, t.size());
+  t = *&t;
+  EXPECT_EQ(1, t.size());
+  EXPECT_THAT(*t.find("a"), Pair("a", "b"));
+}
+
+TEST(Table, MoveAssign) {
+  StringTable t;
+  t.emplace("a", "b");
+  EXPECT_EQ(1, t.size());
+  StringTable u;
+  u = std::move(t);
+  EXPECT_EQ(1, u.size());
+  EXPECT_THAT(*u.find("a"), Pair("a", "b"));
+}
+
+TEST(Table, Equality) {
+  StringTable t;
+  std::vector<std::pair<std::string, std::string>> v = {{"a", "b"},
+                                                        {"aa", "bb"}};
+  t.insert(std::begin(v), std::end(v));
+  StringTable u = t;
+  EXPECT_EQ(u, t);
+}
+
+TEST(Table, Equality2) {
+  StringTable t;
+  std::vector<std::pair<std::string, std::string>> v1 = {{"a", "b"},
+                                                         {"aa", "bb"}};
+  t.insert(std::begin(v1), std::end(v1));
+  StringTable u;
+  std::vector<std::pair<std::string, std::string>> v2 = {{"a", "a"},
+                                                         {"aa", "aa"}};
+  u.insert(std::begin(v2), std::end(v2));
+  EXPECT_NE(u, t);
+}
+
+TEST(Table, Equality3) {
+  StringTable t;
+  std::vector<std::pair<std::string, std::string>> v1 = {{"b", "b"},
+                                                         {"bb", "bb"}};
+  t.insert(std::begin(v1), std::end(v1));
+  StringTable u;
+  std::vector<std::pair<std::string, std::string>> v2 = {{"a", "a"},
+                                                         {"aa", "aa"}};
+  u.insert(std::begin(v2), std::end(v2));
+  EXPECT_NE(u, t);
+}
+
+TEST(Table, NumDeletedRegression) {
+  IntTable t;
+  t.emplace(0);
+  t.erase(t.find(0));
+  // construct over a deleted slot.
+  t.emplace(0);
+  t.clear();
+}
+
+TEST(Table, FindFullDeletedRegression) {
+  IntTable t;
+  for (int i = 0; i < 1000; ++i) {
+    t.emplace(i);
+    t.erase(t.find(i));
+  }
+  EXPECT_EQ(0, t.size());
+}
+
+TEST(Table, ReplacingDeletedSlotDoesNotRehash) {
+  size_t n;
+  {
+    // Compute n such that n is the maximum number of elements before rehash.
+    IntTable t;
+    t.emplace(0);
+    size_t c = t.bucket_count();
+    for (n = 1; c == t.bucket_count(); ++n) t.emplace(n);
+    --n;
+  }
+  IntTable t;
+  t.rehash(n);
+  const size_t c = t.bucket_count();
+  for (size_t i = 0; i != n; ++i) t.emplace(i);
+  EXPECT_EQ(c, t.bucket_count()) << "rehashing threshold = " << n;
+  t.erase(0);
+  t.emplace(0);
+  EXPECT_EQ(c, t.bucket_count()) << "rehashing threshold = " << n;
+}
+
+TEST(Table, NoThrowMoveConstruct) {
+  ASSERT_TRUE(
+      std::is_nothrow_copy_constructible<absl::Hash<absl::string_view>>::value);
+  ASSERT_TRUE(std::is_nothrow_copy_constructible<
+              std::equal_to<absl::string_view>>::value);
+  ASSERT_TRUE(std::is_nothrow_copy_constructible<std::allocator<int>>::value);
+  EXPECT_TRUE(std::is_nothrow_move_constructible<StringTable>::value);
+}
+
+TEST(Table, NoThrowMoveAssign) {
+  ASSERT_TRUE(
+      std::is_nothrow_move_assignable<absl::Hash<absl::string_view>>::value);
+  ASSERT_TRUE(
+      std::is_nothrow_move_assignable<std::equal_to<absl::string_view>>::value);
+  ASSERT_TRUE(std::is_nothrow_move_assignable<std::allocator<int>>::value);
+  ASSERT_TRUE(
+      absl::allocator_traits<std::allocator<int>>::is_always_equal::value);
+  EXPECT_TRUE(std::is_nothrow_move_assignable<StringTable>::value);
+}
+
+TEST(Table, NoThrowSwappable) {
+  ASSERT_TRUE(
+      container_internal::IsNoThrowSwappable<absl::Hash<absl::string_view>>());
+  ASSERT_TRUE(container_internal::IsNoThrowSwappable<
+              std::equal_to<absl::string_view>>());
+  ASSERT_TRUE(container_internal::IsNoThrowSwappable<std::allocator<int>>());
+  EXPECT_TRUE(container_internal::IsNoThrowSwappable<StringTable>());
+}
+
+TEST(Table, HeterogeneousLookup) {
+  struct Hash {
+    size_t operator()(int64_t i) const { return i; }
+    size_t operator()(double i) const {
+      ADD_FAILURE();
+      return i;
+    }
+  };
+  struct Eq {
+    bool operator()(int64_t a, int64_t b) const { return a == b; }
+    bool operator()(double a, int64_t b) const {
+      ADD_FAILURE();
+      return a == b;
+    }
+    bool operator()(int64_t a, double b) const {
+      ADD_FAILURE();
+      return a == b;
+    }
+    bool operator()(double a, double b) const {
+      ADD_FAILURE();
+      return a == b;
+    }
+  };
+
+  struct THash {
+    using is_transparent = void;
+    size_t operator()(int64_t i) const { return i; }
+    size_t operator()(double i) const { return i; }
+  };
+  struct TEq {
+    using is_transparent = void;
+    bool operator()(int64_t a, int64_t b) const { return a == b; }
+    bool operator()(double a, int64_t b) const { return a == b; }
+    bool operator()(int64_t a, double b) const { return a == b; }
+    bool operator()(double a, double b) const { return a == b; }
+  };
+
+  raw_hash_set<IntPolicy, Hash, Eq, Alloc<int64_t>> s{0, 1, 2};
+  // It will convert to int64_t before the query.
+  EXPECT_EQ(1, *s.find(double{1.1}));
+
+  raw_hash_set<IntPolicy, THash, TEq, Alloc<int64_t>> ts{0, 1, 2};
+  // It will try to use the double, and fail to find the object.
+  EXPECT_TRUE(ts.find(1.1) == ts.end());
+}
+
+template <class Table>
+using CallFind = decltype(std::declval<Table&>().find(17));
+
+template <class Table>
+using CallErase = decltype(std::declval<Table&>().erase(17));
+
+template <class Table>
+using CallExtract = decltype(std::declval<Table&>().extract(17));
+
+template <class Table>
+using CallPrefetch = decltype(std::declval<Table&>().prefetch(17));
+
+template <class Table>
+using CallCount = decltype(std::declval<Table&>().count(17));
+
+template <template <typename> class C, class Table, class = void>
+struct VerifyResultOf : std::false_type {};
+
+template <template <typename> class C, class Table>
+struct VerifyResultOf<C, Table, absl::void_t<C<Table>>> : std::true_type {};
+
+TEST(Table, HeterogeneousLookupOverloads) {
+  using NonTransparentTable =
+      raw_hash_set<StringPolicy, absl::Hash<absl::string_view>,
+                   std::equal_to<absl::string_view>, std::allocator<int>>;
+
+  EXPECT_FALSE((VerifyResultOf<CallFind, NonTransparentTable>()));
+  EXPECT_FALSE((VerifyResultOf<CallErase, NonTransparentTable>()));
+  EXPECT_FALSE((VerifyResultOf<CallExtract, NonTransparentTable>()));
+  EXPECT_FALSE((VerifyResultOf<CallPrefetch, NonTransparentTable>()));
+  EXPECT_FALSE((VerifyResultOf<CallCount, NonTransparentTable>()));
+
+  using TransparentTable = raw_hash_set<
+      StringPolicy,
+      absl::container_internal::hash_default_hash<absl::string_view>,
+      absl::container_internal::hash_default_eq<absl::string_view>,
+      std::allocator<int>>;
+
+  EXPECT_TRUE((VerifyResultOf<CallFind, TransparentTable>()));
+  EXPECT_TRUE((VerifyResultOf<CallErase, TransparentTable>()));
+  EXPECT_TRUE((VerifyResultOf<CallExtract, TransparentTable>()));
+  EXPECT_TRUE((VerifyResultOf<CallPrefetch, TransparentTable>()));
+  EXPECT_TRUE((VerifyResultOf<CallCount, TransparentTable>()));
+}
+
+// TODO(alkis): Expand iterator tests.
+TEST(Iterator, IsDefaultConstructible) {
+  StringTable::iterator i;
+  EXPECT_TRUE(i == StringTable::iterator());
+}
+
+TEST(ConstIterator, IsDefaultConstructible) {
+  StringTable::const_iterator i;
+  EXPECT_TRUE(i == StringTable::const_iterator());
+}
+
+TEST(Iterator, ConvertsToConstIterator) {
+  StringTable::iterator i;
+  EXPECT_TRUE(i == StringTable::const_iterator());
+}
+
+TEST(Iterator, Iterates) {
+  IntTable t;
+  for (size_t i = 3; i != 6; ++i) EXPECT_TRUE(t.emplace(i).second);
+  EXPECT_THAT(t, UnorderedElementsAre(3, 4, 5));
+}
+
+TEST(Table, Merge) {
+  StringTable t1, t2;
+  t1.emplace("0", "-0");
+  t1.emplace("1", "-1");
+  t2.emplace("0", "~0");
+  t2.emplace("2", "~2");
+
+  EXPECT_THAT(t1, UnorderedElementsAre(Pair("0", "-0"), Pair("1", "-1")));
+  EXPECT_THAT(t2, UnorderedElementsAre(Pair("0", "~0"), Pair("2", "~2")));
+
+  t1.merge(t2);
+  EXPECT_THAT(t1, UnorderedElementsAre(Pair("0", "-0"), Pair("1", "-1"),
+                                       Pair("2", "~2")));
+  EXPECT_THAT(t2, UnorderedElementsAre(Pair("0", "~0")));
+}
+
+TEST(Nodes, EmptyNodeType) {
+  using node_type = StringTable::node_type;
+  node_type n;
+  EXPECT_FALSE(n);
+  EXPECT_TRUE(n.empty());
+
+  EXPECT_TRUE((std::is_same<node_type::allocator_type,
+                            StringTable::allocator_type>::value));
+}
+
+TEST(Nodes, ExtractInsert) {
+  constexpr char k0[] = "Very long string zero.";
+  constexpr char k1[] = "Very long string one.";
+  constexpr char k2[] = "Very long string two.";
+  StringTable t = {{k0, ""}, {k1, ""}, {k2, ""}};
+  EXPECT_THAT(t,
+              UnorderedElementsAre(Pair(k0, ""), Pair(k1, ""), Pair(k2, "")));
+
+  auto node = t.extract(k0);
+  EXPECT_THAT(t, UnorderedElementsAre(Pair(k1, ""), Pair(k2, "")));
+  EXPECT_TRUE(node);
+  EXPECT_FALSE(node.empty());
+
+  StringTable t2;
+  StringTable::insert_return_type res = t2.insert(std::move(node));
+  EXPECT_TRUE(res.inserted);
+  EXPECT_THAT(*res.position, Pair(k0, ""));
+  EXPECT_FALSE(res.node);
+  EXPECT_THAT(t2, UnorderedElementsAre(Pair(k0, "")));
+
+  // Not there.
+  EXPECT_THAT(t, UnorderedElementsAre(Pair(k1, ""), Pair(k2, "")));
+  node = t.extract("Not there!");
+  EXPECT_THAT(t, UnorderedElementsAre(Pair(k1, ""), Pair(k2, "")));
+  EXPECT_FALSE(node);
+
+  // Inserting nothing.
+  res = t2.insert(std::move(node));
+  EXPECT_FALSE(res.inserted);
+  EXPECT_EQ(res.position, t2.end());
+  EXPECT_FALSE(res.node);
+  EXPECT_THAT(t2, UnorderedElementsAre(Pair(k0, "")));
+
+  t.emplace(k0, "1");
+  node = t.extract(k0);
+
+  // Insert duplicate.
+  res = t2.insert(std::move(node));
+  EXPECT_FALSE(res.inserted);
+  EXPECT_THAT(*res.position, Pair(k0, ""));
+  EXPECT_TRUE(res.node);
+  EXPECT_FALSE(node);
+}
+
+IntTable MakeSimpleTable(size_t size) {
+  IntTable t;
+  while (t.size() < size) t.insert(t.size());
+  return t;
+}
+
+std::vector<int> OrderOfIteration(const IntTable& t) {
+  return {t.begin(), t.end()};
+}
+
+// These IterationOrderChanges tests depend on non-deterministic behavior.
+// We are injecting non-determinism from the pointer of the table, but do so in
+// a way that only the page matters. We have to retry enough times to make sure
+// we are touching different memory pages to cause the ordering to change.
+// We also need to keep the old tables around to avoid getting the same memory
+// blocks over and over.
+TEST(Table, IterationOrderChangesByInstance) {
+  for (size_t size : {2, 6, 12, 20}) {
+    const auto reference_table = MakeSimpleTable(size);
+    const auto reference = OrderOfIteration(reference_table);
+
+    std::vector<IntTable> tables;
+    bool found_difference = false;
+    for (int i = 0; !found_difference && i < 5000; ++i) {
+      tables.push_back(MakeSimpleTable(size));
+      found_difference = OrderOfIteration(tables.back()) != reference;
+    }
+    if (!found_difference) {
+      FAIL()
+          << "Iteration order remained the same across many attempts with size "
+          << size;
+    }
+  }
+}
+
+TEST(Table, IterationOrderChangesOnRehash) {
+  std::vector<IntTable> garbage;
+  for (int i = 0; i < 5000; ++i) {
+    auto t = MakeSimpleTable(20);
+    const auto reference = OrderOfIteration(t);
+    // Force rehash to the same size.
+    t.rehash(0);
+    auto trial = OrderOfIteration(t);
+    if (trial != reference) {
+      // We are done.
+      return;
+    }
+    garbage.push_back(std::move(t));
+  }
+  FAIL() << "Iteration order remained the same across many attempts.";
+}
+
+// Verify that pointers are invalidated as soon as a second element is inserted.
+// This prevents dependency on pointer stability on small tables.
+TEST(Table, UnstablePointers) {
+  IntTable table;
+
+  const auto addr = [&](int i) {
+    return reinterpret_cast<uintptr_t>(&*table.find(i));
+  };
+
+  table.insert(0);
+  const uintptr_t old_ptr = addr(0);
+
+  // This causes a rehash.
+  table.insert(1);
+
+  EXPECT_NE(old_ptr, addr(0));
+}
+
+// Confirm that we assert if we try to erase() end().
+TEST(TableDeathTest, EraseOfEndAsserts) {
+  // Use an assert with side-effects to figure out if they are actually enabled.
+  bool assert_enabled = false;
+  assert([&]() {
+    assert_enabled = true;
+    return true;
+  }());
+  if (!assert_enabled) return;
+
+  IntTable t;
+  // Extra simple "regexp" as regexp support is highly varied across platforms.
+  constexpr char kDeathMsg[] = "IsFull";
+  EXPECT_DEATH_IF_SUPPORTED(t.erase(t.end()), kDeathMsg);
+}
+
+#if defined(ABSL_HASHTABLEZ_SAMPLE)
+TEST(RawHashSamplerTest, Sample) {
+  // Enable the feature even if the prod default is off.
+  SetHashtablezEnabled(true);
+  SetHashtablezSampleParameter(100);
+
+  auto& sampler = HashtablezSampler::Global();
+  size_t start_size = 0;
+  start_size += sampler.Iterate([&](const HashtablezInfo&) { ++start_size; });
+
+  std::vector<IntTable> tables;
+  for (int i = 0; i < 1000000; ++i) {
+    tables.emplace_back();
+    tables.back().insert(1);
+  }
+  size_t end_size = 0;
+  end_size += sampler.Iterate([&](const HashtablezInfo&) { ++end_size; });
+
+  EXPECT_NEAR((end_size - start_size) / static_cast<double>(tables.size()),
+              0.01, 0.005);
+}
+#endif  // ABSL_HASHTABLEZ_SAMPLER
+
+TEST(RawHashSamplerTest, DoNotSampleCustomAllocators) {
+  // Enable the feature even if the prod default is off.
+  SetHashtablezEnabled(true);
+  SetHashtablezSampleParameter(100);
+
+  auto& sampler = HashtablezSampler::Global();
+  size_t start_size = 0;
+  start_size += sampler.Iterate([&](const HashtablezInfo&) { ++start_size; });
+
+  std::vector<CustomAllocIntTable> tables;
+  for (int i = 0; i < 1000000; ++i) {
+    tables.emplace_back();
+    tables.back().insert(1);
+  }
+  size_t end_size = 0;
+  end_size += sampler.Iterate([&](const HashtablezInfo&) { ++end_size; });
+
+  EXPECT_NEAR((end_size - start_size) / static_cast<double>(tables.size()),
+              0.00, 0.001);
+}
+
+#ifdef ADDRESS_SANITIZER
+TEST(Sanitizer, PoisoningUnused) {
+  IntTable t;
+  t.reserve(5);
+  // Insert something to force an allocation.
+  int64_t& v1 = *t.insert(0).first;
+
+  // Make sure there is something to test.
+  ASSERT_GT(t.capacity(), 1);
+
+  int64_t* slots = RawHashSetTestOnlyAccess::GetSlots(t);
+  for (size_t i = 0; i < t.capacity(); ++i) {
+    EXPECT_EQ(slots + i != &v1, __asan_address_is_poisoned(slots + i));
+  }
+}
+
+TEST(Sanitizer, PoisoningOnErase) {
+  IntTable t;
+  int64_t& v = *t.insert(0).first;
+
+  EXPECT_FALSE(__asan_address_is_poisoned(&v));
+  t.erase(0);
+  EXPECT_TRUE(__asan_address_is_poisoned(&v));
+}
+#endif  // ADDRESS_SANITIZER
+
+}  // namespace
+}  // namespace container_internal
+ABSL_NAMESPACE_END
+}  // namespace absl