1
+#include "test-lib.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
+ check_pointer_eq(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
+ if (check(entry != NULL))
92
+ check_str(get_value(entry), "value1");
93
+ free(entry);
94
+
95
+ entry = alloc_test_entry("fooBarFrotz", "value3", ignore_case);
96
+ check_pointer_eq(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
+ if (check(entry != NULL))
102
+ check_str(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
+ check_pointer_eq(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
+ if (check(entry != NULL))
131
+ check_str(get_value(entry), query[i][1]);
132
+ else
133
+ test_msg("query key: %s", query[i][0]);
134
+ }
135
+
136
+ check_pointer_eq(get_test_entry(map, "notInMap", ignore_case), NULL);
137
+ check_int(map->tablesize, ==, 64);
138
+ check_int(hashmap_get_size(map), ==, ARRAY_SIZE(key_val));
139
+}
140
+
141
+static void t_add(struct hashmap *map, unsigned int ignore_case)
142
+{
143
+ struct test_entry *entry;
144
+ const char *key_val[][2] = {
145
+ { "key1", "value1" },
146
+ { ignore_case ? "Key1" : "key1", "value2" },
147
+ { "fooBarFrotz", "value3" },
148
+ { ignore_case ? "FOObarFrotz" : "fooBarFrotz", "value4" }
149
+ };
150
+ const char *query_keys[] = { "key1", ignore_case ? "FOObarFrotz" :
151
+ "fooBarFrotz" };
152
+ char seen[ARRAY_SIZE(key_val)] = { 0 };
153
+
154
+ for (size_t i = 0; i < ARRAY_SIZE(key_val); i++) {
155
+ entry = alloc_test_entry(key_val[i][0], key_val[i][1], ignore_case);
156
+ hashmap_add(map, &entry->ent);
157
+ }
158
+
159
+ for (size_t i = 0; i < ARRAY_SIZE(query_keys); i++) {
160
+ int count = 0;
161
+ entry = hashmap_get_entry_from_hash(map,
162
+ ignore_case ? strihash(query_keys[i]) :
163
+ strhash(query_keys[i]),
164
+ query_keys[i], struct test_entry, ent);
165
+
166
+ hashmap_for_each_entry_from(map, entry, ent)
167
+ {
168
+ int ret;
169
+ if (!check_int((ret = key_val_contains(
170
+ key_val, seen,
171
+ ARRAY_SIZE(key_val), entry)),
172
+ ==, 0)) {
173
+ switch (ret) {
174
+ case 1:
175
+ test_msg("found entry was not given in the input\n"
176
+ " key: %s\n value: %s",
177
+ entry->key, get_value(entry));
178
+ break;
179
+ case 2:
180
+ test_msg("duplicate entry detected\n"
181
+ " key: %s\n value: %s",
182
+ entry->key, get_value(entry));
183
+ break;
184
+ }
185
+ } else {
186
+ count++;
187
+ }
188
+ }
189
+ check_int(count, ==, 2);
190
+ }
191
+
192
+ for (size_t i = 0; i < ARRAY_SIZE(seen); i++) {
193
+ if (!check_int(seen[i], ==, 1))
194
+ test_msg("following key-val pair was not iterated over:\n"
195
+ " key: %s\n value: %s",
196
+ key_val[i][0], key_val[i][1]);
197
+ }
198
+
199
+ check_int(hashmap_get_size(map), ==, ARRAY_SIZE(key_val));
200
+ check_pointer_eq(get_test_entry(map, "notInMap", ignore_case), NULL);
201
+}
202
+
203
+static void t_remove(struct hashmap *map, unsigned int ignore_case)
204
+{
205
+ struct test_entry *entry, *removed;
206
+ const char *key_val[][2] = { { "key1", "value1" },
207
+ { "key2", "value2" },
208
+ { "fooBarFrotz", "value3" } };
209
+ const char *remove[][2] = { { ignore_case ? "Key1" : "key1", "value1" },
210
+ { ignore_case ? "keY2" : "key2", "value2" } };
211
+
212
+ for (size_t i = 0; i < ARRAY_SIZE(key_val); i++) {
213
+ entry = alloc_test_entry(key_val[i][0], key_val[i][1], ignore_case);
214
+ check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
215
+ }
216
+
217
+ for (size_t i = 0; i < ARRAY_SIZE(remove); i++) {
218
+ entry = alloc_test_entry(remove[i][0], "", ignore_case);
219
+ removed = hashmap_remove_entry(map, entry, ent, remove[i][0]);
220
+ if (check(removed != NULL))
221
+ check_str(get_value(removed), remove[i][1]);
222
+ free(entry);
223
+ free(removed);
224
+ }
225
+
226
+ entry = alloc_test_entry("notInMap", "", ignore_case);
227
+ check_pointer_eq(hashmap_remove_entry(map, entry, ent, "notInMap"), NULL);
228
+ free(entry);
229
+
230
+ check_int(map->tablesize, ==, 64);
231
+ check_int(hashmap_get_size(map), ==, ARRAY_SIZE(key_val) - ARRAY_SIZE(remove));
232
+}
233
+
234
+static void t_iterate(struct hashmap *map, unsigned int ignore_case)
235
+{
236
+ struct test_entry *entry;
237
+ struct hashmap_iter iter;
238
+ const char *key_val[][2] = { { "key1", "value1" },
239
+ { "key2", "value2" },
240
+ { "fooBarFrotz", "value3" } };
241
+ char seen[ARRAY_SIZE(key_val)] = { 0 };
242
+
243
+ for (size_t i = 0; i < ARRAY_SIZE(key_val); i++) {
244
+ entry = alloc_test_entry(key_val[i][0], key_val[i][1], ignore_case);
245
+ check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
246
+ }
247
+
248
+ hashmap_for_each_entry(map, &iter, entry, ent /* member name */)
249
+ {
250
+ int ret;
251
+ if (!check_int((ret = key_val_contains(key_val, seen,
252
+ ARRAY_SIZE(key_val),
253
+ entry)), ==, 0)) {
254
+ switch (ret) {
255
+ case 1:
256
+ test_msg("found entry was not given in the input\n"
257
+ " key: %s\n value: %s",
258
+ entry->key, get_value(entry));
259
+ break;
260
+ case 2:
261
+ test_msg("duplicate entry detected\n"
262
+ " key: %s\n value: %s",
263
+ entry->key, get_value(entry));
264
+ break;
265
+ }
266
+ }
267
+ }
268
+
269
+ for (size_t i = 0; i < ARRAY_SIZE(seen); i++) {
270
+ if (!check_int(seen[i], ==, 1))
271
+ test_msg("following key-val pair was not iterated over:\n"
272
+ " key: %s\n value: %s",
273
+ key_val[i][0], key_val[i][1]);
274
+ }
275
+
276
+ check_int(hashmap_get_size(map), ==, ARRAY_SIZE(key_val));
277
+}
278
+
279
+static void t_alloc(struct hashmap *map, unsigned int ignore_case)
280
+{
281
+ struct test_entry *entry, *removed;
282
+
283
+ for (int i = 1; i <= 51; i++) {
284
+ char *key = xstrfmt("key%d", i);
285
+ char *value = xstrfmt("value%d", i);
286
+ entry = alloc_test_entry(key, value, ignore_case);
287
+ check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
288
+ free(key);
289
+ free(value);
290
+ }
291
+ check_int(map->tablesize, ==, 64);
292
+ check_int(hashmap_get_size(map), ==, 51);
293
+
294
+ entry = alloc_test_entry("key52", "value52", ignore_case);
295
+ check_pointer_eq(hashmap_put_entry(map, entry, ent), NULL);
296
+ check_int(map->tablesize, ==, 256);
297
+ check_int(hashmap_get_size(map), ==, 52);
298
+
299
+ for (int i = 1; i <= 12; i++) {
300
+ char *key = xstrfmt("key%d", i);
301
+ char *value = xstrfmt("value%d", i);
302
+
303
+ entry = alloc_test_entry(key, "", ignore_case);
304
+ removed = hashmap_remove_entry(map, entry, ent, key);
305
+ if (check(removed != NULL))
306
+ check_str(value, get_value(removed));
307
+ free(key);
308
+ free(value);
309
+ free(entry);
310
+ free(removed);
311
+ }
312
+ check_int(map->tablesize, ==, 256);
313
+ check_int(hashmap_get_size(map), ==, 40);
314
+
315
+ entry = alloc_test_entry("key40", "", ignore_case);
316
+ removed = hashmap_remove_entry(map, entry, ent, "key40");
317
+ if (check(removed != NULL))
318
+ check_str("value40", get_value(removed));
319
+ check_int(map->tablesize, ==, 64);
320
+ check_int(hashmap_get_size(map), ==, 39);
321
+ free(entry);
322
+ free(removed);
323
+}
324
+
325
+static void t_intern(struct hashmap *map, unsigned int ignore_case)
326
+{
327
+ const char *values[] = { "value1", "Value1", "value2", "value2" };
328
+
329
+ for (size_t i = 0; i < ARRAY_SIZE(values); i++) {
330
+ const char *i1 = strintern(values[i]);
331
+ const char *i2 = strintern(values[i]);
332
+
333
+ if (!check(!strcmp(i1, values[i])))
334
+ test_msg("strintern(%s) returns %s\n", values[i], i1);
335
+ else if (!check(i1 != values[i]))
336
+ test_msg("strintern(%s) returns input pointer\n",
337
+ values[i]);
338
+ else if (!check_pointer_eq(i1, i2))
339
+ test_msg("address('%s') != address('%s'), so strintern('%s') != strintern('%s')",
340
+ i1, i2, values[i], values[i]);
341
+ else
342
+ check_str(i1, values[i]);
343
+ }
344
+}
345
+
346
+int cmd_main(int argc UNUSED, const char **argv UNUSED)
347
+{
348
+ TEST(setup(t_replace, 0), "replace works");
349
+ TEST(setup(t_replace, 1), "replace (case insensitive) works");
350
+ TEST(setup(t_get, 0), "get works");
351
+ TEST(setup(t_get, 1), "get (case insensitive) works");
352
+ TEST(setup(t_add, 0), "add works");
353
+ TEST(setup(t_add, 1), "add (case insensitive) works");
354
+ TEST(setup(t_remove, 0), "remove works");
355
+ TEST(setup(t_remove, 1), "remove (case insensitive) works");
356
+ TEST(setup(t_iterate, 0), "iterate works");
357
+ TEST(setup(t_iterate, 1), "iterate (case insensitive) works");
358
+ TEST(setup(t_alloc, 0), "grow / shrink works");
359
+ TEST(setup(t_intern, 0), "string interning works");
360
+ return test_done();
361
+}