blob: 6d2c5e0f2cc6954da2f8f2d48bedaa61d0a9072e [file] [log] [blame]
Mathieu Chartierc2e20622014-11-03 11:41:47 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "hash_set.h"
18
19#include <map>
Richard Uhlercf7792d2015-08-27 09:04:18 -070020#include <forward_list>
Mathieu Chartierc2e20622014-11-03 11:41:47 -080021#include <sstream>
22#include <string>
23#include <unordered_set>
Richard Uhlercf7792d2015-08-27 09:04:18 -070024#include <vector>
Mathieu Chartierc2e20622014-11-03 11:41:47 -080025
Mathieu Chartier47f867a2015-03-18 10:39:00 -070026#include <gtest/gtest.h>
Mathieu Chartiere7c9a8c2014-11-06 16:35:45 -080027#include "hash_map.h"
Mathieu Chartierc2e20622014-11-03 11:41:47 -080028
29namespace art {
30
Mathieu Chartiere7c9a8c2014-11-06 16:35:45 -080031struct IsEmptyFnString {
Mathieu Chartierc2e20622014-11-03 11:41:47 -080032 void MakeEmpty(std::string& item) const {
33 item.clear();
34 }
35 bool IsEmpty(const std::string& item) const {
36 return item.empty();
37 }
38};
39
Mathieu Chartier47f867a2015-03-18 10:39:00 -070040class HashSetTest : public testing::Test {
Mathieu Chartierc2e20622014-11-03 11:41:47 -080041 public:
42 HashSetTest() : seed_(97421), unique_number_(0) {
43 }
44 std::string RandomString(size_t len) {
45 std::ostringstream oss;
46 for (size_t i = 0; i < len; ++i) {
47 oss << static_cast<char>('A' + PRand() % 64);
48 }
49 static_assert(' ' < 'A', "space must be less than a");
50 oss << " " << unique_number_++; // Relies on ' ' < 'A'
51 return oss.str();
52 }
53 void SetSeed(size_t seed) {
54 seed_ = seed;
55 }
56 size_t PRand() { // Pseudo random.
57 seed_ = seed_ * 1103515245 + 12345;
58 return seed_;
59 }
60
61 private:
62 size_t seed_;
63 size_t unique_number_;
64};
65
66TEST_F(HashSetTest, TestSmoke) {
67 HashSet<std::string, IsEmptyFnString> hash_set;
68 const std::string test_string = "hello world 1234";
69 ASSERT_TRUE(hash_set.Empty());
70 ASSERT_EQ(hash_set.Size(), 0U);
71 hash_set.Insert(test_string);
72 auto it = hash_set.Find(test_string);
73 ASSERT_EQ(*it, test_string);
74 auto after_it = hash_set.Erase(it);
75 ASSERT_TRUE(after_it == hash_set.end());
76 ASSERT_TRUE(hash_set.Empty());
77 ASSERT_EQ(hash_set.Size(), 0U);
78 it = hash_set.Find(test_string);
79 ASSERT_TRUE(it == hash_set.end());
80}
81
82TEST_F(HashSetTest, TestInsertAndErase) {
83 HashSet<std::string, IsEmptyFnString> hash_set;
84 static constexpr size_t count = 1000;
85 std::vector<std::string> strings;
86 for (size_t i = 0; i < count; ++i) {
87 // Insert a bunch of elements and make sure we can find them.
88 strings.push_back(RandomString(10));
89 hash_set.Insert(strings[i]);
90 auto it = hash_set.Find(strings[i]);
91 ASSERT_TRUE(it != hash_set.end());
92 ASSERT_EQ(*it, strings[i]);
93 }
94 ASSERT_EQ(strings.size(), hash_set.Size());
95 // Try to erase the odd strings.
96 for (size_t i = 1; i < count; i += 2) {
97 auto it = hash_set.Find(strings[i]);
98 ASSERT_TRUE(it != hash_set.end());
99 ASSERT_EQ(*it, strings[i]);
100 hash_set.Erase(it);
101 }
102 // Test removed.
103 for (size_t i = 1; i < count; i += 2) {
104 auto it = hash_set.Find(strings[i]);
105 ASSERT_TRUE(it == hash_set.end());
106 }
107 for (size_t i = 0; i < count; i += 2) {
108 auto it = hash_set.Find(strings[i]);
109 ASSERT_TRUE(it != hash_set.end());
110 ASSERT_EQ(*it, strings[i]);
111 }
112}
113
114TEST_F(HashSetTest, TestIterator) {
115 HashSet<std::string, IsEmptyFnString> hash_set;
116 ASSERT_TRUE(hash_set.begin() == hash_set.end());
117 static constexpr size_t count = 1000;
118 std::vector<std::string> strings;
119 for (size_t i = 0; i < count; ++i) {
120 // Insert a bunch of elements and make sure we can find them.
121 strings.push_back(RandomString(10));
122 hash_set.Insert(strings[i]);
123 }
124 // Make sure we visit each string exactly once.
125 std::map<std::string, size_t> found_count;
126 for (const std::string& s : hash_set) {
127 ++found_count[s];
128 }
129 for (size_t i = 0; i < count; ++i) {
130 ASSERT_EQ(found_count[strings[i]], 1U);
131 }
132 found_count.clear();
133 // Remove all the elements with iterator erase.
134 for (auto it = hash_set.begin(); it != hash_set.end();) {
135 ++found_count[*it];
136 it = hash_set.Erase(it);
137 ASSERT_EQ(hash_set.Verify(), 0U);
138 }
139 for (size_t i = 0; i < count; ++i) {
140 ASSERT_EQ(found_count[strings[i]], 1U);
141 }
142}
143
144TEST_F(HashSetTest, TestSwap) {
145 HashSet<std::string, IsEmptyFnString> hash_seta, hash_setb;
146 std::vector<std::string> strings;
147 static constexpr size_t count = 1000;
148 for (size_t i = 0; i < count; ++i) {
149 strings.push_back(RandomString(10));
150 hash_seta.Insert(strings[i]);
151 }
152 std::swap(hash_seta, hash_setb);
153 hash_seta.Insert("TEST");
154 hash_setb.Insert("TEST2");
155 for (size_t i = 0; i < count; ++i) {
156 strings.push_back(RandomString(10));
157 hash_seta.Insert(strings[i]);
158 }
159}
160
Igor Murashkin3552d962015-06-22 15:57:38 -0700161TEST_F(HashSetTest, TestShrink) {
162 HashSet<std::string, IsEmptyFnString> hash_set;
163 std::vector<std::string> strings = {"a", "b", "c", "d", "e", "f", "g"};
164 for (size_t i = 0; i < strings.size(); ++i) {
165 // Insert some strings into the beginning of our hash set to establish an initial size
166 hash_set.Insert(strings[i]);
167 }
168
169 hash_set.ShrinkToMaximumLoad();
170 const double initial_load = hash_set.CalculateLoadFactor();
171
172 // Insert a bunch of random strings to guarantee that we grow the capacity.
173 std::vector<std::string> random_strings;
174 static constexpr size_t count = 1000;
175 for (size_t i = 0; i < count; ++i) {
176 random_strings.push_back(RandomString(10));
177 hash_set.Insert(random_strings[i]);
178 }
179
180 // Erase all the extra strings which guarantees that our load factor will be really bad.
181 for (size_t i = 0; i < count; ++i) {
182 hash_set.Erase(hash_set.Find(random_strings[i]));
183 }
184
185 const double bad_load = hash_set.CalculateLoadFactor();
186 EXPECT_GT(initial_load, bad_load);
187
188 // Shrink again, the load factor should be good again.
189 hash_set.ShrinkToMaximumLoad();
190 EXPECT_DOUBLE_EQ(initial_load, hash_set.CalculateLoadFactor());
Igor Murashkine2facc52015-07-10 13:49:08 -0700191
192 // Make sure all the initial elements we had are still there
193 for (const std::string& initial_string : strings) {
194 EXPECT_NE(hash_set.end(), hash_set.Find(initial_string))
195 << "expected to find " << initial_string;
196 }
Igor Murashkin3552d962015-06-22 15:57:38 -0700197}
198
Mathieu Chartierc2e20622014-11-03 11:41:47 -0800199TEST_F(HashSetTest, TestStress) {
200 HashSet<std::string, IsEmptyFnString> hash_set;
201 std::unordered_multiset<std::string> std_set;
202 std::vector<std::string> strings;
203 static constexpr size_t string_count = 2000;
204 static constexpr size_t operations = 100000;
205 static constexpr size_t target_size = 5000;
206 for (size_t i = 0; i < string_count; ++i) {
207 strings.push_back(RandomString(i % 10 + 1));
208 }
209 const size_t seed = time(nullptr);
210 SetSeed(seed);
211 LOG(INFO) << "Starting stress test with seed " << seed;
212 for (size_t i = 0; i < operations; ++i) {
213 ASSERT_EQ(hash_set.Size(), std_set.size());
214 size_t delta = std::abs(static_cast<ssize_t>(target_size) -
215 static_cast<ssize_t>(hash_set.Size()));
216 size_t n = PRand();
217 if (n % target_size == 0) {
218 hash_set.Clear();
219 std_set.clear();
220 ASSERT_TRUE(hash_set.Empty());
221 ASSERT_TRUE(std_set.empty());
222 } else if (n % target_size < delta) {
223 // Skew towards adding elements until we are at the desired size.
224 const std::string& s = strings[PRand() % string_count];
225 hash_set.Insert(s);
226 std_set.insert(s);
227 ASSERT_EQ(*hash_set.Find(s), *std_set.find(s));
228 } else {
229 const std::string& s = strings[PRand() % string_count];
230 auto it1 = hash_set.Find(s);
231 auto it2 = std_set.find(s);
232 ASSERT_EQ(it1 == hash_set.end(), it2 == std_set.end());
233 if (it1 != hash_set.end()) {
234 ASSERT_EQ(*it1, *it2);
235 hash_set.Erase(it1);
236 std_set.erase(it2);
237 }
238 }
239 }
240}
241
Mathieu Chartiere7c9a8c2014-11-06 16:35:45 -0800242struct IsEmptyStringPair {
243 void MakeEmpty(std::pair<std::string, int>& pair) const {
244 pair.first.clear();
245 }
246 bool IsEmpty(const std::pair<std::string, int>& pair) const {
247 return pair.first.empty();
248 }
249};
250
251TEST_F(HashSetTest, TestHashMap) {
252 HashMap<std::string, int, IsEmptyStringPair> hash_map;
253 hash_map.Insert(std::make_pair(std::string("abcd"), 123));
254 hash_map.Insert(std::make_pair(std::string("abcd"), 124));
255 hash_map.Insert(std::make_pair(std::string("bags"), 444));
256 auto it = hash_map.Find(std::string("abcd"));
257 ASSERT_EQ(it->second, 123);
258 hash_map.Erase(it);
259 it = hash_map.Find(std::string("abcd"));
260 ASSERT_EQ(it->second, 124);
261}
262
Richard Uhlercf7792d2015-08-27 09:04:18 -0700263struct IsEmptyFnVectorInt {
264 void MakeEmpty(std::vector<int>& item) const {
265 item.clear();
266 }
267 bool IsEmpty(const std::vector<int>& item) const {
268 return item.empty();
269 }
270};
271
272template <typename T>
273size_t HashIntSequence(T begin, T end) {
274 size_t hash = 0;
275 for (auto iter = begin; iter != end; ++iter) {
276 hash = hash * 2 + *iter;
277 }
278 return hash;
279};
280
281struct VectorIntHashEquals {
282 std::size_t operator()(const std::vector<int>& item) const {
283 return HashIntSequence(item.begin(), item.end());
284 }
285
286 std::size_t operator()(const std::forward_list<int>& item) const {
287 return HashIntSequence(item.begin(), item.end());
288 }
289
290 bool operator()(const std::vector<int>& a, const std::vector<int>& b) const {
291 return a == b;
292 }
293
294 bool operator()(const std::vector<int>& a, const std::forward_list<int>& b) const {
295 auto aiter = a.begin();
296 auto biter = b.begin();
297 while (aiter != a.end() && biter != b.end()) {
298 if (*aiter != *biter) {
299 return false;
300 }
301 aiter++;
302 biter++;
303 }
304 return (aiter == a.end() && biter == b.end());
305 }
306};
307
308TEST_F(HashSetTest, TestLookupByAlternateKeyType) {
309 HashSet<std::vector<int>, IsEmptyFnVectorInt, VectorIntHashEquals, VectorIntHashEquals> hash_set;
310 hash_set.Insert(std::vector<int>({1, 2, 3, 4}));
311 hash_set.Insert(std::vector<int>({4, 2}));
312 ASSERT_EQ(hash_set.end(), hash_set.Find(std::vector<int>({1, 1, 1, 1})));
313 ASSERT_NE(hash_set.end(), hash_set.Find(std::vector<int>({1, 2, 3, 4})));
314 ASSERT_EQ(hash_set.end(), hash_set.Find(std::forward_list<int>({1, 1, 1, 1})));
315 ASSERT_NE(hash_set.end(), hash_set.Find(std::forward_list<int>({1, 2, 3, 4})));
316}
317
Mathieu Chartierc2e20622014-11-03 11:41:47 -0800318} // namespace art