| // 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. | |
| // | |
| // ----------------------------------------------------------------------------- | |
| // File: btree_test.cc | |
| // ----------------------------------------------------------------------------- | |
| // | |
| // General tests for btree containers without many diverse value types. | |
| // NOTE: There are more tests in btree_instantiate_test.cc that test | |
| // diverse value types. | |
| #include "absl/container/btree_test.h" | |
| #include <algorithm> | |
| #include <array> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <functional> | |
| #include <iostream> | |
| #include <iterator> | |
| #include <limits> | |
| #include <map> | |
| #include <memory> | |
| #include <numeric> | |
| #include <optional> | |
| #include <set> | |
| #include <stdexcept> | |
| #include <string> | |
| #include <type_traits> | |
| #include <utility> | |
| #include <vector> | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "absl/algorithm/container.h" | |
| #include "absl/base/config.h" | |
| #include "absl/base/internal/raw_logging.h" | |
| #include "absl/base/macros.h" | |
| #include "absl/container/btree_map.h" | |
| #include "absl/container/btree_set.h" | |
| #include "absl/container/internal/test_allocator.h" | |
| #include "absl/container/internal/test_instance_tracker.h" | |
| #include "absl/hash/hash_testing.h" | |
| #include "absl/memory/memory.h" | |
| #include "absl/random/random.h" | |
| #include "absl/strings/cord.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/str_split.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/types/compare.h" | |
| namespace absl { | |
| ABSL_NAMESPACE_BEGIN | |
| namespace container_internal { | |
| namespace { | |
| using ::absl::test_internal::CopyableMovableInstance; | |
| using ::absl::test_internal::InstanceTracker; | |
| using ::absl::test_internal::MovableOnlyInstance; | |
| using ::testing::ElementsAre; | |
| using ::testing::ElementsAreArray; | |
| using ::testing::IsEmpty; | |
| using ::testing::IsNull; | |
| using ::testing::Pair; | |
| using ::testing::SizeIs; | |
| } // namespace | |
| namespace { | |
| struct CompareIntToString { | |
| bool operator()(const std::string &a, const std::string &b) const { | |
| return a < b; | |
| } | |
| bool operator()(const std::string &a, int b) const { | |
| return a < absl::StrCat(b); | |
| } | |
| bool operator()(int a, const std::string &b) const { | |
| return absl::StrCat(a) < b; | |
| } | |
| using is_transparent = void; | |
| }; | |
| struct NonTransparentCompare { | |
| template <typename T, typename U> | |
| bool operator()(const T &t, const U &u) const { | |
| // Treating all comparators as transparent can cause inefficiencies (see | |
| // N3657 C++ proposal). Test that for comparators without 'is_transparent' | |
| // alias (like this one), we do not attempt heterogeneous lookup. | |
| EXPECT_TRUE((std::is_same<T, U>())); | |
| return t < u; | |
| } | |
| }; | |
| template <class T, class = void> | |
| struct CanEraseWithEmptyBrace : std::false_type {}; | |
| template <class T> | |
| struct CanEraseWithEmptyBrace< | |
| T, std::void_t<decltype(std::declval<T>().erase({}))*>> : std::true_type {}; | |
| template <typename T> | |
| void TestHeterogeneous(T table) { | |
| auto lb = table.lower_bound("3"); | |
| EXPECT_EQ(lb, table.lower_bound(3)); | |
| EXPECT_NE(lb, table.lower_bound(4)); | |
| EXPECT_EQ(lb, table.lower_bound({"3"})); | |
| EXPECT_NE(lb, table.lower_bound({})); | |
| auto ub = table.upper_bound("3"); | |
| EXPECT_EQ(ub, table.upper_bound(3)); | |
| EXPECT_NE(ub, table.upper_bound(5)); | |
| EXPECT_EQ(ub, table.upper_bound({"3"})); | |
| EXPECT_NE(ub, table.upper_bound({})); | |
| auto er = table.equal_range("3"); | |
| EXPECT_EQ(er, table.equal_range(3)); | |
| EXPECT_NE(er, table.equal_range(4)); | |
| EXPECT_EQ(er, table.equal_range({"3"})); | |
| EXPECT_NE(er, table.equal_range({})); | |
| auto it = table.find("3"); | |
| EXPECT_EQ(it, table.find(3)); | |
| EXPECT_NE(it, table.find(4)); | |
| EXPECT_EQ(it, table.find({"3"})); | |
| EXPECT_NE(it, table.find({})); | |
| EXPECT_TRUE(table.contains(3)); | |
| EXPECT_FALSE(table.contains(4)); | |
| EXPECT_TRUE(table.count({"3"})); | |
| EXPECT_FALSE(table.contains({})); | |
| EXPECT_EQ(1, table.count(3)); | |
| EXPECT_EQ(0, table.count(4)); | |
| EXPECT_EQ(1, table.count({"3"})); | |
| EXPECT_EQ(0, table.count({})); | |
| auto copy = table; | |
| copy.erase(3); | |
| EXPECT_EQ(table.size() - 1, copy.size()); | |
| copy.erase(4); | |
| EXPECT_EQ(table.size() - 1, copy.size()); | |
| copy.erase({"5"}); | |
| EXPECT_EQ(table.size() - 2, copy.size()); | |
| EXPECT_FALSE(CanEraseWithEmptyBrace<T>::value); | |
| // Also run it with const T&. | |
| if (std::is_class<T>()) TestHeterogeneous<const T &>(table); | |
| } | |
| TEST(Btree, HeterogeneousLookup) { | |
| TestHeterogeneous(btree_set<std::string, CompareIntToString>{"1", "3", "5"}); | |
| TestHeterogeneous(btree_map<std::string, int, CompareIntToString>{ | |
| {"1", 1}, {"3", 3}, {"5", 5}}); | |
| TestHeterogeneous( | |
| btree_multiset<std::string, CompareIntToString>{"1", "3", "5"}); | |
| TestHeterogeneous(btree_multimap<std::string, int, CompareIntToString>{ | |
| {"1", 1}, {"3", 3}, {"5", 5}}); | |
| // Only maps have .at() | |
| btree_map<std::string, int, CompareIntToString> map{ | |
| {"", -1}, {"1", 1}, {"3", 3}, {"5", 5}}; | |
| EXPECT_EQ(1, map.at(1)); | |
| EXPECT_EQ(3, map.at({"3"})); | |
| EXPECT_EQ(-1, map.at({})); | |
| const auto &cmap = map; | |
| EXPECT_EQ(1, cmap.at(1)); | |
| EXPECT_EQ(3, cmap.at({"3"})); | |
| EXPECT_EQ(-1, cmap.at({})); | |
| } | |
| TEST(Btree, NoHeterogeneousLookupWithoutAlias) { | |
| using StringSet = absl::btree_set<std::string, NonTransparentCompare>; | |
| StringSet s; | |
| ASSERT_TRUE(s.insert("hello").second); | |
| ASSERT_TRUE(s.insert("world").second); | |
| EXPECT_TRUE(s.end() == s.find("blah")); | |
| EXPECT_TRUE(s.begin() == s.lower_bound("hello")); | |
| EXPECT_EQ(1, s.count("world")); | |
| EXPECT_TRUE(s.contains("hello")); | |
| EXPECT_TRUE(s.contains("world")); | |
| EXPECT_FALSE(s.contains("blah")); | |
| using StringMultiSet = | |
| absl::btree_multiset<std::string, NonTransparentCompare>; | |
| StringMultiSet ms; | |
| ms.insert("hello"); | |
| ms.insert("world"); | |
| ms.insert("world"); | |
| EXPECT_TRUE(ms.end() == ms.find("blah")); | |
| EXPECT_TRUE(ms.begin() == ms.lower_bound("hello")); | |
| EXPECT_EQ(2, ms.count("world")); | |
| EXPECT_TRUE(ms.contains("hello")); | |
| EXPECT_TRUE(ms.contains("world")); | |
| EXPECT_FALSE(ms.contains("blah")); | |
| } | |
| TEST(Btree, DefaultTransparent) { | |
| { | |
| // `int` does not have a default transparent comparator. | |
| // The input value is converted to key_type. | |
| btree_set<int> s = {1}; | |
| double d = 1.1; | |
| EXPECT_EQ(s.begin(), s.find(d)); | |
| EXPECT_TRUE(s.contains(d)); | |
| } | |
| { | |
| // `std::string` has heterogeneous support. | |
| btree_set<std::string> s = {"A"}; | |
| EXPECT_EQ(s.begin(), s.find(absl::string_view("A"))); | |
| EXPECT_TRUE(s.contains(absl::string_view("A"))); | |
| } | |
| } | |
| class StringLike { | |
| public: | |
| StringLike() = default; | |
| StringLike(const char *s) : s_(s) { // NOLINT | |
| ++constructor_calls_; | |
| } | |
| bool operator<(const StringLike &a) const { return s_ < a.s_; } | |
| static void clear_constructor_call_count() { constructor_calls_ = 0; } | |
| static int constructor_calls() { return constructor_calls_; } | |
| private: | |
| static int constructor_calls_; | |
| std::string s_; | |
| }; | |
| int StringLike::constructor_calls_ = 0; | |
| TEST(Btree, HeterogeneousLookupDoesntDegradePerformance) { | |
| using StringSet = absl::btree_set<StringLike>; | |
| StringSet s; | |
| for (int i = 0; i < 100; ++i) { | |
| ASSERT_TRUE(s.insert(absl::StrCat(i).c_str()).second); | |
| } | |
| StringLike::clear_constructor_call_count(); | |
| s.find("50"); | |
| ASSERT_EQ(1, StringLike::constructor_calls()); | |
| StringLike::clear_constructor_call_count(); | |
| s.contains("50"); | |
| ASSERT_EQ(1, StringLike::constructor_calls()); | |
| StringLike::clear_constructor_call_count(); | |
| s.count("50"); | |
| ASSERT_EQ(1, StringLike::constructor_calls()); | |
| StringLike::clear_constructor_call_count(); | |
| s.lower_bound("50"); | |
| ASSERT_EQ(1, StringLike::constructor_calls()); | |
| StringLike::clear_constructor_call_count(); | |
| s.upper_bound("50"); | |
| ASSERT_EQ(1, StringLike::constructor_calls()); | |
| StringLike::clear_constructor_call_count(); | |
| s.equal_range("50"); | |
| ASSERT_EQ(1, StringLike::constructor_calls()); | |
| StringLike::clear_constructor_call_count(); | |
| s.erase("50"); | |
| ASSERT_EQ(1, StringLike::constructor_calls()); | |
| } | |
| // Verify that swapping btrees swaps the key comparison functors and that we can | |
| // use non-default constructible comparators. | |
| struct SubstringLess { | |
| SubstringLess() = delete; | |
| explicit SubstringLess(int length) : n(length) {} | |
| bool operator()(const std::string &a, const std::string &b) const { | |
| return absl::string_view(a).substr(0, n) < | |
| absl::string_view(b).substr(0, n); | |
| } | |
| int n; | |
| }; | |
| TEST(Btree, SwapKeyCompare) { | |
| using SubstringSet = absl::btree_set<std::string, SubstringLess>; | |
| SubstringSet s1(SubstringLess(1), SubstringSet::allocator_type()); | |
| SubstringSet s2(SubstringLess(2), SubstringSet::allocator_type()); | |
| ASSERT_TRUE(s1.insert("a").second); | |
| ASSERT_FALSE(s1.insert("aa").second); | |
| ASSERT_TRUE(s2.insert("a").second); | |
| ASSERT_TRUE(s2.insert("aa").second); | |
| ASSERT_FALSE(s2.insert("aaa").second); | |
| swap(s1, s2); | |
| ASSERT_TRUE(s1.insert("b").second); | |
| ASSERT_TRUE(s1.insert("bb").second); | |
| ASSERT_FALSE(s1.insert("bbb").second); | |
| ASSERT_TRUE(s2.insert("b").second); | |
| ASSERT_FALSE(s2.insert("bb").second); | |
| } | |
| TEST(Btree, UpperBoundRegression) { | |
| // Regress a bug where upper_bound would default-construct a new key_compare | |
| // instead of copying the existing one. | |
| using SubstringSet = absl::btree_set<std::string, SubstringLess>; | |
| SubstringSet my_set(SubstringLess(3)); | |
| my_set.insert("aab"); | |
| my_set.insert("abb"); | |
| // We call upper_bound("aaa"). If this correctly uses the length 3 | |
| // comparator, aaa < aab < abb, so we should get aab as the result. | |
| // If it instead uses the default-constructed length 2 comparator, | |
| // aa == aa < ab, so we'll get abb as our result. | |
| SubstringSet::iterator it = my_set.upper_bound("aaa"); | |
| ASSERT_TRUE(it != my_set.end()); | |
| EXPECT_EQ("aab", *it); | |
| } | |
| TEST(Btree, Comparison) { | |
| const int kSetSize = 1201; | |
| absl::btree_set<int64_t> my_set; | |
| for (int i = 0; i < kSetSize; ++i) { | |
| my_set.insert(i); | |
| } | |
| absl::btree_set<int64_t> my_set_copy(my_set); | |
| EXPECT_TRUE(my_set_copy == my_set); | |
| EXPECT_TRUE(my_set == my_set_copy); | |
| EXPECT_FALSE(my_set_copy != my_set); | |
| EXPECT_FALSE(my_set != my_set_copy); | |
| my_set.insert(kSetSize); | |
| EXPECT_FALSE(my_set_copy == my_set); | |
| EXPECT_FALSE(my_set == my_set_copy); | |
| EXPECT_TRUE(my_set_copy != my_set); | |
| EXPECT_TRUE(my_set != my_set_copy); | |
| my_set.erase(kSetSize - 1); | |
| EXPECT_FALSE(my_set_copy == my_set); | |
| EXPECT_FALSE(my_set == my_set_copy); | |
| EXPECT_TRUE(my_set_copy != my_set); | |
| EXPECT_TRUE(my_set != my_set_copy); | |
| absl::btree_map<std::string, int64_t> my_map; | |
| for (int i = 0; i < kSetSize; ++i) { | |
| my_map[std::string(i, 'a')] = i; | |
| } | |
| absl::btree_map<std::string, int64_t> my_map_copy(my_map); | |
| EXPECT_TRUE(my_map_copy == my_map); | |
| EXPECT_TRUE(my_map == my_map_copy); | |
| EXPECT_FALSE(my_map_copy != my_map); | |
| EXPECT_FALSE(my_map != my_map_copy); | |
| ++my_map_copy[std::string(7, 'a')]; | |
| EXPECT_FALSE(my_map_copy == my_map); | |
| EXPECT_FALSE(my_map == my_map_copy); | |
| EXPECT_TRUE(my_map_copy != my_map); | |
| EXPECT_TRUE(my_map != my_map_copy); | |
| my_map_copy = my_map; | |
| my_map["hello"] = kSetSize; | |
| EXPECT_FALSE(my_map_copy == my_map); | |
| EXPECT_FALSE(my_map == my_map_copy); | |
| EXPECT_TRUE(my_map_copy != my_map); | |
| EXPECT_TRUE(my_map != my_map_copy); | |
| my_map.erase(std::string(kSetSize - 1, 'a')); | |
| EXPECT_FALSE(my_map_copy == my_map); | |
| EXPECT_FALSE(my_map == my_map_copy); | |
| EXPECT_TRUE(my_map_copy != my_map); | |
| EXPECT_TRUE(my_map != my_map_copy); | |
| } | |
| TEST(Btree, RangeCtorSanity) { | |
| std::vector<int> ivec; | |
| ivec.push_back(1); | |
| std::map<int, int> imap; | |
| imap.insert(std::make_pair(1, 2)); | |
| absl::btree_multiset<int> tmset(ivec.begin(), ivec.end()); | |
| absl::btree_multimap<int, int> tmmap(imap.begin(), imap.end()); | |
| absl::btree_set<int> tset(ivec.begin(), ivec.end()); | |
| absl::btree_map<int, int> tmap(imap.begin(), imap.end()); | |
| EXPECT_EQ(1, tmset.size()); | |
| EXPECT_EQ(1, tmmap.size()); | |
| EXPECT_EQ(1, tset.size()); | |
| EXPECT_EQ(1, tmap.size()); | |
| } | |
| } // namespace | |
| class BtreeNodePeer { | |
| public: | |
| // Yields the size of a leaf node with a specific number of values. | |
| template <typename ValueType> | |
| constexpr static size_t GetTargetNodeSize(size_t target_values_per_node) { | |
| return btree_node< | |
| set_params<ValueType, std::less<ValueType>, std::allocator<ValueType>, | |
| /*TargetNodeSize=*/256, // This parameter isn't used here. | |
| /*Multi=*/false>>::SizeWithNSlots(target_values_per_node); | |
| } | |
| // Yields the number of slots in a (non-root) leaf node for this btree. | |
| template <typename Btree> | |
| constexpr static size_t GetNumSlotsPerNode() { | |
| return btree_node<typename Btree::params_type>::kNodeSlots; | |
| } | |
| template <typename Btree> | |
| constexpr static size_t GetMaxFieldType() { | |
| return std::numeric_limits< | |
| typename btree_node<typename Btree::params_type>::field_type>::max(); | |
| } | |
| template <typename Btree> | |
| constexpr static bool UsesLinearNodeSearch() { | |
| return btree_node<typename Btree::params_type>::use_linear_search::value; | |
| } | |
| template <typename Btree> | |
| constexpr static bool FieldTypeEqualsSlotType() { | |
| return std::is_same_v< | |
| typename btree_node<typename Btree::params_type>::field_type, | |
| typename btree_node<typename Btree::params_type>::slot_type>; | |
| } | |
| }; | |
| namespace { | |
| class BtreeMapTest : public ::testing::Test { | |
| public: | |
| struct Key {}; | |
| struct Cmp { | |
| template <typename T> | |
| [[maybe_unused]] bool operator()(T, T) const { | |
| return false; | |
| } | |
| }; | |
| struct KeyLin { | |
| using absl_btree_prefer_linear_node_search = std::true_type; | |
| }; | |
| struct CmpLin : Cmp { | |
| using absl_btree_prefer_linear_node_search = std::true_type; | |
| }; | |
| struct KeyBin { | |
| using absl_btree_prefer_linear_node_search = std::false_type; | |
| }; | |
| struct CmpBin : Cmp { | |
| using absl_btree_prefer_linear_node_search = std::false_type; | |
| }; | |
| template <typename K, typename C> | |
| static bool IsLinear() { | |
| return BtreeNodePeer::UsesLinearNodeSearch<absl::btree_map<K, int, C>>(); | |
| } | |
| }; | |
| TEST_F(BtreeMapTest, TestLinearSearchPreferredForKeyLinearViaAlias) { | |
| // Test requesting linear search by directly exporting an alias. | |
| EXPECT_FALSE((IsLinear<Key, Cmp>())); | |
| EXPECT_TRUE((IsLinear<KeyLin, Cmp>())); | |
| EXPECT_TRUE((IsLinear<Key, CmpLin>())); | |
| EXPECT_TRUE((IsLinear<KeyLin, CmpLin>())); | |
| } | |
| TEST_F(BtreeMapTest, LinearChoiceTree) { | |
| // Cmp has precedence, and is forcing binary | |
| EXPECT_FALSE((IsLinear<Key, CmpBin>())); | |
| EXPECT_FALSE((IsLinear<KeyLin, CmpBin>())); | |
| EXPECT_FALSE((IsLinear<KeyBin, CmpBin>())); | |
| EXPECT_FALSE((IsLinear<int, CmpBin>())); | |
| EXPECT_FALSE((IsLinear<std::string, CmpBin>())); | |
| // Cmp has precedence, and is forcing linear | |
| EXPECT_TRUE((IsLinear<Key, CmpLin>())); | |
| EXPECT_TRUE((IsLinear<KeyLin, CmpLin>())); | |
| EXPECT_TRUE((IsLinear<KeyBin, CmpLin>())); | |
| EXPECT_TRUE((IsLinear<int, CmpLin>())); | |
| EXPECT_TRUE((IsLinear<std::string, CmpLin>())); | |
| // Cmp has no preference, Key determines linear vs binary. | |
| EXPECT_FALSE((IsLinear<Key, Cmp>())); | |
| EXPECT_TRUE((IsLinear<KeyLin, Cmp>())); | |
| EXPECT_FALSE((IsLinear<KeyBin, Cmp>())); | |
| // arithmetic key w/ std::less or std::greater: linear | |
| EXPECT_TRUE((IsLinear<int, std::less<int>>())); | |
| EXPECT_TRUE((IsLinear<double, std::greater<double>>())); | |
| // arithmetic key w/ custom compare: binary | |
| EXPECT_FALSE((IsLinear<int, Cmp>())); | |
| // non-arithmetic key: binary | |
| EXPECT_FALSE((IsLinear<std::string, std::less<std::string>>())); | |
| } | |
| TEST(Btree, BtreeMapCanHoldMoveOnlyTypes) { | |
| absl::btree_map<std::string, std::unique_ptr<std::string>> m; | |
| std::unique_ptr<std::string> &v = m["A"]; | |
| EXPECT_TRUE(v == nullptr); | |
| v = std::make_unique<std::string>("X"); | |
| auto iter = m.find("A"); | |
| EXPECT_EQ("X", *iter->second); | |
| } | |
| TEST(Btree, InitializerListConstructor) { | |
| absl::btree_set<std::string> set({"a", "b"}); | |
| EXPECT_EQ(set.count("a"), 1); | |
| EXPECT_EQ(set.count("b"), 1); | |
| absl::btree_multiset<int> mset({1, 1, 4}); | |
| EXPECT_EQ(mset.count(1), 2); | |
| EXPECT_EQ(mset.count(4), 1); | |
| absl::btree_map<int, int> map({{1, 5}, {2, 10}}); | |
| EXPECT_EQ(map[1], 5); | |
| EXPECT_EQ(map[2], 10); | |
| absl::btree_multimap<int, int> mmap({{1, 5}, {1, 10}}); | |
| auto range = mmap.equal_range(1); | |
| auto it = range.first; | |
| ASSERT_NE(it, range.second); | |
| EXPECT_EQ(it->second, 5); | |
| ASSERT_NE(++it, range.second); | |
| EXPECT_EQ(it->second, 10); | |
| EXPECT_EQ(++it, range.second); | |
| } | |
| TEST(Btree, InitializerListInsert) { | |
| absl::btree_set<std::string> set; | |
| set.insert({"a", "b"}); | |
| EXPECT_EQ(set.count("a"), 1); | |
| EXPECT_EQ(set.count("b"), 1); | |
| absl::btree_multiset<int> mset; | |
| mset.insert({1, 1, 4}); | |
| EXPECT_EQ(mset.count(1), 2); | |
| EXPECT_EQ(mset.count(4), 1); | |
| absl::btree_map<int, int> map; | |
| map.insert({{1, 5}, {2, 10}}); | |
| // Test that inserting one element using an initializer list also works. | |
| map.insert({3, 15}); | |
| EXPECT_EQ(map[1], 5); | |
| EXPECT_EQ(map[2], 10); | |
| EXPECT_EQ(map[3], 15); | |
| absl::btree_multimap<int, int> mmap; | |
| mmap.insert({{1, 5}, {1, 10}}); | |
| auto range = mmap.equal_range(1); | |
| auto it = range.first; | |
| ASSERT_NE(it, range.second); | |
| EXPECT_EQ(it->second, 5); | |
| ASSERT_NE(++it, range.second); | |
| EXPECT_EQ(it->second, 10); | |
| EXPECT_EQ(++it, range.second); | |
| } | |
| template <typename Compare, typename Key> | |
| void AssertKeyCompareStringAdapted() { | |
| using Adapted = typename key_compare_adapter<Compare, Key>::type; | |
| static_assert( | |
| std::is_same_v<Adapted, StringBtreeDefaultLess> || | |
| std::is_same_v<Adapted, StringBtreeDefaultGreater>, | |
| "key_compare_adapter should have string-adapted this comparator."); | |
| } | |
| template <typename Compare, typename Key> | |
| void AssertKeyCompareNotStringAdapted() { | |
| using Adapted = typename key_compare_adapter<Compare, Key>::type; | |
| static_assert( | |
| !std::is_same_v<Adapted, StringBtreeDefaultLess> && | |
| !std::is_same_v<Adapted, StringBtreeDefaultGreater>, | |
| "key_compare_adapter shouldn't have string-adapted this comparator."); | |
| } | |
| TEST(Btree, KeyCompareAdapter) { | |
| AssertKeyCompareStringAdapted<std::less<std::string>, std::string>(); | |
| AssertKeyCompareStringAdapted<std::greater<std::string>, std::string>(); | |
| AssertKeyCompareStringAdapted<std::less<absl::string_view>, | |
| absl::string_view>(); | |
| AssertKeyCompareStringAdapted<std::greater<absl::string_view>, | |
| absl::string_view>(); | |
| AssertKeyCompareStringAdapted<std::less<absl::Cord>, absl::Cord>(); | |
| AssertKeyCompareStringAdapted<std::greater<absl::Cord>, absl::Cord>(); | |
| AssertKeyCompareNotStringAdapted<std::less<int>, int>(); | |
| AssertKeyCompareNotStringAdapted<std::greater<int>, int>(); | |
| } | |
| TEST(Btree, RValueInsert) { | |
| InstanceTracker tracker; | |
| absl::btree_set<MovableOnlyInstance> set; | |
| set.insert(MovableOnlyInstance(1)); | |
| set.insert(MovableOnlyInstance(3)); | |
| MovableOnlyInstance two(2); | |
| set.insert(set.find(MovableOnlyInstance(3)), std::move(two)); | |
| auto it = set.find(MovableOnlyInstance(2)); | |
| ASSERT_NE(it, set.end()); | |
| ASSERT_NE(++it, set.end()); | |
| EXPECT_EQ(it->value(), 3); | |
| absl::btree_multiset<MovableOnlyInstance> mset; | |
| MovableOnlyInstance zero(0); | |
| MovableOnlyInstance zero2(0); | |
| mset.insert(std::move(zero)); | |
| mset.insert(mset.find(MovableOnlyInstance(0)), std::move(zero2)); | |
| EXPECT_EQ(mset.count(MovableOnlyInstance(0)), 2); | |
| absl::btree_map<int, MovableOnlyInstance> map; | |
| std::pair<const int, MovableOnlyInstance> p1 = {1, MovableOnlyInstance(5)}; | |
| std::pair<const int, MovableOnlyInstance> p2 = {2, MovableOnlyInstance(10)}; | |
| std::pair<const int, MovableOnlyInstance> p3 = {3, MovableOnlyInstance(15)}; | |
| map.insert(std::move(p1)); | |
| map.insert(std::move(p3)); | |
| map.insert(map.find(3), std::move(p2)); | |
| ASSERT_NE(map.find(2), map.end()); | |
| EXPECT_EQ(map.find(2)->second.value(), 10); | |
| absl::btree_multimap<int, MovableOnlyInstance> mmap; | |
| std::pair<const int, MovableOnlyInstance> p4 = {1, MovableOnlyInstance(5)}; | |
| std::pair<const int, MovableOnlyInstance> p5 = {1, MovableOnlyInstance(10)}; | |
| mmap.insert(std::move(p4)); | |
| mmap.insert(mmap.find(1), std::move(p5)); | |
| auto range = mmap.equal_range(1); | |
| auto it1 = range.first; | |
| ASSERT_NE(it1, range.second); | |
| EXPECT_EQ(it1->second.value(), 10); | |
| ASSERT_NE(++it1, range.second); | |
| EXPECT_EQ(it1->second.value(), 5); | |
| EXPECT_EQ(++it1, range.second); | |
| EXPECT_EQ(tracker.copies(), 0); | |
| EXPECT_EQ(tracker.swaps(), 0); | |
| } | |
| template <typename Cmp> | |
| struct CheckedCompareOptedOutCmp : Cmp, BtreeTestOnlyCheckedCompareOptOutBase { | |
| using Cmp::Cmp; | |
| CheckedCompareOptedOutCmp() {} | |
| CheckedCompareOptedOutCmp(Cmp cmp) : Cmp(std::move(cmp)) {} // NOLINT | |
| }; | |
| // A btree set with a specific number of values per node. Opt out of | |
| // checked_compare so that we can expect exact numbers of comparisons. | |
| template <typename Key, int TargetValuesPerNode, typename Cmp = std::less<Key>> | |
| class SizedBtreeSet | |
| : public btree_set_container<btree< | |
| set_params<Key, CheckedCompareOptedOutCmp<Cmp>, std::allocator<Key>, | |
| BtreeNodePeer::GetTargetNodeSize<Key>(TargetValuesPerNode), | |
| /*Multi=*/false>>> { | |
| using Base = typename SizedBtreeSet::btree_set_container; | |
| public: | |
| SizedBtreeSet() = default; | |
| using Base::Base; | |
| }; | |
| template <typename Set> | |
| void ExpectOperationCounts(const int expected_moves, | |
| const int expected_comparisons, | |
| const std::vector<int> &values, | |
| InstanceTracker *tracker, Set *set) { | |
| for (const int v : values) set->insert(MovableOnlyInstance(v)); | |
| set->clear(); | |
| EXPECT_EQ(tracker->moves(), expected_moves); | |
| EXPECT_EQ(tracker->comparisons(), expected_comparisons); | |
| EXPECT_EQ(tracker->copies(), 0); | |
| EXPECT_EQ(tracker->swaps(), 0); | |
| tracker->ResetCopiesMovesSwaps(); | |
| } | |
| #if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ | |
| defined(ABSL_HAVE_HWADDRESS_SANITIZER) | |
| constexpr bool kAsan = true; | |
| #else | |
| constexpr bool kAsan = false; | |
| #endif | |
| // Note: when the values in this test change, it is expected to have an impact | |
| // on performance. | |
| TEST(Btree, MovesComparisonsCopiesSwapsTracking) { | |
| if (kAsan) GTEST_SKIP() << "We do extra operations in ASan mode."; | |
| InstanceTracker tracker; | |
| // Note: this is minimum number of values per node. | |
| SizedBtreeSet<MovableOnlyInstance, /*TargetValuesPerNode=*/4> set4; | |
| // Note: this is the default number of values per node for a set of int32s | |
| // (with 64-bit pointers). | |
| SizedBtreeSet<MovableOnlyInstance, /*TargetValuesPerNode=*/61> set61; | |
| SizedBtreeSet<MovableOnlyInstance, /*TargetValuesPerNode=*/100> set100; | |
| // Don't depend on flags for random values because then the expectations will | |
| // fail if the flags change. | |
| std::vector<int> values = | |
| GenerateValuesWithSeed<int>(10000, 1 << 22, /*seed=*/23); | |
| EXPECT_EQ(BtreeNodePeer::GetNumSlotsPerNode<decltype(set4)>(), 4); | |
| EXPECT_EQ(BtreeNodePeer::GetNumSlotsPerNode<decltype(set61)>(), 61); | |
| EXPECT_EQ(BtreeNodePeer::GetNumSlotsPerNode<decltype(set100)>(), 100); | |
| if (sizeof(void *) == 8) { | |
| EXPECT_EQ(BtreeNodePeer::GetNumSlotsPerNode<absl::btree_set<int32_t>>(), | |
| // When we have generations, there is one fewer slot. | |
| BtreeGenerationsEnabled() ? 60 : 61); | |
| } | |
| // Test key insertion/deletion in random order. | |
| ExpectOperationCounts(56540, 134212, values, &tracker, &set4); | |
| ExpectOperationCounts(386718, 129807, values, &tracker, &set61); | |
| ExpectOperationCounts(586761, 130310, values, &tracker, &set100); | |
| // Test key insertion/deletion in sorted order. | |
| std::sort(values.begin(), values.end()); | |
| ExpectOperationCounts(24972, 85563, values, &tracker, &set4); | |
| ExpectOperationCounts(20208, 87757, values, &tracker, &set61); | |
| ExpectOperationCounts(20124, 96583, values, &tracker, &set100); | |
| // Test key insertion/deletion in reverse sorted order. | |
| std::reverse(values.begin(), values.end()); | |
| ExpectOperationCounts(54949, 127531, values, &tracker, &set4); | |
| ExpectOperationCounts(338813, 118266, values, &tracker, &set61); | |
| ExpectOperationCounts(534529, 125279, values, &tracker, &set100); | |
| } | |
| struct MovableOnlyInstanceThreeWayCompare { | |
| absl::weak_ordering operator()(const MovableOnlyInstance &a, | |
| const MovableOnlyInstance &b) const { | |
| return a.compare(b); | |
| } | |
| }; | |
| // Note: when the values in this test change, it is expected to have an impact | |
| // on performance. | |
| TEST(Btree, MovesComparisonsCopiesSwapsTrackingThreeWayCompare) { | |
| if (kAsan) GTEST_SKIP() << "We do extra operations in ASan mode."; | |
| InstanceTracker tracker; | |
| // Note: this is minimum number of values per node. | |
| SizedBtreeSet<MovableOnlyInstance, /*TargetValuesPerNode=*/4, | |
| MovableOnlyInstanceThreeWayCompare> | |
| set4; | |
| // Note: this is the default number of values per node for a set of int32s | |
| // (with 64-bit pointers). | |
| SizedBtreeSet<MovableOnlyInstance, /*TargetValuesPerNode=*/61, | |
| MovableOnlyInstanceThreeWayCompare> | |
| set61; | |
| SizedBtreeSet<MovableOnlyInstance, /*TargetValuesPerNode=*/100, | |
| MovableOnlyInstanceThreeWayCompare> | |
| set100; | |
| // Don't depend on flags for random values because then the expectations will | |
| // fail if the flags change. | |
| std::vector<int> values = | |
| GenerateValuesWithSeed<int>(10000, 1 << 22, /*seed=*/23); | |
| EXPECT_EQ(BtreeNodePeer::GetNumSlotsPerNode<decltype(set4)>(), 4); | |
| EXPECT_EQ(BtreeNodePeer::GetNumSlotsPerNode<decltype(set61)>(), 61); | |
| EXPECT_EQ(BtreeNodePeer::GetNumSlotsPerNode<decltype(set100)>(), 100); | |
| if (sizeof(void *) == 8) { | |
| EXPECT_EQ(BtreeNodePeer::GetNumSlotsPerNode<absl::btree_set<int32_t>>(), | |
| // When we have generations, there is one fewer slot. | |
| BtreeGenerationsEnabled() ? 60 : 61); | |
| } | |
| // Test key insertion/deletion in random order. | |
| ExpectOperationCounts(56540, 124221, values, &tracker, &set4); | |
| ExpectOperationCounts(386718, 119816, values, &tracker, &set61); | |
| ExpectOperationCounts(586761, 120319, values, &tracker, &set100); | |
| // Test key insertion/deletion in sorted order. | |
| std::sort(values.begin(), values.end()); | |
| ExpectOperationCounts(24972, 85563, values, &tracker, &set4); | |
| ExpectOperationCounts(20208, 87757, values, &tracker, &set61); | |
| ExpectOperationCounts(20124, 96583, values, &tracker, &set100); | |
| // Test key insertion/deletion in reverse sorted order. | |
| std::reverse(values.begin(), values.end()); | |
| ExpectOperationCounts(54949, 117532, values, &tracker, &set4); | |
| ExpectOperationCounts(338813, 108267, values, &tracker, &set61); | |
| ExpectOperationCounts(534529, 115280, values, &tracker, &set100); | |
| } | |
| struct NoDefaultCtor { | |
| int num; | |
| explicit NoDefaultCtor(int i) : num(i) {} | |
| friend bool operator<(const NoDefaultCtor &a, const NoDefaultCtor &b) { | |
| return a.num < b.num; | |
| } | |
| }; | |
| TEST(Btree, BtreeMapCanHoldNoDefaultCtorTypes) { | |
| absl::btree_map<NoDefaultCtor, NoDefaultCtor> m; | |
| for (int i = 1; i <= 99; ++i) { | |
| SCOPED_TRACE(i); | |
| EXPECT_TRUE(m.emplace(NoDefaultCtor(i), NoDefaultCtor(100 - i)).second); | |
| } | |
| EXPECT_FALSE(m.emplace(NoDefaultCtor(78), NoDefaultCtor(0)).second); | |
| auto iter99 = m.find(NoDefaultCtor(99)); | |
| ASSERT_NE(iter99, m.end()); | |
| EXPECT_EQ(iter99->second.num, 1); | |
| auto iter1 = m.find(NoDefaultCtor(1)); | |
| ASSERT_NE(iter1, m.end()); | |
| EXPECT_EQ(iter1->second.num, 99); | |
| auto iter50 = m.find(NoDefaultCtor(50)); | |
| ASSERT_NE(iter50, m.end()); | |
| EXPECT_EQ(iter50->second.num, 50); | |
| auto iter25 = m.find(NoDefaultCtor(25)); | |
| ASSERT_NE(iter25, m.end()); | |
| EXPECT_EQ(iter25->second.num, 75); | |
| } | |
| TEST(Btree, BtreeMultimapCanHoldNoDefaultCtorTypes) { | |
| absl::btree_multimap<NoDefaultCtor, NoDefaultCtor> m; | |
| for (int i = 1; i <= 99; ++i) { | |
| SCOPED_TRACE(i); | |
| m.emplace(NoDefaultCtor(i), NoDefaultCtor(100 - i)); | |
| } | |
| auto iter99 = m.find(NoDefaultCtor(99)); | |
| ASSERT_NE(iter99, m.end()); | |
| EXPECT_EQ(iter99->second.num, 1); | |
| auto iter1 = m.find(NoDefaultCtor(1)); | |
| ASSERT_NE(iter1, m.end()); | |
| EXPECT_EQ(iter1->second.num, 99); | |
| auto iter50 = m.find(NoDefaultCtor(50)); | |
| ASSERT_NE(iter50, m.end()); | |
| EXPECT_EQ(iter50->second.num, 50); | |
| auto iter25 = m.find(NoDefaultCtor(25)); | |
| ASSERT_NE(iter25, m.end()); | |
| EXPECT_EQ(iter25->second.num, 75); | |
| } | |
| TEST(Btree, MapAt) { | |
| absl::btree_map<int, int> map = {{1, 2}, {2, 4}}; | |
| EXPECT_EQ(map.at(1), 2); | |
| EXPECT_EQ(map.at(2), 4); | |
| map.at(2) = 8; | |
| const absl::btree_map<int, int> &const_map = map; | |
| EXPECT_EQ(const_map.at(1), 2); | |
| EXPECT_EQ(const_map.at(2), 8); | |
| #ifdef ABSL_HAVE_EXCEPTIONS | |
| EXPECT_THROW(map.at(3), std::out_of_range); | |
| #else | |
| EXPECT_DEATH_IF_SUPPORTED(map.at(3), "absl::btree_map::at"); | |
| #endif | |
| } | |
| TEST(Btree, BtreeMultisetEmplace) { | |
| const int value_to_insert = 123456; | |
| absl::btree_multiset<int> s; | |
| auto iter = s.emplace(value_to_insert); | |
| ASSERT_NE(iter, s.end()); | |
| EXPECT_EQ(*iter, value_to_insert); | |
| iter = s.emplace(value_to_insert); | |
| ASSERT_NE(iter, s.end()); | |
| EXPECT_EQ(*iter, value_to_insert); | |
| auto result = s.equal_range(value_to_insert); | |
| EXPECT_EQ(std::distance(result.first, result.second), 2); | |
| } | |
| TEST(Btree, BtreeMultisetEmplaceHint) { | |
| const int value_to_insert = 123456; | |
| absl::btree_multiset<int> s; | |
| auto iter = s.emplace(value_to_insert); | |
| ASSERT_NE(iter, s.end()); | |
| EXPECT_EQ(*iter, value_to_insert); | |
| iter = s.emplace_hint(iter, value_to_insert); | |
| // The new element should be before the previously inserted one. | |
| EXPECT_EQ(iter, s.lower_bound(value_to_insert)); | |
| ASSERT_NE(iter, s.end()); | |
| EXPECT_EQ(*iter, value_to_insert); | |
| } | |
| TEST(Btree, BtreeMultimapEmplace) { | |
| const int key_to_insert = 123456; | |
| const char value0[] = "a"; | |
| absl::btree_multimap<int, std::string> m; | |
| auto iter = m.emplace(key_to_insert, value0); | |
| ASSERT_NE(iter, m.end()); | |
| EXPECT_EQ(iter->first, key_to_insert); | |
| EXPECT_EQ(iter->second, value0); | |
| const char value1[] = "b"; | |
| iter = m.emplace(key_to_insert, value1); | |
| ASSERT_NE(iter, m.end()); | |
| EXPECT_EQ(iter->first, key_to_insert); | |
| EXPECT_EQ(iter->second, value1); | |
| auto result = m.equal_range(key_to_insert); | |
| EXPECT_EQ(std::distance(result.first, result.second), 2); | |
| } | |
| TEST(Btree, BtreeMultimapEmplaceHint) { | |
| const int key_to_insert = 123456; | |
| const char value0[] = "a"; | |
| absl::btree_multimap<int, std::string> m; | |
| auto iter = m.emplace(key_to_insert, value0); | |
| ASSERT_NE(iter, m.end()); | |
| EXPECT_EQ(iter->first, key_to_insert); | |
| EXPECT_EQ(iter->second, value0); | |
| const char value1[] = "b"; | |
| iter = m.emplace_hint(iter, key_to_insert, value1); | |
| // The new element should be before the previously inserted one. | |
| EXPECT_EQ(iter, m.lower_bound(key_to_insert)); | |
| ASSERT_NE(iter, m.end()); | |
| EXPECT_EQ(iter->first, key_to_insert); | |
| EXPECT_EQ(iter->second, value1); | |
| } | |
| TEST(Btree, ConstIteratorAccessors) { | |
| absl::btree_set<int> set; | |
| for (int i = 0; i < 100; ++i) { | |
| set.insert(i); | |
| } | |
| auto it = set.cbegin(); | |
| auto r_it = set.crbegin(); | |
| for (int i = 0; i < 100; ++i, ++it, ++r_it) { | |
| ASSERT_EQ(*it, i); | |
| ASSERT_EQ(*r_it, 99 - i); | |
| } | |
| EXPECT_EQ(it, set.cend()); | |
| EXPECT_EQ(r_it, set.crend()); | |
| } | |
| TEST(Btree, StrSplitCompatible) { | |
| const absl::btree_set<std::string> split_set = absl::StrSplit("a,b,c", ','); | |
| const absl::btree_set<std::string> expected_set = {"a", "b", "c"}; | |
| EXPECT_EQ(split_set, expected_set); | |
| } | |
| TEST(Btree, KeyComp) { | |
| absl::btree_set<int> s; | |
| EXPECT_TRUE(s.key_comp()(1, 2)); | |
| EXPECT_FALSE(s.key_comp()(2, 2)); | |
| EXPECT_FALSE(s.key_comp()(2, 1)); | |
| absl::btree_map<int, int> m1; | |
| EXPECT_TRUE(m1.key_comp()(1, 2)); | |
| EXPECT_FALSE(m1.key_comp()(2, 2)); | |
| EXPECT_FALSE(m1.key_comp()(2, 1)); | |
| // Even though we internally adapt the comparator of `m2` to be three-way and | |
| // heterogeneous, the comparator we expose through key_comp() is the original | |
| // unadapted comparator. | |
| absl::btree_map<std::string, int> m2; | |
| EXPECT_TRUE(m2.key_comp()("a", "b")); | |
| EXPECT_FALSE(m2.key_comp()("b", "b")); | |
| EXPECT_FALSE(m2.key_comp()("b", "a")); | |
| } | |
| TEST(Btree, ValueComp) { | |
| absl::btree_set<int> s; | |
| EXPECT_TRUE(s.value_comp()(1, 2)); | |
| EXPECT_FALSE(s.value_comp()(2, 2)); | |
| EXPECT_FALSE(s.value_comp()(2, 1)); | |
| absl::btree_map<int, int> m1; | |
| EXPECT_TRUE(m1.value_comp()(std::make_pair(1, 0), std::make_pair(2, 0))); | |
| EXPECT_FALSE(m1.value_comp()(std::make_pair(2, 0), std::make_pair(2, 0))); | |
| EXPECT_FALSE(m1.value_comp()(std::make_pair(2, 0), std::make_pair(1, 0))); | |
| // Even though we internally adapt the comparator of `m2` to be three-way and | |
| // heterogeneous, the comparator we expose through value_comp() is based on | |
| // the original unadapted comparator. | |
| absl::btree_map<std::string, int> m2; | |
| EXPECT_TRUE(m2.value_comp()(std::make_pair("a", 0), std::make_pair("b", 0))); | |
| EXPECT_FALSE(m2.value_comp()(std::make_pair("b", 0), std::make_pair("b", 0))); | |
| EXPECT_FALSE(m2.value_comp()(std::make_pair("b", 0), std::make_pair("a", 0))); | |
| } | |
| // Test that we have the protected members from the std::map::value_compare API. | |
| // See https://en.cppreference.com/w/cpp/container/map/value_compare. | |
| TEST(Btree, MapValueCompProtected) { | |
| struct key_compare { | |
| bool operator()(int l, int r) const { return l < r; } | |
| int id; | |
| }; | |
| using value_compare = absl::btree_map<int, int, key_compare>::value_compare; | |
| struct value_comp_child : public value_compare { | |
| explicit value_comp_child(key_compare kc) : value_compare(kc) {} | |
| int GetId() const { return comp.id; } | |
| }; | |
| value_comp_child c(key_compare{10}); | |
| EXPECT_EQ(c.GetId(), 10); | |
| } | |
| TEST(Btree, DefaultConstruction) { | |
| absl::btree_set<int> s; | |
| absl::btree_map<int, int> m; | |
| absl::btree_multiset<int> ms; | |
| absl::btree_multimap<int, int> mm; | |
| EXPECT_TRUE(s.empty()); | |
| EXPECT_TRUE(m.empty()); | |
| EXPECT_TRUE(ms.empty()); | |
| EXPECT_TRUE(mm.empty()); | |
| } | |
| TEST(Btree, SwissTableHashable) { | |
| static constexpr int kValues = 10000; | |
| std::vector<int> values(kValues); | |
| std::iota(values.begin(), values.end(), 0); | |
| std::vector<std::pair<int, int>> map_values; | |
| for (int v : values) map_values.emplace_back(v, -v); | |
| using set = absl::btree_set<int>; | |
| EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({ | |
| set{}, | |
| set{1}, | |
| set{2}, | |
| set{1, 2}, | |
| set{2, 1}, | |
| set(values.begin(), values.end()), | |
| set(values.rbegin(), values.rend()), | |
| })); | |
| using mset = absl::btree_multiset<int>; | |
| EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({ | |
| mset{}, | |
| mset{1}, | |
| mset{1, 1}, | |
| mset{2}, | |
| mset{2, 2}, | |
| mset{1, 2}, | |
| mset{1, 1, 2}, | |
| mset{1, 2, 2}, | |
| mset{1, 1, 2, 2}, | |
| mset(values.begin(), values.end()), | |
| mset(values.rbegin(), values.rend()), | |
| })); | |
| using map = absl::btree_map<int, int>; | |
| EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({ | |
| map{}, | |
| map{{1, 0}}, | |
| map{{1, 1}}, | |
| map{{2, 0}}, | |
| map{{2, 2}}, | |
| map{{1, 0}, {2, 1}}, | |
| map(map_values.begin(), map_values.end()), | |
| map(map_values.rbegin(), map_values.rend()), | |
| })); | |
| using mmap = absl::btree_multimap<int, int>; | |
| EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({ | |
| mmap{}, | |
| mmap{{1, 0}}, | |
| mmap{{1, 1}}, | |
| mmap{{1, 0}, {1, 1}}, | |
| mmap{{1, 1}, {1, 0}}, | |
| mmap{{2, 0}}, | |
| mmap{{2, 2}}, | |
| mmap{{1, 0}, {2, 1}}, | |
| mmap(map_values.begin(), map_values.end()), | |
| mmap(map_values.rbegin(), map_values.rend()), | |
| })); | |
| } | |
| TEST(Btree, ComparableSet) { | |
| absl::btree_set<int> s1 = {1, 2}; | |
| absl::btree_set<int> s2 = {2, 3}; | |
| EXPECT_LT(s1, s2); | |
| EXPECT_LE(s1, s2); | |
| EXPECT_LE(s1, s1); | |
| EXPECT_GT(s2, s1); | |
| EXPECT_GE(s2, s1); | |
| EXPECT_GE(s1, s1); | |
| } | |
| TEST(Btree, ComparableSetsDifferentLength) { | |
| absl::btree_set<int> s1 = {1, 2}; | |
| absl::btree_set<int> s2 = {1, 2, 3}; | |
| EXPECT_LT(s1, s2); | |
| EXPECT_LE(s1, s2); | |
| EXPECT_GT(s2, s1); | |
| EXPECT_GE(s2, s1); | |
| } | |
| TEST(Btree, ComparableMultiset) { | |
| absl::btree_multiset<int> s1 = {1, 2}; | |
| absl::btree_multiset<int> s2 = {2, 3}; | |
| EXPECT_LT(s1, s2); | |
| EXPECT_LE(s1, s2); | |
| EXPECT_LE(s1, s1); | |
| EXPECT_GT(s2, s1); | |
| EXPECT_GE(s2, s1); | |
| EXPECT_GE(s1, s1); | |
| } | |
| TEST(Btree, ComparableMap) { | |
| absl::btree_map<int, int> s1 = {{1, 2}}; | |
| absl::btree_map<int, int> s2 = {{2, 3}}; | |
| EXPECT_LT(s1, s2); | |
| EXPECT_LE(s1, s2); | |
| EXPECT_LE(s1, s1); | |
| EXPECT_GT(s2, s1); | |
| EXPECT_GE(s2, s1); | |
| EXPECT_GE(s1, s1); | |
| } | |
| TEST(Btree, ComparableMultimap) { | |
| absl::btree_multimap<int, int> s1 = {{1, 2}}; | |
| absl::btree_multimap<int, int> s2 = {{2, 3}}; | |
| EXPECT_LT(s1, s2); | |
| EXPECT_LE(s1, s2); | |
| EXPECT_LE(s1, s1); | |
| EXPECT_GT(s2, s1); | |
| EXPECT_GE(s2, s1); | |
| EXPECT_GE(s1, s1); | |
| } | |
| TEST(Btree, ComparableSetWithCustomComparator) { | |
| // As specified by | |
| // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf section | |
| // [container.requirements.general].12, ordering associative containers always | |
| // uses default '<' operator | |
| // - even if otherwise the container uses custom functor. | |
| absl::btree_set<int, std::greater<int>> s1 = {1, 2}; | |
| absl::btree_set<int, std::greater<int>> s2 = {2, 3}; | |
| EXPECT_LT(s1, s2); | |
| EXPECT_LE(s1, s2); | |
| EXPECT_LE(s1, s1); | |
| EXPECT_GT(s2, s1); | |
| EXPECT_GE(s2, s1); | |
| EXPECT_GE(s1, s1); | |
| } | |
| TEST(Btree, EraseReturnsIterator) { | |
| absl::btree_set<int> set = {1, 2, 3, 4, 5}; | |
| auto result_it = set.erase(set.begin(), set.find(3)); | |
| EXPECT_EQ(result_it, set.find(3)); | |
| result_it = set.erase(set.find(5)); | |
| EXPECT_EQ(result_it, set.end()); | |
| } | |
| TEST(Btree, ExtractAndInsertNodeHandleSet) { | |
| absl::btree_set<int> src1 = {1, 2, 3, 4, 5}; | |
| auto nh = src1.extract(src1.find(3)); | |
| EXPECT_THAT(src1, ElementsAre(1, 2, 4, 5)); | |
| absl::btree_set<int> other; | |
| absl::btree_set<int>::insert_return_type res = other.insert(std::move(nh)); | |
| EXPECT_THAT(other, ElementsAre(3)); | |
| EXPECT_EQ(res.position, other.find(3)); | |
| EXPECT_TRUE(res.inserted); | |
| EXPECT_TRUE(res.node.empty()); | |
| absl::btree_set<int> src2 = {3, 4}; | |
| nh = src2.extract(src2.find(3)); | |
| EXPECT_THAT(src2, ElementsAre(4)); | |
| res = other.insert(std::move(nh)); | |
| EXPECT_THAT(other, ElementsAre(3)); | |
| EXPECT_EQ(res.position, other.find(3)); | |
| EXPECT_FALSE(res.inserted); | |
| ASSERT_FALSE(res.node.empty()); | |
| EXPECT_EQ(res.node.value(), 3); | |
| } | |
| template <typename Set> | |
| void TestExtractWithTrackingForSet() { | |
| InstanceTracker tracker; | |
| { | |
| Set s; | |
| // Add enough elements to make sure we test internal nodes too. | |
| const size_t kSize = 1000; | |
| while (s.size() < kSize) { | |
| s.insert(MovableOnlyInstance(s.size())); | |
| } | |
| for (int i = 0; i < kSize; ++i) { | |
| // Extract with key | |
| auto nh = s.extract(MovableOnlyInstance(i)); | |
| EXPECT_EQ(s.size(), kSize - 1); | |
| EXPECT_EQ(nh.value().value(), i); | |
| // Insert with node | |
| s.insert(std::move(nh)); | |
| EXPECT_EQ(s.size(), kSize); | |
| // Extract with iterator | |
| auto it = s.find(MovableOnlyInstance(i)); | |
| nh = s.extract(it); | |
| EXPECT_EQ(s.size(), kSize - 1); | |
| EXPECT_EQ(nh.value().value(), i); | |
| // Insert with node and hint | |
| s.insert(s.begin(), std::move(nh)); | |
| EXPECT_EQ(s.size(), kSize); | |
| } | |
| } | |
| EXPECT_EQ(0, tracker.instances()); | |
| } | |
| template <typename Map> | |
| void TestExtractWithTrackingForMap() { | |
| InstanceTracker tracker; | |
| { | |
| Map m; | |
| // Add enough elements to make sure we test internal nodes too. | |
| const size_t kSize = 1000; | |
| while (m.size() < kSize) { | |
| m.insert( | |
| {CopyableMovableInstance(m.size()), MovableOnlyInstance(m.size())}); | |
| } | |
| for (int i = 0; i < kSize; ++i) { | |
| // Extract with key | |
| auto nh = m.extract(CopyableMovableInstance(i)); | |
| EXPECT_EQ(m.size(), kSize - 1); | |
| EXPECT_EQ(nh.key().value(), i); | |
| EXPECT_EQ(nh.mapped().value(), i); | |
| // Insert with node | |
| m.insert(std::move(nh)); | |
| EXPECT_EQ(m.size(), kSize); | |
| // Extract with iterator | |
| auto it = m.find(CopyableMovableInstance(i)); | |
| nh = m.extract(it); | |
| EXPECT_EQ(m.size(), kSize - 1); | |
| EXPECT_EQ(nh.key().value(), i); | |
| EXPECT_EQ(nh.mapped().value(), i); | |
| // Insert with node and hint | |
| m.insert(m.begin(), std::move(nh)); | |
| EXPECT_EQ(m.size(), kSize); | |
| } | |
| } | |
| EXPECT_EQ(0, tracker.instances()); | |
| } | |
| TEST(Btree, ExtractTracking) { | |
| TestExtractWithTrackingForSet<absl::btree_set<MovableOnlyInstance>>(); | |
| TestExtractWithTrackingForSet<absl::btree_multiset<MovableOnlyInstance>>(); | |
| TestExtractWithTrackingForMap< | |
| absl::btree_map<CopyableMovableInstance, MovableOnlyInstance>>(); | |
| TestExtractWithTrackingForMap< | |
| absl::btree_multimap<CopyableMovableInstance, MovableOnlyInstance>>(); | |
| } | |
| TEST(Btree, ExtractAndInsertNodeHandleMultiSet) { | |
| absl::btree_multiset<int> src1 = {1, 2, 3, 3, 4, 5}; | |
| auto nh = src1.extract(src1.find(3)); | |
| EXPECT_THAT(src1, ElementsAre(1, 2, 3, 4, 5)); | |
| absl::btree_multiset<int> other; | |
| auto res = other.insert(std::move(nh)); | |
| EXPECT_THAT(other, ElementsAre(3)); | |
| EXPECT_EQ(res, other.find(3)); | |
| absl::btree_multiset<int> src2 = {3, 4}; | |
| nh = src2.extract(src2.find(3)); | |
| EXPECT_THAT(src2, ElementsAre(4)); | |
| res = other.insert(std::move(nh)); | |
| EXPECT_THAT(other, ElementsAre(3, 3)); | |
| EXPECT_EQ(res, ++other.find(3)); | |
| } | |
| TEST(Btree, ExtractAndInsertNodeHandleMap) { | |
| absl::btree_map<int, int> src1 = {{1, 2}, {3, 4}, {5, 6}}; | |
| auto nh = src1.extract(src1.find(3)); | |
| EXPECT_THAT(src1, ElementsAre(Pair(1, 2), Pair(5, 6))); | |
| absl::btree_map<int, int> other; | |
| absl::btree_map<int, int>::insert_return_type res = | |
| other.insert(std::move(nh)); | |
| EXPECT_THAT(other, ElementsAre(Pair(3, 4))); | |
| EXPECT_EQ(res.position, other.find(3)); | |
| EXPECT_TRUE(res.inserted); | |
| EXPECT_TRUE(res.node.empty()); | |
| absl::btree_map<int, int> src2 = {{3, 6}}; | |
| nh = src2.extract(src2.find(3)); | |
| EXPECT_TRUE(src2.empty()); | |
| res = other.insert(std::move(nh)); | |
| EXPECT_THAT(other, ElementsAre(Pair(3, 4))); | |
| EXPECT_EQ(res.position, other.find(3)); | |
| EXPECT_FALSE(res.inserted); | |
| ASSERT_FALSE(res.node.empty()); | |
| EXPECT_EQ(res.node.key(), 3); | |
| EXPECT_EQ(res.node.mapped(), 6); | |
| } | |
| TEST(Btree, ExtractAndInsertNodeHandleMultiMap) { | |
| absl::btree_multimap<int, int> src1 = {{1, 2}, {3, 4}, {5, 6}}; | |
| auto nh = src1.extract(src1.find(3)); | |
| EXPECT_THAT(src1, ElementsAre(Pair(1, 2), Pair(5, 6))); | |
| absl::btree_multimap<int, int> other; | |
| auto res = other.insert(std::move(nh)); | |
| EXPECT_THAT(other, ElementsAre(Pair(3, 4))); | |
| EXPECT_EQ(res, other.find(3)); | |
| absl::btree_multimap<int, int> src2 = {{3, 6}}; | |
| nh = src2.extract(src2.find(3)); | |
| EXPECT_TRUE(src2.empty()); | |
| res = other.insert(std::move(nh)); | |
| EXPECT_THAT(other, ElementsAre(Pair(3, 4), Pair(3, 6))); | |
| EXPECT_EQ(res, ++other.begin()); | |
| } | |
| TEST(Btree, ExtractMultiMapEquivalentKeys) { | |
| // Note: using string keys means a three-way comparator. | |
| absl::btree_multimap<std::string, int> map; | |
| for (int i = 0; i < 100; ++i) { | |
| for (int j = 0; j < 100; ++j) { | |
| map.insert({absl::StrCat(i), j}); | |
| } | |
| } | |
| for (int i = 0; i < 100; ++i) { | |
| const std::string key = absl::StrCat(i); | |
| auto node_handle = map.extract(key); | |
| EXPECT_EQ(node_handle.key(), key); | |
| EXPECT_EQ(node_handle.mapped(), 0) << i; | |
| } | |
| for (int i = 0; i < 100; ++i) { | |
| const std::string key = absl::StrCat(i); | |
| auto node_handle = map.extract(key); | |
| EXPECT_EQ(node_handle.key(), key); | |
| EXPECT_EQ(node_handle.mapped(), 1) << i; | |
| } | |
| } | |
| TEST(Btree, ExtractAndGetNextSet) { | |
| absl::btree_set<int> src = {1, 2, 3, 4, 5}; | |
| auto it = src.find(3); | |
| auto extracted_and_next = src.extract_and_get_next(it); | |
| EXPECT_THAT(src, ElementsAre(1, 2, 4, 5)); | |
| EXPECT_EQ(extracted_and_next.node.value(), 3); | |
| EXPECT_EQ(*extracted_and_next.next, 4); | |
| } | |
| TEST(Btree, ExtractAndGetNextMultiSet) { | |
| absl::btree_multiset<int> src = {1, 2, 3, 4, 5}; | |
| auto it = src.find(3); | |
| auto extracted_and_next = src.extract_and_get_next(it); | |
| EXPECT_THAT(src, ElementsAre(1, 2, 4, 5)); | |
| EXPECT_EQ(extracted_and_next.node.value(), 3); | |
| EXPECT_EQ(*extracted_and_next.next, 4); | |
| } | |
| TEST(Btree, ExtractAndGetNextMap) { | |
| absl::btree_map<int, int> src = {{1, 2}, {3, 4}, {5, 6}}; | |
| auto it = src.find(3); | |
| auto extracted_and_next = src.extract_and_get_next(it); | |
| EXPECT_THAT(src, ElementsAre(Pair(1, 2), Pair(5, 6))); | |
| EXPECT_EQ(extracted_and_next.node.key(), 3); | |
| EXPECT_EQ(extracted_and_next.node.mapped(), 4); | |
| EXPECT_THAT(*extracted_and_next.next, Pair(5, 6)); | |
| } | |
| TEST(Btree, ExtractAndGetNextMultiMap) { | |
| absl::btree_multimap<int, int> src = {{1, 2}, {3, 4}, {5, 6}}; | |
| auto it = src.find(3); | |
| auto extracted_and_next = src.extract_and_get_next(it); | |
| EXPECT_THAT(src, ElementsAre(Pair(1, 2), Pair(5, 6))); | |
| EXPECT_EQ(extracted_and_next.node.key(), 3); | |
| EXPECT_EQ(extracted_and_next.node.mapped(), 4); | |
| EXPECT_THAT(*extracted_and_next.next, Pair(5, 6)); | |
| } | |
| TEST(Btree, ExtractAndGetNextEndIter) { | |
| absl::btree_set<int> src = {1, 2, 3, 4, 5}; | |
| auto it = src.find(5); | |
| auto extracted_and_next = src.extract_and_get_next(it); | |
| EXPECT_THAT(src, ElementsAre(1, 2, 3, 4)); | |
| EXPECT_EQ(extracted_and_next.node.value(), 5); | |
| EXPECT_EQ(extracted_and_next.next, src.end()); | |
| } | |
| TEST(Btree, ExtractDoesntCauseExtraMoves) { | |
| #ifdef _MSC_VER | |
| // This conditional is to avoid an unreachable code warning. | |
| if (_MSC_VER > 0) { | |
| GTEST_SKIP() << "This test fails on MSVC."; | |
| } | |
| #endif | |
| using Set = absl::btree_set<MovableOnlyInstance>; | |
| std::array<std::function<void(Set &)>, 3> extracters = { | |
| [](Set &s) { auto node = s.extract(s.begin()); }, | |
| [](Set &s) { auto ret = s.extract_and_get_next(s.begin()); }, | |
| [](Set &s) { auto node = s.extract(MovableOnlyInstance(0)); }}; | |
| InstanceTracker tracker; | |
| for (int i = 0; i < 3; ++i) { | |
| Set s; | |
| s.insert(MovableOnlyInstance(0)); | |
| tracker.ResetCopiesMovesSwaps(); | |
| extracters[i](s); | |
| // We expect to see exactly 1 move: from the original slot into the | |
| // extracted node. | |
| EXPECT_EQ(tracker.copies(), 0) << i; | |
| EXPECT_EQ(tracker.moves(), 1) << i; | |
| EXPECT_EQ(tracker.swaps(), 0) << i; | |
| } | |
| } | |
| // For multisets, insert with hint also affects correctness because we need to | |
| // insert immediately before the hint if possible. | |
| struct InsertMultiHintData { | |
| int key; | |
| int not_key; | |
| bool operator==(const InsertMultiHintData other) const { | |
| return key == other.key && not_key == other.not_key; | |
| } | |
| }; | |
| struct InsertMultiHintDataKeyCompare { | |
| using is_transparent = void; | |
| bool operator()(const InsertMultiHintData a, | |
| const InsertMultiHintData b) const { | |
| return a.key < b.key; | |
| } | |
| bool operator()(const int a, const InsertMultiHintData b) const { | |
| return a < b.key; | |
| } | |
| bool operator()(const InsertMultiHintData a, const int b) const { | |
| return a.key < b; | |
| } | |
| }; | |
| TEST(Btree, InsertHintNodeHandle) { | |
| // For unique sets, insert with hint is just a performance optimization. | |
| // Test that insert works correctly when the hint is right or wrong. | |
| { | |
| absl::btree_set<int> src = {1, 2, 3, 4, 5}; | |
| auto nh = src.extract(src.find(3)); | |
| EXPECT_THAT(src, ElementsAre(1, 2, 4, 5)); | |
| absl::btree_set<int> other = {0, 100}; | |
| // Test a correct hint. | |
| auto it = other.insert(other.lower_bound(3), std::move(nh)); | |
| EXPECT_THAT(other, ElementsAre(0, 3, 100)); | |
| EXPECT_EQ(it, other.find(3)); | |
| nh = src.extract(src.find(5)); | |
| // Test an incorrect hint. | |
| it = other.insert(other.end(), std::move(nh)); | |
| EXPECT_THAT(other, ElementsAre(0, 3, 5, 100)); | |
| EXPECT_EQ(it, other.find(5)); | |
| } | |
| absl::btree_multiset<InsertMultiHintData, InsertMultiHintDataKeyCompare> src = | |
| {{1, 2}, {3, 4}, {3, 5}}; | |
| auto nh = src.extract(src.lower_bound(3)); | |
| EXPECT_EQ(nh.value(), (InsertMultiHintData{3, 4})); | |
| absl::btree_multiset<InsertMultiHintData, InsertMultiHintDataKeyCompare> | |
| other = {{3, 1}, {3, 2}, {3, 3}}; | |
| auto it = other.insert(--other.end(), std::move(nh)); | |
| EXPECT_THAT( | |
| other, ElementsAre(InsertMultiHintData{3, 1}, InsertMultiHintData{3, 2}, | |
| InsertMultiHintData{3, 4}, InsertMultiHintData{3, 3})); | |
| EXPECT_EQ(it, --(--other.end())); | |
| nh = src.extract(src.find(3)); | |
| EXPECT_EQ(nh.value(), (InsertMultiHintData{3, 5})); | |
| it = other.insert(other.begin(), std::move(nh)); | |
| EXPECT_THAT(other, | |
| ElementsAre(InsertMultiHintData{3, 5}, InsertMultiHintData{3, 1}, | |
| InsertMultiHintData{3, 2}, InsertMultiHintData{3, 4}, | |
| InsertMultiHintData{3, 3})); | |
| EXPECT_EQ(it, other.begin()); | |
| } | |
| struct IntCompareToCmp { | |
| absl::weak_ordering operator()(int a, int b) const { | |
| if (a < b) return absl::weak_ordering::less; | |
| if (a > b) return absl::weak_ordering::greater; | |
| return absl::weak_ordering::equivalent; | |
| } | |
| }; | |
| TEST(Btree, MergeIntoUniqueContainers) { | |
| absl::btree_set<int, IntCompareToCmp> src1 = {1, 2, 3}; | |
| absl::btree_multiset<int> src2 = {3, 4, 4, 5}; | |
| absl::btree_set<int> dst; | |
| dst.merge(src1); | |
| EXPECT_TRUE(src1.empty()); | |
| EXPECT_THAT(dst, ElementsAre(1, 2, 3)); | |
| dst.merge(src2); | |
| EXPECT_THAT(src2, ElementsAre(3, 4)); | |
| EXPECT_THAT(dst, ElementsAre(1, 2, 3, 4, 5)); | |
| } | |
| TEST(Btree, MergeIntoUniqueContainersWithCompareTo) { | |
| absl::btree_set<int, IntCompareToCmp> src1 = {1, 2, 3}; | |
| absl::btree_multiset<int> src2 = {3, 4, 4, 5}; | |
| absl::btree_set<int, IntCompareToCmp> dst; | |
| dst.merge(src1); | |
| EXPECT_TRUE(src1.empty()); | |
| EXPECT_THAT(dst, ElementsAre(1, 2, 3)); | |
| dst.merge(src2); | |
| EXPECT_THAT(src2, ElementsAre(3, 4)); | |
| EXPECT_THAT(dst, ElementsAre(1, 2, 3, 4, 5)); | |
| } | |
| TEST(Btree, MergeIntoMultiContainers) { | |
| absl::btree_set<int, IntCompareToCmp> src1 = {1, 2, 3}; | |
| absl::btree_multiset<int> src2 = {3, 4, 4, 5}; | |
| absl::btree_multiset<int> dst; | |
| dst.merge(src1); | |
| EXPECT_TRUE(src1.empty()); | |
| EXPECT_THAT(dst, ElementsAre(1, 2, 3)); | |
| dst.merge(src2); | |
| EXPECT_TRUE(src2.empty()); | |
| EXPECT_THAT(dst, ElementsAre(1, 2, 3, 3, 4, 4, 5)); | |
| } | |
| TEST(Btree, MergeIntoMultiContainersWithCompareTo) { | |
| absl::btree_set<int, IntCompareToCmp> src1 = {1, 2, 3}; | |
| absl::btree_multiset<int> src2 = {3, 4, 4, 5}; | |
| absl::btree_multiset<int, IntCompareToCmp> dst; | |
| dst.merge(src1); | |
| EXPECT_TRUE(src1.empty()); | |
| EXPECT_THAT(dst, ElementsAre(1, 2, 3)); | |
| dst.merge(src2); | |
| EXPECT_TRUE(src2.empty()); | |
| EXPECT_THAT(dst, ElementsAre(1, 2, 3, 3, 4, 4, 5)); | |
| } | |
| TEST(Btree, MergeIntoMultiMapsWithDifferentComparators) { | |
| absl::btree_map<int, int, IntCompareToCmp> src1 = {{1, 1}, {2, 2}, {3, 3}}; | |
| absl::btree_multimap<int, int, std::greater<int>> src2 = { | |
| {5, 5}, {4, 1}, {4, 4}, {3, 2}}; | |
| absl::btree_multimap<int, int> dst; | |
| dst.merge(src1); | |
| EXPECT_TRUE(src1.empty()); | |
| EXPECT_THAT(dst, ElementsAre(Pair(1, 1), Pair(2, 2), Pair(3, 3))); | |
| dst.merge(src2); | |
| EXPECT_TRUE(src2.empty()); | |
| EXPECT_THAT(dst, ElementsAre(Pair(1, 1), Pair(2, 2), Pair(3, 3), Pair(3, 2), | |
| Pair(4, 1), Pair(4, 4), Pair(5, 5))); | |
| } | |
| TEST(Btree, MergeIntoSetMovableOnly) { | |
| absl::btree_set<MovableOnlyInstance> src; | |
| src.insert(MovableOnlyInstance(1)); | |
| absl::btree_multiset<MovableOnlyInstance> dst1; | |
| dst1.insert(MovableOnlyInstance(2)); | |
| absl::btree_set<MovableOnlyInstance> dst2; | |
| // Test merge into multiset. | |
| dst1.merge(src); | |
| EXPECT_TRUE(src.empty()); | |
| // ElementsAre/ElementsAreArray don't work with move-only types. | |
| ASSERT_THAT(dst1, SizeIs(2)); | |
| EXPECT_EQ(*dst1.begin(), MovableOnlyInstance(1)); | |
| EXPECT_EQ(*std::next(dst1.begin()), MovableOnlyInstance(2)); | |
| // Test merge into set. | |
| dst2.merge(dst1); | |
| EXPECT_TRUE(dst1.empty()); | |
| ASSERT_THAT(dst2, SizeIs(2)); | |
| EXPECT_EQ(*dst2.begin(), MovableOnlyInstance(1)); | |
| EXPECT_EQ(*std::next(dst2.begin()), MovableOnlyInstance(2)); | |
| } | |
| struct KeyCompareToWeakOrdering { | |
| template <typename T> | |
| absl::weak_ordering operator()(const T &a, const T &b) const { | |
| return a < b ? absl::weak_ordering::less | |
| : a == b ? absl::weak_ordering::equivalent | |
| : absl::weak_ordering::greater; | |
| } | |
| }; | |
| struct KeyCompareToStrongOrdering { | |
| template <typename T> | |
| absl::strong_ordering operator()(const T &a, const T &b) const { | |
| return a < b ? absl::strong_ordering::less | |
| : a == b ? absl::strong_ordering::equal | |
| : absl::strong_ordering::greater; | |
| } | |
| }; | |
| TEST(Btree, UserProvidedKeyCompareToComparators) { | |
| absl::btree_set<int, KeyCompareToWeakOrdering> weak_set = {1, 2, 3}; | |
| EXPECT_TRUE(weak_set.contains(2)); | |
| EXPECT_FALSE(weak_set.contains(4)); | |
| absl::btree_set<int, KeyCompareToStrongOrdering> strong_set = {1, 2, 3}; | |
| EXPECT_TRUE(strong_set.contains(2)); | |
| EXPECT_FALSE(strong_set.contains(4)); | |
| } | |
| TEST(Btree, TryEmplaceBasicTest) { | |
| absl::btree_map<int, std::string> m; | |
| // Should construct a string from the literal. | |
| m.try_emplace(1, "one"); | |
| EXPECT_EQ(1, m.size()); | |
| // Try other string constructors and const lvalue key. | |
| const int key(42); | |
| m.try_emplace(key, 3, 'a'); | |
| m.try_emplace(2, std::string("two")); | |
| EXPECT_TRUE(std::is_sorted(m.begin(), m.end())); | |
| EXPECT_THAT(m, ElementsAreArray(std::vector<std::pair<int, std::string>>{ | |
| {1, "one"}, {2, "two"}, {42, "aaa"}})); | |
| } | |
| TEST(Btree, TryEmplaceWithHintWorks) { | |
| // Use a counting comparator here to verify that hint is used. | |
| int calls = 0; | |
| auto cmp = [&calls](int x, int y) { | |
| ++calls; | |
| return x < y; | |
| }; | |
| using Cmp = decltype(cmp); | |
| // Use a map that is opted out of key_compare being adapted so we can expect | |
| // strict comparison call limits. | |
| absl::btree_map<int, int, CheckedCompareOptedOutCmp<Cmp>> m(cmp); | |
| for (int i = 0; i < 128; ++i) { | |
| m.emplace(i, i); | |
| } | |
| // Sanity check for the comparator | |
| calls = 0; | |
| m.emplace(127, 127); | |
| EXPECT_GE(calls, 4); | |
| // Try with begin hint: | |
| calls = 0; | |
| auto it = m.try_emplace(m.begin(), -1, -1); | |
| EXPECT_EQ(129, m.size()); | |
| EXPECT_EQ(it, m.begin()); | |
| EXPECT_LE(calls, 2); | |
| // Try with end hint: | |
| calls = 0; | |
| std::pair<int, int> pair1024 = {1024, 1024}; | |
| it = m.try_emplace(m.end(), pair1024.first, pair1024.second); | |
| EXPECT_EQ(130, m.size()); | |
| EXPECT_EQ(it, --m.end()); | |
| EXPECT_LE(calls, 2); | |
| // Try value already present, bad hint; ensure no duplicate added: | |
| calls = 0; | |
| it = m.try_emplace(m.end(), 16, 17); | |
| EXPECT_EQ(130, m.size()); | |
| EXPECT_GE(calls, 4); | |
| EXPECT_EQ(it, m.find(16)); | |
| // Try value already present, hint points directly to it: | |
| calls = 0; | |
| it = m.try_emplace(it, 16, 17); | |
| EXPECT_EQ(130, m.size()); | |
| EXPECT_LE(calls, 2); | |
| EXPECT_EQ(it, m.find(16)); | |
| m.erase(2); | |
| EXPECT_EQ(129, m.size()); | |
| auto hint = m.find(3); | |
| // Try emplace in the middle of two other elements. | |
| calls = 0; | |
| m.try_emplace(hint, 2, 2); | |
| EXPECT_EQ(130, m.size()); | |
| EXPECT_LE(calls, 2); | |
| EXPECT_TRUE(std::is_sorted(m.begin(), m.end())); | |
| } | |
| TEST(Btree, TryEmplaceWithBadHint) { | |
| absl::btree_map<int, int> m = {{1, 1}, {9, 9}}; | |
| // Bad hint (too small), should still emplace: | |
| auto it = m.try_emplace(m.begin(), 2, 2); | |
| EXPECT_EQ(it, ++m.begin()); | |
| EXPECT_THAT(m, ElementsAreArray( | |
| std::vector<std::pair<int, int>>{{1, 1}, {2, 2}, {9, 9}})); | |
| // Bad hint, too large this time: | |
| it = m.try_emplace(++(++m.begin()), 0, 0); | |
| EXPECT_EQ(it, m.begin()); | |
| EXPECT_THAT(m, ElementsAreArray(std::vector<std::pair<int, int>>{ | |
| {0, 0}, {1, 1}, {2, 2}, {9, 9}})); | |
| } | |
| TEST(Btree, TryEmplaceMaintainsSortedOrder) { | |
| absl::btree_map<int, std::string> m; | |
| std::pair<int, std::string> pair5 = {5, "five"}; | |
| // Test both lvalue & rvalue emplace. | |
| m.try_emplace(10, "ten"); | |
| m.try_emplace(pair5.first, pair5.second); | |
| EXPECT_EQ(2, m.size()); | |
| EXPECT_TRUE(std::is_sorted(m.begin(), m.end())); | |
| int int100{100}; | |
| m.try_emplace(int100, "hundred"); | |
| m.try_emplace(1, "one"); | |
| EXPECT_EQ(4, m.size()); | |
| EXPECT_TRUE(std::is_sorted(m.begin(), m.end())); | |
| } | |
| TEST(Btree, TryEmplaceWithHintAndNoValueArgsWorks) { | |
| absl::btree_map<int, int> m; | |
| m.try_emplace(m.end(), 1); | |
| EXPECT_EQ(0, m[1]); | |
| } | |
| TEST(Btree, TryEmplaceWithHintAndMultipleValueArgsWorks) { | |
| absl::btree_map<int, std::string> m; | |
| m.try_emplace(m.end(), 1, 10, 'a'); | |
| EXPECT_EQ(std::string(10, 'a'), m[1]); | |
| } | |
| template <typename Alloc> | |
| using BtreeSetAlloc = absl::btree_set<int, std::less<int>, Alloc>; | |
| TEST(Btree, AllocatorPropagation) { | |
| TestAllocPropagation<BtreeSetAlloc>(); | |
| } | |
| TEST(Btree, MinimumAlignmentAllocator) { | |
| absl::btree_set<int8_t, std::less<int8_t>, MinimumAlignmentAlloc<int8_t>> set; | |
| // Do some basic operations. Test that everything is fine when allocator uses | |
| // minimal alignment. | |
| for (int8_t i = 0; i < 100; ++i) set.insert(i); | |
| set.erase(set.find(50), set.end()); | |
| for (int8_t i = 51; i < 101; ++i) set.insert(i); | |
| EXPECT_EQ(set.size(), 100); | |
| } | |
| TEST(Btree, EmptyTree) { | |
| absl::btree_set<int> s; | |
| EXPECT_TRUE(s.empty()); | |
| EXPECT_EQ(s.size(), 0); | |
| EXPECT_GT(s.max_size(), 0); | |
| } | |
| bool IsEven(int k) { return k % 2 == 0; } | |
| TEST(Btree, EraseIf) { | |
| // Test that erase_if works with all the container types and supports lambdas. | |
| { | |
| absl::btree_set<int> s = {1, 3, 5, 6, 100}; | |
| EXPECT_EQ(erase_if(s, [](int k) { return k > 3; }), 3); | |
| EXPECT_THAT(s, ElementsAre(1, 3)); | |
| } | |
| { | |
| absl::btree_multiset<int> s = {1, 3, 3, 5, 6, 6, 100}; | |
| EXPECT_EQ(erase_if(s, [](int k) { return k <= 3; }), 3); | |
| EXPECT_THAT(s, ElementsAre(5, 6, 6, 100)); | |
| } | |
| { | |
| absl::btree_map<int, int> m = {{1, 1}, {3, 3}, {6, 6}, {100, 100}}; | |
| EXPECT_EQ( | |
| erase_if(m, [](std::pair<const int, int> kv) { return kv.first > 3; }), | |
| 2); | |
| EXPECT_THAT(m, ElementsAre(Pair(1, 1), Pair(3, 3))); | |
| } | |
| { | |
| absl::btree_multimap<int, int> m = {{1, 1}, {3, 3}, {3, 6}, | |
| {6, 6}, {6, 7}, {100, 6}}; | |
| EXPECT_EQ( | |
| erase_if(m, | |
| [](std::pair<const int, int> kv) { return kv.second == 6; }), | |
| 3); | |
| EXPECT_THAT(m, ElementsAre(Pair(1, 1), Pair(3, 3), Pair(6, 7))); | |
| } | |
| // Test that erasing all elements from a large set works and test support for | |
| // function pointers. | |
| { | |
| absl::btree_set<int> s; | |
| for (int i = 0; i < 1000; ++i) s.insert(2 * i); | |
| EXPECT_EQ(erase_if(s, IsEven), 1000); | |
| EXPECT_THAT(s, IsEmpty()); | |
| } | |
| // Test that erase_if supports other format of function pointers. | |
| { | |
| absl::btree_set<int> s = {1, 3, 5, 6, 100}; | |
| EXPECT_EQ(erase_if(s, &IsEven), 2); | |
| EXPECT_THAT(s, ElementsAre(1, 3, 5)); | |
| } | |
| // Test that erase_if invokes the predicate once per element. | |
| { | |
| absl::btree_set<int> s; | |
| for (int i = 0; i < 1000; ++i) s.insert(i); | |
| int pred_calls = 0; | |
| EXPECT_EQ(erase_if(s, | |
| [&pred_calls](int k) { | |
| ++pred_calls; | |
| return k % 2; | |
| }), | |
| 500); | |
| EXPECT_THAT(s, SizeIs(500)); | |
| EXPECT_EQ(pred_calls, 1000); | |
| } | |
| } | |
| TEST(Btree, InsertOrAssign) { | |
| absl::btree_map<int, int> m = {{1, 1}, {3, 3}}; | |
| using value_type = typename decltype(m)::value_type; | |
| auto ret = m.insert_or_assign(4, 4); | |
| EXPECT_EQ(*ret.first, value_type(4, 4)); | |
| EXPECT_TRUE(ret.second); | |
| ret = m.insert_or_assign(3, 100); | |
| EXPECT_EQ(*ret.first, value_type(3, 100)); | |
| EXPECT_FALSE(ret.second); | |
| auto hint_ret = m.insert_or_assign(ret.first, 3, 200); | |
| EXPECT_EQ(*hint_ret, value_type(3, 200)); | |
| hint_ret = m.insert_or_assign(m.find(1), 0, 1); | |
| EXPECT_EQ(*hint_ret, value_type(0, 1)); | |
| // Test with bad hint. | |
| hint_ret = m.insert_or_assign(m.end(), -1, 1); | |
| EXPECT_EQ(*hint_ret, value_type(-1, 1)); | |
| EXPECT_THAT(m, ElementsAre(Pair(-1, 1), Pair(0, 1), Pair(1, 1), Pair(3, 200), | |
| Pair(4, 4))); | |
| } | |
| TEST(Btree, InsertOrAssignMovableOnly) { | |
| absl::btree_map<int, MovableOnlyInstance> m; | |
| using value_type = typename decltype(m)::value_type; | |
| auto ret = m.insert_or_assign(4, MovableOnlyInstance(4)); | |
| EXPECT_EQ(*ret.first, value_type(4, MovableOnlyInstance(4))); | |
| EXPECT_TRUE(ret.second); | |
| ret = m.insert_or_assign(4, MovableOnlyInstance(100)); | |
| EXPECT_EQ(*ret.first, value_type(4, MovableOnlyInstance(100))); | |
| EXPECT_FALSE(ret.second); | |
| auto hint_ret = m.insert_or_assign(ret.first, 3, MovableOnlyInstance(200)); | |
| EXPECT_EQ(*hint_ret, value_type(3, MovableOnlyInstance(200))); | |
| EXPECT_EQ(m.size(), 2); | |
| } | |
| TEST(Btree, BitfieldArgument) { | |
| union { | |
| int n : 1; | |
| }; | |
| n = 0; | |
| absl::btree_map<int, int> m; | |
| m.erase(n); | |
| m.count(n); | |
| m.find(n); | |
| m.contains(n); | |
| m.equal_range(n); | |
| m.insert_or_assign(n, n); | |
| m.insert_or_assign(m.end(), n, n); | |
| m.try_emplace(n); | |
| m.try_emplace(m.end(), n); | |
| m.at(n); | |
| m[n]; | |
| } | |
| TEST(Btree, SetRangeConstructorAndInsertSupportExplicitConversionComparable) { | |
| const absl::string_view names[] = {"n1", "n2"}; | |
| absl::btree_set<std::string> name_set1{std::begin(names), std::end(names)}; | |
| EXPECT_THAT(name_set1, ElementsAreArray(names)); | |
| absl::btree_set<std::string> name_set2; | |
| name_set2.insert(std::begin(names), std::end(names)); | |
| EXPECT_THAT(name_set2, ElementsAreArray(names)); | |
| } | |
| // A type that is explicitly convertible from int and counts constructor calls. | |
| struct ConstructorCounted { | |
| explicit ConstructorCounted(int i) : i(i) { ++constructor_calls; } | |
| bool operator==(int other) const { return i == other; } | |
| int i; | |
| static int constructor_calls; | |
| }; | |
| int ConstructorCounted::constructor_calls = 0; | |
| struct ConstructorCountedCompare { | |
| bool operator()(int a, const ConstructorCounted &b) const { return a < b.i; } | |
| bool operator()(const ConstructorCounted &a, int b) const { return a.i < b; } | |
| bool operator()(const ConstructorCounted &a, | |
| const ConstructorCounted &b) const { | |
| return a.i < b.i; | |
| } | |
| using is_transparent = void; | |
| }; | |
| TEST(Btree, | |
| SetRangeConstructorAndInsertExplicitConvComparableLimitConstruction) { | |
| const int i[] = {0, 1, 1}; | |
| ConstructorCounted::constructor_calls = 0; | |
| absl::btree_set<ConstructorCounted, ConstructorCountedCompare> set{ | |
| std::begin(i), std::end(i)}; | |
| EXPECT_THAT(set, ElementsAre(0, 1)); | |
| EXPECT_EQ(ConstructorCounted::constructor_calls, 2); | |
| set.insert(std::begin(i), std::end(i)); | |
| EXPECT_THAT(set, ElementsAre(0, 1)); | |
| EXPECT_EQ(ConstructorCounted::constructor_calls, 2); | |
| } | |
| TEST(Btree, | |
| SetRangeConstructorAndInsertSupportExplicitConversionNonComparable) { | |
| const int i[] = {0, 1}; | |
| absl::btree_set<std::vector<void *>> s1{std::begin(i), std::end(i)}; | |
| EXPECT_THAT(s1, ElementsAre(IsEmpty(), ElementsAre(IsNull()))); | |
| absl::btree_set<std::vector<void *>> s2; | |
| s2.insert(std::begin(i), std::end(i)); | |
| EXPECT_THAT(s2, ElementsAre(IsEmpty(), ElementsAre(IsNull()))); | |
| } | |
| // libstdc++ included with GCC 4.9 has a bug in the std::pair constructors that | |
| // prevents explicit conversions between pair types. | |
| // We only run this test for the libstdc++ from GCC 7 or newer because we can't | |
| // reliably check the libstdc++ version prior to that release. | |
| #if !defined(__GLIBCXX__) || \ | |
| (defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE >= 7) | |
| TEST(Btree, MapRangeConstructorAndInsertSupportExplicitConversionComparable) { | |
| const std::pair<absl::string_view, int> names[] = {{"n1", 1}, {"n2", 2}}; | |
| absl::btree_map<std::string, int> name_map1{std::begin(names), | |
| std::end(names)}; | |
| EXPECT_THAT(name_map1, ElementsAre(Pair("n1", 1), Pair("n2", 2))); | |
| absl::btree_map<std::string, int> name_map2; | |
| name_map2.insert(std::begin(names), std::end(names)); | |
| EXPECT_THAT(name_map2, ElementsAre(Pair("n1", 1), Pair("n2", 2))); | |
| } | |
| TEST(Btree, | |
| MapRangeConstructorAndInsertExplicitConvComparableLimitConstruction) { | |
| const std::pair<int, int> i[] = {{0, 1}, {1, 2}, {1, 3}}; | |
| ConstructorCounted::constructor_calls = 0; | |
| absl::btree_map<ConstructorCounted, int, ConstructorCountedCompare> map{ | |
| std::begin(i), std::end(i)}; | |
| EXPECT_THAT(map, ElementsAre(Pair(0, 1), Pair(1, 2))); | |
| EXPECT_EQ(ConstructorCounted::constructor_calls, 2); | |
| map.insert(std::begin(i), std::end(i)); | |
| EXPECT_THAT(map, ElementsAre(Pair(0, 1), Pair(1, 2))); | |
| EXPECT_EQ(ConstructorCounted::constructor_calls, 2); | |
| } | |
| TEST(Btree, | |
| MapRangeConstructorAndInsertSupportExplicitConversionNonComparable) { | |
| const std::pair<int, int> i[] = {{0, 1}, {1, 2}}; | |
| absl::btree_map<std::vector<void *>, int> m1{std::begin(i), std::end(i)}; | |
| EXPECT_THAT(m1, | |
| ElementsAre(Pair(IsEmpty(), 1), Pair(ElementsAre(IsNull()), 2))); | |
| absl::btree_map<std::vector<void *>, int> m2; | |
| m2.insert(std::begin(i), std::end(i)); | |
| EXPECT_THAT(m2, | |
| ElementsAre(Pair(IsEmpty(), 1), Pair(ElementsAre(IsNull()), 2))); | |
| } | |
| TEST(Btree, HeterogeneousTryEmplace) { | |
| absl::btree_map<std::string, int> m; | |
| std::string s = "key"; | |
| absl::string_view sv = s; | |
| m.try_emplace(sv, 1); | |
| EXPECT_EQ(m[s], 1); | |
| m.try_emplace(m.end(), sv, 2); | |
| EXPECT_EQ(m[s], 1); | |
| } | |
| TEST(Btree, HeterogeneousOperatorMapped) { | |
| absl::btree_map<std::string, int> m; | |
| std::string s = "key"; | |
| absl::string_view sv = s; | |
| m[sv] = 1; | |
| EXPECT_EQ(m[s], 1); | |
| m[sv] = 2; | |
| EXPECT_EQ(m[s], 2); | |
| } | |
| TEST(Btree, HeterogeneousInsertOrAssign) { | |
| absl::btree_map<std::string, int> m; | |
| std::string s = "key"; | |
| absl::string_view sv = s; | |
| m.insert_or_assign(sv, 1); | |
| EXPECT_EQ(m[s], 1); | |
| m.insert_or_assign(m.end(), sv, 2); | |
| EXPECT_EQ(m[s], 2); | |
| } | |
| #endif | |
| TEST(Btree, NodeHandleMutableKeyAccess) { | |
| { | |
| absl::btree_map<std::string, std::string> map; | |
| map["key1"] = "mapped"; | |
| auto nh = map.extract(map.begin()); | |
| nh.key().resize(3); | |
| map.insert(std::move(nh)); | |
| EXPECT_THAT(map, ElementsAre(Pair("key", "mapped"))); | |
| } | |
| // Also for multimap. | |
| { | |
| absl::btree_multimap<std::string, std::string> map; | |
| map.emplace("key1", "mapped"); | |
| auto nh = map.extract(map.begin()); | |
| nh.key().resize(3); | |
| map.insert(std::move(nh)); | |
| EXPECT_THAT(map, ElementsAre(Pair("key", "mapped"))); | |
| } | |
| } | |
| struct MultiKey { | |
| int i1; | |
| int i2; | |
| }; | |
| bool operator==(const MultiKey a, const MultiKey b) { | |
| return a.i1 == b.i1 && a.i2 == b.i2; | |
| } | |
| // A heterogeneous comparator that has different equivalence classes for | |
| // different lookup types. | |
| struct MultiKeyComp { | |
| using is_transparent = void; | |
| bool operator()(const MultiKey a, const MultiKey b) const { | |
| if (a.i1 != b.i1) return a.i1 < b.i1; | |
| return a.i2 < b.i2; | |
| } | |
| bool operator()(const int a, const MultiKey b) const { return a < b.i1; } | |
| bool operator()(const MultiKey a, const int b) const { return a.i1 < b; } | |
| }; | |
| // A heterogeneous, three-way comparator that has different equivalence classes | |
| // for different lookup types. | |
| struct MultiKeyThreeWayComp { | |
| using is_transparent = void; | |
| absl::weak_ordering operator()(const MultiKey a, const MultiKey b) const { | |
| if (a.i1 < b.i1) return absl::weak_ordering::less; | |
| if (a.i1 > b.i1) return absl::weak_ordering::greater; | |
| if (a.i2 < b.i2) return absl::weak_ordering::less; | |
| if (a.i2 > b.i2) return absl::weak_ordering::greater; | |
| return absl::weak_ordering::equivalent; | |
| } | |
| absl::weak_ordering operator()(const int a, const MultiKey b) const { | |
| if (a < b.i1) return absl::weak_ordering::less; | |
| if (a > b.i1) return absl::weak_ordering::greater; | |
| return absl::weak_ordering::equivalent; | |
| } | |
| absl::weak_ordering operator()(const MultiKey a, const int b) const { | |
| if (a.i1 < b) return absl::weak_ordering::less; | |
| if (a.i1 > b) return absl::weak_ordering::greater; | |
| return absl::weak_ordering::equivalent; | |
| } | |
| }; | |
| template <typename Compare> | |
| class BtreeMultiKeyTest : public ::testing::Test {}; | |
| using MultiKeyComps = ::testing::Types<MultiKeyComp, MultiKeyThreeWayComp>; | |
| TYPED_TEST_SUITE(BtreeMultiKeyTest, MultiKeyComps); | |
| TYPED_TEST(BtreeMultiKeyTest, EqualRange) { | |
| absl::btree_set<MultiKey, TypeParam> set; | |
| for (int i = 0; i < 100; ++i) { | |
| for (int j = 0; j < 100; ++j) { | |
| set.insert({i, j}); | |
| } | |
| } | |
| for (int i = 0; i < 100; ++i) { | |
| auto equal_range = set.equal_range(i); | |
| EXPECT_EQ(equal_range.first->i1, i); | |
| EXPECT_EQ(equal_range.first->i2, 0) << i; | |
| EXPECT_EQ(std::distance(equal_range.first, equal_range.second), 100) << i; | |
| } | |
| } | |
| TYPED_TEST(BtreeMultiKeyTest, Extract) { | |
| absl::btree_set<MultiKey, TypeParam> set; | |
| for (int i = 0; i < 100; ++i) { | |
| for (int j = 0; j < 100; ++j) { | |
| set.insert({i, j}); | |
| } | |
| } | |
| for (int i = 0; i < 100; ++i) { | |
| auto node_handle = set.extract(i); | |
| EXPECT_EQ(node_handle.value().i1, i); | |
| EXPECT_EQ(node_handle.value().i2, 0) << i; | |
| } | |
| for (int i = 0; i < 100; ++i) { | |
| auto node_handle = set.extract(i); | |
| EXPECT_EQ(node_handle.value().i1, i); | |
| EXPECT_EQ(node_handle.value().i2, 1) << i; | |
| } | |
| } | |
| TYPED_TEST(BtreeMultiKeyTest, Erase) { | |
| absl::btree_set<MultiKey, TypeParam> set = { | |
| {1, 1}, {2, 1}, {2, 2}, {3, 1}}; | |
| EXPECT_EQ(set.erase(2), 2); | |
| EXPECT_THAT(set, ElementsAre(MultiKey{1, 1}, MultiKey{3, 1})); | |
| } | |
| TYPED_TEST(BtreeMultiKeyTest, Count) { | |
| const absl::btree_set<MultiKey, TypeParam> set = { | |
| {1, 1}, {2, 1}, {2, 2}, {3, 1}}; | |
| EXPECT_EQ(set.count(2), 2); | |
| } | |
| TEST(Btree, SetIteratorsAreConst) { | |
| using Set = absl::btree_set<int>; | |
| EXPECT_TRUE((std::is_same_v<typename Set::iterator::reference, const int&>)); | |
| EXPECT_TRUE((std::is_same_v<typename Set::iterator::pointer, const int*>)); | |
| using MSet = absl::btree_multiset<int>; | |
| EXPECT_TRUE((std::is_same_v<typename MSet::iterator::reference, const int&>)); | |
| EXPECT_TRUE((std::is_same_v<typename MSet::iterator::pointer, const int*>)); | |
| } | |
| TEST(Btree, AllocConstructor) { | |
| using Alloc = CountingAllocator<int>; | |
| using Set = absl::btree_set<int, std::less<int>, Alloc>; | |
| int64_t bytes_used = 0; | |
| Alloc alloc(&bytes_used); | |
| Set set(alloc); | |
| set.insert({1, 2, 3}); | |
| EXPECT_THAT(set, ElementsAre(1, 2, 3)); | |
| EXPECT_GT(bytes_used, set.size() * sizeof(int)); | |
| } | |
| TEST(Btree, AllocInitializerListConstructor) { | |
| using Alloc = CountingAllocator<int>; | |
| using Set = absl::btree_set<int, std::less<int>, Alloc>; | |
| int64_t bytes_used = 0; | |
| Alloc alloc(&bytes_used); | |
| Set set({1, 2, 3}, alloc); | |
| EXPECT_THAT(set, ElementsAre(1, 2, 3)); | |
| EXPECT_GT(bytes_used, set.size() * sizeof(int)); | |
| } | |
| TEST(Btree, AllocRangeConstructor) { | |
| using Alloc = CountingAllocator<int>; | |
| using Set = absl::btree_set<int, std::less<int>, Alloc>; | |
| int64_t bytes_used = 0; | |
| Alloc alloc(&bytes_used); | |
| std::vector<int> v = {1, 2, 3}; | |
| Set set(v.begin(), v.end(), alloc); | |
| EXPECT_THAT(set, ElementsAre(1, 2, 3)); | |
| EXPECT_GT(bytes_used, set.size() * sizeof(int)); | |
| } | |
| TEST(Btree, AllocCopyConstructor) { | |
| using Alloc = CountingAllocator<int>; | |
| using Set = absl::btree_set<int, std::less<int>, Alloc>; | |
| int64_t bytes_used1 = 0; | |
| Alloc alloc1(&bytes_used1); | |
| Set set1(alloc1); | |
| set1.insert({1, 2, 3}); | |
| int64_t bytes_used2 = 0; | |
| Alloc alloc2(&bytes_used2); | |
| Set set2(set1, alloc2); | |
| EXPECT_THAT(set1, ElementsAre(1, 2, 3)); | |
| EXPECT_THAT(set2, ElementsAre(1, 2, 3)); | |
| EXPECT_GT(bytes_used1, set1.size() * sizeof(int)); | |
| EXPECT_EQ(bytes_used1, bytes_used2); | |
| } | |
| TEST(Btree, AllocMoveConstructor_SameAlloc) { | |
| using Alloc = CountingAllocator<int>; | |
| using Set = absl::btree_set<int, std::less<int>, Alloc>; | |
| int64_t bytes_used = 0; | |
| Alloc alloc(&bytes_used); | |
| Set set1(alloc); | |
| set1.insert({1, 2, 3}); | |
| const int64_t original_bytes_used = bytes_used; | |
| EXPECT_GT(original_bytes_used, set1.size() * sizeof(int)); | |
| Set set2(std::move(set1), alloc); | |
| EXPECT_THAT(set2, ElementsAre(1, 2, 3)); | |
| EXPECT_EQ(bytes_used, original_bytes_used); | |
| } | |
| TEST(Btree, AllocMoveConstructor_DifferentAlloc) { | |
| using Alloc = CountingAllocator<int>; | |
| using Set = absl::btree_set<int, std::less<int>, Alloc>; | |
| int64_t bytes_used1 = 0; | |
| Alloc alloc1(&bytes_used1); | |
| Set set1(alloc1); | |
| set1.insert({1, 2, 3}); | |
| const int64_t original_bytes_used = bytes_used1; | |
| EXPECT_GT(original_bytes_used, set1.size() * sizeof(int)); | |
| int64_t bytes_used2 = 0; | |
| Alloc alloc2(&bytes_used2); | |
| Set set2(std::move(set1), alloc2); | |
| EXPECT_THAT(set2, ElementsAre(1, 2, 3)); | |
| // We didn't free these bytes allocated by `set1` yet. | |
| EXPECT_EQ(bytes_used1, original_bytes_used); | |
| EXPECT_EQ(bytes_used2, original_bytes_used); | |
| } | |
| bool IntCmp(const int a, const int b) { return a < b; } | |
| TEST(Btree, SupportsFunctionPtrComparator) { | |
| absl::btree_set<int, decltype(IntCmp) *> set(IntCmp); | |
| set.insert({1, 2, 3}); | |
| EXPECT_THAT(set, ElementsAre(1, 2, 3)); | |
| EXPECT_TRUE(set.key_comp()(1, 2)); | |
| EXPECT_TRUE(set.value_comp()(1, 2)); | |
| absl::btree_map<int, int, decltype(IntCmp) *> map(&IntCmp); | |
| map[1] = 1; | |
| EXPECT_THAT(map, ElementsAre(Pair(1, 1))); | |
| EXPECT_TRUE(map.key_comp()(1, 2)); | |
| EXPECT_TRUE(map.value_comp()(std::make_pair(1, 1), std::make_pair(2, 2))); | |
| } | |
| template <typename Compare> | |
| struct TransparentPassThroughComp { | |
| using is_transparent = void; | |
| // This will fail compilation if we attempt a comparison that Compare does not | |
| // support, and the failure will happen inside the function implementation so | |
| // it can't be avoided by using SFINAE on this comparator. | |
| template <typename T, typename U> | |
| bool operator()(const T &lhs, const U &rhs) const { | |
| return Compare()(lhs, rhs); | |
| } | |
| }; | |
| TEST(Btree, | |
| SupportsTransparentComparatorThatDoesNotImplementAllVisibleOperators) { | |
| absl::btree_set<MultiKey, TransparentPassThroughComp<MultiKeyComp>> set; | |
| set.insert(MultiKey{1, 2}); | |
| EXPECT_TRUE(set.contains(1)); | |
| } | |
| TEST(Btree, ConstructImplicitlyWithUnadaptedComparator) { | |
| absl::btree_set<MultiKey, MultiKeyComp> set = {{}, MultiKeyComp{}}; | |
| EXPECT_TRUE(set.empty()); | |
| } | |
| TEST(Btree, InvalidComparatorsCaught) { | |
| if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; | |
| { | |
| struct ZeroAlwaysLessCmp { | |
| bool operator()(int lhs, int rhs) const { | |
| if (lhs == 0) return true; | |
| return lhs < rhs; | |
| } | |
| }; | |
| absl::btree_set<int, ZeroAlwaysLessCmp> set; | |
| EXPECT_DEATH(set.insert({0, 1, 2}), "is_self_equivalent"); | |
| } | |
| { | |
| struct ThreeWayAlwaysLessCmp { | |
| absl::weak_ordering operator()(int, int) const { | |
| return absl::weak_ordering::less; | |
| } | |
| }; | |
| absl::btree_set<int, ThreeWayAlwaysLessCmp> set; | |
| EXPECT_DEATH(set.insert({0, 1, 2}), "is_self_equivalent"); | |
| } | |
| { | |
| struct SumGreaterZeroCmp { | |
| bool operator()(int lhs, int rhs) const { | |
| // First, do equivalence correctly - so we can test later condition. | |
| if (lhs == rhs) return false; | |
| return lhs + rhs > 0; | |
| } | |
| }; | |
| absl::btree_set<int, SumGreaterZeroCmp> set; | |
| // Note: '!' only needs to be escaped when it's the first character. | |
| EXPECT_DEATH(set.insert({0, 1, 2}), | |
| R"regex(\!lhs_comp_rhs \|\| !comp\(\)\(rhs, lhs\))regex"); | |
| } | |
| { | |
| struct ThreeWaySumGreaterZeroCmp { | |
| absl::weak_ordering operator()(int lhs, int rhs) const { | |
| // First, do equivalence correctly - so we can test later condition. | |
| if (lhs == rhs) return absl::weak_ordering::equivalent; | |
| if (lhs + rhs > 0) return absl::weak_ordering::less; | |
| if (lhs + rhs == 0) return absl::weak_ordering::equivalent; | |
| return absl::weak_ordering::greater; | |
| } | |
| }; | |
| absl::btree_set<int, ThreeWaySumGreaterZeroCmp> set; | |
| EXPECT_DEATH(set.insert({0, 1, 2}), "lhs_comp_rhs < 0 -> rhs_comp_lhs > 0"); | |
| } | |
| // Verify that we detect cases of comparators that violate transitivity. | |
| // When the comparators below check for the presence of an optional field, | |
| // they violate transitivity because instances that have the optional field | |
| // compare differently with each other from how they compare with instances | |
| // that don't have the optional field. | |
| struct ClockTime { | |
| std::optional<int> hour; | |
| int minute; | |
| }; | |
| // `comp(a,b) && comp(b,c) && !comp(a,c)` violates transitivity. | |
| ClockTime a = {std::nullopt, 1}; | |
| ClockTime b = {2, 5}; | |
| ClockTime c = {6, 0}; | |
| { | |
| struct NonTransitiveTimeCmp { | |
| bool operator()(ClockTime lhs, ClockTime rhs) const { | |
| if (lhs.hour.has_value() && rhs.hour.has_value() && | |
| *lhs.hour != *rhs.hour) { | |
| return *lhs.hour < *rhs.hour; | |
| } | |
| return lhs.minute < rhs.minute; | |
| } | |
| }; | |
| NonTransitiveTimeCmp cmp; | |
| ASSERT_TRUE(cmp(a, b) && cmp(b, c) && !cmp(a, c)); | |
| absl::btree_set<ClockTime, NonTransitiveTimeCmp> set; | |
| EXPECT_DEATH(set.insert({a, b, c}), "is_ordered_correctly"); | |
| absl::btree_multiset<ClockTime, NonTransitiveTimeCmp> mset; | |
| EXPECT_DEATH(mset.insert({a, a, b, b, c, c}), "is_ordered_correctly"); | |
| } | |
| { | |
| struct ThreeWayNonTransitiveTimeCmp { | |
| absl::weak_ordering operator()(ClockTime lhs, ClockTime rhs) const { | |
| if (lhs.hour.has_value() && rhs.hour.has_value() && | |
| *lhs.hour != *rhs.hour) { | |
| return *lhs.hour < *rhs.hour ? absl::weak_ordering::less | |
| : absl::weak_ordering::greater; | |
| } | |
| return lhs.minute < rhs.minute ? absl::weak_ordering::less | |
| : lhs.minute == rhs.minute ? absl::weak_ordering::equivalent | |
| : absl::weak_ordering::greater; | |
| } | |
| }; | |
| ThreeWayNonTransitiveTimeCmp cmp; | |
| ASSERT_TRUE(cmp(a, b) < 0 && cmp(b, c) < 0 && cmp(a, c) > 0); | |
| absl::btree_set<ClockTime, ThreeWayNonTransitiveTimeCmp> set; | |
| EXPECT_DEATH(set.insert({a, b, c}), "is_ordered_correctly"); | |
| absl::btree_multiset<ClockTime, ThreeWayNonTransitiveTimeCmp> mset; | |
| EXPECT_DEATH(mset.insert({a, a, b, b, c, c}), "is_ordered_correctly"); | |
| } | |
| } | |
| TEST(Btree, MutatedKeysCaught) { | |
| if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; | |
| struct IntPtrCmp { | |
| bool operator()(int *lhs, int *rhs) const { return *lhs < *rhs; } | |
| }; | |
| { | |
| absl::btree_set<int *, IntPtrCmp> set; | |
| int arr[] = {0, 1, 2}; | |
| set.insert({&arr[0], &arr[1], &arr[2]}); | |
| arr[0] = 100; | |
| EXPECT_DEATH(set.insert(&arr[0]), "is_ordered_correctly"); | |
| } | |
| { | |
| absl::btree_multiset<int *, IntPtrCmp> set; | |
| int arr[] = {0, 1, 2}; | |
| set.insert({&arr[0], &arr[0], &arr[1], &arr[1], &arr[2], &arr[2]}); | |
| arr[0] = 100; | |
| EXPECT_DEATH(set.insert(&arr[0]), "is_ordered_correctly"); | |
| } | |
| } | |
| #ifndef _MSC_VER | |
| // This test crashes on MSVC. | |
| TEST(Btree, InvalidIteratorUse) { | |
| if (!BtreeGenerationsEnabled()) | |
| GTEST_SKIP() << "Generation validation for iterators is disabled."; | |
| // Invalid memory use can trigger use-after-free in ASan, HWASAN or | |
| // invalidated iterator assertions. | |
| constexpr const char *kInvalidMemoryDeathMessage = | |
| "use-after-free|invalidated iterator"; | |
| { | |
| absl::btree_set<int> set; | |
| for (int i = 0; i < 10; ++i) set.insert(i); | |
| auto it = set.begin(); | |
| set.erase(it++); | |
| EXPECT_DEATH(set.erase(it++), kInvalidMemoryDeathMessage); | |
| } | |
| { | |
| absl::btree_set<int> set; | |
| for (int i = 0; i < 10; ++i) set.insert(i); | |
| auto it = set.insert(20).first; | |
| set.insert(30); | |
| EXPECT_DEATH(*it, kInvalidMemoryDeathMessage); | |
| } | |
| { | |
| absl::btree_set<int> set; | |
| for (int i = 0; i < 10000; ++i) set.insert(i); | |
| auto it = set.find(5000); | |
| ASSERT_NE(it, set.end()); | |
| set.erase(1); | |
| EXPECT_DEATH(*it, kInvalidMemoryDeathMessage); | |
| } | |
| { | |
| absl::btree_set<int> set; | |
| for (int i = 0; i < 10; ++i) set.insert(i); | |
| auto it = set.insert(20).first; | |
| set.insert(30); | |
| EXPECT_DEATH(void(it == set.begin()), kInvalidMemoryDeathMessage); | |
| EXPECT_DEATH(void(set.begin() == it), kInvalidMemoryDeathMessage); | |
| } | |
| } | |
| #endif | |
| class OnlyConstructibleByAllocator { | |
| explicit OnlyConstructibleByAllocator(int i) : i_(i) {} | |
| public: | |
| OnlyConstructibleByAllocator(const OnlyConstructibleByAllocator &other) | |
| : i_(other.i_) {} | |
| OnlyConstructibleByAllocator &operator=( | |
| const OnlyConstructibleByAllocator &other) { | |
| i_ = other.i_; | |
| return *this; | |
| } | |
| int Get() const { return i_; } | |
| bool operator==(int i) const { return i_ == i; } | |
| private: | |
| template <typename T> | |
| friend class OnlyConstructibleAllocator; | |
| int i_; | |
| }; | |
| template <typename T = OnlyConstructibleByAllocator> | |
| class OnlyConstructibleAllocator : public std::allocator<T> { | |
| public: | |
| OnlyConstructibleAllocator() = default; | |
| template <class U> | |
| explicit OnlyConstructibleAllocator(const OnlyConstructibleAllocator<U> &) {} | |
| void construct(OnlyConstructibleByAllocator *p, int i) { | |
| new (p) OnlyConstructibleByAllocator(i); | |
| } | |
| template <typename Pair> | |
| void construct(Pair *p, const int i) { | |
| OnlyConstructibleByAllocator only(i); | |
| new (p) Pair(std::move(only), i); | |
| } | |
| template <class U> | |
| struct rebind { | |
| using other = OnlyConstructibleAllocator<U>; | |
| }; | |
| }; | |
| struct OnlyConstructibleByAllocatorComp { | |
| using is_transparent = void; | |
| bool operator()(OnlyConstructibleByAllocator a, | |
| OnlyConstructibleByAllocator b) const { | |
| return a.Get() < b.Get(); | |
| } | |
| bool operator()(int a, OnlyConstructibleByAllocator b) const { | |
| return a < b.Get(); | |
| } | |
| bool operator()(OnlyConstructibleByAllocator a, int b) const { | |
| return a.Get() < b; | |
| } | |
| }; | |
| TEST(Btree, OnlyConstructibleByAllocatorType) { | |
| const std::array<int, 2> arr = {3, 4}; | |
| { | |
| absl::btree_set<OnlyConstructibleByAllocator, | |
| OnlyConstructibleByAllocatorComp, | |
| OnlyConstructibleAllocator<>> | |
| set; | |
| set.emplace(1); | |
| set.emplace_hint(set.end(), 2); | |
| set.insert(arr.begin(), arr.end()); | |
| EXPECT_THAT(set, ElementsAre(1, 2, 3, 4)); | |
| } | |
| { | |
| absl::btree_multiset<OnlyConstructibleByAllocator, | |
| OnlyConstructibleByAllocatorComp, | |
| OnlyConstructibleAllocator<>> | |
| set; | |
| set.emplace(1); | |
| set.emplace_hint(set.end(), 2); | |
| // TODO(ezb): fix insert_multi to allow this to compile. | |
| // set.insert(arr.begin(), arr.end()); | |
| EXPECT_THAT(set, ElementsAre(1, 2)); | |
| } | |
| { | |
| absl::btree_map<OnlyConstructibleByAllocator, int, | |
| OnlyConstructibleByAllocatorComp, | |
| OnlyConstructibleAllocator<>> | |
| map; | |
| map.emplace(1); | |
| map.emplace_hint(map.end(), 2); | |
| map.insert(arr.begin(), arr.end()); | |
| EXPECT_THAT(map, | |
| ElementsAre(Pair(1, 1), Pair(2, 2), Pair(3, 3), Pair(4, 4))); | |
| } | |
| { | |
| absl::btree_multimap<OnlyConstructibleByAllocator, int, | |
| OnlyConstructibleByAllocatorComp, | |
| OnlyConstructibleAllocator<>> | |
| map; | |
| map.emplace(1); | |
| map.emplace_hint(map.end(), 2); | |
| // TODO(ezb): fix insert_multi to allow this to compile. | |
| // map.insert(arr.begin(), arr.end()); | |
| EXPECT_THAT(map, ElementsAre(Pair(1, 1), Pair(2, 2))); | |
| } | |
| } | |
| class NotAssignable { | |
| public: | |
| explicit NotAssignable(int i) : i_(i) {} | |
| NotAssignable(const NotAssignable &other) : i_(other.i_) {} | |
| NotAssignable &operator=(NotAssignable &&other) = delete; | |
| int Get() const { return i_; } | |
| bool operator==(int i) const { return i_ == i; } | |
| friend bool operator<(NotAssignable a, NotAssignable b) { | |
| return a.i_ < b.i_; | |
| } | |
| private: | |
| int i_; | |
| }; | |
| TEST(Btree, NotAssignableType) { | |
| { | |
| absl::btree_set<NotAssignable> set; | |
| set.emplace(1); | |
| set.emplace_hint(set.end(), 2); | |
| set.insert(NotAssignable(3)); | |
| set.insert(set.end(), NotAssignable(4)); | |
| EXPECT_THAT(set, ElementsAre(1, 2, 3, 4)); | |
| set.erase(set.begin()); | |
| EXPECT_THAT(set, ElementsAre(2, 3, 4)); | |
| } | |
| { | |
| absl::btree_multiset<NotAssignable> set; | |
| set.emplace(1); | |
| set.emplace_hint(set.end(), 2); | |
| set.insert(NotAssignable(2)); | |
| set.insert(set.end(), NotAssignable(3)); | |
| EXPECT_THAT(set, ElementsAre(1, 2, 2, 3)); | |
| set.erase(set.begin()); | |
| EXPECT_THAT(set, ElementsAre(2, 2, 3)); | |
| } | |
| { | |
| absl::btree_map<NotAssignable, int> map; | |
| map.emplace(NotAssignable(1), 1); | |
| map.emplace_hint(map.end(), NotAssignable(2), 2); | |
| map.insert({NotAssignable(3), 3}); | |
| map.insert(map.end(), {NotAssignable(4), 4}); | |
| EXPECT_THAT(map, | |
| ElementsAre(Pair(1, 1), Pair(2, 2), Pair(3, 3), Pair(4, 4))); | |
| map.erase(map.begin()); | |
| EXPECT_THAT(map, ElementsAre(Pair(2, 2), Pair(3, 3), Pair(4, 4))); | |
| } | |
| { | |
| absl::btree_multimap<NotAssignable, int> map; | |
| map.emplace(NotAssignable(1), 1); | |
| map.emplace_hint(map.end(), NotAssignable(2), 2); | |
| map.insert({NotAssignable(2), 3}); | |
| map.insert(map.end(), {NotAssignable(3), 3}); | |
| EXPECT_THAT(map, | |
| ElementsAre(Pair(1, 1), Pair(2, 2), Pair(2, 3), Pair(3, 3))); | |
| map.erase(map.begin()); | |
| EXPECT_THAT(map, ElementsAre(Pair(2, 2), Pair(2, 3), Pair(3, 3))); | |
| } | |
| } | |
| struct ArenaLike { | |
| void* recycled = nullptr; | |
| size_t recycled_size = 0; | |
| }; | |
| // A very simple implementation of arena allocation. | |
| template <typename T> | |
| class ArenaLikeAllocator : public std::allocator<T> { | |
| public: | |
| // Standard library containers require the ability to allocate objects of | |
| // different types which they can do so via rebind.other. | |
| template <typename U> | |
| struct rebind { | |
| using other = ArenaLikeAllocator<U>; | |
| }; | |
| explicit ArenaLikeAllocator(ArenaLike* arena) noexcept : arena_(arena) {} | |
| ~ArenaLikeAllocator() { | |
| if (arena_->recycled != nullptr) { | |
| delete [] static_cast<T*>(arena_->recycled); | |
| arena_->recycled = nullptr; | |
| } | |
| } | |
| template<typename U> | |
| explicit ArenaLikeAllocator(const ArenaLikeAllocator<U>& other) noexcept | |
| : arena_(other.arena_) {} | |
| T* allocate(size_t num_objects, const void* = nullptr) { | |
| size_t size = num_objects * sizeof(T); | |
| if (arena_->recycled != nullptr && arena_->recycled_size == size) { | |
| T* result = static_cast<T*>(arena_->recycled); | |
| arena_->recycled = nullptr; | |
| return result; | |
| } | |
| return new T[num_objects]; | |
| } | |
| void deallocate(T* p, size_t num_objects) { | |
| size_t size = num_objects * sizeof(T); | |
| // Simulate writing to the freed memory as an actual arena allocator might | |
| // do. This triggers an error report if the memory is poisoned. | |
| memset(p, 0xde, size); | |
| if (arena_->recycled == nullptr) { | |
| arena_->recycled = p; | |
| arena_->recycled_size = size; | |
| } else { | |
| delete [] p; | |
| } | |
| } | |
| ArenaLike* arena_; | |
| }; | |
| // This test verifies that an arena allocator that reuses memory will not be | |
| // asked to free poisoned BTree memory. | |
| TEST(Btree, ReusePoisonMemory) { | |
| using Alloc = ArenaLikeAllocator<int64_t>; | |
| using Set = absl::btree_set<int64_t, std::less<int64_t>, Alloc>; | |
| ArenaLike arena; | |
| Alloc alloc(&arena); | |
| Set set(alloc); | |
| set.insert(0); | |
| set.erase(0); | |
| set.insert(0); | |
| } | |
| TEST(Btree, IteratorDifference) { | |
| absl::BitGen bitgen; | |
| std::vector<int> vec; | |
| // Randomize the set's insertion order so the nodes aren't all full. | |
| for (int i = 0; i < 1000000; ++i) vec.push_back(i); | |
| absl::c_shuffle(vec, bitgen); | |
| absl::btree_set<int> set; | |
| for (int i : vec) set.insert(i); | |
| for (int i = 0; i < 1000; ++i) { | |
| size_t begin = absl::Uniform(bitgen, 0u, set.size()); | |
| size_t end = absl::Uniform(bitgen, begin, set.size()); | |
| ASSERT_EQ(end - begin, set.find(end) - set.find(begin)) | |
| << begin << " " << end; | |
| } | |
| } | |
| TEST(Btree, IteratorAddition) { | |
| absl::BitGen bitgen; | |
| std::vector<int> vec; | |
| // Randomize the set's insertion order so the nodes aren't all full. | |
| constexpr int kSetSize = 1000000; | |
| for (int i = 0; i < kSetSize; ++i) vec.push_back(i); | |
| absl::c_shuffle(vec, bitgen); | |
| absl::btree_set<int> set; | |
| for (int i : vec) set.insert(i); | |
| for (int i = 0; i < 1000; ++i) { | |
| int begin = absl::Uniform(bitgen, 0, kSetSize); | |
| int end = absl::Uniform(bitgen, begin, kSetSize); | |
| ASSERT_LE(begin, end); | |
| auto it = set.find(begin); | |
| it += end - begin; | |
| ASSERT_EQ(it, set.find(end)) << end; | |
| it += begin - end; | |
| ASSERT_EQ(it, set.find(begin)) << begin; | |
| } | |
| } | |
| TEST(Btree, IteratorAdditionOutOfBounds) { | |
| const absl::btree_set<int> set({5}); | |
| auto it = set.find(5); | |
| auto forward = it; | |
| forward += 1; | |
| EXPECT_EQ(forward, set.end()); | |
| auto backward = it; | |
| EXPECT_EQ(backward, set.begin()); | |
| if (IsAssertEnabled()) { | |
| EXPECT_DEATH(forward += 1, ""); | |
| EXPECT_DEATH(backward += -1, ""); | |
| } | |
| } | |
| TEST(Btree, IteratorSubtraction) { | |
| absl::BitGen bitgen; | |
| std::vector<int> vec; | |
| // Randomize the set's insertion order so the nodes aren't all full. | |
| constexpr int kSetSize = 1000000; | |
| for (int i = 0; i < kSetSize; ++i) vec.push_back(i); | |
| absl::c_shuffle(vec, bitgen); | |
| absl::btree_set<int> set; | |
| for (int i : vec) set.insert(i); | |
| for (int i = 0; i < 1000; ++i) { | |
| int begin = absl::Uniform(bitgen, 0, kSetSize); | |
| int end = absl::Uniform(bitgen, begin, kSetSize); | |
| ASSERT_LE(begin, end); | |
| auto it = set.find(end); | |
| it -= end - begin; | |
| ASSERT_EQ(it, set.find(begin)) << begin; | |
| it -= begin - end; | |
| ASSERT_EQ(it, set.find(end)) << end; | |
| } | |
| } | |
| TEST(Btree, IteratorSubtractionOutOfBounds) { | |
| const absl::btree_set<int> set({5}); | |
| auto it = set.find(5); | |
| auto backward = it; | |
| EXPECT_EQ(backward, set.begin()); | |
| auto forward = it; | |
| forward -= -1; | |
| EXPECT_EQ(forward, set.end()); | |
| if (IsAssertEnabled()) { | |
| EXPECT_DEATH(backward -= 1, ""); | |
| EXPECT_DEATH(forward -= -1, ""); | |
| } | |
| } | |
| TEST(Btree, DereferencingEndIterator) { | |
| if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; | |
| absl::btree_set<int> set; | |
| for (int i = 0; i < 1000; ++i) set.insert(i); | |
| EXPECT_DEATH(*set.end(), ""); | |
| } | |
| TEST(Btree, InvalidIteratorComparison) { | |
| if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; | |
| absl::btree_set<int> set1, set2; | |
| for (int i = 0; i < 1000; ++i) { | |
| set1.insert(i); | |
| set2.insert(i); | |
| } | |
| typename absl::btree_set<int>::iterator iter1, iter2; | |
| EXPECT_EQ(iter1, iter2); | |
| EXPECT_DEATH(void(set1.begin() == iter1), ""); | |
| EXPECT_DEATH(void(iter1 == set1.begin()), ""); | |
| constexpr const char *kDifferentContainerDeathMessage = | |
| "Comparing iterators from different containers"; | |
| iter1 = set1.begin(); | |
| iter2 = set2.begin(); | |
| EXPECT_DEATH(void(iter1 == iter2), kDifferentContainerDeathMessage); | |
| EXPECT_DEATH(void(iter2 == iter1), kDifferentContainerDeathMessage); | |
| } | |
| TEST(Btree, InvalidPointerUse) { | |
| if (!kAsan) | |
| GTEST_SKIP() << "We only detect invalid pointer use in ASan mode."; | |
| absl::btree_set<int> set; | |
| set.insert(0); | |
| const int *ptr = &*set.begin(); | |
| set.insert(1); | |
| EXPECT_DEATH(std::cout << *ptr, "use-after-free"); | |
| size_t slots_per_node = BtreeNodePeer::GetNumSlotsPerNode<decltype(set)>(); | |
| for (int i = 2; i < slots_per_node - 1; ++i) set.insert(i); | |
| ptr = &*set.begin(); | |
| set.insert(static_cast<int>(slots_per_node)); | |
| EXPECT_DEATH(std::cout << *ptr, "use-after-free"); | |
| } | |
| template<typename Set> | |
| void TestBasicFunctionality(Set set) { | |
| using value_type = typename Set::value_type; | |
| for (int i = 0; i < 100; ++i) { set.insert(value_type(i)); } | |
| for (int i = 50; i < 100; ++i) { set.erase(value_type(i)); } | |
| auto it = set.begin(); | |
| for (int i = 0; i < 50; ++i, ++it) { | |
| ASSERT_EQ(set.find(value_type(i)), it) << i; | |
| } | |
| } | |
| template<size_t align> | |
| struct alignas(align) OveralignedKey { | |
| explicit OveralignedKey(int i) : key(i) {} | |
| bool operator<(const OveralignedKey &other) const { return key < other.key; } | |
| int key = 0; | |
| }; | |
| TEST(Btree, OveralignedKey) { | |
| // Test basic functionality with both even and odd numbers of slots per node. | |
| // The goal here is to detect cases where alignment may be incorrect. | |
| TestBasicFunctionality( | |
| SizedBtreeSet<OveralignedKey<16>, /*TargetValuesPerNode=*/8>()); | |
| TestBasicFunctionality( | |
| SizedBtreeSet<OveralignedKey<16>, /*TargetValuesPerNode=*/9>()); | |
| } | |
| TEST(Btree, FieldTypeEqualsSlotType) { | |
| // This breaks if we try to do layout_type::Pointer<slot_type> because | |
| // slot_type is the same as field_type. | |
| using set_type = absl::btree_set<uint8_t>; | |
| static_assert(BtreeNodePeer::FieldTypeEqualsSlotType<set_type>()); | |
| TestBasicFunctionality(set_type()); | |
| } | |
| // Alias whose only purpose is to have the same length as set_params for better | |
| // alignment in the test below. | |
| template <typename... T> | |
| using set_p_impl = set_params_impl<T...>; | |
| TEST(BtreeTest, SetParamsStripsDefaults) { | |
| using K = int; | |
| using DA = btree_set_defaults<int>::Compare; | |
| using DB = btree_set_defaults<int>::Alloc; | |
| using DC = btree_set_defaults<int>::TargetNodeSize; | |
| using DD = btree_set_defaults<int>::IsMulti; | |
| using XA = std::greater<int>; | |
| struct XB {}; | |
| using XC = std::integral_constant<int, 100>; | |
| using XD = std::true_type; | |
| EXPECT_TRUE((std::is_same_v<set_params<K, XA, XB, XC{}, XD{}>, | |
| set_p_impl<K, XA, XB, XC, XD>>)); | |
| EXPECT_TRUE((std::is_same_v<set_params<K, XA, XB, XC{}, DD{}>, | |
| set_p_impl<K, XA, XB, XC>>)); | |
| EXPECT_TRUE((std::is_same_v<set_params<K, XA, XB, DC{}, XD{}>, | |
| set_p_impl<K, XA, XB, DC, XD>>)); | |
| EXPECT_TRUE((std::is_same_v<set_params<K, XA, XB, DC{}, DD{}>, | |
| set_p_impl<K, XA, XB>>)); | |
| EXPECT_TRUE((std::is_same_v<set_params<K, XA, DB, XC{}, XD{}>, | |
| set_p_impl<K, XA, DB, XC, XD>>)); | |
| EXPECT_TRUE((std::is_same_v<set_params<K, XA, DB, XC{}, DD{}>, | |
| set_p_impl<K, XA, DB, XC>>)); | |
| EXPECT_TRUE((std::is_same_v<set_params<K, XA, DB, DC{}, XD{}>, | |
| set_p_impl<K, XA, DB, DC, XD>>)); | |
| EXPECT_TRUE((std::is_same_v<set_params<K, XA, DB, DC{}, DD{}>, | |
| set_p_impl<K, XA>>)); | |
| EXPECT_TRUE((std::is_same_v<set_params<K, DA, XB, XC{}, XD{}>, | |
| set_p_impl<K, DA, XB, XC, XD>>)); | |
| EXPECT_TRUE((std::is_same_v<set_params<K, DA, XB, XC{}, DD{}>, | |
| set_p_impl<K, DA, XB, XC>>)); | |
| EXPECT_TRUE((std::is_same_v<set_params<K, DA, XB, DC{}, XD{}>, | |
| set_p_impl<K, DA, XB, DC, XD>>)); | |
| EXPECT_TRUE((std::is_same_v<set_params<K, DA, XB, DC{}, DD{}>, | |
| set_p_impl<K, DA, XB>>)); | |
| EXPECT_TRUE((std::is_same_v<set_params<K, DA, DB, XC{}, XD{}>, | |
| set_p_impl<K, DA, DB, XC, XD>>)); | |
| EXPECT_TRUE((std::is_same_v<set_params<K, DA, DB, XC{}, DD{}>, | |
| set_p_impl<K, DA, DB, XC>>)); | |
| EXPECT_TRUE((std::is_same_v<set_params<K, DA, DB, DC{}, XD{}>, | |
| set_p_impl<K, DA, DB, DC, XD>>)); | |
| EXPECT_TRUE((std::is_same_v<set_params<K, DA, DB, DC{}, DD{}>, | |
| set_p_impl<K>>)); | |
| } | |
| // Alias whose only purpose is to have the same length as map_params for better | |
| // alignment in the test below. | |
| template <typename... T> | |
| using map_p_impl = map_params_impl<T...>; | |
| TEST(BtreeTest, MapParamsStripsDefaults) { | |
| using K = int; | |
| using V = double; | |
| using DA = btree_map_defaults<int, double>::Compare; | |
| using DB = btree_map_defaults<int, double>::Alloc; | |
| using DC = btree_map_defaults<int, double>::TargetNodeSize; | |
| using DD = btree_map_defaults<int, double>::IsMulti; | |
| using XA = std::greater<int>; | |
| struct XB {}; | |
| using XC = std::integral_constant<int, 100>; | |
| using XD = std::true_type; | |
| EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, XB, XC{}, XD{}>, | |
| map_p_impl<K, V, XA, XB, XC, XD>>)); | |
| EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, XB, XC{}, DD{}>, | |
| map_p_impl<K, V, XA, XB, XC>>)); | |
| EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, XB, DC{}, XD{}>, | |
| map_p_impl<K, V, XA, XB, DC, XD>>)); | |
| EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, XB, DC{}, DD{}>, | |
| map_p_impl<K, V, XA, XB>>)); | |
| EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, DB, XC{}, XD{}>, | |
| map_p_impl<K, V, XA, DB, XC, XD>>)); | |
| EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, DB, XC{}, DD{}>, | |
| map_p_impl<K, V, XA, DB, XC>>)); | |
| EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, DB, DC{}, XD{}>, | |
| map_p_impl<K, V, XA, DB, DC, XD>>)); | |
| EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, DB, DC{}, DD{}>, | |
| map_p_impl<K, V, XA>>)); | |
| EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, XB, XC{}, XD{}>, | |
| map_p_impl<K, V, DA, XB, XC, XD>>)); | |
| EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, XB, XC{}, DD{}>, | |
| map_p_impl<K, V, DA, XB, XC>>)); | |
| EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, XB, DC{}, XD{}>, | |
| map_p_impl<K, V, DA, XB, DC, XD>>)); | |
| EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, XB, DC{}, DD{}>, | |
| map_p_impl<K, V, DA, XB>>)); | |
| EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, DB, XC{}, XD{}>, | |
| map_p_impl<K, V, DA, DB, XC, XD>>)); | |
| EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, DB, XC{}, DD{}>, | |
| map_p_impl<K, V, DA, DB, XC>>)); | |
| EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, DB, DC{}, XD{}>, | |
| map_p_impl<K, V, DA, DB, DC, XD>>)); | |
| EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, DB, DC{}, DD{}>, | |
| map_p_impl<K, V>>)); | |
| } | |
| } // namespace | |
| } // namespace container_internal | |
| ABSL_NAMESPACE_END | |
| } // namespace absl |