Repository navigation
Expand file tree
/
Copy pathht_test.c
More file actions
298 lines (224 loc) · 7.53 KB
/
Copy pathht_test.c
File metadata and controls
298 lines (224 loc) · 7.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
/*
* This file is part of Hashtable.
* Hashtable is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Hashtable is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Hashtable. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include "ht.h"
ht_options options = { 0, NULL };
void test_ht_has(void) {
ht *ht = ht_create(10, options);
ht_set(ht, "asdf", "1");
/* Should return true if the key exists */
assert(ht_has(ht, "asdf"));
/* Should return false if the key doesn't exist */
assert(!ht_has(ht, "bsdf"));
ht_free(&ht);
}
void test_ht_set(void) {
ht *ht = ht_create(10, options);
ht_set(ht, "asdf", "1");
/* Should go into the third bucket */
assert(!strcmp(ll_get_key(ht->array[2]), "asdf"));
assert(!strcmp(ll_get_value(ht->array[2]), "1"));
ht_set(ht, "asdf", "2");
/* Setting the same key should just overwrite the same value */
assert(!strcmp(ll_get_key(ht->array[2]), "asdf"));
assert(!strcmp(ll_get_value(ht->array[2]), "2"));
ht_set(ht, "bsdf", "1");
/* Setting another key should not overwrite the previous inserted value */
assert(!strcmp(ll_get_key(ht->array[2]), "asdf"));
assert(!strcmp(ll_get_value(ht->array[2]), "2"));
/* The new key should go into the fourth bucket */
assert(!strcmp(ll_get_key(ht->array[3]), "bsdf"));
assert(!strcmp(ll_get_value(ht->array[3]), "1"));
ht_free(&ht);
}
void test_ht_get(void) {
ht *ht = ht_create(10, options);
ht_set(ht, "asdf", "1");
/* Should be equal to '1' for the 'asdf' key */
assert(!strcmp(ht_get(ht, "asdf"), "1"));
/* Should return NULL on a key that doesn't exist */
assert(ht_get(ht, "bsdf") == NULL);
ht_free(&ht);
}
void test_ht_unset(void) {
ht *ht = ht_create(10, options);
int retval;
ht_set(ht, "asdf", "1"); /* Third bucket */
ht_set(ht, "bsdf", "2");
retval = ht_unset(ht, "asdf");
/* unset should return true on successful removal, "asdf" should be removed from the third bucket */
assert(retval && ht->array[2] == NULL);
retval = ht_unset(ht, "asdf");
/* unset should return false when nothing is removed */
assert(!retval);
/* "bsdf" should remain in the fourth bucket */
assert(!strcmp(ll_get_key(ht->array[3]), "bsdf"));
assert(!strcmp(ll_get_value(ht->array[3]), "2"));
ht_free(&ht);
}
void test_ht_loadfactor(void) {
ht *ht = ht_create(10, options);
ht_set(ht, "asdf", "1"); /* Third bucket */
ht_set(ht, "bsdf", "2");
/* We have 2 elements in 10 buckets, so the load factor is 2/10 == 0.2 */
assert(ht_loadfactor(ht) == 0.2f);
ht_set(ht, "asdf", "10"); /* Third bucket */
/* Load factor should not be affected when updating values for the same key */
assert(ht_loadfactor(ht) == 0.2f);
ht_unset(ht, "asdf");
/* Load factor should be affected when removing existing keys (1/10) */
assert(ht_loadfactor(ht) == 0.1f);
ht_unset(ht, "asdf");
/* Load factor should not be affected when removing non-existing keys (1/10) */
assert(ht_loadfactor(ht) == 0.1f);
ht_free(&ht);
}
void test_ht_rehash(void) {
ht *ht = ht_create(3, options);
ht_set(ht, "x1", "1"); /* Third bucket */
ht_set(ht, "y2", "2"); /* Second bucket */
ht_set(ht, "z3", "3"); /* First bucket */
/* Load factor should be equal to 1 (3/3) */
assert(ht_loadfactor(ht) == 1.0f);
ht_set(ht, "a4", "3"); /* First bucket */
ht_set(ht, "b5", "3"); /* Third bucket */
ht_set(ht, "c6", "3"); /* Second bucket */
/* Load factor should be equal to 2 (6/3) */
assert(ht_loadfactor(ht) == 2.0f);
ht_rehash(ht, 6);
/* Load factor should be equal to 1 after rehash (6/6) */
assert(ht_loadfactor(ht) == 1.0f);
ht_rehash(ht, 12);
/* Load factor should be equal to 0.5 after rehash (6/12) */
assert(ht_loadfactor(ht) == 0.5f);
ht_rehash(ht, 2);
/* Load factor should be equal to 3 after rehash (6/2) */
assert(ht_loadfactor(ht) == 3.0f);
ht_free(&ht);
}
void test_ht_set_self_alias(void) {
ht *ht = ht_create(4, options);
char *v;
ht_set(ht, "k", "hello");
/* regression test: re-setting a key using a value pointer read back
* from the table itself used to be a heap use-after-free (ll_set_value
* freed the old value before duplicating the "new" one, which aliased
* the pointer just freed) */
v = ht_get(ht, "k");
ht_set(ht, "k", v);
assert(!strcmp(ht_get(ht, "k"), "hello"));
ht_free(&ht);
}
void test_ht_rehash_honors_requested_buckets_with_loadfactor(void) {
ht_options opt = { 0.5f, NULL };
ht *ht = ht_create(4, opt);
int i;
char key[16], val[16];
for (i = 0; i < 20; i++) {
sprintf(key, "k%d", i);
sprintf(val, "v%d", i);
ht_set(ht, key, val);
}
/* regression test: explicitly requesting a small bucket count (even
* though it leaves the load factor above max_loadfactor) used to be
* silently overridden -- ht_rehash's copy loop called ht_set() on
* new_table, which shares the same max_loadfactor option, so
* new_table kept auto-doubling itself while being filled, regardless
* of the bucket count the caller asked for */
ht_rehash(ht, 5);
assert(ht->buckets == 5);
for (i = 0; i < 20; i++) {
sprintf(key, "k%d", i);
sprintf(val, "v%d", i);
assert(!strcmp(ht_get(ht, key), val));
}
ht_free(&ht);
}
void test_ht_null_safety(void) {
ht *null_table = NULL;
ht *ht;
/* none of these should crash */
assert(!ht_has(NULL, "x"));
assert(ht_get(NULL, "x") == NULL);
assert(!ht_unset(NULL, "x"));
assert(ht_loadfactor(NULL) == 0.0f);
ht_set(NULL, "x", "1");
ht_rehash(NULL, 4);
ht_print(NULL);
ht_free(&null_table);
ht_free(NULL);
ht = ht_create(4, options);
assert(!ht_has(ht, NULL));
assert(ht_get(ht, NULL) == NULL);
assert(!ht_unset(ht, NULL));
ht_set(ht, NULL, "1");
ht_set(ht, "k", NULL);
/* both calls above must have been no-ops */
assert(ht->elements == 0);
ht_free(&ht);
}
void test_ht_print_valid_json(void) {
ht *ht = ht_create(2, options);
int saved_stdout;
FILE *tmp;
char buf[4096];
size_t n;
/* key/value containing characters that would break a naive
* single-quoted, unescaped print (quote, backslash, newline) */
ht_set(ht, "plain", "1");
ht_set(ht, "a\"b\\c", "line1\nline2");
tmp = tmpfile();
assert(tmp != NULL);
fflush(stdout);
saved_stdout = dup(fileno(stdout));
dup2(fileno(tmp), fileno(stdout));
ht_print(ht);
fflush(stdout);
dup2(saved_stdout, fileno(stdout));
close(saved_stdout);
rewind(tmp);
n = fread(buf, 1, sizeof(buf) - 1, tmp);
buf[n] = '\0';
fclose(tmp);
/* no trailing comma before a closing brace/bracket */
assert(strstr(buf, ",}") == NULL);
assert(strstr(buf, ", }") == NULL);
assert(strstr(buf, ",\n }") == NULL);
assert(strstr(buf, ",]") == NULL);
assert(strstr(buf, ",\n]") == NULL);
/* keys/values are double-quoted, not single-quoted */
assert(strstr(buf, "\"plain\": \"1\"") != NULL);
/* special characters are escaped rather than corrupting the output */
assert(strstr(buf, "\"a\\\"b\\\\c\": \"line1\\nline2\"") != NULL);
ht_free(&ht);
}
int main(void) {
test_ht_has();
test_ht_set();
test_ht_get();
test_ht_unset();
test_ht_loadfactor();
test_ht_rehash();
test_ht_set_self_alias();
test_ht_rehash_honors_requested_buckets_with_loadfactor();
test_ht_null_safety();
test_ht_print_valid_json();
printf("ht: All tests pass.\n");
return 0;
}