diff options
author | Devtools Arcadia <[email protected]> | 2022-02-07 18:08:42 +0300 |
---|---|---|
committer | Devtools Arcadia <[email protected]> | 2022-02-07 18:08:42 +0300 |
commit | 1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch) | |
tree | e26c9fed0de5d9873cce7e00bc214573dc2195b7 /util/generic/hash_ut.cpp |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'util/generic/hash_ut.cpp')
-rw-r--r-- | util/generic/hash_ut.cpp | 1271 |
1 files changed, 1271 insertions, 0 deletions
diff --git a/util/generic/hash_ut.cpp b/util/generic/hash_ut.cpp new file mode 100644 index 00000000000..0551d587708 --- /dev/null +++ b/util/generic/hash_ut.cpp @@ -0,0 +1,1271 @@ +#include "hash.h" +#include "vector.h" +#include "hash_set.h" + +#include <library/cpp/testing/common/probe.h> +#include <library/cpp/testing/unittest/registar.h> + +#include <utility> +#include <util/str_stl.h> +#include <util/digest/multi.h> + +static const char star = 42; + +class THashTest: public TTestBase { + UNIT_TEST_SUITE(THashTest); + UNIT_TEST(TestHMapConstructorsAndAssignments); + UNIT_TEST(TestHMap1); + UNIT_TEST(TestHMapEqualityOperator); + UNIT_TEST(TestHMMapEqualityOperator); + UNIT_TEST(TestHMMapConstructorsAndAssignments); + UNIT_TEST(TestHMMap1); + UNIT_TEST(TestHMMapHas); + UNIT_TEST(TestHSetConstructorsAndAssignments); + UNIT_TEST(TestHSetSize); + UNIT_TEST(TestHSet2); + UNIT_TEST(TestHSetEqualityOperator); + UNIT_TEST(TestHMSetConstructorsAndAssignments); + UNIT_TEST(TestHMSetSize); + UNIT_TEST(TestHMSet1); + UNIT_TEST(TestHMSetEqualityOperator); + UNIT_TEST(TestHMSetEmplace); + UNIT_TEST(TestInsertErase); + UNIT_TEST(TestResizeOnInsertSmartPtrBug) + UNIT_TEST(TestEmpty); + UNIT_TEST(TestDefaultConstructor); + UNIT_TEST(TestSizeOf); + UNIT_TEST(TestInvariants); + UNIT_TEST(TestAllocation); + UNIT_TEST(TestInsertCopy); + UNIT_TEST(TestEmplace); + UNIT_TEST(TestEmplaceNoresize); + UNIT_TEST(TestEmplaceDirect); + UNIT_TEST(TestTryEmplace); + UNIT_TEST(TestTryEmplaceCopyKey); + UNIT_TEST(TestHMMapEmplace); + UNIT_TEST(TestHMMapEmplaceNoresize); + UNIT_TEST(TestHMMapEmplaceDirect); + UNIT_TEST(TestHSetEmplace); + UNIT_TEST(TestHSetEmplaceNoresize); + UNIT_TEST(TestHSetEmplaceDirect); + UNIT_TEST(TestNonCopyable); + UNIT_TEST(TestValueInitialization); + UNIT_TEST(TestAssignmentClear); + UNIT_TEST(TestReleaseNodes); + UNIT_TEST(TestAt); + UNIT_TEST(TestHMapInitializerList); + UNIT_TEST(TestHMMapInitializerList); + UNIT_TEST(TestHSetInitializerList); + UNIT_TEST(TestHMSetInitializerList); + UNIT_TEST(TestHSetInsertInitializerList); + UNIT_TEST(TestTupleHash); + UNIT_TEST_SUITE_END(); + + using hmset = THashMultiSet<char, hash<char>, TEqualTo<char>>; + +protected: + void TestHMapConstructorsAndAssignments(); + void TestHMap1(); + void TestHMapEqualityOperator(); + void TestHMMapEqualityOperator(); + void TestHMMapConstructorsAndAssignments(); + void TestHMMap1(); + void TestHMMapHas(); + void TestHSetConstructorsAndAssignments(); + void TestHSetSize(); + void TestHSet2(); + void TestHSetEqualityOperator(); + void TestHMSetConstructorsAndAssignments(); + void TestHMSetSize(); + void TestHMSet1(); + void TestHMSetEqualityOperator(); + void TestHMSetEmplace(); + void TestInsertErase(); + void TestResizeOnInsertSmartPtrBug(); + void TestEmpty(); + void TestDefaultConstructor(); + void TestSizeOf(); + void TestInvariants(); + void TestAllocation(); + void TestInsertCopy(); + void TestEmplace(); + void TestEmplaceNoresize(); + void TestEmplaceDirect(); + void TestTryEmplace(); + void TestTryEmplaceCopyKey(); + void TestHSetEmplace(); + void TestHSetEmplaceNoresize(); + void TestHSetEmplaceDirect(); + void TestHMMapEmplace(); + void TestHMMapEmplaceNoresize(); + void TestHMMapEmplaceDirect(); + void TestNonCopyable(); + void TestValueInitialization(); + void TestAssignmentClear(); + void TestReleaseNodes(); + void TestAt(); + void TestHMapInitializerList(); + void TestHMMapInitializerList(); + void TestHSetInitializerList(); + void TestHMSetInitializerList(); + void TestHSetInsertInitializerList(); + void TestTupleHash(); +}; + +UNIT_TEST_SUITE_REGISTRATION(THashTest); + +void THashTest::TestHMapConstructorsAndAssignments() { + using container = THashMap<TString, int>; + + container c1; + c1["one"] = 1; + c1["two"] = 2; + + container c2(c1); + + UNIT_ASSERT_VALUES_EQUAL(2, c1.size()); + UNIT_ASSERT_VALUES_EQUAL(2, c2.size()); + UNIT_ASSERT_VALUES_EQUAL(1, c1.at("one")); /* Note: fails under MSVC since it does not support implicit generation of move constructors. */ + UNIT_ASSERT_VALUES_EQUAL(2, c2.at("two")); + + container c3(std::move(c1)); + + UNIT_ASSERT_VALUES_EQUAL(0, c1.size()); + UNIT_ASSERT_VALUES_EQUAL(2, c3.size()); + UNIT_ASSERT_VALUES_EQUAL(1, c3.at("one")); + + c2["three"] = 3; + c3 = c2; + + UNIT_ASSERT_VALUES_EQUAL(3, c2.size()); + UNIT_ASSERT_VALUES_EQUAL(3, c3.size()); + UNIT_ASSERT_VALUES_EQUAL(3, c3.at("three")); + + c2["four"] = 4; + c3 = std::move(c2); + + UNIT_ASSERT_VALUES_EQUAL(0, c2.size()); + UNIT_ASSERT_VALUES_EQUAL(4, c3.size()); + UNIT_ASSERT_VALUES_EQUAL(4, c3.at("four")); + + const container c4{ + {"one", 1}, + {"two", 2}, + {"three", 3}, + {"four", 4}, + }; + + UNIT_ASSERT_VALUES_EQUAL(4, c4.size()); + UNIT_ASSERT_VALUES_EQUAL(1, c4.at("one")); + UNIT_ASSERT_VALUES_EQUAL(2, c4.at("two")); + UNIT_ASSERT_VALUES_EQUAL(3, c4.at("three")); + UNIT_ASSERT_VALUES_EQUAL(4, c4.at("four")); + + // non-existent values must be zero-initialized + UNIT_ASSERT_VALUES_EQUAL(c1["nonexistent"], 0); +} + +void THashTest::TestHMap1() { + using maptype = THashMap<char, TString, THash<char>, TEqualTo<char>>; + maptype m; + // Store mappings between roman numerals and decimals. + m['l'] = "50"; + m['x'] = "20"; // Deliberate mistake. + m['v'] = "5"; + m['i'] = "1"; + UNIT_ASSERT(!strcmp(m['x'].c_str(), "20")); + m['x'] = "10"; // Correct mistake. + UNIT_ASSERT(!strcmp(m['x'].c_str(), "10")); + + UNIT_ASSERT(!m.contains('z')); + UNIT_ASSERT(!strcmp(m['z'].c_str(), "")); + UNIT_ASSERT(m.contains('z')); + + UNIT_ASSERT(m.count('z') == 1); + auto p = m.insert(std::pair<const char, TString>('c', TString("100"))); + + UNIT_ASSERT(p.second); + + p = m.insert(std::pair<const char, TString>('c', TString("100"))); + UNIT_ASSERT(!p.second); + + //Some iterators compare check, really compile time checks + maptype::iterator ite(m.begin()); + maptype::const_iterator cite(m.begin()); + cite = m.begin(); + maptype const& cm = m; + cite = cm.begin(); + + UNIT_ASSERT((maptype::const_iterator)ite == cite); + UNIT_ASSERT(!((maptype::const_iterator)ite != cite)); + UNIT_ASSERT(cite == (maptype::const_iterator)ite); + UNIT_ASSERT(!(cite != (maptype::const_iterator)ite)); +} + +void THashTest::TestHMapEqualityOperator() { + using container = THashMap<TString, int>; + + container base; + base["one"] = 1; + base["two"] = 2; + + container c1(base); + UNIT_ASSERT(c1 == base); + + container c2; + c2["two"] = 2; + c2["one"] = 1; + UNIT_ASSERT(c2 == base); + + c2["three"] = 3; + UNIT_ASSERT(c2 != base); + + container c3(base); + c3["one"] = 0; + UNIT_ASSERT(c3 != base); +} + +void THashTest::TestHMMapEqualityOperator() { + using container = THashMultiMap<TString, int>; + using value = container::value_type; + + container base; + base.insert(value("one", 1)); + base.insert(value("one", -1)); + base.insert(value("two", 2)); + + container c1(base); + UNIT_ASSERT(c1 == base); + + container c2; + c2.insert(value("two", 2)); + c2.insert(value("one", -1)); + c2.insert(value("one", 1)); + UNIT_ASSERT(c2 == base); + + c2.insert(value("three", 3)); + UNIT_ASSERT(c2 != base); + + container c3; + c3.insert(value("one", 0)); + c3.insert(value("one", -1)); + c3.insert(value("two", 2)); + UNIT_ASSERT(c3 != base); + + container c4; + c4.insert(value("one", 1)); + c4.insert(value("one", -1)); + c4.insert(value("one", 0)); + c4.insert(value("two", 2)); + UNIT_ASSERT(c3 != base); +} + +void THashTest::TestHMMapConstructorsAndAssignments() { + using container = THashMultiMap<TString, int>; + + container c1; + c1.insert(container::value_type("one", 1)); + c1.insert(container::value_type("two", 2)); + + container c2(c1); + + UNIT_ASSERT_VALUES_EQUAL(2, c1.size()); + UNIT_ASSERT_VALUES_EQUAL(2, c2.size()); + + container c3(std::move(c1)); + + UNIT_ASSERT_VALUES_EQUAL(0, c1.size()); + UNIT_ASSERT_VALUES_EQUAL(2, c3.size()); + + c2.insert(container::value_type("three", 3)); + c3 = c2; + + UNIT_ASSERT_VALUES_EQUAL(3, c2.size()); + UNIT_ASSERT_VALUES_EQUAL(3, c3.size()); + + c2.insert(container::value_type("four", 4)); + c3 = std::move(c2); + + UNIT_ASSERT_VALUES_EQUAL(0, c2.size()); + UNIT_ASSERT_VALUES_EQUAL(4, c3.size()); +} + +void THashTest::TestHMMap1() { + using mmap = THashMultiMap<char, int, THash<char>, TEqualTo<char>>; + mmap m; + + UNIT_ASSERT(m.count('X') == 0); + m.insert(std::pair<const char, int>('X', 10)); // Standard way. + UNIT_ASSERT(m.count('X') == 1); + + m.insert(std::pair<const char, int>('X', 20)); // jbuck: standard way + UNIT_ASSERT(m.count('X') == 2); + + m.insert(std::pair<const char, int>('Y', 32)); // jbuck: standard way + mmap::iterator i = m.find('X'); // Find first match. + + UNIT_ASSERT((*i).first == 'X'); + UNIT_ASSERT((*i).second == 10); + ++i; + UNIT_ASSERT((*i).first == 'X'); + UNIT_ASSERT((*i).second == 20); + + i = m.find('Y'); + UNIT_ASSERT((*i).first == 'Y'); + UNIT_ASSERT((*i).second == 32); + + i = m.find('Z'); + UNIT_ASSERT(i == m.end()); + + size_t count = m.erase('X'); + UNIT_ASSERT(count == 2); + + //Some iterators compare check, really compile time checks + mmap::iterator ite(m.begin()); + mmap::const_iterator cite(m.begin()); + + UNIT_ASSERT((mmap::const_iterator)ite == cite); + UNIT_ASSERT(!((mmap::const_iterator)ite != cite)); + UNIT_ASSERT(cite == (mmap::const_iterator)ite); + UNIT_ASSERT(!(cite != (mmap::const_iterator)ite)); + + using HMapType = THashMultiMap<size_t, size_t>; + HMapType hmap; + + //We fill the map to implicitely start a rehash. + for (size_t counter = 0; counter < 3077; ++counter) { + hmap.insert(HMapType::value_type(1, counter)); + } + + hmap.insert(HMapType::value_type(12325, 1)); + hmap.insert(HMapType::value_type(12325, 2)); + + UNIT_ASSERT(hmap.count(12325) == 2); + + //At this point 23 goes to the same bucket as 12325, it used to reveal a bug. + hmap.insert(HMapType::value_type(23, 0)); + + UNIT_ASSERT(hmap.count(12325) == 2); + + UNIT_ASSERT(hmap.bucket_count() > 3000); + for (size_t n = 0; n < 10; n++) { + hmap.clear(); + hmap.insert(HMapType::value_type(1, 2)); + } + UNIT_ASSERT(hmap.bucket_count() < 30); +} + +void THashTest::TestHMMapHas() { + using mmap = THashMultiMap<char, int, THash<char>, TEqualTo<char>>; + mmap m; + m.insert(std::pair<const char, int>('X', 10)); + m.insert(std::pair<const char, int>('X', 20)); + m.insert(std::pair<const char, int>('Y', 32)); + UNIT_ASSERT(m.contains('X')); + UNIT_ASSERT(m.contains('Y')); + UNIT_ASSERT(!m.contains('Z')); +} + +void THashTest::TestHSetConstructorsAndAssignments() { + using container = THashSet<int>; + + container c1; + c1.insert(100); + c1.insert(200); + + container c2(c1); + + UNIT_ASSERT_VALUES_EQUAL(2, c1.size()); + UNIT_ASSERT_VALUES_EQUAL(2, c2.size()); + UNIT_ASSERT(c1.contains(100)); + UNIT_ASSERT(c2.contains(200)); + + container c3(std::move(c1)); + + UNIT_ASSERT_VALUES_EQUAL(0, c1.size()); + UNIT_ASSERT_VALUES_EQUAL(2, c3.size()); + UNIT_ASSERT(c3.contains(100)); + + c2.insert(300); + c3 = c2; + + UNIT_ASSERT_VALUES_EQUAL(3, c2.size()); + UNIT_ASSERT_VALUES_EQUAL(3, c3.size()); + UNIT_ASSERT(c3.contains(300)); + + c2.insert(400); + c3 = std::move(c2); + + UNIT_ASSERT_VALUES_EQUAL(0, c2.size()); + UNIT_ASSERT_VALUES_EQUAL(4, c3.size()); + UNIT_ASSERT(c3.contains(400)); + + container c4 = {1, 2, 3}; + UNIT_ASSERT_VALUES_EQUAL(c4.size(), 3); + UNIT_ASSERT(c4.contains(1)); + UNIT_ASSERT(c4.contains(2)); + UNIT_ASSERT(c4.contains(3)); +} + +void THashTest::TestHSetSize() { + using container = THashSet<int>; + + container c; + c.insert(100); + c.insert(200); + + UNIT_ASSERT_VALUES_EQUAL(2, c.size()); + + c.insert(200); + + UNIT_ASSERT_VALUES_EQUAL(2, c.size()); +} + +void THashTest::TestHSet2() { + THashSet<int, THash<int>, TEqualTo<int>> s; + auto p = s.insert(42); + UNIT_ASSERT(p.second); + UNIT_ASSERT(*(p.first) == 42); + + p = s.insert(42); + UNIT_ASSERT(!p.second); +} + +void THashTest::TestHSetEqualityOperator() { + using container = THashSet<int>; + + container base; + base.insert(1); + base.insert(2); + + container c1(base); + UNIT_ASSERT(c1 == base); + + c1.insert(1); + UNIT_ASSERT(c1 == base); + + c1.insert(3); + UNIT_ASSERT(c1 != base); + + container c2; + c2.insert(2); + c2.insert(1); + UNIT_ASSERT(c2 == base); + + container c3; + c3.insert(1); + UNIT_ASSERT(c3 != base); +} + +void THashTest::TestHMSetConstructorsAndAssignments() { + using container = THashMultiSet<int>; + + container c1; + c1.insert(100); + c1.insert(200); + + container c2(c1); + + UNIT_ASSERT_VALUES_EQUAL(2, c1.size()); + UNIT_ASSERT_VALUES_EQUAL(2, c2.size()); + UNIT_ASSERT(c1.find(100) != c1.end()); + UNIT_ASSERT(c2.find(200) != c2.end()); + + container c3(std::move(c1)); + + UNIT_ASSERT_VALUES_EQUAL(0, c1.size()); + UNIT_ASSERT_VALUES_EQUAL(2, c3.size()); + UNIT_ASSERT(c3.find(100) != c3.end()); + + c2.insert(300); + c3 = c2; + + UNIT_ASSERT_VALUES_EQUAL(3, c2.size()); + UNIT_ASSERT_VALUES_EQUAL(3, c3.size()); + UNIT_ASSERT(c3.find(300) != c3.end()); + + c2.insert(400); + c3 = std::move(c2); + + UNIT_ASSERT_VALUES_EQUAL(0, c2.size()); + UNIT_ASSERT_VALUES_EQUAL(4, c3.size()); + UNIT_ASSERT(c3.find(400) != c3.end()); +} + +void THashTest::TestHMSetSize() { + using container = THashMultiSet<int>; + + container c; + c.insert(100); + c.insert(200); + + UNIT_ASSERT_VALUES_EQUAL(2, c.size()); + + c.insert(200); + + UNIT_ASSERT_VALUES_EQUAL(3, c.size()); +} + +void THashTest::TestHMSet1() { + hmset s; + UNIT_ASSERT(s.count(star) == 0); + s.insert(star); + UNIT_ASSERT(s.count(star) == 1); + s.insert(star); + UNIT_ASSERT(s.count(star) == 2); + auto i = s.find(char(40)); + UNIT_ASSERT(i == s.end()); + + i = s.find(star); + UNIT_ASSERT(i != s.end()); + UNIT_ASSERT(*i == '*'); + UNIT_ASSERT(s.erase(star) == 2); +} + +void THashTest::TestHMSetEqualityOperator() { + using container = THashMultiSet<int>; + + container base; + base.insert(1); + base.insert(1); + base.insert(2); + + container c1(base); + UNIT_ASSERT(c1 == base); + + c1.insert(1); + UNIT_ASSERT(!(c1 == base)); + + container c2; + c2.insert(2); + c2.insert(1); + c2.insert(1); + UNIT_ASSERT(c2 == base); + + container c3; + c3.insert(1); + c3.insert(2); + UNIT_ASSERT(!(c3 == base)); + + c3.insert(1); + UNIT_ASSERT(c3 == base); + + c3.insert(3); + UNIT_ASSERT(!(c3 == base)); +} + +void THashTest::TestHMSetEmplace() { + class TKey: public NTesting::TProbe { + public: + TKey(NTesting::TProbeState* state, int key) + : TProbe(state) + , Key_(key) + { + } + + operator size_t() const { + return THash<int>()(Key_); + } + + bool operator==(const TKey& other) const { + return Key_ == other.Key_; + } + + private: + int Key_; + }; + + NTesting::TProbeState state; + + { + THashMultiSet<TKey> c; + c.emplace(&state, 1); + c.emplace(&state, 1); + c.emplace(&state, 2); + + UNIT_ASSERT_EQUAL(state.CopyAssignments, 0); + UNIT_ASSERT_EQUAL(state.MoveAssignments, 0); + UNIT_ASSERT_EQUAL(state.Constructors, 3); + UNIT_ASSERT_EQUAL(state.MoveConstructors, 0); + + UNIT_ASSERT_EQUAL(c.count(TKey(&state, 1)), 2); + UNIT_ASSERT_EQUAL(c.count(TKey(&state, 2)), 1); + UNIT_ASSERT_EQUAL(c.count(TKey(&state, 3)), 0); + + UNIT_ASSERT_EQUAL(state.Constructors, 6); + UNIT_ASSERT_EQUAL(state.Destructors, 3); + } + + UNIT_ASSERT_EQUAL(state.CopyAssignments, 0); + UNIT_ASSERT_EQUAL(state.MoveAssignments, 0); + UNIT_ASSERT_EQUAL(state.CopyConstructors, 0); + UNIT_ASSERT_EQUAL(state.MoveConstructors, 0); + UNIT_ASSERT_EQUAL(state.Constructors, 6); + UNIT_ASSERT_EQUAL(state.Destructors, 6); +} + +void THashTest::TestInsertErase() { + using hmap = THashMap<TString, size_t, THash<TString>, TEqualTo<TString>>; + using val_type = hmap::value_type; + + { + hmap values; + + UNIT_ASSERT(values.insert(val_type("foo", 0)).second); + UNIT_ASSERT(values.insert(val_type("bar", 0)).second); + UNIT_ASSERT(values.insert(val_type("abc", 0)).second); + + UNIT_ASSERT(values.erase("foo") == 1); + UNIT_ASSERT(values.erase("bar") == 1); + UNIT_ASSERT(values.erase("abc") == 1); + } + + { + hmap values; + + UNIT_ASSERT(values.insert(val_type("foo", 0)).second); + UNIT_ASSERT(values.insert(val_type("bar", 0)).second); + UNIT_ASSERT(values.insert(val_type("abc", 0)).second); + + UNIT_ASSERT(values.erase("abc") == 1); + UNIT_ASSERT(values.erase("bar") == 1); + UNIT_ASSERT(values.erase("foo") == 1); + } +} + +namespace { + struct TItem: public TSimpleRefCount<TItem> { + const TString Key; + const TString Value; + + TItem(const TString& key, const TString& value) + : Key(key) + , Value(value) + { + } + }; + + using TItemPtr = TIntrusivePtr<TItem>; + + struct TSelectKey { + const TString& operator()(const TItemPtr& item) const { + return item->Key; + } + }; + + using TItemMapBase = THashTable< + TItemPtr, + TString, + THash<TString>, + TSelectKey, + TEqualTo<TString>, + std::allocator<TItemPtr>>; + + struct TItemMap: public TItemMapBase { + TItemMap() + : TItemMapBase(1, THash<TString>(), TEqualTo<TString>()) + { + } + + TItem& Add(const TString& key, const TString& value) { + insert_ctx ins; + iterator it = find_i(key, ins); + if (it == end()) { + it = insert_direct(new TItem(key, value), ins); + } + return **it; + } + }; +} + +void THashTest::TestResizeOnInsertSmartPtrBug() { + TItemMap map; + map.Add("key1", "value1"); + map.Add("key2", "value2"); + map.Add("key3", "value3"); + map.Add("key4", "value4"); + map.Add("key5", "value5"); + map.Add("key6", "value6"); + map.Add("key7", "value7"); + TItem& item = map.Add("key8", "value8"); + UNIT_ASSERT_EQUAL(item.Key, "key8"); + UNIT_ASSERT_EQUAL(item.Value, "value8"); +} + +template <typename T> +static void EmptyAndInsertTest(typename T::value_type v) { + T c; + UNIT_ASSERT(!c); + c.insert(v); + UNIT_ASSERT(c); +} + +void THashTest::TestEmpty() { + EmptyAndInsertTest<THashSet<int>>(1); + EmptyAndInsertTest<THashMap<int, int>>(std::pair<int, int>(1, 2)); + EmptyAndInsertTest<THashMultiMap<int, int>>(std::pair<int, int>(1, 2)); +} + +void THashTest::TestDefaultConstructor() { + THashSet<int> set; + + UNIT_ASSERT(set.begin() == set.end()); + + UNIT_ASSERT(set.find(0) == set.end()); + + auto range = set.equal_range(0); + UNIT_ASSERT(range.first == range.second); +} + +void THashTest::TestSizeOf() { + /* This test checks that we don't waste memory when all functors passed to + * THashTable are empty. It does rely on knowledge of THashTable internals, + * so if those change, the test will have to be adjusted accordingly. */ + + size_t expectedSize = sizeof(uintptr_t) + 3 * sizeof(size_t); + + UNIT_ASSERT_VALUES_EQUAL(sizeof(THashMap<int, int>), expectedSize); + UNIT_ASSERT_VALUES_EQUAL(sizeof(THashMap<std::pair<int, int>, std::pair<int, int>>), expectedSize); +} + +void THashTest::TestInvariants() { + std::set<int> reference_set; + THashSet<int> set; + + for (int i = 0; i < 1000; i++) { + set.insert(i); + reference_set.insert(i); + } + UNIT_ASSERT_VALUES_EQUAL(set.size(), 1000); + + int count0 = 0; + for (int i = 0; i < 1000; i++) { + count0 += (set.find(i) != set.end()) ? 1 : 0; + } + UNIT_ASSERT_VALUES_EQUAL(count0, 1000); + + int count1 = 0; + for (auto pos = set.begin(); pos != set.end(); pos++) { + ++count1; + } + UNIT_ASSERT_VALUES_EQUAL(count1, 1000); + + int count2 = 0; + for (const int& value : set) { + count2 += (reference_set.find(value) != reference_set.end()) ? 1 : 0; + } + UNIT_ASSERT_VALUES_EQUAL(count2, 1000); +} + +struct TAllocatorCounters { + TAllocatorCounters() + : Allocations(0) + , Deallocations(0) + { + } + + ~TAllocatorCounters() { + std::allocator<char> allocator; + + /* Release whatever was (intentionally) leaked. */ + for (const auto& chunk : Chunks) { + allocator.deallocate(static_cast<char*>(chunk.first), chunk.second); + } + } + + size_t Allocations; + size_t Deallocations; + TSet<std::pair<void*, size_t>> Chunks; +}; + +template <class T> +class TCountingAllocator: public std::allocator<T> { + using base_type = std::allocator<T>; + +public: + using size_type = typename base_type::size_type; + + template <class Other> + struct rebind { + using other = TCountingAllocator<Other>; + }; + + TCountingAllocator() + : Counters_(nullptr) + { + } + + TCountingAllocator(TAllocatorCounters* counters) + : Counters_(counters) + { + Y_ASSERT(counters); + } + + template <class Other> + TCountingAllocator(const TCountingAllocator<Other>& other) + : Counters_(other.Counters) + { + } + + T* allocate(size_type n) { + auto result = base_type::allocate(n); + + if (Counters_) { + ++Counters_->Allocations; + Counters_->Chunks.emplace(result, n * sizeof(T)); + } + + return result; + } + + void deallocate(T* p, size_type n) { + if (Counters_) { + ++Counters_->Deallocations; + Counters_->Chunks.erase(std::make_pair(p, n * sizeof(T))); + } + + base_type::deallocate(p, n); + } + +private: + TAllocatorCounters* Counters_; +}; + +void THashTest::TestAllocation() { + TAllocatorCounters counters; + + using int_set = THashSet<int, THash<int>, TEqualTo<int>, TCountingAllocator<int>>; + + { + int_set set0(&counters); + int_set set1(set0); + set0.clear(); + int_set set2(&counters); + set2 = set1; + UNIT_ASSERT_VALUES_EQUAL(counters.Allocations, 0); /* Copying around null sets should not trigger allocations. */ + + set0.insert(0); + UNIT_ASSERT_VALUES_EQUAL(counters.Allocations, 2); /* One for buckets array, one for a new node. */ + + set0.clear(); + set1 = set0; + int_set set3(set0); + UNIT_ASSERT_VALUES_EQUAL(counters.Allocations, 2); /* Copying from an empty set with allocated buckets should not trigger allocations. */ + + for (int i = 0; i < 1000; i++) { + set0.insert(i); + } + size_t allocations = counters.Allocations; + set0.clear(); + UNIT_ASSERT_VALUES_EQUAL(counters.Allocations, allocations); /* clear() should not trigger allocations. */ + } + + UNIT_ASSERT_VALUES_EQUAL(counters.Allocations, counters.Deallocations); +} + +template <int Value> +class TNonCopyableInt { +public: + explicit TNonCopyableInt(int) { + } + + TNonCopyableInt() = delete; + TNonCopyableInt(const TNonCopyableInt&) = delete; + TNonCopyableInt(TNonCopyable&&) = delete; + TNonCopyableInt& operator=(const TNonCopyable&) = delete; + TNonCopyableInt& operator=(TNonCopyable&&) = delete; + + operator int() const { + return Value; + } +}; + +void THashTest::TestInsertCopy() { + THashMap<int, int> hash; + + /* Insertion should not make copies of the provided key. */ + hash[TNonCopyableInt<0>(0)] = 0; +} + +void THashTest::TestEmplace() { + using hash_t = THashMap<int, TNonCopyableInt<0>>; + hash_t hash; + hash.emplace(std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(0)); + auto it = hash.find(1); + UNIT_ASSERT_VALUES_EQUAL(static_cast<int>(it->second), 0); +} + +void THashTest::TestEmplaceNoresize() { + using hash_t = THashMap<int, TNonCopyableInt<0>>; + hash_t hash; + hash.reserve(1); + hash.emplace_noresize(std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(0)); + auto it = hash.find(1); + UNIT_ASSERT_VALUES_EQUAL(static_cast<int>(it->second), 0); +} + +void THashTest::TestEmplaceDirect() { + using hash_t = THashMap<int, TNonCopyableInt<0>>; + hash_t hash; + hash_t::insert_ctx ins; + hash.find(1, ins); + hash.emplace_direct(ins, std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(0)); + auto it = hash.find(1); + UNIT_ASSERT_VALUES_EQUAL(static_cast<int>(it->second), 0); +} + +void THashTest::TestTryEmplace() { + static unsigned counter = 0u; + + struct TCountConstruct { + explicit TCountConstruct(int v) + : value(v) + { + ++counter; + } + TCountConstruct(const TCountConstruct&) = delete; + int value; + }; + + THashMap<int, TCountConstruct> hash; + { + // try_emplace does not copy key if key is rvalue + auto r = hash.try_emplace(TNonCopyableInt<0>(0), 1); + UNIT_ASSERT(r.second); + UNIT_ASSERT_VALUES_EQUAL(1, counter); + UNIT_ASSERT_VALUES_EQUAL(1, r.first->second.value); + } + { + auto r = hash.try_emplace(0, 2); + UNIT_ASSERT(!r.second); + UNIT_ASSERT_VALUES_EQUAL(1, counter); + UNIT_ASSERT_VALUES_EQUAL(1, r.first->second.value); + } +} + +void THashTest::TestTryEmplaceCopyKey() { + static unsigned counter = 0u; + + struct TCountCopy { + explicit TCountCopy(int i) + : Value(i) + { + } + TCountCopy(const TCountCopy& other) + : Value(other.Value) + { + ++counter; + } + + operator int() const { + return Value; + } + + int Value; + }; + + THashMap<TCountCopy, TNonCopyableInt<0>> hash; + TCountCopy key(1); + { + // try_emplace copy key if key is lvalue + auto r = hash.try_emplace(key, 1); + UNIT_ASSERT(r.second); + UNIT_ASSERT_VALUES_EQUAL(1, counter); + } + { + // no insert - no copy + auto r = hash.try_emplace(key, 2); + UNIT_ASSERT(!r.second); + UNIT_ASSERT_VALUES_EQUAL(1, counter); + } +} + +void THashTest::TestHMMapEmplace() { + using hash_t = THashMultiMap<int, TNonCopyableInt<0>>; + hash_t hash; + hash.emplace(std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(0)); + auto it = hash.find(1); + UNIT_ASSERT_VALUES_EQUAL(static_cast<int>(it->second), 0); +} + +void THashTest::TestHMMapEmplaceNoresize() { + using hash_t = THashMultiMap<int, TNonCopyableInt<0>>; + hash_t hash; + hash.reserve(1); + hash.emplace_noresize(std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(0)); + auto it = hash.find(1); + UNIT_ASSERT_VALUES_EQUAL(static_cast<int>(it->second), 0); +} + +void THashTest::TestHMMapEmplaceDirect() { + using hash_t = THashMultiMap<int, TNonCopyableInt<0>>; + hash_t hash; + hash_t::insert_ctx ins; + hash.find(1, ins); + hash.emplace_direct(ins, std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(0)); + auto it = hash.find(1); + UNIT_ASSERT_VALUES_EQUAL(static_cast<int>(it->second), 0); +} + +void THashTest::TestHSetEmplace() { + using hash_t = THashSet<TNonCopyableInt<0>, THash<int>, TEqualTo<int>>; + hash_t hash; + UNIT_ASSERT(!hash.contains(0)); + hash.emplace(0); + UNIT_ASSERT(hash.contains(0)); + UNIT_ASSERT(!hash.contains(1)); +} + +void THashTest::TestHSetEmplaceNoresize() { + using hash_t = THashSet<TNonCopyableInt<0>, THash<int>, TEqualTo<int>>; + hash_t hash; + hash.reserve(1); + UNIT_ASSERT(!hash.contains(0)); + hash.emplace_noresize(0); + UNIT_ASSERT(hash.contains(0)); + UNIT_ASSERT(!hash.contains(1)); +} + +void THashTest::TestHSetEmplaceDirect() { + using hash_t = THashSet<TNonCopyableInt<0>, THash<int>, TEqualTo<int>>; + hash_t hash; + UNIT_ASSERT(!hash.contains(0)); + hash_t::insert_ctx ins; + hash.find(0, ins); + hash.emplace_direct(ins, 1); + UNIT_ASSERT(hash.contains(0)); + UNIT_ASSERT(!hash.contains(1)); +} + +void THashTest::TestNonCopyable() { + struct TValue: public TNonCopyable { + int value; + TValue(int _value = 0) + : value(_value) + { + } + operator int() { + return value; + } + }; + + THashMap<int, TValue> hash; + hash.emplace(std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(5)); + auto&& value = hash[1]; + UNIT_ASSERT_VALUES_EQUAL(static_cast<int>(value), 5); + auto&& not_inserted = hash[2]; + UNIT_ASSERT_VALUES_EQUAL(static_cast<int>(not_inserted), 0); +} + +void THashTest::TestValueInitialization() { + THashMap<int, int> hash; + + int& value = hash[0]; + + /* Implicitly inserted values should be value-initialized. */ + UNIT_ASSERT_VALUES_EQUAL(value, 0); +} + +void THashTest::TestAssignmentClear() { + /* This one tests that assigning an empty hash resets the buckets array. + * See operator= for details. */ + + THashMap<int, int> hash; + size_t emptyBucketCount = hash.bucket_count(); + + for (int i = 0; i < 100; i++) { + hash[i] = i; + } + + hash = THashMap<int, int>(); + + UNIT_ASSERT_VALUES_EQUAL(hash.bucket_count(), emptyBucketCount); +} + +void THashTest::TestReleaseNodes() { + TAllocatorCounters counters; + using TIntSet = THashSet<int, THash<int>, TEqualTo<int>, TCountingAllocator<int>>; + + TIntSet set(&counters); + for (int i = 0; i < 3; i++) { + set.insert(i); + } + UNIT_ASSERT_VALUES_EQUAL(counters.Allocations, 4); + + set.release_nodes(); + UNIT_ASSERT_VALUES_EQUAL(counters.Allocations, 4); + UNIT_ASSERT_VALUES_EQUAL(set.size(), 0); + + for (int i = 10; i < 13; i++) { + set.insert(i); + } + UNIT_ASSERT_VALUES_EQUAL(counters.Allocations, 7); + UNIT_ASSERT(set.contains(10)); + UNIT_ASSERT(!set.contains(0)); + + set.basic_clear(); + UNIT_ASSERT_VALUES_EQUAL(counters.Deallocations, 3); + + TIntSet set2; + set2.release_nodes(); + set2.insert(1); + UNIT_ASSERT_VALUES_EQUAL(set2.size(), 1); +} + +void THashTest::TestAt() { +#define TEST_AT_THROWN_EXCEPTION(SRC_TYPE, DST_TYPE, KEY_TYPE, KEY, MESSAGE) \ + { \ + THashMap<SRC_TYPE, DST_TYPE> testMap; \ + try { \ + KEY_TYPE testKey = KEY; \ + testMap.at(testKey); \ + UNIT_ASSERT_C(false, "THashMap::at(\"" << KEY << "\") should throw"); \ + } catch (const yexception& e) { \ + UNIT_ASSERT_C(e.AsStrBuf().Contains(MESSAGE), "Incorrect exception description: got \"" << e.what() << "\", expected: \"" << MESSAGE << "\""); \ + } catch (...) { \ + UNIT_ASSERT_C(false, "THashMap::at(\"" << KEY << "\") should throw yexception"); \ + } \ + } + + TEST_AT_THROWN_EXCEPTION(TString, TString, TString, "111", "111"); + TEST_AT_THROWN_EXCEPTION(TString, TString, const TString, "111", "111"); + TEST_AT_THROWN_EXCEPTION(TString, TString, TStringBuf, "111", "111"); + TEST_AT_THROWN_EXCEPTION(TString, TString, const TStringBuf, "111", "111"); + TEST_AT_THROWN_EXCEPTION(TStringBuf, TStringBuf, const char*, "111", "111"); + TEST_AT_THROWN_EXCEPTION(int, int, short, 11, "11"); + TEST_AT_THROWN_EXCEPTION(int, int, int, -1, "-1"); + TEST_AT_THROWN_EXCEPTION(int, int, long, 111, "111"); + TEST_AT_THROWN_EXCEPTION(int, int, long long, -1000000000000ll, "-1000000000000"); + TEST_AT_THROWN_EXCEPTION(int, int, unsigned short, 11, "11"); + TEST_AT_THROWN_EXCEPTION(int, int, unsigned int, 2, "2"); + TEST_AT_THROWN_EXCEPTION(int, int, unsigned long, 131, "131"); + TEST_AT_THROWN_EXCEPTION(int, int, unsigned long long, 1000000000000ll, "1000000000000"); + + char key[] = {11, 12, 0, 1, 2, 11, 0}; + TEST_AT_THROWN_EXCEPTION(TString, TString, char*, key, "\\x0B\\x0C"); + TEST_AT_THROWN_EXCEPTION(TString, TString, TStringBuf, TStringBuf(key, sizeof(key) - 1), "\\x0B\\x0C\\0\\1\\2\\x0B"); + +#undef TEST_AT_THROWN_EXCEPTION +} + +void THashTest::TestHMapInitializerList() { + THashMap<TString, TString> h1 = {{"foo", "bar"}, {"bar", "baz"}, {"baz", "qux"}}; + THashMap<TString, TString> h2; + h2.insert(std::pair<TString, TString>("foo", "bar")); + h2.insert(std::pair<TString, TString>("bar", "baz")); + h2.insert(std::pair<TString, TString>("baz", "qux")); + UNIT_ASSERT_EQUAL(h1, h2); +} + +void THashTest::TestHMMapInitializerList() { + THashMultiMap<TString, TString> h1 = { + {"foo", "bar"}, + {"foo", "baz"}, + {"baz", "qux"}}; + THashMultiMap<TString, TString> h2; + h2.insert(std::pair<TString, TString>("foo", "bar")); + h2.insert(std::pair<TString, TString>("foo", "baz")); + h2.insert(std::pair<TString, TString>("baz", "qux")); + UNIT_ASSERT_EQUAL(h1, h2); +} + +void THashTest::TestHSetInitializerList() { + THashSet<TString> h1 = {"foo", "bar", "baz"}; + THashSet<TString> h2; + h2.insert("foo"); + h2.insert("bar"); + h2.insert("baz"); + UNIT_ASSERT_EQUAL(h1, h2); +} + +void THashTest::TestHMSetInitializerList() { + THashMultiSet<TString> h1 = {"foo", "foo", "bar", "baz"}; + THashMultiSet<TString> h2; + h2.insert("foo"); + h2.insert("foo"); + h2.insert("bar"); + h2.insert("baz"); + UNIT_ASSERT_EQUAL(h1, h2); +} + +namespace { + struct TFoo { + int A; + int B; + + bool operator==(const TFoo& o) const { + return A == o.A && B == o.B; + } + }; +} + +template <> +struct THash<TFoo> { + size_t operator()(const TFoo& v) const { + return v.A ^ v.B; + } +}; + +template <> +void Out<TFoo>(IOutputStream& o, const TFoo& v) { + o << '{' << v.A << ';' << v.B << '}'; +} + +void THashTest::TestHSetInsertInitializerList() { + { + const THashSet<int> x = {1}; + THashSet<int> y; + y.insert({1}); + UNIT_ASSERT_VALUES_EQUAL(x, y); + } + { + const THashSet<int> x = {1, 2}; + THashSet<int> y; + y.insert({1, 2}); + UNIT_ASSERT_VALUES_EQUAL(x, y); + } + { + const THashSet<int> x = {1, 2, 3, 4, 5}; + THashSet<int> y; + y.insert({ + 1, + 2, + 3, + 4, + 5, + }); + UNIT_ASSERT_VALUES_EQUAL(x, y); + } + { + const THashSet<TFoo> x = {{1, 2}}; + THashSet<TFoo> y; + y.insert({{1, 2}}); + UNIT_ASSERT_VALUES_EQUAL(x, y); + } + { + const THashSet<TFoo> x = {{1, 2}, {3, 4}}; + THashSet<TFoo> y; + y.insert({{1, 2}, {3, 4}}); + UNIT_ASSERT_VALUES_EQUAL(x, y); + } +} + +/* +* Sequence for MultiHash is reversed as it calculates hash as +* f(head:tail) = f(tail)xHash(head) +*/ +void THashTest::TestTupleHash() { + std::tuple<int, int> tuple{1, 3}; + UNIT_ASSERT_VALUES_EQUAL(THash<decltype(tuple)>()(tuple), MultiHash(3, 1)); + + /* + * This thing checks that we didn't break STL code + * See https://a.yandex-team.ru/arc/commit/2864838#comment-401 + * for example + */ + struct A { + A Foo(const std::tuple<A, float>& v) { + return std::get<A>(v); + } + }; +} |