blob: 660303b6e24c74a1e64cb4a08ecaf92dfdb78292 [file] [log] [blame]
mtkleinfb8307c2015-04-01 11:21:27 -07001/*
2 * Copyright 2015 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "include/core/SkRefCnt.h"
9#include "include/core/SkString.h"
10#include "include/private/SkChecksum.h"
11#include "include/private/SkTHash.h"
12#include "tests/Test.h"
mtklein979e0ea2015-02-12 13:20:08 -080013
John Stiles7df731b2020-12-22 14:57:16 -050014#include <tuple>
15
mtklein02f46cf2015-03-20 13:48:42 -070016// Tests use of const foreach(). map.count() is of course the better way to do this.
17static int count(const SkTHashMap<int, double>& map) {
18 int n = 0;
19 map.foreach([&n](int, double) { n++; });
20 return n;
21}
mtklein979e0ea2015-02-12 13:20:08 -080022
23DEF_TEST(HashMap, r) {
mtklein02f46cf2015-03-20 13:48:42 -070024 SkTHashMap<int, double> map;
mtklein979e0ea2015-02-12 13:20:08 -080025
26 map.set(3, 4.0);
27 REPORTER_ASSERT(r, map.count() == 1);
28
mtklein469a3fe2015-08-07 09:33:37 -070029 REPORTER_ASSERT(r, map.approxBytesUsed() > 0);
30
mtklein979e0ea2015-02-12 13:20:08 -080031 double* found = map.find(3);
32 REPORTER_ASSERT(r, found);
33 REPORTER_ASSERT(r, *found == 4.0);
34
mtklein02f46cf2015-03-20 13:48:42 -070035 map.foreach([](int key, double* d){ *d = -key; });
36 REPORTER_ASSERT(r, count(map) == 1);
37
mtklein979e0ea2015-02-12 13:20:08 -080038 found = map.find(3);
39 REPORTER_ASSERT(r, found);
40 REPORTER_ASSERT(r, *found == -3.0);
41
42 REPORTER_ASSERT(r, !map.find(2));
43
44 const int N = 20;
45
46 for (int i = 0; i < N; i++) {
47 map.set(i, 2.0*i);
48 }
John Stiles9c0b79a2020-10-08 11:01:00 -040049
John Stiles7df731b2020-12-22 14:57:16 -050050 // Test walking the map with iterators, using preincrement (++iter).
51 for (SkTHashMap<int, double>::Iter iter = map.begin(); iter != map.end(); ++iter) {
52 REPORTER_ASSERT(r, iter->first * 2 == (*iter).second);
53 }
54
55 // Test walking the map with range-based for.
56 for (auto& entry : map) {
57 REPORTER_ASSERT(r, entry.first * 2 == entry.second);
58 }
59
60 // Ensure that iteration works equally well on a const map, using postincrement (iter++).
61 const auto& cmap = map;
62 for (SkTHashMap<int, double>::Iter iter = cmap.begin(); iter != cmap.end(); iter++) {
63 REPORTER_ASSERT(r, iter->first * 2 == (*iter).second);
64 }
65
66 // Ensure that range-based for works equally well on a const map.
67 for (const auto& entry : cmap) {
68 REPORTER_ASSERT(r, entry.first * 2 == entry.second);
69 }
70
71 // Ensure that structured bindings work.
72 for (const auto& [number, timesTwo] : cmap) {
73 REPORTER_ASSERT(r, number * 2 == timesTwo);
74 }
75
John Stiles9c0b79a2020-10-08 11:01:00 -040076 SkTHashMap<int, double> clone = map;
77
mtklein979e0ea2015-02-12 13:20:08 -080078 for (int i = 0; i < N; i++) {
tfarina567ff2f2015-04-27 07:01:44 -070079 double* found = map.find(i);
mtklein979e0ea2015-02-12 13:20:08 -080080 REPORTER_ASSERT(r, found);
81 REPORTER_ASSERT(r, *found == i*2.0);
John Stiles9c0b79a2020-10-08 11:01:00 -040082
83 found = clone.find(i);
84 REPORTER_ASSERT(r, found);
85 REPORTER_ASSERT(r, *found == i*2.0);
mtklein979e0ea2015-02-12 13:20:08 -080086 }
87 for (int i = N; i < 2*N; i++) {
88 REPORTER_ASSERT(r, !map.find(i));
John Stiles9c0b79a2020-10-08 11:01:00 -040089 REPORTER_ASSERT(r, !clone.find(i));
mtklein979e0ea2015-02-12 13:20:08 -080090 }
91
92 REPORTER_ASSERT(r, map.count() == N);
John Stiles9c0b79a2020-10-08 11:01:00 -040093 REPORTER_ASSERT(r, clone.count() == N);
mtklein2aa1f7e2015-02-20 12:35:32 -080094
mtklein33d73c32015-04-21 06:53:56 -070095 for (int i = 0; i < N/2; i++) {
96 map.remove(i);
97 }
98 for (int i = 0; i < N; i++) {
99 double* found = map.find(i);
John Stiles9c0b79a2020-10-08 11:01:00 -0400100 REPORTER_ASSERT(r, (found == nullptr) == (i < N/2));
101
102 found = clone.find(i);
103 REPORTER_ASSERT(r, *found == i*2.0);
mtklein33d73c32015-04-21 06:53:56 -0700104 }
105 REPORTER_ASSERT(r, map.count() == N/2);
John Stiles9c0b79a2020-10-08 11:01:00 -0400106 REPORTER_ASSERT(r, clone.count() == N);
mtklein33d73c32015-04-21 06:53:56 -0700107
mtklein2aa1f7e2015-02-20 12:35:32 -0800108 map.reset();
109 REPORTER_ASSERT(r, map.count() == 0);
John Stiles9c0b79a2020-10-08 11:01:00 -0400110 REPORTER_ASSERT(r, clone.count() == N);
111
112 clone = map;
113 REPORTER_ASSERT(r, clone.count() == 0);
Florin Malita053730d2017-03-10 11:51:07 -0500114
115 {
116 // Test that we don't leave dangling values in empty slots.
117 SkTHashMap<int, sk_sp<SkRefCnt>> refMap;
118 auto ref = sk_make_sp<SkRefCnt>();
119 REPORTER_ASSERT(r, ref->unique());
120
121 refMap.set(0, ref);
122 REPORTER_ASSERT(r, refMap.count() == 1);
123 REPORTER_ASSERT(r, !ref->unique());
124
125 refMap.remove(0);
126 REPORTER_ASSERT(r, refMap.count() == 0);
127 REPORTER_ASSERT(r, ref->unique());
128 }
mtklein979e0ea2015-02-12 13:20:08 -0800129}
130
mtklein979e0ea2015-02-12 13:20:08 -0800131DEF_TEST(HashSet, r) {
mtklein02f46cf2015-03-20 13:48:42 -0700132 SkTHashSet<SkString> set;
mtklein979e0ea2015-02-12 13:20:08 -0800133
134 set.add(SkString("Hello"));
135 set.add(SkString("World"));
mtklein979e0ea2015-02-12 13:20:08 -0800136 REPORTER_ASSERT(r, set.count() == 2);
mtklein979e0ea2015-02-12 13:20:08 -0800137 REPORTER_ASSERT(r, set.contains(SkString("Hello")));
138 REPORTER_ASSERT(r, set.contains(SkString("World")));
139 REPORTER_ASSERT(r, !set.contains(SkString("Goodbye")));
mtkleinfb8307c2015-04-01 11:21:27 -0700140 REPORTER_ASSERT(r, set.find(SkString("Hello")));
141 REPORTER_ASSERT(r, *set.find(SkString("Hello")) == SkString("Hello"));
142
John Stiles7df731b2020-12-22 14:57:16 -0500143 // Test walking the set with iterators, using preincrement (++iter).
144 for (SkTHashSet<SkString>::Iter iter = set.begin(); iter != set.end(); ++iter) {
145 REPORTER_ASSERT(r, iter->equals("Hello") || (*iter).equals("World"));
146 }
147
148 // Test walking the set with iterators, using postincrement (iter++).
149 for (SkTHashSet<SkString>::Iter iter = set.begin(); iter != set.end(); iter++) {
150 REPORTER_ASSERT(r, iter->equals("Hello") || (*iter).equals("World"));
151 }
152
153 // Test walking the set with range-based for.
154 for (auto& entry : set) {
155 REPORTER_ASSERT(r, entry.equals("Hello") || entry.equals("World"));
156 }
157
158 // Ensure that iteration works equally well on a const set.
159 const auto& cset = set;
160 for (SkTHashSet<SkString>::Iter iter = cset.begin(); iter != cset.end(); iter++) {
161 REPORTER_ASSERT(r, iter->equals("Hello") || (*iter).equals("World"));
162 }
163
164 // Ensure that range-based for works equally well on a const set.
165 for (auto& entry : cset) {
166 REPORTER_ASSERT(r, entry.equals("Hello") || entry.equals("World"));
167 }
168
John Stiles9c0b79a2020-10-08 11:01:00 -0400169 SkTHashSet<SkString> clone = set;
170 REPORTER_ASSERT(r, clone.count() == 2);
171 REPORTER_ASSERT(r, clone.contains(SkString("Hello")));
172 REPORTER_ASSERT(r, clone.contains(SkString("World")));
173 REPORTER_ASSERT(r, !clone.contains(SkString("Goodbye")));
174 REPORTER_ASSERT(r, clone.find(SkString("Hello")));
175 REPORTER_ASSERT(r, *clone.find(SkString("Hello")) == SkString("Hello"));
176
mtklein33d73c32015-04-21 06:53:56 -0700177 set.remove(SkString("Hello"));
178 REPORTER_ASSERT(r, !set.contains(SkString("Hello")));
179 REPORTER_ASSERT(r, set.count() == 1);
John Stiles9c0b79a2020-10-08 11:01:00 -0400180 REPORTER_ASSERT(r, clone.contains(SkString("Hello")));
181 REPORTER_ASSERT(r, clone.count() == 2);
mtklein33d73c32015-04-21 06:53:56 -0700182
mtklein2aa1f7e2015-02-20 12:35:32 -0800183 set.reset();
184 REPORTER_ASSERT(r, set.count() == 0);
John Stiles9c0b79a2020-10-08 11:01:00 -0400185
186 clone = set;
187 REPORTER_ASSERT(r, clone.count() == 0);
mtklein979e0ea2015-02-12 13:20:08 -0800188}
fmalita79ca0812015-02-12 17:32:49 -0800189
190namespace {
191
192class CopyCounter {
193public:
halcanary96fcdcc2015-08-27 07:41:13 -0700194 CopyCounter() : fID(0), fCounter(nullptr) {}
fmalita79ca0812015-02-12 17:32:49 -0800195
196 CopyCounter(uint32_t id, uint32_t* counter) : fID(id), fCounter(counter) {}
197
198 CopyCounter(const CopyCounter& other)
199 : fID(other.fID)
200 , fCounter(other.fCounter) {
201 SkASSERT(fCounter);
202 *fCounter += 1;
203 }
204
205 void operator=(const CopyCounter& other) {
206 fID = other.fID;
207 fCounter = other.fCounter;
208 *fCounter += 1;
209 }
210
Mike Kleindb402ca2016-12-13 12:46:05 -0500211 CopyCounter(CopyCounter&& other) { *this = std::move(other); }
212 void operator=(CopyCounter&& other) {
213 fID = other.fID;
214 fCounter = other.fCounter;
215 }
216
217
fmalita79ca0812015-02-12 17:32:49 -0800218 bool operator==(const CopyCounter& other) const {
219 return fID == other.fID;
220 }
221
222private:
223 uint32_t fID;
224 uint32_t* fCounter;
225};
226
mtkleinc8d1dd42015-10-15 12:23:01 -0700227struct HashCopyCounter {
228 uint32_t operator()(const CopyCounter&) const {
229 return 0; // let them collide, what do we care?
230 }
231};
fmalita79ca0812015-02-12 17:32:49 -0800232
John Stilesa6841be2020-08-06 14:11:56 -0400233} // namespace
fmalita79ca0812015-02-12 17:32:49 -0800234
235DEF_TEST(HashSetCopyCounter, r) {
mtkleinc8d1dd42015-10-15 12:23:01 -0700236 SkTHashSet<CopyCounter, HashCopyCounter> set;
fmalita79ca0812015-02-12 17:32:49 -0800237
238 uint32_t globalCounter = 0;
239 CopyCounter copyCounter1(1, &globalCounter);
240 CopyCounter copyCounter2(2, &globalCounter);
241 REPORTER_ASSERT(r, globalCounter == 0);
242
243 set.add(copyCounter1);
244 REPORTER_ASSERT(r, globalCounter == 1);
245 REPORTER_ASSERT(r, set.contains(copyCounter1));
246 REPORTER_ASSERT(r, globalCounter == 1);
247 set.add(copyCounter1);
248 // We allow copies for same-value adds for now.
249 REPORTER_ASSERT(r, globalCounter == 2);
250
251 set.add(copyCounter2);
252 REPORTER_ASSERT(r, globalCounter == 3);
253 REPORTER_ASSERT(r, set.contains(copyCounter1));
254 REPORTER_ASSERT(r, set.contains(copyCounter2));
255 REPORTER_ASSERT(r, globalCounter == 3);
256 set.add(copyCounter1);
257 set.add(copyCounter2);
258 // We allow copies for same-value adds for now.
259 REPORTER_ASSERT(r, globalCounter == 5);
260}
Mike Klein4284ec62019-01-09 13:20:57 -0500261
262
263DEF_TEST(HashFindOrNull, r) {
264 struct Entry {
265 int key = 0;
266 int val = 0;
267 };
268
269 struct HashTraits {
270 static int GetKey(const Entry* e) { return e->key; }
271 static uint32_t Hash(int key) { return key; }
272 };
273
274 SkTHashTable<Entry*, int, HashTraits> table;
275
276 REPORTER_ASSERT(r, nullptr == table.findOrNull(7));
277
278 Entry seven = { 7, 24 };
279 table.set(&seven);
280
281 REPORTER_ASSERT(r, &seven == table.findOrNull(7));
282}
Herb Derby2fc9fa62019-12-12 15:07:11 -0500283
Mike Klein16701ee2020-03-14 10:03:50 -0500284DEF_TEST(HashTableGrowsAndShrinks, r) {
285 SkTHashSet<int> s;
286 auto check_count_cap = [&](int count, int cap) {
287 REPORTER_ASSERT(r, s.count() == count);
288 REPORTER_ASSERT(r, s.approxBytesUsed() == (sizeof(int) + sizeof(uint32_t)) * cap);
289 };
290
291 // Add and remove some elements to test basic growth and shrink patterns.
292 check_count_cap(0,0);
293 s.add(1); check_count_cap(1,4);
294 s.add(2); check_count_cap(2,4);
295 s.add(3); check_count_cap(3,4);
296 s.add(4); check_count_cap(4,8);
297
298 s.remove(4); check_count_cap(3,8);
299 s.remove(3); check_count_cap(2,4);
300 s.remove(2); check_count_cap(1,4);
301 s.remove(1); check_count_cap(0,4);
302
303 s.add(1); check_count_cap(1,4);
304 s.add(2); check_count_cap(2,4);
305 s.add(3); check_count_cap(3,4);
306 s.add(4); check_count_cap(4,8);
307
308 // Add and remove single elements repeatedly to test hysteresis
309 // avoids reallocating these small tables all the time.
310 for (int i = 0; i < 10; i++) {
311 s. add(5); check_count_cap(5,8);
312 s.remove(5); check_count_cap(4,8);
313 }
314
315 s.remove(4); check_count_cap(3,8);
316 for (int i = 0; i < 10; i++) {
317 s. add(4); check_count_cap(4,8);
318 s.remove(4); check_count_cap(3,8);
319 }
320
321 s.remove(3); check_count_cap(2,4);
322 for (int i = 0; i < 10; i++) {
323 s. add(4); check_count_cap(3,4);
324 s.remove(4); check_count_cap(2,4);
325 }
326
327 s.remove(2); check_count_cap(1,4);
328 for (int i = 0; i < 10; i++) {
329 s. add(2); check_count_cap(2,4);
330 s.remove(2); check_count_cap(1,4);
331 }
332}