Merge branch 'ib/test-selectively-run'
[git] / test-hashmap.c
1 #include "git-compat-util.h"
2 #include "hashmap.h"
3
4 struct test_entry
5 {
6         struct hashmap_entry ent;
7         /* key and value as two \0-terminated strings */
8         char key[FLEX_ARRAY];
9 };
10
11 static const char *get_value(const struct test_entry *e)
12 {
13         return e->key + strlen(e->key) + 1;
14 }
15
16 static int test_entry_cmp(const struct test_entry *e1,
17                 const struct test_entry *e2, const char* key)
18 {
19         return strcmp(e1->key, key ? key : e2->key);
20 }
21
22 static int test_entry_cmp_icase(const struct test_entry *e1,
23                 const struct test_entry *e2, const char* key)
24 {
25         return strcasecmp(e1->key, key ? key : e2->key);
26 }
27
28 static struct test_entry *alloc_test_entry(int hash, char *key, int klen,
29                 char *value, int vlen)
30 {
31         struct test_entry *entry = malloc(sizeof(struct test_entry) + klen
32                         + vlen + 2);
33         hashmap_entry_init(entry, hash);
34         memcpy(entry->key, key, klen + 1);
35         memcpy(entry->key + klen + 1, value, vlen + 1);
36         return entry;
37 }
38
39 #define HASH_METHOD_FNV 0
40 #define HASH_METHOD_I 1
41 #define HASH_METHOD_IDIV10 2
42 #define HASH_METHOD_0 3
43 #define HASH_METHOD_X2 4
44 #define TEST_SPARSE 8
45 #define TEST_ADD 16
46 #define TEST_SIZE 100000
47
48 static unsigned int hash(unsigned int method, unsigned int i, const char *key)
49 {
50         unsigned int hash;
51         switch (method & 3)
52         {
53         case HASH_METHOD_FNV:
54                 hash = strhash(key);
55                 break;
56         case HASH_METHOD_I:
57                 hash = i;
58                 break;
59         case HASH_METHOD_IDIV10:
60                 hash = i / 10;
61                 break;
62         case HASH_METHOD_0:
63                 hash = 0;
64                 break;
65         }
66
67         if (method & HASH_METHOD_X2)
68                 hash = 2 * hash;
69         return hash;
70 }
71
72 /*
73  * Test performance of hashmap.[ch]
74  * Usage: time echo "perfhashmap method rounds" | test-hashmap
75  */
76 static void perf_hashmap(unsigned int method, unsigned int rounds)
77 {
78         struct hashmap map;
79         char buf[16];
80         struct test_entry **entries;
81         unsigned int *hashes;
82         unsigned int i, j;
83
84         entries = malloc(TEST_SIZE * sizeof(struct test_entry *));
85         hashes = malloc(TEST_SIZE * sizeof(int));
86         for (i = 0; i < TEST_SIZE; i++) {
87                 snprintf(buf, sizeof(buf), "%i", i);
88                 entries[i] = alloc_test_entry(0, buf, strlen(buf), "", 0);
89                 hashes[i] = hash(method, i, entries[i]->key);
90         }
91
92         if (method & TEST_ADD) {
93                 /* test adding to the map */
94                 for (j = 0; j < rounds; j++) {
95                         hashmap_init(&map, (hashmap_cmp_fn) test_entry_cmp, 0);
96
97                         /* add entries */
98                         for (i = 0; i < TEST_SIZE; i++) {
99                                 hashmap_entry_init(entries[i], hashes[i]);
100                                 hashmap_add(&map, entries[i]);
101                         }
102
103                         hashmap_free(&map, 0);
104                 }
105         } else {
106                 /* test map lookups */
107                 hashmap_init(&map, (hashmap_cmp_fn) test_entry_cmp, 0);
108
109                 /* fill the map (sparsely if specified) */
110                 j = (method & TEST_SPARSE) ? TEST_SIZE / 10 : TEST_SIZE;
111                 for (i = 0; i < j; i++) {
112                         hashmap_entry_init(entries[i], hashes[i]);
113                         hashmap_add(&map, entries[i]);
114                 }
115
116                 for (j = 0; j < rounds; j++) {
117                         for (i = 0; i < TEST_SIZE; i++) {
118                                 struct hashmap_entry key;
119                                 hashmap_entry_init(&key, hashes[i]);
120                                 hashmap_get(&map, &key, entries[i]->key);
121                         }
122                 }
123
124                 hashmap_free(&map, 0);
125         }
126 }
127
128 #define DELIM " \t\r\n"
129
130 /*
131  * Read stdin line by line and print result of commands to stdout:
132  *
133  * hash key -> strhash(key) memhash(key) strihash(key) memihash(key)
134  * put key value -> NULL / old value
135  * get key -> NULL / value
136  * remove key -> NULL / old value
137  * iterate -> key1 value1\nkey2 value2\n...
138  * size -> tablesize numentries
139  *
140  * perfhashmap method rounds -> test hashmap.[ch] performance
141  */
142 int main(int argc, char *argv[])
143 {
144         char line[1024];
145         struct hashmap map;
146         int icase;
147
148         /* init hash map */
149         icase = argc > 1 && !strcmp("ignorecase", argv[1]);
150         hashmap_init(&map, (hashmap_cmp_fn) (icase ? test_entry_cmp_icase
151                         : test_entry_cmp), 0);
152
153         /* process commands from stdin */
154         while (fgets(line, sizeof(line), stdin)) {
155                 char *cmd, *p1 = NULL, *p2 = NULL;
156                 int l1 = 0, l2 = 0, hash = 0;
157                 struct test_entry *entry;
158
159                 /* break line into command and up to two parameters */
160                 cmd = strtok(line, DELIM);
161                 /* ignore empty lines */
162                 if (!cmd || *cmd == '#')
163                         continue;
164
165                 p1 = strtok(NULL, DELIM);
166                 if (p1) {
167                         l1 = strlen(p1);
168                         hash = icase ? strihash(p1) : strhash(p1);
169                         p2 = strtok(NULL, DELIM);
170                         if (p2)
171                                 l2 = strlen(p2);
172                 }
173
174                 if (!strcmp("hash", cmd) && l1) {
175
176                         /* print results of different hash functions */
177                         printf("%u %u %u %u\n", strhash(p1), memhash(p1, l1),
178                                         strihash(p1), memihash(p1, l1));
179
180                 } else if (!strcmp("add", cmd) && l1 && l2) {
181
182                         /* create entry with key = p1, value = p2 */
183                         entry = alloc_test_entry(hash, p1, l1, p2, l2);
184
185                         /* add to hashmap */
186                         hashmap_add(&map, entry);
187
188                 } else if (!strcmp("put", cmd) && l1 && l2) {
189
190                         /* create entry with key = p1, value = p2 */
191                         entry = alloc_test_entry(hash, p1, l1, p2, l2);
192
193                         /* add / replace entry */
194                         entry = hashmap_put(&map, entry);
195
196                         /* print and free replaced entry, if any */
197                         puts(entry ? get_value(entry) : "NULL");
198                         free(entry);
199
200                 } else if (!strcmp("get", cmd) && l1) {
201
202                         /* setup static key */
203                         struct hashmap_entry key;
204                         hashmap_entry_init(&key, hash);
205
206                         /* lookup entry in hashmap */
207                         entry = hashmap_get(&map, &key, p1);
208
209                         /* print result */
210                         if (!entry)
211                                 puts("NULL");
212                         while (entry) {
213                                 puts(get_value(entry));
214                                 entry = hashmap_get_next(&map, entry);
215                         }
216
217                 } else if (!strcmp("remove", cmd) && l1) {
218
219                         /* setup static key */
220                         struct hashmap_entry key;
221                         hashmap_entry_init(&key, hash);
222
223                         /* remove entry from hashmap */
224                         entry = hashmap_remove(&map, &key, p1);
225
226                         /* print result and free entry*/
227                         puts(entry ? get_value(entry) : "NULL");
228                         free(entry);
229
230                 } else if (!strcmp("iterate", cmd)) {
231
232                         struct hashmap_iter iter;
233                         hashmap_iter_init(&map, &iter);
234                         while ((entry = hashmap_iter_next(&iter)))
235                                 printf("%s %s\n", entry->key, get_value(entry));
236
237                 } else if (!strcmp("size", cmd)) {
238
239                         /* print table sizes */
240                         printf("%u %u\n", map.tablesize, map.size);
241
242                 } else if (!strcmp("perfhashmap", cmd) && l1 && l2) {
243
244                         perf_hashmap(atoi(p1), atoi(p2));
245
246                 } else {
247
248                         printf("Unknown command %s\n", cmd);
249
250                 }
251         }
252
253         hashmap_free(&map, 1);
254         return 0;
255 }