t: port helper/test-hashmap.c to unit-tests/t-hashmap.c

helper/test-hashmap.c along with t0011-hashmap.sh test the hashmap.h library. Migrate them to the unit testing framework for better debugging, runtime performance and concise code. Along with the migration, make 'add' tests from the shell script order agnostic in unit tests, since they iterate over entries with the same keys and we do not guarantee the order. This was already done for the 'iterate' tests[1]. The helper/test-hashmap.c is still not removed because it contains a performance test meant to be run by the user directly (not used in t/perf). And it makes sense for such a utility to be a helper. [1]: e1e7a77141 (t: sort output of hashmap iteration, 2019-07-30) Mentored-by: Christian Couder <chriscool@tuxfamily.org> Mentored-by: Kaartic Sivaraam <kaartic.sivaraam@gmail.com> Helped-by: Josh Steadmon <steadmon@google.com> Helped-by: Junio C Hamano <gitster@pobox.com> Helped-by: Phillip Wood <phillip.wood123@gmail.com> Signed-off-by: Ghanshyam Thakkar <shyamthakkar001@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Ghanshyam Thakkar committed Aug 3, 2024 at 19:04 UTC 3469a23659d8197190d2765cf9f31dec5ab602fa
4 files changed +363 -357
Makefile
+1
@@ -1336,6 +1336,7 @@ THIRD_PARTY_SOURCES += sha1dc/%
1336 UNIT_TEST_PROGRAMS += t-ctype
1337 UNIT_TEST_PROGRAMS += t-example-decorate
1338 UNIT_TEST_PROGRAMS += t-hash
1339 +UNIT_TEST_PROGRAMS += t-hashmap
1340 UNIT_TEST_PROGRAMS += t-mem-pool
1341 UNIT_TEST_PROGRAMS += t-oidtree
1342 UNIT_TEST_PROGRAMS += t-prio-queue
t/helper/test-hashmap.c
+1 -97
@@ -12,11 +12,6 @@ struct test_entry
12 char key[FLEX_ARRAY];
13 };
14
15 -static const char *get_value(const struct test_entry *e)
16 -{
17 - return e->key + strlen(e->key) + 1;
18 -}
19 -
15 static int test_entry_cmp(const void *cmp_data,
16 const struct hashmap_entry *eptr,
17 const struct hashmap_entry *entry_or_key,
@@ -141,30 +136,16 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)
136 /*
137 * Read stdin line by line and print result of commands to stdout:
138 *
144 - * hash key -> strhash(key) memhash(key) strihash(key) memihash(key)
145 - * put key value -> NULL / old value
146 - * get key -> NULL / value
147 - * remove key -> NULL / old value
148 - * iterate -> key1 value1\nkey2 value2\n...
149 - * size -> tablesize numentries
150 - *
139 * perfhashmap method rounds -> test hashmap.[ch] performance
140 */
141 int cmd__hashmap(int argc, const char **argv)
142 {
143 struct string_list parts = STRING_LIST_INIT_NODUP;
144 struct strbuf line = STRBUF_INIT;
157 - int icase;
158 - struct hashmap map = HASHMAP_INIT(test_entry_cmp, &icase);
159 -
160 - /* init hash map */
161 - icase = argc > 1 && !strcmp("ignorecase", argv[1]);
145
146 /* process commands from stdin */
147 while (strbuf_getline(&line, stdin) != EOF) {
148 char *cmd, *p1, *p2;
166 - unsigned int hash = 0;
167 - struct test_entry *entry;
149
150 /* break line into command and up to two parameters */
151 string_list_setlen(&parts, 0);
@@ -180,84 +161,8 @@ int cmd__hashmap(int argc, const char **argv)
161 cmd = parts.items[0].string;
162 p1 = parts.nr >= 1 ? parts.items[1].string : NULL;
163 p2 = parts.nr >= 2 ? parts.items[2].string : NULL;
183 - if (p1)
184 - hash = icase ? strihash(p1) : strhash(p1);
185 -
186 - if (!strcmp("add", cmd) && p1 && p2) {
187 -
188 - /* create entry with key = p1, value = p2 */
189 - entry = alloc_test_entry(hash, p1, p2);
190 -
191 - /* add to hashmap */
192 - hashmap_add(&map, &entry->ent);
193 -
194 - } else if (!strcmp("put", cmd) && p1 && p2) {
195 -
196 - /* create entry with key = p1, value = p2 */
197 - entry = alloc_test_entry(hash, p1, p2);
198 -
199 - /* add / replace entry */
200 - entry = hashmap_put_entry(&map, entry, ent);
201 -
202 - /* print and free replaced entry, if any */
203 - puts(entry ? get_value(entry) : "NULL");
204 - free(entry);
205 -
206 - } else if (!strcmp("get", cmd) && p1) {
207 - /* lookup entry in hashmap */
208 - entry = hashmap_get_entry_from_hash(&map, hash, p1,
209 - struct test_entry, ent);
210 -
211 - /* print result */
212 - if (!entry)
213 - puts("NULL");
214 - hashmap_for_each_entry_from(&map, entry, ent)
215 - puts(get_value(entry));
216 -
217 - } else if (!strcmp("remove", cmd) && p1) {
218 -
219 - /* setup static key */
220 - struct hashmap_entry key;
221 - struct hashmap_entry *rm;
222 - hashmap_entry_init(&key, hash);
223 -
224 - /* remove entry from hashmap */
225 - rm = hashmap_remove(&map, &key, p1);
226 - entry = rm ? container_of(rm, struct test_entry, ent)
227 - : NULL;
228 -
229 - /* print result and free entry*/
230 - puts(entry ? get_value(entry) : "NULL");
231 - free(entry);
232 -
233 - } else if (!strcmp("iterate", cmd)) {
234 - struct hashmap_iter iter;
235 -
236 - hashmap_for_each_entry(&map, &iter, entry,
237 - ent /* member name */)
238 - printf("%s %s\n", entry->key, get_value(entry));
239 -
240 - } else if (!strcmp("size", cmd)) {
241 -
242 - /* print table sizes */
243 - printf("%u %u\n", map.tablesize,
244 - hashmap_get_size(&map));
245 -
246 - } else if (!strcmp("intern", cmd) && p1) {
247 -
248 - /* test that strintern works */
249 - const char *i1 = strintern(p1);
250 - const char *i2 = strintern(p1);
251 - if (strcmp(i1, p1))
252 - printf("strintern(%s) returns %s\n", p1, i1);
253 - else if (i1 == p1)
254 - printf("strintern(%s) returns input pointer\n", p1);
255 - else if (i1 != i2)
256 - printf("strintern(%s) != strintern(%s)", i1, i2);
257 - else
258 - printf("%s\n", i1);
164
260 - } else if (!strcmp("perfhashmap", cmd) && p1 && p2) {
165 + if (!strcmp("perfhashmap", cmd) && p1 && p2) {
166
167 perf_hashmap(atoi(p1), atoi(p2));
168
@@ -270,6 +175,5 @@ int cmd__hashmap(int argc, const char **argv)
175
176 string_list_clear(&parts, 0);
177 strbuf_release(&line);
273 - hashmap_clear_and_free(&map, struct test_entry, ent);
178 return 0;
179 }
t/t0011-hashmap.sh deleted
-260
@@ -1,260 +0,0 @@
1 -#!/bin/sh
2 -
3 -test_description='test hashmap and string hash functions'
4 -
5 -TEST_PASSES_SANITIZE_LEAK=true
6 -. ./test-lib.sh
7 -
8 -test_hashmap() {
9 - echo "$1" | test-tool hashmap $3 > actual &&
10 - echo "$2" > expect &&
11 - test_cmp expect actual
12 -}
13 -
14 -test_expect_success 'put' '
15 -
16 -test_hashmap "put key1 value1
17 -put key2 value2
18 -put fooBarFrotz value3
19 -put foobarfrotz value4
20 -size" "NULL
21 -NULL
22 -NULL
23 -NULL
24 -64 4"
25 -
26 -'
27 -
28 -test_expect_success 'put (case insensitive)' '
29 -
30 -test_hashmap "put key1 value1
31 -put key2 value2
32 -put fooBarFrotz value3
33 -size" "NULL
34 -NULL
35 -NULL
36 -64 3" ignorecase
37 -
38 -'
39 -
40 -test_expect_success 'replace' '
41 -
42 -test_hashmap "put key1 value1
43 -put key1 value2
44 -put fooBarFrotz value3
45 -put fooBarFrotz value4
46 -size" "NULL
47 -value1
48 -NULL
49 -value3
50 -64 2"
51 -
52 -'
53 -
54 -test_expect_success 'replace (case insensitive)' '
55 -
56 -test_hashmap "put key1 value1
57 -put Key1 value2
58 -put fooBarFrotz value3
59 -put foobarfrotz value4
60 -size" "NULL
61 -value1
62 -NULL
63 -value3
64 -64 2" ignorecase
65 -
66 -'
67 -
68 -test_expect_success 'get' '
69 -
70 -test_hashmap "put key1 value1
71 -put key2 value2
72 -put fooBarFrotz value3
73 -put foobarfrotz value4
74 -get key1
75 -get key2
76 -get fooBarFrotz
77 -get notInMap" "NULL
78 -NULL
79 -NULL
80 -NULL
81 -value1
82 -value2
83 -value3
84 -NULL"
85 -
86 -'
87 -
88 -test_expect_success 'get (case insensitive)' '
89 -
90 -test_hashmap "put key1 value1
91 -put key2 value2
92 -put fooBarFrotz value3
93 -get Key1
94 -get keY2
95 -get foobarfrotz
96 -get notInMap" "NULL
97 -NULL
98 -NULL
99 -value1
100 -value2
101 -value3
102 -NULL" ignorecase
103 -
104 -'
105 -
106 -test_expect_success 'add' '
107 -
108 -test_hashmap "add key1 value1
109 -add key1 value2
110 -add fooBarFrotz value3
111 -add fooBarFrotz value4
112 -get key1
113 -get fooBarFrotz
114 -get notInMap" "value2
115 -value1
116 -value4
117 -value3
118 -NULL"
119 -
120 -'
121 -
122 -test_expect_success 'add (case insensitive)' '
123 -
124 -test_hashmap "add key1 value1
125 -add Key1 value2
126 -add fooBarFrotz value3
127 -add foobarfrotz value4
128 -get key1
129 -get Foobarfrotz
130 -get notInMap" "value2
131 -value1
132 -value4
133 -value3
134 -NULL" ignorecase
135 -
136 -'
137 -
138 -test_expect_success 'remove' '
139 -
140 -test_hashmap "put key1 value1
141 -put key2 value2
142 -put fooBarFrotz value3
143 -remove key1
144 -remove key2
145 -remove notInMap
146 -size" "NULL
147 -NULL
148 -NULL
149 -value1
150 -value2
151 -NULL
152 -64 1"
153 -
154 -'
155 -
156 -test_expect_success 'remove (case insensitive)' '
157 -
158 -test_hashmap "put key1 value1
159 -put key2 value2
160 -put fooBarFrotz value3
161 -remove Key1
162 -remove keY2
163 -remove notInMap
164 -size" "NULL
165 -NULL
166 -NULL
167 -value1
168 -value2
169 -NULL
170 -64 1" ignorecase
171 -
172 -'
173 -
174 -test_expect_success 'iterate' '
175 - test-tool hashmap >actual.raw <<-\EOF &&
176 - put key1 value1
177 - put key2 value2
178 - put fooBarFrotz value3
179 - iterate
180 - EOF
181 -
182 - cat >expect <<-\EOF &&
183 - NULL
184 - NULL
185 - NULL
186 - fooBarFrotz value3
187 - key1 value1
188 - key2 value2
189 - EOF
190 -
191 - sort <actual.raw >actual &&
192 - test_cmp expect actual
193 -'
194 -
195 -test_expect_success 'iterate (case insensitive)' '
196 - test-tool hashmap ignorecase >actual.raw <<-\EOF &&
197 - put key1 value1
198 - put key2 value2
199 - put fooBarFrotz value3
200 - iterate
201 - EOF
202 -
203 - cat >expect <<-\EOF &&
204 - NULL
205 - NULL
206 - NULL
207 - fooBarFrotz value3
208 - key1 value1
209 - key2 value2
210 - EOF
211 -
212 - sort <actual.raw >actual &&
213 - test_cmp expect actual
214 -'
215 -
216 -test_expect_success 'grow / shrink' '
217 -
218 - rm -f in &&
219 - rm -f expect &&
220 - for n in $(test_seq 51)
221 - do
222 - echo put key$n value$n >> in &&
223 - echo NULL >> expect || return 1
224 - done &&
225 - echo size >> in &&
226 - echo 64 51 >> expect &&
227 - echo put key52 value52 >> in &&
228 - echo NULL >> expect &&
229 - echo size >> in &&
230 - echo 256 52 >> expect &&
231 - for n in $(test_seq 12)
232 - do
233 - echo remove key$n >> in &&
234 - echo value$n >> expect || return 1
235 - done &&
236 - echo size >> in &&
237 - echo 256 40 >> expect &&
238 - echo remove key40 >> in &&
239 - echo value40 >> expect &&
240 - echo size >> in &&
241 - echo 64 39 >> expect &&
242 - test-tool hashmap <in >out &&
243 - test_cmp expect out
244 -
245 -'
246 -
247 -test_expect_success 'string interning' '
248 -
249 -test_hashmap "intern value1
250 -intern Value1
251 -intern value2
252 -intern value2
253 -" "value1
254 -Value1
255 -value2
256 -value2"
257 -
258 -'
259 -
260 -test_done
t/unit-tests/t-hashmap.c new
+361
@@ -0,0 +1,361 @@
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 +}