folly/folly/container/test/small_vector_test.cpp

/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * 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
 *
 *     http://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 <folly/small_vector.h>

#include <iostream>
#include <iterator>
#include <limits>
#include <memory>
#include <numeric>
#include <sstream>
#include <string>
#include <unordered_set>
#include <vector>

#include <boost/algorithm/string.hpp>
#include <fmt/format.h>

#include <folly/Conv.h>
#include <folly/Traits.h>
#include <folly/container/Iterator.h>
#include <folly/portability/GTest.h>
#include <folly/sorted_vector_types.h>

using folly::small_vector;

using folly::small_vector_policy::policy_in_situ_only;
using folly::small_vector_policy::policy_size_type;

#if FOLLY_X64 || FOLLY_PPC64 || FOLLY_AARCH64 || FOLLY_RISCV64

template <typename...>
struct same_;
template <typename T>
struct same_<T, T> {};

auto s0 = same_<
    folly::small_vector_policy::
        merge<policy_size_type<uint32_t>, policy_size_type<uint8_t>>::size_type,
    uint8_t>{};

// Fast access. Heap only small vector
static_assert(
    sizeof(small_vector<int, 0>) == 16,
    "Object size is not what we expect for small_vector<int, 0>");

static_assert(
    sizeof(small_vector<int, 0, policy_size_type<uint32_t>>) ==
        4 /* size_ */ + 8,
    "Object size is not what we expect for small_vector<int, 0, uint32_t>");

static_assert(
    sizeof(small_vector<double, 0, policy_size_type<uint8_t>>) == 9,
    "Object size is not what we expect for small_vector<double, 0, uint32_t>");

static_assert(
    sizeof(small_vector<
           std::pair<int64_t, int64_t>,
           0,
           policy_size_type<uint32_t>>) == 12,
    "Object size is not what we expect for small_vector<pair<int64_t, int64_t>, 0, uint32_t>");

static_assert(
    sizeof(small_vector<int>) == 16,
    "Object size is not what we expect for small_vector<int>");
static_assert(
    sizeof(small_vector<int32_t, 2>) == 16,
    "Object size is not what we expect for "
    "small_vector<int32_t,2>");
static_assert(
    sizeof(small_vector<int, 10>) == 10 * sizeof(int) + sizeof(std::size_t),
    "Object size is not what we expect for small_vector<int,10>");

static_assert(
    sizeof(small_vector<int32_t, 1, policy_size_type<uint32_t>>) == 8 + 4,
    "small_vector<int32_t,1,uint32_t> is wrong size");

// Extra 2 bytes needed for alignment.
static_assert(
    sizeof(small_vector<int32_t, 1, policy_size_type<uint16_t>>) == 8 + 2 + 2,
    "small_vector<int32_t,1,uint16_t> is wrong size");
static_assert(
    alignof(small_vector<int32_t, 1, policy_size_type<uint16_t>>) >= 4,
    "small_vector not aligned correctly");

// Extra 3 bytes needed for alignment.
static_assert(
    sizeof(small_vector<int32_t, 1, policy_size_type<uint8_t>>) == 8 + 1 + 3,
    "small_vector<int32_t,1,uint8_t> is wrong size");
static_assert(
    alignof(small_vector<int32_t, 1, policy_size_type<uint8_t>>) >= 4,
    "small_vector not aligned correctly");

static_assert(
    sizeof(small_vector<int16_t, 4, policy_size_type<uint16_t>>) == 10,
    "Sizeof unexpectedly large");

#endif

static_assert(
    !std::is_trivially_copyable<std::unique_ptr<int>>::value,
    "std::unique_ptr<> is trivially copyable");

static_assert(
    alignof(small_vector<std::aligned_storage<32, 32>::type, 4>) == 32,
    "small_vector not aligned correctly");

namespace {

template <typename Key, typename Value, size_t N>
using small_sorted_vector_map = folly::sorted_vector_map<
    Key,
    Value,
    std::less<Key>,
    std::allocator<std::pair<Key, Value>>,
    void,
    folly::small_vector<std::pair<Key, Value>, N>>;

template <typename Key, typename Value, size_t N>
using noheap_sorted_vector_map = folly::sorted_vector_map<
    Key,
    Value,
    std::less<Key>,
    std::allocator<std::pair<Key, Value>>,
    void,
    folly::small_vector<std::pair<Key, Value>, N, policy_in_situ_only<true>>>;

template <typename T, size_t N>
using small_sorted_vector_set = folly::sorted_vector_set<
    T,
    std::less<T>,
    std::allocator<T>,
    void,
    folly::small_vector<T, N>>;

template <typename T, size_t N>
using noheap_sorted_vector_set = folly::sorted_vector_set<
    T,
    std::less<T>,
    std::allocator<T>,
    void,
    folly::small_vector<T, N, policy_in_situ_only<true>>>;

struct NontrivialType {
  static int ctored;
  explicit NontrivialType() : a(0) {}

  /* implicit */ NontrivialType(int a_) : a(a_) { ++ctored; }

  NontrivialType(NontrivialType const& /* s */) { ++ctored; }

  NontrivialType& operator=(NontrivialType const& o) {
    a = o.a;
    return *this;
  }

  int32_t a;
};
static_assert(
    !std::is_trivially_copyable<NontrivialType>::value,
    "NontrivialType is trivially copyable");

int NontrivialType::ctored = 0;

struct TestException {};

int throwCounter = 1;
void MaybeThrow() {
  if (!--throwCounter) {
    throw TestException();
  }
}

const int kMagic = 0xdeadbeef;
struct Thrower {
  static int alive;

  Thrower() : magic(kMagic) {
    EXPECT_EQ(magic, kMagic);
    MaybeThrow();
    ++alive;
  }
  Thrower(Thrower const& other) : magic(other.magic) {
    EXPECT_EQ(magic, kMagic);
    MaybeThrow();
    ++alive;
  }
  ~Thrower() noexcept {
    EXPECT_EQ(magic, kMagic);
    magic = 0;
    --alive;
  }

  Thrower& operator=(Thrower const& /* other */) {
    EXPECT_EQ(magic, kMagic);
    MaybeThrow();
    return *this;
  }

  // This is just to try to make sure we don't get our member
  // functions called on uninitialized memory.
  int magic;
};

int Thrower::alive = 0;

// Type that counts how many exist and doesn't support copy
// construction.
struct NoncopyableCounter {
  static int alive;
  NoncopyableCounter() { ++alive; }
  ~NoncopyableCounter() { --alive; }
  NoncopyableCounter(NoncopyableCounter&&) noexcept { ++alive; }
  NoncopyableCounter(NoncopyableCounter const&) = delete;
  NoncopyableCounter& operator=(NoncopyableCounter const&) const = delete;
  NoncopyableCounter& operator=(NoncopyableCounter&&) { return *this; }
};
int NoncopyableCounter::alive = 0;

static_assert(
    !std::is_trivially_copyable<NoncopyableCounter>::value,
    "NoncopyableCounter is trivially copyable");

// Check that throws don't break the basic guarantee for some cases.
// Uses the method for testing exception safety described at
// http://www.boost.org/community/exception_safety.html, to force all
// throwing code paths to occur.
template <int N>
struct TestBasicGuarantee {
  folly::small_vector<Thrower, N> vec;
  int const prepopulate;

  explicit TestBasicGuarantee(int prepopulate_) : prepopulate(prepopulate_) {
    throwCounter = 1000;
    for (int i = 0; i < prepopulate; ++i) {
      vec.emplace_back();
    }
  }

  ~TestBasicGuarantee() { throwCounter = 1000; }

  template <class Operation>
  void operator()(int insertCount, Operation const& op) {
    bool done = false;

    std::unique_ptr<folly::small_vector<Thrower, N>> workingVec;
    for (int counter = 1; !done; ++counter) {
      throwCounter = 1000;
      workingVec = std::make_unique<folly::small_vector<Thrower, N>>(vec);
      throwCounter = counter;
      EXPECT_EQ(Thrower::alive, prepopulate * 2);
      try {
        op(*workingVec);
        done = true;
      } catch (...) {
        // Note that the size of the vector can change if we were
        // inserting somewhere other than the end (it's a basic only
        // guarantee).  All we're testing here is that we have the
        // right amount of uninitialized vs initialized memory.
        EXPECT_EQ(Thrower::alive, workingVec->size() + vec.size());
        continue;
      }

      // If things succeeded.
      EXPECT_EQ(workingVec->size(), prepopulate + insertCount);
      EXPECT_EQ(Thrower::alive, prepopulate * 2 + insertCount);
    }
  }
};

} // namespace

template <int N>
void testBasicGuarantee() {
  for (int prepop = 1; prepop < 30; ++prepop) {
    (TestBasicGuarantee<N>(prepop))( // parens or a mildly vexing parse :(
        1,
        [&](folly::small_vector<Thrower, N>& v) { v.emplace_back(); });

    EXPECT_EQ(Thrower::alive, 0);

    (TestBasicGuarantee<N>(prepop))(1, [&](folly::small_vector<Thrower, N>& v) {
      v.insert(v.begin(), Thrower());
    });

    EXPECT_EQ(Thrower::alive, 0);

    (TestBasicGuarantee<N>(prepop))(1, [&](folly::small_vector<Thrower, N>& v) {
      v.insert(v.begin() + 1, Thrower());
    });

    EXPECT_EQ(Thrower::alive, 0);
  }

  TestBasicGuarantee<N>(4)(3, [&](folly::small_vector<Thrower, N>& v) {
    std::vector<Thrower> b;
    b.emplace_back();
    b.emplace_back();
    b.emplace_back();

    /*
     * Apparently if you do the following initializer_list instead
     * of the above push_back's, and one of the Throwers throws,
     * g++4.6 doesn't destruct the previous ones.  Heh.
     */
    // b = { Thrower(), Thrower(), Thrower() };
    v.insert(v.begin() + 1, b.begin(), b.end());
  });

  TestBasicGuarantee<N>(2)(6, [&](folly::small_vector<Thrower, N>& v) {
    std::vector<Thrower> b;
    for (int i = 0; i < 6; ++i) {
      b.emplace_back();
    }

    v.insert(v.begin() + 1, b.begin(), b.end());
  });
}

TEST(smallVector, BasicGuarantee) {
  testBasicGuarantee<3>();

  EXPECT_EQ(Thrower::alive, 0);
  try {
    throwCounter = 4;
    folly::small_vector<Thrower, 1> p(14, Thrower());
  } catch (...) {
  }
  EXPECT_EQ(Thrower::alive, 0);

  // Heap only small_vector
  testBasicGuarantee<0>();
}

// Run this with.
// MALLOC_CONF=prof_leak:true
// LD_PRELOAD=${JEMALLOC_PATH}/lib/libjemalloc.so.2
// LD_PRELOAD="$LD_PRELOAD:"${UNWIND_PATH}/lib/libunwind.so.7
TEST(smallVector, leakTest) {
  for (int j = 0; j < 1000; ++j) {
    folly::small_vector<int, 10> someVec(300);
    for (int i = 0; i < 10000; ++i) {
      someVec.push_back(12);
    }
  }

  for (int j = 0; j < 1000; ++j) {
    folly::small_vector<int, 0> someVec(300);
    for (int i = 0; i < 10000; ++i) {
      someVec.push_back(12);
    }
  }
}

TEST(smallVector, leakTestWithTracking) {
  constexpr size_t size = 97;
  constexpr auto count = folly::annotate_object_count_leaked_uncollected;
  auto const base = count();
  small_vector<int> vec;
  vec.resize(size);
  EXPECT_EQ(size, vec.size());
  EXPECT_EQ(base + size_t(folly::kIsSanitizeAddress), count());
  vec.resize(0);
  EXPECT_EQ(0, vec.size());
  EXPECT_EQ(base + size_t(folly::kIsSanitizeAddress), count());
  vec.shrink_to_fit();
  EXPECT_LT(vec.capacity(), size);
  EXPECT_EQ(base, count());
}

TEST(smallVector, leakTestWithLeakedObject) {
  {
    // this case does not actually need leak-tracking
    using vec_t = folly::small_vector<int, 1>;
    constexpr size_t size = 97;
    static auto& vec = *new vec_t();
    vec.resize(size);
    EXPECT_EQ(size, vec.size());
  }

  {
    // this case does need leak-tracking
    using policy_t = folly::small_vector_policy::policy_size_type<uint32_t>;
    using vec_t = folly::small_vector<int, 1, policy_t>;
    constexpr size_t size = 97;
    static auto& vec = *new vec_t();
    vec.resize(size);
    EXPECT_EQ(size, vec.size());
  }
}

TEST(smallVector, InsertTrivial) {
  folly::small_vector<int> someVec(3, 3);
  someVec.insert(someVec.begin(), 12, 12);
  EXPECT_EQ(someVec.size(), 15);
  for (size_t i = 0; i < someVec.size(); ++i) {
    if (i < 12) {
      EXPECT_EQ(someVec[i], 12);
    } else {
      EXPECT_EQ(someVec[i], 3);
    }
  }

  // Make sure we insert a larger range so we can test placement new
  // and move inserts
  auto oldSize = someVec.size();
  someVec.insert(someVec.begin() + 1, 30, 30);
  EXPECT_EQ(someVec.size(), oldSize + 30);
  EXPECT_EQ(someVec[0], 12);
  EXPECT_EQ(someVec[1], 30);
  EXPECT_EQ(someVec[31], 12);
}

TEST(smallVector, InsertNontrivial) {
  folly::small_vector<std::string> v1(6, "asd"), v2(7, "wat");
  v1.insert(v1.begin() + 1, v2.begin(), v2.end());
  EXPECT_TRUE(v1.size() == 6 + 7);
  EXPECT_EQ(v1.front(), "asd");
  EXPECT_EQ(v1[1], "wat");

  // Insert without default constructor
  class TestClass {
   public:
    // explicit TestClass() = default;
    explicit TestClass(std::string s_) : s(s_) {}
    std::string s;
  };
  folly::small_vector<TestClass> v3(5, TestClass("asd"));
  folly::small_vector<TestClass> v4(10, TestClass("wat"));
  v3.insert(v3.begin() + 1, v4.begin(), v4.end());
  EXPECT_TRUE(v3.size() == 5 + 10);
  EXPECT_EQ(v3[0].s, "asd");
  EXPECT_EQ(v3[1].s, "wat");
  EXPECT_EQ(v3[10].s, "wat");
  EXPECT_EQ(v3[11].s, "asd");
}

TEST(smallVector, InsertFromBidirectionalList) {
  folly::small_vector<std::string> v(6, "asd");
  std::list<std::string> l(6, "wat");
  v.insert(v.end(), l.begin(), l.end());
  EXPECT_EQ(v[0], "asd");
  EXPECT_EQ(v[5], "asd");
  EXPECT_EQ(v[6], "wat");
  EXPECT_EQ(v[11], "wat");
}

template <int N>
void testSwap() {
  folly::small_vector<int, N> somethingVec, emptyVec;
  somethingVec.push_back(1);
  somethingVec.push_back(2);
  somethingVec.push_back(3);
  somethingVec.push_back(4);

  // Swapping intern'd with intern'd.
  auto vec = somethingVec;
  EXPECT_TRUE(vec == somethingVec);
  EXPECT_FALSE(vec == emptyVec);
  EXPECT_FALSE(somethingVec == emptyVec);

  // Swapping a heap vector with an intern vector.
  folly::small_vector<int, N> junkVec;
  junkVec.assign(12, 12);
  EXPECT_EQ(junkVec.size(), 12);
  for (auto i : junkVec) {
    EXPECT_EQ(i, 12);
  }
  swap(junkVec, vec);
  EXPECT_TRUE(junkVec == somethingVec);
  EXPECT_EQ(vec.size(), 12);
  for (auto i : vec) {
    EXPECT_EQ(i, 12);
  }

  // Swapping two heap vectors.
  folly::small_vector<int, N> moreJunk(15, 15);
  EXPECT_EQ(moreJunk.size(), 15);
  for (auto i : moreJunk) {
    EXPECT_EQ(i, 15);
  }
  swap(vec, moreJunk);
  EXPECT_EQ(moreJunk.size(), 12);
  for (auto i : moreJunk) {
    EXPECT_EQ(i, 12);
  }
  EXPECT_EQ(vec.size(), 15);
  for (auto i : vec) {
    EXPECT_EQ(i, 15);
  }
}

TEST(smallVector, Swap) {
  testSwap<10>();

  // heap only small_vector
  testSwap<0>();

  // Making a vector heap, then smaller than another non-heap vector,
  // then swapping.
  folly::small_vector<int, 5> shrinker, other(4, 10);
  shrinker = {0, 1, 2, 3, 4, 5, 6, 7, 8};
  shrinker.erase(shrinker.begin() + 2, shrinker.end());
  EXPECT_LT(shrinker.size(), other.size());
  swap(shrinker, other);
  EXPECT_EQ(shrinker.size(), 4);
  EXPECT_TRUE(boost::all(shrinker, boost::is_any_of(std::vector<int>{10})));
  EXPECT_TRUE((other == small_vector<int, 5>{0, 1}));
}

template <int N>
void testEmplace() {
  NontrivialType::ctored = 0;

  folly::small_vector<NontrivialType, N> vec;
  vec.reserve(1024);
  {
    auto& emplaced = vec.emplace_back(12);
    EXPECT_EQ(NontrivialType::ctored, 1);
    EXPECT_EQ(vec.front().a, 12);
    EXPECT_TRUE(std::addressof(emplaced) == std::addressof(vec.back()));
  }
  {
    auto& emplaced = vec.emplace_back(13);
    EXPECT_EQ(vec.front().a, 12);
    EXPECT_EQ(vec.back().a, 13);
    EXPECT_EQ(NontrivialType::ctored, 2);
    EXPECT_TRUE(std::addressof(emplaced) == std::addressof(vec.back()));
  }

  NontrivialType::ctored = 0;
  for (int i = 0; i < 120; ++i) {
    auto& emplaced = vec.emplace_back(i);
    EXPECT_TRUE(std::addressof(emplaced) == std::addressof(vec.back()));
  }
  EXPECT_EQ(NontrivialType::ctored, 120);
  EXPECT_EQ(vec[0].a, 12);
  EXPECT_EQ(vec[1].a, 13);
  EXPECT_EQ(vec.back().a, 119);

  // We implement emplace() with a temporary (see the implementation
  // for a comment about why), so this should make 2 ctor calls.
  NontrivialType::ctored = 0;
  vec.emplace(vec.begin(), 12);
  EXPECT_EQ(NontrivialType::ctored, 2);
}

TEST(smallVector, Emplace) {
  testEmplace<1>();
  // heap only
  testEmplace<0>();
}

TEST(smallVector, Erase) {
  folly::small_vector<int, 4> notherVec = {1, 2, 3, 4, 5};
  EXPECT_EQ(notherVec.front(), 1);
  EXPECT_EQ(notherVec.size(), 5);
  notherVec.erase(notherVec.begin());
  EXPECT_EQ(notherVec.front(), 2);
  EXPECT_EQ(notherVec.size(), 4);
  EXPECT_EQ(notherVec[2], 4);
  EXPECT_EQ(notherVec[3], 5);
  notherVec.erase(notherVec.begin() + 2);
  EXPECT_EQ(notherVec.size(), 3);
  EXPECT_EQ(notherVec[2], 5);

  folly::small_vector<int, 2> vec2 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  vec2.erase(vec2.begin() + 1, vec2.end() - 1);
  folly::small_vector<int, 2> expected = {1, 10};
  EXPECT_TRUE(vec2 == expected);

  folly::small_vector<std::string, 3> v(102, "ASD");
  v.resize(1024, "D");
  EXPECT_EQ(v.size(), 1024);
  EXPECT_EQ(v.back(), "D");
  EXPECT_EQ(v.front(), "ASD");
  v.resize(1);
  EXPECT_EQ(v.front(), "ASD");
  EXPECT_EQ(v.size(), 1);
  v.resize(0);
  EXPECT_TRUE(v.empty());

  // test heap only
  folly::small_vector<int, 0> vec3 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  vec3.erase(vec3.begin() + 1, vec3.end() - 1);
  folly::small_vector<int, 0> expected3 = {1, 10};
  EXPECT_TRUE(vec3 == expected3);
}

template <int N>
void testGrowShrinkGrow() {
  folly::small_vector<NontrivialType, N> vec = {1, 2, 3, 4, 5};
  std::generate_n(std::back_inserter(vec), 102, std::rand);

  auto capacity = vec.capacity();

  auto oldSize = vec.size();
  for (size_t i = 0; i < oldSize; ++i) {
    vec.erase(vec.begin() + (std::rand() % vec.size()));
    EXPECT_EQ(vec.capacity(), capacity);
  }
  EXPECT_TRUE(vec.empty());

  EXPECT_EQ(vec.capacity(), capacity);
  std::generate_n(std::back_inserter(vec), 102, std::rand);
  EXPECT_EQ(vec.capacity(), capacity);

  std::generate_n(std::back_inserter(vec), 4096, std::rand);
  EXPECT_GT(vec.capacity(), capacity);

  vec.resize(10);
  vec.shrink_to_fit();
  EXPECT_LT(vec.capacity(), capacity);
  auto cap = vec.capacity();
  vec.resize(4);
  vec.shrink_to_fit();
  if (N > 4)
    EXPECT_EQ(vec.capacity(), N); // in situ size
  else
    EXPECT_LT(vec.capacity(), cap); // on heap
}

TEST(smallVector, GrowShrinkGrow) {
  testGrowShrinkGrow<7>();

  testGrowShrinkGrow<0>();
}

TEST(smallVector, Iteration) {
  folly::small_vector<std::string, 3> vec = {"foo", "bar"};
  vec.push_back("blah");
  vec.push_back("blah2");
  vec.push_back("blah3");
  vec.erase(vec.begin() + 2);

  std::vector<std::string> otherVec;
  for (auto& s : vec) {
    otherVec.push_back(s);
  }
  EXPECT_EQ(otherVec.size(), vec.size());
  if (otherVec.size() == vec.size()) {
    EXPECT_TRUE(std::equal(otherVec.begin(), otherVec.end(), vec.begin()));
  }

  std::reverse(otherVec.begin(), otherVec.end());
  auto oit = otherVec.begin();
  auto rit = vec.crbegin();
  for (; rit != vec.crend(); ++oit, ++rit) {
    EXPECT_EQ(*oit, *rit);
  }
}

TEST(smallVector, NonCopyableType) {
  folly::small_vector<NontrivialType, 2> vec;

  for (int i = 0; i < 10; ++i) {
    vec.emplace(vec.begin(), 13);
  }
  EXPECT_EQ(vec.size(), 10);
  auto vec2 = std::move(vec);
  EXPECT_EQ(vec.size(), 0);
  EXPECT_EQ(vec2.size(), 10);
  vec2.clear();

  folly::small_vector<NoncopyableCounter, 3> vec3;
  for (int i = 0; i < 10; ++i) {
    EXPECT_EQ(vec3.size(), i);
    EXPECT_EQ(NoncopyableCounter::alive, i);
    vec3.insert(vec3.begin(), NoncopyableCounter());
  }
  EXPECT_EQ(vec3.size(), 10);
  EXPECT_EQ(NoncopyableCounter::alive, 10);

  vec3.insert(vec3.begin() + 3, NoncopyableCounter());
  EXPECT_EQ(NoncopyableCounter::alive, 11);
  auto vec4 = std::move(vec3);
  EXPECT_EQ(NoncopyableCounter::alive, 11);
  vec4.resize(30);
  EXPECT_EQ(NoncopyableCounter::alive, 30);
  vec4.erase(vec4.begin(), vec4.end());
  EXPECT_EQ(vec4.size(), 0);
  EXPECT_EQ(NoncopyableCounter::alive, 0);

  {
    folly::small_vector<NontrivialType, 0> vec0;

    for (int i = 0; i < 10; ++i) {
      vec0.emplace(vec0.begin(), 13);
    }
    EXPECT_EQ(vec0.size(), 10);
    auto vec02 = std::move(vec0);
    EXPECT_EQ(vec0.size(), 0);
    EXPECT_EQ(vec02.size(), 10);
    vec02.clear();
  }
}

template <int N>
void testMoveConstructor() {
  folly::small_vector<std::string, N> v1;
  v1.push_back("asd");
  v1.push_back("bsd");
  auto v2 = std::move(v1);
  EXPECT_EQ(v2.size(), 2);
  EXPECT_EQ(v2[0], "asd");
  EXPECT_EQ(v2[1], "bsd");

  v1 = std::move(v2);
  EXPECT_EQ(v1.size(), 2);
  EXPECT_EQ(v1[0], "asd");
  EXPECT_EQ(v1[1], "bsd");
}

TEST(smallVector, MoveConstructor) {
  testMoveConstructor<10>();
  testMoveConstructor<0>();
}

TEST(smallVector, NoHeap) {
  typedef folly::small_vector<std::string, 10, policy_in_situ_only<true>>
      Vector;

  Vector v;
  static_assert(v.max_size() == 10, "max_size is incorrect");

  for (int i = 0; i < 10; ++i) {
    v.push_back(folly::to<std::string>(i));
    EXPECT_EQ(v.size(), i + 1);
  }

  bool caught = false;
  try {
    v.insert(v.begin(), "ha");
  } catch (const std::length_error&) {
    caught = true;
  }
  EXPECT_TRUE(caught);

  // Check max_size works right with various policy combinations.
  folly::small_vector<std::string, 32, policy_size_type<uint32_t>> v4;
  EXPECT_EQ(v4.max_size(), (uint32_t(1) << 30) - 1);

  /*
   * Test that even when we ask for a small number inlined it'll still
   * inline at least as much as it takes to store the value_type
   * pointer.
   */
  folly::small_vector<char, 1, policy_in_situ_only<true>> notsosmall;
  static_assert(
      notsosmall.max_size() == sizeof(char*), "max_size is incorrect");
  caught = false;
  try {
    notsosmall.push_back(12);
    notsosmall.push_back(13);
    notsosmall.push_back(14);
  } catch (const std::length_error&) {
    caught = true;
  }
  EXPECT_FALSE(caught);
}

TEST(smallVector, MaxSize) {
  folly::small_vector<int, 2, policy_size_type<uint8_t>> vec;
  EXPECT_EQ(vec.max_size(), 63);
  folly::small_vector<int, 2, policy_size_type<uint16_t>> vec2;
  EXPECT_EQ(vec2.max_size(), (1 << 14) - 1);
}

TEST(smallVector, AllHeap) {
  // Use something bigger than the pointer so it can't get inlined.
  struct SomeObj {
    double a, b, c, d, e;
    int val;
    SomeObj(int val_) : val(val_) {}
    bool operator==(SomeObj const& o) const { return o.val == val; }
  };

  folly::small_vector<SomeObj, 0> vec = {1};
  EXPECT_EQ(vec.size(), 1);
  if (!vec.empty()) {
    EXPECT_TRUE(vec[0] == 1);
  }
  vec.insert(vec.begin(), {0, 1, 2, 3});
  EXPECT_EQ(vec.size(), 5);
  EXPECT_TRUE((vec == folly::small_vector<SomeObj, 0>{0, 1, 2, 3, 1}));
}
template <int N>
void testBasic() {
  typedef folly::small_vector<int, N, policy_size_type<uint32_t>> Vector;

  Vector a;

  a.push_back(12);
  EXPECT_EQ(a.front(), 12);
  EXPECT_EQ(a.size(), 1);
  a.push_back(13);
  EXPECT_EQ(a.size(), 2);
  EXPECT_EQ(a.front(), 12);
  EXPECT_EQ(a.back(), 13);

  a.emplace(a.end(), 32);
  EXPECT_EQ(a.back(), 32);

  a.emplace(a.begin(), 12);
  EXPECT_EQ(a.front(), 12);
  EXPECT_EQ(a.back(), 32);
  a.erase(a.end() - 1);
  EXPECT_EQ(a.back(), 13);

  a.push_back(12);
  EXPECT_EQ(a.back(), 12);
  a.pop_back();
  EXPECT_EQ(a.back(), 13);

  const int s = 12;
  a.push_back(s); // lvalue reference

  Vector b, c;
  b = a;
  EXPECT_TRUE(b == a);
  c = std::move(b);
  EXPECT_TRUE(c == a);
  EXPECT_TRUE(c != b && b != a);

  EXPECT_GT(c.size(), 0);
  c.resize(1);
  EXPECT_EQ(c.size(), 1);

  Vector intCtor(12);
}

TEST(smallVector, Basic) {
  testBasic<3>();
  testBasic<0>();
}

TEST(smallVector, Capacity) {
  folly::small_vector<uint64_t, 1> vec;
  EXPECT_EQ(vec.size(), 0);
  EXPECT_EQ(vec.capacity(), 1);

  vec.push_back(0);
  EXPECT_EQ(vec.size(), 1);
  EXPECT_EQ(vec.capacity(), 1);

  vec.push_back(1);
  EXPECT_EQ(vec.size(), 2);
  EXPECT_GT(vec.capacity(), 1);

  folly::small_vector<uint64_t, 2> vec2;
  EXPECT_EQ(vec2.size(), 0);
  EXPECT_EQ(vec2.capacity(), 2);

  vec2.push_back(0);
  vec2.push_back(1);
  EXPECT_EQ(vec2.size(), 2);
  EXPECT_EQ(vec2.capacity(), 2);

  vec2.push_back(2);
  EXPECT_EQ(vec2.size(), 3);
  EXPECT_GT(vec2.capacity(), 2);

  // Test capacity heapifying logic
  folly::small_vector<unsigned char, 1> vec3;
  const size_t hc_size = 100000;
  for (size_t i = 0; i < hc_size; ++i) {
    auto v = (unsigned char)i;
    vec3.push_back(v);
    EXPECT_EQ(vec3[i], v);
    EXPECT_EQ(vec3.size(), i + 1);
    EXPECT_GT(vec3.capacity(), i);
  }
  for (auto i = hc_size; i > 0; --i) {
    auto v = (unsigned char)(i - 1);
    EXPECT_EQ(vec3.back(), v);
    vec3.pop_back();
    EXPECT_EQ(vec3.size(), i - 1);
  }
}

template <int N>
void testSelfPushBack() {
  for (int i = 1; i < 33; ++i) {
    folly::small_vector<std::string, N> vec;
    for (int j = 0; j < i; ++j) {
      vec.push_back("abc");
    }
    EXPECT_EQ(vec.size(), i);
    vec.push_back(std::move(vec[0]));
    EXPECT_EQ(vec.size(), i + 1);

    EXPECT_EQ(vec[i], "abc");
  }
}

TEST(smallVector, SelfPushBack) {
  testSelfPushBack<1>();
  testSelfPushBack<0>();
}

template <int N>
void testSelfEmplaceBack() {
  for (int i = 1; i < 33; ++i) {
    folly::small_vector<std::string, N> vec;
    for (int j = 0; j < i; ++j) {
      vec.emplace_back("abc");
    }
    EXPECT_EQ(vec.size(), i);
    vec.emplace_back(std::move(vec[0]));
    EXPECT_EQ(vec.size(), i + 1);

    EXPECT_EQ(vec[i], "abc");
  }
}

TEST(smallVector, SelfEmplaceBack) {
  testSelfEmplaceBack<1>();
  testSelfEmplaceBack<0>();
}

template <int N>
void testSelfInsert() {
  // end insert
  for (int i = 1; i < 33; ++i) {
    folly::small_vector<std::string, N> vec;
    for (int j = 0; j < i; ++j) {
      vec.push_back("abc");
    }
    EXPECT_EQ(vec.size(), i);
    vec.insert(vec.end(), std::move(vec[0]));
    EXPECT_EQ(vec.size(), i + 1);

    EXPECT_EQ(vec[i], "abc");
    EXPECT_EQ(vec[vec.size() - 1], "abc");
  }

  // middle insert
  for (int i = 2; i < 33; ++i) {
    folly::small_vector<std::string, N> vec;
    for (int j = 0; j < i; ++j) {
      vec.push_back("abc");
    }
    EXPECT_EQ(vec.size(), i);
    vec.insert(vec.end() - 1, std::move(vec[0]));
    EXPECT_EQ(vec.size(), i + 1);

    EXPECT_EQ(vec[i - 1], "abc");
    EXPECT_EQ(vec[i], "abc");
  }

  // range insert
  for (int i = 2; i < 33; ++i) {
    folly::small_vector<std::string, N> vec;
    // reserve 2 * i space so we don't grow and invalidate references.
    vec.reserve(2 * i);
    for (int j = 0; j < i; ++j) {
      vec.push_back("abc");
    }
    EXPECT_EQ(vec.size(), i);
    vec.insert(vec.end() - 1, vec.begin(), vec.end() - 1);
    EXPECT_EQ(vec.size(), 2 * i - 1);

    for (auto const& val : vec) {
      EXPECT_EQ(val, "abc");
    }
  }
}

TEST(smallVector, SelfInsert) {
  testSelfInsert<1>();

  testSelfInsert<0>();
}

struct CheckedInt {
  static const int DEFAULT_VALUE = (int)0xdeadbeef;
  CheckedInt() : value(DEFAULT_VALUE) {}
  explicit CheckedInt(int value_) : value(value_) {}
  CheckedInt(const CheckedInt& rhs, int) : value(rhs.value) {}
  CheckedInt(const CheckedInt& rhs) : value(rhs.value) {}
  CheckedInt(CheckedInt&& rhs) noexcept : value(rhs.value) {
    rhs.value = DEFAULT_VALUE;
  }
  CheckedInt& operator=(const CheckedInt& rhs) {
    value = rhs.value;
    return *this;
  }
  CheckedInt& operator=(CheckedInt&& rhs) noexcept {
    value = rhs.value;
    rhs.value = DEFAULT_VALUE;
    return *this;
  }
  ~CheckedInt() {}
  int value;
};

TEST(smallVector, ForwardingEmplaceInsideVector) {
  folly::small_vector<CheckedInt> v;
  v.push_back(CheckedInt(1));
  for (int i = 1; i < 20; ++i) {
    v.emplace_back(v[0], 42);
    ASSERT_EQ(1, v.back().value);
  }
}

TEST(smallVector, LVEmplaceInsideVector) {
  folly::small_vector<CheckedInt> v;
  v.push_back(CheckedInt(1));
  for (int i = 1; i < 20; ++i) {
    v.emplace_back(v[0]);
    ASSERT_EQ(1, v.back().value);
  }
}

TEST(smallVector, CLVEmplaceInsideVector) {
  folly::small_vector<CheckedInt> v;
  const folly::small_vector<CheckedInt>& cv = v;
  v.push_back(CheckedInt(1));
  for (int i = 1; i < 20; ++i) {
    v.emplace_back(cv[0]);
    ASSERT_EQ(1, v.back().value);
  }
}

TEST(smallVector, RVEmplaceInsideVector) {
  folly::small_vector<CheckedInt> v;
  v.push_back(CheckedInt(0));
  for (int i = 1; i < 20; ++i) {
    v[0] = CheckedInt(1);
    v.emplace_back(std::move(v[0]));
    ASSERT_EQ(1, v.back().value);
  }
}

TEST(smallVector, LVPushValueInsideVector) {
  folly::small_vector<CheckedInt> v;
  v.push_back(CheckedInt(1));
  for (int i = 1; i < 20; ++i) {
    v.push_back(v[0]);
    ASSERT_EQ(1, v.back().value);
  }
}

TEST(smallVector, RVPushValueInsideVector) {
  folly::small_vector<CheckedInt> v;
  v.push_back(CheckedInt(0));
  for (int i = 1; i < 20; ++i) {
    v[0] = CheckedInt(1);
    v.push_back(v[0]);
    ASSERT_EQ(1, v.back().value);
  }
}

TEST(smallVector, EmplaceIterCtor) {
  std::vector<int*> v{new int(1), new int(2)};
  std::vector<std::unique_ptr<int>> uv(v.begin(), v.end());

  std::vector<int*> w{new int(1), new int(2)};
  small_vector<std::unique_ptr<int>> uw(w.begin(), w.end());
}

TEST(smallVector, InputIterator) {
  std::vector<int> expected{125, 320, 512, 750, 333};
  std::string values = "125 320 512 750 333";
  std::istringstream is1(values);
  std::istringstream is2(values);

  std::vector<int> stdV{
      std::istream_iterator<int>(is1), std::istream_iterator<int>()};
  ASSERT_EQ(stdV.size(), expected.size());
  for (size_t i = 0; i < expected.size(); i++) {
    ASSERT_EQ(stdV[i], expected[i]);
  }

  small_vector<int> smallV{
      std::istream_iterator<int>(is2), std::istream_iterator<int>()};
  ASSERT_EQ(smallV.size(), expected.size());
  for (size_t i = 0; i < expected.size(); i++) {
    ASSERT_EQ(smallV[i], expected[i]);
  }
}

TEST(smallVector, NoCopyCtor) {
  struct Tester {
    Tester() = default;
    Tester(const Tester&) = delete;
    Tester(Tester&&) = default;

    int field = 42;
  };

  small_vector<Tester> test(10);
  ASSERT_EQ(test.size(), 10);
  for (const auto& element : test) {
    EXPECT_EQ(element.field, 42);
  }
}

TEST(smallVector, ZeroInitializable) {
  small_vector<int> test(10);
  ASSERT_EQ(test.size(), 10);
  for (const auto& element : test) {
    EXPECT_EQ(element, 0);
  }
}

TEST(smallVector, InsertMoreThanGrowth) {
  small_vector<int, 10> test;
  test.insert(test.end(), 30, 0);
  for (auto element : test) {
    EXPECT_EQ(element, 0);
  }
}

TEST(smallVector, EmplaceBackExponentialGrowth) {
  small_vector<std::pair<int, int>> test;
  std::vector<size_t> capacities;
  capacities.push_back(test.capacity());
  for (int i = 0; i < 10000; ++i) {
    test.emplace_back(0, 0);
    if (test.capacity() != capacities.back()) {
      capacities.push_back(test.capacity());
    }
  }
  EXPECT_LE(capacities.size(), 25);
}

TEST(smallVector, InsertExponentialGrowth) {
  small_vector<std::pair<int, int>> test;
  std::vector<size_t> capacities;
  capacities.push_back(test.capacity());
  for (int i = 0; i < 10000; ++i) {
    test.insert(test.begin(), std::make_pair(0, 0));
    if (test.capacity() != capacities.back()) {
      capacities.push_back(test.capacity());
    }
  }
  EXPECT_LE(capacities.size(), 25);
}

TEST(smallVector, InsertNExponentialGrowth) {
  small_vector<int> test;
  std::vector<size_t> capacities;
  capacities.push_back(test.capacity());
  for (int i = 0; i < 10000; ++i) {
    test.insert(test.begin(), 100, 0);
    if (test.capacity() != capacities.back()) {
      capacities.push_back(test.capacity());
    }
  }
  EXPECT_LE(capacities.size(), 25);
}

namespace {
struct Counts {
  size_t copyCount{0};
  size_t moveCount{0};
};

class Counter {
  Counts* counts;

 public:
  explicit Counter(Counts& counts_) : counts(&counts_) {}
  Counter(Counter const& other) noexcept : counts(other.counts) {
    ++counts->copyCount;
  }
  Counter(Counter&& other) noexcept : counts(other.counts) {
    ++counts->moveCount;
  }
  Counter& operator=(Counter const& rhs) noexcept {
    EXPECT_EQ(counts, rhs.counts);
    ++counts->copyCount;
    return *this;
  }
  Counter& operator=(Counter&& rhs) noexcept {
    EXPECT_EQ(counts, rhs.counts);
    ++counts->moveCount;
    return *this;
  }
};
} // namespace

TEST(smallVector, EmplaceBackEfficiency) {
  small_vector<Counter, 2> test;
  Counts counts;
  for (size_t i = 1; i <= test.capacity(); ++i) {
    test.emplace_back(counts);
    EXPECT_EQ(0, counts.copyCount);
    EXPECT_EQ(0, counts.moveCount);
  }
  EXPECT_EQ(test.size(), test.capacity());
  test.emplace_back(counts);
  // Every element except the last has to be moved to the new position
  EXPECT_EQ(0, counts.copyCount);
  EXPECT_EQ(test.size() - 1, counts.moveCount);
  EXPECT_LT(test.size(), test.capacity());
}

TEST(smallVector, RVPushBackEfficiency) {
  small_vector<Counter, 2> test;
  Counts counts;
  for (size_t i = 1; i <= test.capacity(); ++i) {
    test.push_back(Counter(counts));
    // 1 copy for each push_back()
    EXPECT_EQ(0, counts.copyCount);
    EXPECT_EQ(i, counts.moveCount);
  }
  EXPECT_EQ(test.size(), test.capacity());
  test.push_back(Counter(counts));
  // 1 move for each push_back()
  // Every element except the last has to be moved to the new position
  EXPECT_EQ(0, counts.copyCount);
  EXPECT_EQ(test.size() + test.size() - 1, counts.moveCount);
  EXPECT_LT(test.size(), test.capacity());
}

TEST(smallVector, CLVPushBackEfficiency) {
  small_vector<Counter, 2> test;
  Counts counts;
  Counter const counter(counts);
  for (size_t i = 1; i <= test.capacity(); ++i) {
    test.push_back(counter);
    // 1 copy for each push_back()
    EXPECT_EQ(i, counts.copyCount);
    EXPECT_EQ(0, counts.moveCount);
  }
  EXPECT_EQ(test.size(), test.capacity());
  test.push_back(counter);
  // 1 copy for each push_back()
  EXPECT_EQ(test.size(), counts.copyCount);
  // Every element except the last has to be moved to the new position
  EXPECT_EQ(test.size() - 1, counts.moveCount);
  EXPECT_LT(test.size(), test.capacity());
}

TEST(smallVector, StorageForSortedVectorMap) {
  small_sorted_vector_map<int32_t, int32_t, 2> test;
  test.insert(std::make_pair(10, 10));
  EXPECT_EQ(test.size(), 1);
  test.insert(std::make_pair(10, 10));
  EXPECT_EQ(test.size(), 1);
  test.insert(std::make_pair(20, 10));
  EXPECT_EQ(test.size(), 2);
  test.insert(std::make_pair(30, 10));
  EXPECT_EQ(test.size(), 3);
}

TEST(smallVector, NoHeapStorageForSortedVectorMap) {
  noheap_sorted_vector_map<int32_t, int32_t, 2> test;
  test.insert(std::make_pair(10, 10));
  EXPECT_EQ(test.size(), 1);
  test.insert(std::make_pair(10, 10));
  EXPECT_EQ(test.size(), 1);
  test.insert(std::make_pair(20, 10));
  EXPECT_EQ(test.size(), 2);
  EXPECT_THROW(test.insert(std::make_pair(30, 10)), std::length_error);
  EXPECT_EQ(test.size(), 2);
}

TEST(smallVector, StorageForSortedVectorSet) {
  small_sorted_vector_set<int32_t, 2> test;
  test.insert(10);
  EXPECT_EQ(test.size(), 1);
  test.insert(10);
  EXPECT_EQ(test.size(), 1);
  test.insert(20);
  EXPECT_EQ(test.size(), 2);
  test.insert(30);
  EXPECT_EQ(test.size(), 3);
}

TEST(smallVector, NoHeapStorageForSortedVectorSet) {
  noheap_sorted_vector_set<int32_t, 2> test;
  test.insert(10);
  EXPECT_EQ(test.size(), 1);
  test.insert(10);
  EXPECT_EQ(test.size(), 1);
  test.insert(20);
  EXPECT_EQ(test.size(), 2);
  EXPECT_THROW(test.insert(30), std::length_error);
  EXPECT_EQ(test.size(), 2);
}

TEST(smallVector, SelfMoveAssignmentForVectorOfPair) {
  folly::small_vector<std::pair<int, int>, 2> test;
  test.emplace_back(13, 2);
  EXPECT_EQ(test.size(), 1);
  EXPECT_EQ(test[0].first, 13);
  test = static_cast<decltype(test)&&>(test); // suppress self-move warning
  EXPECT_EQ(test.size(), 1);
  EXPECT_EQ(test[0].first, 13);
}

TEST(smallVector, SelfCopyAssignmentForVectorOfPair) {
  folly::small_vector<std::pair<int, int>, 2> test;
  test.emplace_back(13, 2);
  EXPECT_EQ(test.size(), 1);
  EXPECT_EQ(test[0].first, 13);
  test = static_cast<decltype(test)&>(test); // suppress self-assign warning
  EXPECT_EQ(test.size(), 1);
  EXPECT_EQ(test[0].first, 13);
}

namespace {
struct NonAssignableType {
  int const i_{};
};
} // namespace

TEST(smallVector, PopBackNonAssignableType) {
  small_vector<NonAssignableType> v;
  v.emplace_back();
  EXPECT_EQ(1, v.size());
  v.pop_back();
  EXPECT_EQ(0, v.size());
}

TEST(smallVector, erase) {
  small_vector<int> v(3);
  std::iota(v.begin(), v.end(), 1);
  v.push_back(2);
  erase(v, 2);
  ASSERT_EQ(2u, v.size());
  EXPECT_EQ(1u, v[0]);
  EXPECT_EQ(3u, v[1]);
}

TEST(smallVector, eraseIf) {
  small_vector<int> v(6);
  std::iota(v.begin(), v.end(), 1);
  erase_if(v, [](const auto& x) { return x % 2 == 0; });
  ASSERT_EQ(3u, v.size());
  EXPECT_EQ(1u, v[0]);
  EXPECT_EQ(3u, v[1]);
  EXPECT_EQ(5u, v[2]);
}

namespace {

class NonTrivialInt {
 public:
  NonTrivialInt() {}
  /* implicit */ NonTrivialInt(int value)
      : value_(std::make_shared<int>(value)) {}

  operator int() const { return *value_; }

 private:
  std::shared_ptr<const int> value_;
};

// Move and copy constructor and assignment have several special cases depending
// on relative sizes, so test all combinations.
template <class T, size_t N>
void testMoveAndCopy() {
  const auto fill = [](auto& v, size_t n) {
    v.resize(n);
    std::iota(v.begin(), v.end(), 0);
  };
  const auto verify = [](const auto& v, size_t n) {
    ASSERT_EQ(v.size(), n);
    for (size_t i = 0; i < n; ++i) {
      EXPECT_EQ(v[i], i);
    }
  };

  using Vec = small_vector<T, N>;
  SCOPED_TRACE(fmt::format("N = {}", N));

  const size_t kMinCapacity = Vec{}.capacity();

  for (size_t from = 0; from < 16; ++from) {
    SCOPED_TRACE(fmt::format("from = {}", from));

    {
      SCOPED_TRACE("Move-construction");
      Vec a;
      fill(a, from);
      const auto aCapacity = a.capacity();
      Vec b = std::move(a);
      verify(b, from);
      EXPECT_EQ(b.capacity(), aCapacity);
      verify(a, 0);
      EXPECT_EQ(a.capacity(), kMinCapacity);
    }
    {
      SCOPED_TRACE("Copy-construction");
      Vec a;
      fill(a, from);
      Vec b = a;
      verify(b, from);
      verify(a, from);
    }

    for (size_t to = 0; to < 16; ++to) {
      SCOPED_TRACE(fmt::format("to = {}", to));
      {
        SCOPED_TRACE("Move-assignment");
        Vec a;
        fill(a, from);
        Vec b;
        fill(b, to);

        const auto aCapacity = a.capacity();
        b = std::move(a);
        verify(b, from);
        EXPECT_EQ(b.capacity(), aCapacity);
        verify(a, 0);
        EXPECT_EQ(a.capacity(), kMinCapacity);
      }
      {
        SCOPED_TRACE("Copy-assignment");
        Vec a;
        fill(a, from);
        Vec b;
        fill(b, to);

        b = a;
        verify(b, from);
        verify(a, from);
      }
      {
        SCOPED_TRACE("swap");
        Vec a;
        fill(a, from);
        Vec b;
        fill(b, to);

        const auto aCapacity = a.capacity();
        const auto bCapacity = b.capacity();
        swap(a, b);
        verify(b, from);
        EXPECT_EQ(b.capacity(), aCapacity);
        verify(a, to);
        EXPECT_EQ(a.capacity(), bCapacity);
      }
    }
  }
}

} // namespace

TEST(smallVector, MoveAndCopyTrivial) {
  testMoveAndCopy<int, 0>();
  // Capacity does not fit inline.
  testMoveAndCopy<int, 2>();
  // Capacity does fits inline.
  testMoveAndCopy<int, 4>();
}

TEST(smallVector, MoveAndCopyNonTrivial) {
  testMoveAndCopy<NonTrivialInt, 0>();
  testMoveAndCopy<NonTrivialInt, 4>();
}

TEST(smallVector, PolicyMaxSizeExceeded) {
  struct Obj {
    char a[350];
  };

  small_vector<Obj, 10, policy_size_type<uint8_t>> v;

  EXPECT_THROW(
      for (size_t i = 0; i < 0x100; ++i) { v.push_back(Obj()); },
      std::length_error);
}

TEST(smallVector, Hashable) {
  small_vector<int> v0{{0, 1}};
  small_vector<int> v1{{1, 2}};
  std::unordered_set<small_vector<int>> s;
  s.insert(v0);
  EXPECT_NE(s.end(), s.find(v0));
  EXPECT_EQ(s.end(), s.find(v1));
}

TEST(smallVector, overflowConstruct) {
  EXPECT_THROW(
      folly::small_vector<std::string>(SIZE_MAX / sizeof(std::string) + 1),
      std::length_error);
}

TEST(smallVector, overflowResize) {
  folly::small_vector<std::string> vec;
  EXPECT_THROW(
      vec.resize(SIZE_MAX / sizeof(std::string) + 1), std::length_error);
}

TEST(smallVector, overflowAssign) {
  folly::small_vector<std::string> vec;
  EXPECT_THROW(
      vec.assign(SIZE_MAX / sizeof(std::string) + 1, "hello"),
      std::length_error);
}

TEST(smallVector, assignZeroElementsNoInline) {
  folly::small_vector<int, 0> v;
  v.assign(0, 42);
  EXPECT_TRUE(v.empty());
}

namespace {
struct MaybeThrowOnCopy {
  std::unique_ptr<bool> throwOnCopy;

  explicit MaybeThrowOnCopy(bool t) : throwOnCopy(std::make_unique<bool>(t)) {}
  MaybeThrowOnCopy(const MaybeThrowOnCopy& other)
      : throwOnCopy(std::make_unique<bool>(other.throwOnCopy)) {
    if (*other.throwOnCopy) {
      throw std::runtime_error{"test"};
    }
  }
};
} // namespace

TEST(smallVector, rangeConstructorForwardIteratorThrows) {
  std::array<MaybeThrowOnCopy, 3> arr = {
      MaybeThrowOnCopy{false}, MaybeThrowOnCopy{true}, MaybeThrowOnCopy{false}};

  using ForwardIterator =
      folly::index_iterator<std::array<MaybeThrowOnCopy, 3>>;

  auto first = ForwardIterator{arr, 0};
  auto last = ForwardIterator{arr, arr.size()};

  using SV1 = small_vector<MaybeThrowOnCopy, 1>;
  using SV3 = small_vector<MaybeThrowOnCopy, 3>;

  EXPECT_THROW(SV1(first, last), std::runtime_error);
  EXPECT_THROW(SV3(first, last), std::runtime_error);
}

TEST(smallVector, rangeConstructorInputIteratorThrows) {
  std::array<MaybeThrowOnCopy, 3> arr = {
      MaybeThrowOnCopy{false}, MaybeThrowOnCopy{true}, MaybeThrowOnCopy{false}};

  class InputIterator
      : public folly::index_iterator<std::array<MaybeThrowOnCopy, 3>> {
   public:
    using difference_type = std::ptrdiff_t;
    using value_type = MaybeThrowOnCopy const;
    using reference = MaybeThrowOnCopy const&;
    using pointer = MaybeThrowOnCopy const*;
    using iterator_category = std::input_iterator_tag;

    using index_iterator<std::array<MaybeThrowOnCopy, 3>>::index_iterator;
  };

  auto first = InputIterator{arr, 0};
  auto last = InputIterator{arr, arr.size()};

  using SV1 = small_vector<MaybeThrowOnCopy, 1>;
  using SV3 = small_vector<MaybeThrowOnCopy, 3>;

  EXPECT_THROW(SV1(first, last), std::runtime_error);
  EXPECT_THROW(SV3(first, last), std::runtime_error);
}