Raw
1 #include "unit-test.h"
2 #include "hashmap.h"
3 #include "strbuf.h"
4
5 struct test_entry {
6 int padding; /* hashmap entry no longer needs to be the first member */
7 struct hashmap_entry ent;
8 /* key and value as two \0-terminated strings */
9 char key[FLEX_ARRAY];
10 };
11
12 static int test_entry_cmp(const void *cmp_data,
13 const struct hashmap_entry *eptr,
14 const struct hashmap_entry *entry_or_key,
15 const void *keydata)
16 {
17 const unsigned int ignore_case = cmp_data ? *((int *)cmp_data) : 0;
18 const struct test_entry *e1, *e2;
19 const char *key = keydata;
20
21 e1 = container_of(eptr, const struct test_entry, ent);
22 e2 = container_of(entry_or_key, const struct test_entry, ent);
23
24 if (ignore_case)
25 return strcasecmp(e1->key, key ? key : e2->key);
26 else
27 return strcmp(e1->key, key ? key : e2->key);
28 }
29
30 static const char *get_value(const struct test_entry *e)
31 {
32 return e->key + strlen(e->key) + 1;
33 }
34
35 static struct test_entry *alloc_test_entry(const char *key, const char *value,
36 unsigned int ignore_case)
37 {
38 size_t klen = strlen(key);
39 size_t vlen = strlen(value);
40 unsigned int hash = ignore_case ? strihash(key) : strhash(key);
41 struct test_entry *entry = xmalloc(st_add4(sizeof(*entry), klen, vlen, 2));
42
43 hashmap_entry_init(&entry->ent, hash);
44 memcpy(entry->key, key, klen + 1);
45 memcpy(entry->key + klen + 1, value, vlen + 1);
46 return entry;
47 }
48
49 static struct test_entry *get_test_entry(struct hashmap *map, const char *key,
50 unsigned int ignore_case)
51 {
52 return hashmap_get_entry_from_hash(
53 map, ignore_case ? strihash(key) : strhash(key), key,
54 struct test_entry, ent);
55 }
56
57 static int key_val_contains(const char *key_val[][2], char seen[], size_t n,
58 struct test_entry *entry)
59 {
60 for (size_t i = 0; i < n; i++) {
61 if (!strcmp(entry->key, key_val[i][0]) &&
62 !strcmp(get_value(entry), key_val[i][1])) {
63 if (seen[i])
64 return 2;
65 seen[i] = 1;
66 return 0;
67 }
68 }
69 return 1;
70 }
71
72 static void setup(void (*f)(struct hashmap *map, unsigned int ignore_case),
73 unsigned int ignore_case)
74 {
75 struct hashmap map = HASHMAP_INIT(test_entry_cmp, &ignore_case);
76
77 f(&map, ignore_case);
78 hashmap_clear_and_free(&map, struct test_entry, ent);
79 }
80
81 static void t_replace(struct hashmap *map, unsigned int ignore_case)
82 {
83 struct test_entry *entry;
84
85 entry = alloc_test_entry("key1", "value1", ignore_case);
86 cl_assert_equal_p(hashmap_put_entry(map, entry, ent), NULL);
87
88 entry = alloc_test_entry(ignore_case ? "Key1" : "key1", "value2",
89 ignore_case);
90 entry = hashmap_put_entry(map, entry, ent);
91 cl_assert(entry != NULL);
92 cl_assert_equal_s(get_value(entry), "value1");
93 free(entry);
94
95 entry = alloc_test_entry("fooBarFrotz", "value3", ignore_case);
96 cl_assert_equal_p(hashmap_put_entry(map, entry, ent), NULL);
97
98 entry = alloc_test_entry(ignore_case ? "FOObarFrotz" : "fooBarFrotz",
99 "value4", ignore_case);
100 entry = hashmap_put_entry(map, entry, ent);
101 cl_assert(entry != NULL);
102 cl_assert_equal_s(get_value(entry), "value3");
103 free(entry);
104 }
105
106 static void t_get(struct hashmap *map, unsigned int ignore_case)
107 {
108 struct test_entry *entry;
109 const char *key_val[][2] = { { "key1", "value1" },
110 { "key2", "value2" },
111 { "fooBarFrotz", "value3" },
112 { ignore_case ? "key4" : "foobarfrotz",
113 "value4" } };
114 const char *query[][2] = {
115 { ignore_case ? "Key1" : "key1", "value1" },
116 { ignore_case ? "keY2" : "key2", "value2" },
117 { ignore_case ? "FOObarFrotz" : "fooBarFrotz", "value3" },
118 { ignore_case ? "FOObarFrotz" : "foobarfrotz",
119 ignore_case ? "value3" : "value4" }
120 };
121
122 for (size_t i = 0; i < ARRAY_SIZE(key_val); i++) {
123 entry = alloc_test_entry(key_val[i][0], key_val[i][1],
124 ignore_case);
125 cl_assert_equal_p(hashmap_put_entry(map, entry, ent), NULL);
126 }
127
128 for (size_t i = 0; i < ARRAY_SIZE(query); i++) {
129 entry = get_test_entry(map, query[i][0], ignore_case);
130 cl_assert(entry != NULL);
131 cl_assert_equal_s(get_value(entry), query[i][1]);
132 }
133
134 cl_assert_equal_p(get_test_entry(map, "notInMap", ignore_case), NULL);
135 cl_assert_equal_i(map->tablesize, 64);
136 cl_assert_equal_i(hashmap_get_size(map), ARRAY_SIZE(key_val));
137 }
138
139 static void t_add(struct hashmap *map, unsigned int ignore_case)
140 {
141 struct test_entry *entry;
142 const char *key_val[][2] = {
143 { "key1", "value1" },
144 { ignore_case ? "Key1" : "key1", "value2" },
145 { "fooBarFrotz", "value3" },
146 { ignore_case ? "FOObarFrotz" : "fooBarFrotz", "value4" }
147 };
148 const char *query_keys[] = { "key1", ignore_case ? "FOObarFrotz" :
149 "fooBarFrotz" };
150 char seen[ARRAY_SIZE(key_val)] = { 0 };
151
152 for (size_t i = 0; i < ARRAY_SIZE(key_val); i++) {
153 entry = alloc_test_entry(key_val[i][0], key_val[i][1], ignore_case);
154 hashmap_add(map, &entry->ent);
155 }
156
157 for (size_t i = 0; i < ARRAY_SIZE(query_keys); i++) {
158 int count = 0;
159 entry = hashmap_get_entry_from_hash(map,
160 ignore_case ? strihash(query_keys[i]) :
161 strhash(query_keys[i]),
162 query_keys[i], struct test_entry, ent);
163
164 hashmap_for_each_entry_from(map, entry, ent)
165 {
166 int ret = key_val_contains(key_val, seen,
167 ARRAY_SIZE(key_val), entry);
168 cl_assert_equal_i(ret, 0);
169 count++;
170 }
171 cl_assert_equal_i(count, 2);
172 }
173
174 for (size_t i = 0; i < ARRAY_SIZE(seen); i++)
175 cl_assert_equal_i(seen[i], 1);
176
177 cl_assert_equal_i(hashmap_get_size(map), ARRAY_SIZE(key_val));
178 cl_assert_equal_p(get_test_entry(map, "notInMap", ignore_case), NULL);
179 }
180
181 static void t_remove(struct hashmap *map, unsigned int ignore_case)
182 {
183 struct test_entry *entry, *removed;
184 const char *key_val[][2] = { { "key1", "value1" },
185 { "key2", "value2" },
186 { "fooBarFrotz", "value3" } };
187 const char *remove[][2] = { { ignore_case ? "Key1" : "key1", "value1" },
188 { ignore_case ? "keY2" : "key2", "value2" } };
189
190 for (size_t i = 0; i < ARRAY_SIZE(key_val); i++) {
191 entry = alloc_test_entry(key_val[i][0], key_val[i][1], ignore_case);
192 cl_assert_equal_p(hashmap_put_entry(map, entry, ent), NULL);
193 }
194
195 for (size_t i = 0; i < ARRAY_SIZE(remove); i++) {
196 entry = alloc_test_entry(remove[i][0], "", ignore_case);
197 removed = hashmap_remove_entry(map, entry, ent, remove[i][0]);
198 cl_assert(removed != NULL);
199 cl_assert_equal_s(get_value(removed), remove[i][1]);
200 free(entry);
201 free(removed);
202 }
203
204 entry = alloc_test_entry("notInMap", "", ignore_case);
205 cl_assert_equal_p(hashmap_remove_entry(map, entry, ent, "notInMap"), NULL);
206 free(entry);
207
208 cl_assert_equal_i(map->tablesize, 64);
209 cl_assert_equal_i(hashmap_get_size(map),
210 ARRAY_SIZE(key_val) - ARRAY_SIZE(remove));
211 }
212
213 static void t_iterate(struct hashmap *map, unsigned int ignore_case)
214 {
215 struct test_entry *entry;
216 struct hashmap_iter iter;
217 const char *key_val[][2] = { { "key1", "value1" },
218 { "key2", "value2" },
219 { "fooBarFrotz", "value3" } };
220 char seen[ARRAY_SIZE(key_val)] = { 0 };
221
222 for (size_t i = 0; i < ARRAY_SIZE(key_val); i++) {
223 entry = alloc_test_entry(key_val[i][0], key_val[i][1], ignore_case);
224 cl_assert_equal_p(hashmap_put_entry(map, entry, ent), NULL);
225 }
226
227 hashmap_for_each_entry(map, &iter, entry, ent /* member name */)
228 {
229 int ret = key_val_contains(key_val, seen,
230 ARRAY_SIZE(key_val),
231 entry);
232 cl_assert(ret == 0);
233 }
234
235 for (size_t i = 0; i < ARRAY_SIZE(seen); i++)
236 cl_assert_equal_i(seen[i], 1);
237
238 cl_assert_equal_i(hashmap_get_size(map), ARRAY_SIZE(key_val));
239 }
240
241 static void t_alloc(struct hashmap *map, unsigned int ignore_case)
242 {
243 struct test_entry *entry, *removed;
244
245 for (int i = 1; i <= 51; i++) {
246 char *key = xstrfmt("key%d", i);
247 char *value = xstrfmt("value%d", i);
248 entry = alloc_test_entry(key, value, ignore_case);
249 cl_assert_equal_p(hashmap_put_entry(map, entry, ent), NULL);
250 free(key);
251 free(value);
252 }
253 cl_assert_equal_i(map->tablesize, 64);
254 cl_assert_equal_i(hashmap_get_size(map), 51);
255
256 entry = alloc_test_entry("key52", "value52", ignore_case);
257 cl_assert_equal_p(hashmap_put_entry(map, entry, ent), NULL);
258 cl_assert_equal_i(map->tablesize, 256);
259 cl_assert_equal_i(hashmap_get_size(map), 52);
260
261 for (int i = 1; i <= 12; i++) {
262 char *key = xstrfmt("key%d", i);
263 char *value = xstrfmt("value%d", i);
264
265 entry = alloc_test_entry(key, "", ignore_case);
266 removed = hashmap_remove_entry(map, entry, ent, key);
267 cl_assert(removed != NULL);
268 cl_assert_equal_s(value, get_value(removed));
269 free(key);
270 free(value);
271 free(entry);
272 free(removed);
273 }
274 cl_assert_equal_i(map->tablesize, 256);
275 cl_assert_equal_i(hashmap_get_size(map), 40);
276
277 entry = alloc_test_entry("key40", "", ignore_case);
278 removed = hashmap_remove_entry(map, entry, ent, "key40");
279 cl_assert(removed != NULL);
280 cl_assert_equal_s("value40", get_value(removed));
281 cl_assert_equal_i(map->tablesize, 64);
282 cl_assert_equal_i(hashmap_get_size(map), 39);
283 free(entry);
284 free(removed);
285 }
286
287 void test_hashmap__intern(void)
288 {
289 const char *values[] = { "value1", "Value1", "value2", "value2" };
290
291 for (size_t i = 0; i < ARRAY_SIZE(values); i++) {
292 const char *i1 = strintern(values[i]);
293 const char *i2 = strintern(values[i]);
294
295 cl_assert_equal_s(i1, values[i]);
296 cl_assert(i1 != values[i]);
297 cl_assert_equal_p(i1, i2);
298 }
299 }
300
301 void test_hashmap__replace_case_sensitive(void)
302 {
303 setup(t_replace, 0);
304 }
305
306 void test_hashmap__replace_case_insensitive(void)
307 {
308 setup(t_replace, 1);
309 }
310
311 void test_hashmap__get_case_sensitive(void)
312 {
313 setup(t_get, 0);
314 }
315
316 void test_hashmap__get_case_insensitive(void)
317 {
318 setup(t_get, 1);
319 }
320
321 void test_hashmap__add_case_sensitive(void)
322 {
323 setup(t_add, 0);
324 }
325
326 void test_hashmap__add_case_insensitive(void)
327 {
328 setup(t_add, 1);
329 }
330
331 void test_hashmap__remove_case_sensitive(void)
332 {
333 setup(t_remove, 0);
334 }
335
336 void test_hashmap__remove_case_insensitive(void)
337 {
338 setup(t_remove, 1);
339 }
340
341 void test_hashmap__iterate_case_sensitive(void)
342 {
343 setup(t_iterate, 0);
344 }
345
346 void test_hashmap__iterate_case_insensitive(void)
347 {
348 setup(t_iterate, 1);
349 }
350
351 void test_hashmap__alloc_case_sensitive(void)
352 {
353 setup(t_alloc, 0);
354 }
355
356 void test_hashmap__alloc_case_insensitive(void)
357 {
358 setup(t_alloc, 1);
359 }