1 /* check-sources:disable-copyright-check */
2 #include <check.h>
3 #include <droplet.h>
4
5 #include "utest_main.h"
6
7 /*
8 * TODO: Conditions known to be untested:
9 *
10 * - passing prefix=NULL to dpl_dict_filter_{no_,}prefix() should
11 * be semantically identical to prefix="". This will fail.
12 *
13 * - passing src=NULL to dpl_dict_filter_{no_,}prefix() should do
14 * nothing. This test will fail.
15 *
16 * - malloc() failures anywhere (really hard to test with
17 * the check.h framework).
18 *
19 * - dpl_dict_get_value() looking up a missing key
20 *
21 * - dpl_dict_print() printing a dict inside a dict
22 *
23 * - passing dst=NULL to dpl_dict_copy()
24 */
25
26 /* strings courtesy http://hipsteripsum.me/ */
27 static const char* const keys[]
28 = {"Sriracha", "Banksy", "trust", "fund", "Brooklyn", "polaroid",
29 "Viral", "selfies", "kogi", "Austin", "PBR", "stumptown",
30 "artisan", "bespoke", "8-bit", "Odd", "Future", "Pinterest",
31 "mlkshk", "McSweeney's", "ennui", "Wes", "Anderson"};
32 static const int nkeys = sizeof(keys) / sizeof(keys[0]);
33
make_key(int i,char * buf,size_t maxlen)34 static const char* make_key(int i, char* buf, size_t maxlen)
35 {
36 int j;
37 static const char chars[] = "abcdefghijklmnopqrstuvwxyz";
38 if (i >= maxlen) return NULL;
39 for (j = 0; j < i; j++) buf[j] = chars[j % (sizeof(chars) - 1)];
40 buf[j] = '\0';
41 return buf;
42 }
43
make_value(int i,char * buf,size_t maxlen)44 static const char* make_value(int i, char* buf, size_t maxlen)
45 {
46 snprintf(buf, maxlen, "X%dY", i);
47 return buf;
48 }
49
50
utest_dpl_dict_dump_one(dpl_dict_var_t * v,void * user_data)51 static int utest_dpl_dict_dump_one(dpl_dict_var_t* v, void* user_data)
52 {
53 dpl_assert_ptr_not_null(v);
54 dpl_assert_int_eq(v->val->type, DPL_VALUE_STRING);
55 fprintf((FILE*)user_data, "%s: %s\n", v->key, v->val->string->buf);
56
57 return 0;
58 }
59
START_TEST(dict_test)60 START_TEST(dict_test)
61 {
62 dpl_dict_t* dict;
63 const char* const* it;
64 char init_value[] = "a";
65 dpl_dict_var_t* var;
66 const dpl_value_t* value;
67 dpl_status_t ret;
68 int c;
69 FILE* fp = NULL;
70 char pbuf[1024];
71
72 static const char* const utest_strings[] = {"Foo", "bAr", "baz", NULL};
73
74 dict = dpl_dict_new(0);
75 dpl_assert_ptr_null(dict);
76 dict = dpl_dict_new(10);
77
78 it = utest_strings;
79 while (*it != NULL) {
80 ret = dpl_dict_add(dict, *it, init_value, 1);
81 fail_unless(DPL_SUCCESS == ret, NULL);
82 (*init_value)++;
83 it++;
84 }
85 //
86 // new, free, dup, copy combo
87 //
88 dpl_dict_t* d2 = dpl_dict_new(5);
89 ret = dpl_dict_copy(d2, dict);
90 fail_unless(DPL_SUCCESS == ret, NULL);
91 dpl_dict_free(dict);
92 dict = dpl_dict_new(20);
93 ret = dpl_dict_copy(dict, d2);
94 fail_unless(DPL_SUCCESS == ret, NULL);
95 dpl_dict_free(d2);
96 d2 = dpl_dict_dup(dict);
97 fail_unless(NULL != d2, NULL);
98 dpl_dict_free(dict);
99 dict = d2;
100
101 it = utest_strings;
102 var = dpl_dict_get(dict, *it);
103 fail_unless(var == NULL, NULL);
104 it++;
105 var = dpl_dict_get(dict, *it);
106 dpl_assert_ptr_null(var);
107 it++;
108 var = dpl_dict_get(dict, *it);
109 dpl_assert_ptr_not_null(var);
110 value = var->val;
111 fail_unless(DPL_VALUE_STRING == value->type, NULL);
112 fail_unless(0 == strcmp(value->string->buf, "c"), NULL);
113
114 it = utest_strings;
115 dpl_dict_get_lowered(dict, *it, &var);
116 dpl_assert_ptr_not_null(var);
117 value = var->val;
118 fail_unless(DPL_VALUE_STRING == value->type, NULL);
119 fail_unless(0 == strcmp(value->string->buf, "a"), NULL);
120 it++;
121 dpl_dict_get_lowered(dict, *it, &var);
122 dpl_assert_ptr_not_null(var);
123 value = var->val;
124 fail_unless(DPL_VALUE_STRING == value->type, NULL);
125 fail_unless(0 == strcmp(value->string->buf, "b"), NULL);
126 it++;
127 dpl_dict_get_lowered(dict, *it, &var);
128 dpl_assert_ptr_not_null(var);
129 value = var->val;
130 fail_unless(DPL_VALUE_STRING == value->type, NULL);
131 fail_unless(0 == strcmp(value->string->buf, "c"), NULL);
132
133 memset(pbuf, 0xff, sizeof(pbuf));
134 fp = fmemopen(pbuf, sizeof(pbuf), "w");
135 dpl_assert_ptr_not_null(fp);
136
137 dpl_dict_iterate(dict, utest_dpl_dict_dump_one, fp);
138 dpl_dict_print(dict, fp, 0);
139
140 fflush(fp);
141 static const char expected[]
142 = "baz: c\nbar: b\nfoo: a\n"
143 "baz=c\nbar=b\nfoo=a";
144 fail_unless(0 == memcmp(expected, pbuf, sizeof(expected) - 1), NULL);
145
146 c = dpl_dict_count(dict);
147 fail_unless(3 == c, NULL);
148
149 dpl_dict_free(dict);
150 fclose(fp);
151 }
152 END_TEST
153
154 /*
155 * Use the dict with long key strings; tests
156 * some corner cases e.g. in the hash function.
157 */
START_TEST(long_key_test)158 START_TEST(long_key_test)
159 {
160 #define N 512
161 dpl_dict_t* dict;
162 int i;
163 int j;
164 const char* act;
165 const char* exp;
166 char keybuf[1024];
167 char valbuf[1024];
168
169 dict = dpl_dict_new(13);
170 dpl_assert_ptr_not_null(dict);
171
172 for (i = 0; i < N; i++) {
173 dpl_dict_add(dict, make_key(i, keybuf, sizeof(keybuf)),
174 make_value(i, valbuf, sizeof(valbuf)),
175 /* lowered */ 0);
176 }
177 dpl_assert_int_eq(N, dpl_dict_count(dict));
178
179 for (i = 0; i < N; i++) {
180 act = dpl_dict_get_value(dict, make_key(i, keybuf, sizeof(keybuf)));
181 exp = make_value(i, valbuf, sizeof(valbuf));
182 dpl_assert_str_eq(act, exp);
183 }
184
185 dpl_dict_free(dict);
186 #undef N
187 }
188 END_TEST
189
190 /*
191 * Test that the dpl_dict_iterate() function will return early
192 * if the callback returns a non-zero number.
193 */
194 struct break_test_state {
195 int count;
196 int special_count;
197 int special_code;
198 };
199
break_test_cb(dpl_dict_var_t * var,void * arg)200 static dpl_status_t break_test_cb(dpl_dict_var_t* var, void* arg)
201 {
202 struct break_test_state* state = arg;
203 state->count++;
204 return (state->count == state->special_count ? state->special_code
205 : DPL_SUCCESS);
206 }
207
START_TEST(iterate_break_test)208 START_TEST(iterate_break_test)
209 {
210 dpl_dict_t* dict;
211 int i;
212 dpl_status_t r;
213 struct break_test_state state = {0, 0, 0};
214 char valbuf[128];
215
216 dict = dpl_dict_new(13);
217 dpl_assert_ptr_not_null(dict);
218
219 for (i = 0; i < nkeys; i++) {
220 dpl_dict_add(dict, keys[i], make_value(i, valbuf, sizeof(valbuf)),
221 /* lowered */ 0);
222 }
223 dpl_assert_int_eq(nkeys, dpl_dict_count(dict));
224
225 /* Basic check: can iterate over everything */
226 state.count = 0; /* initialise */
227 state.special_count = 0; /* no special return */
228 state.special_code = 0;
229 r = dpl_dict_iterate(dict, break_test_cb, &state);
230 dpl_assert_int_eq(DPL_SUCCESS, r);
231 dpl_assert_int_eq(nkeys, state.count);
232
233 /* If the callback returns non-zero partway through,
234 * we see that return code from dpl_dict_iterate(). */
235 state.count = 0; /* initialise */
236 state.special_count = 10; /* return an error on the 10th element */
237 state.special_code = DPL_ECONNECT;
238 r = dpl_dict_iterate(dict, break_test_cb, &state);
239 dpl_assert_int_eq(DPL_ECONNECT, r);
240 /* iteration does not proceed beyond the error return */
241 dpl_assert_int_eq(10, state.count);
242
243 /* Ditto for the 1st element */
244 state.count = 0; /* initialise */
245 state.special_count = 1; /* return an error on the 1st element */
246 state.special_code = DPL_ECONNECT;
247 r = dpl_dict_iterate(dict, break_test_cb, &state);
248 dpl_assert_int_eq(DPL_ECONNECT, r);
249 /* iteration does not proceed beyond the error return */
250 dpl_assert_int_eq(1, state.count);
251
252 /* Ditto for the last element */
253 state.count = 0; /* initialise */
254 state.special_count = nkeys; /* return an error on the last element */
255 state.special_code = DPL_ECONNECT;
256 r = dpl_dict_iterate(dict, break_test_cb, &state);
257 dpl_assert_int_eq(DPL_ECONNECT, r);
258 /* iteration does not proceed beyond the error return */
259 dpl_assert_int_eq(nkeys, state.count);
260
261 /* This also works for non-zero numbers which aren't real error codes */
262 state.count = 0; /* initialise */
263 state.special_count = 10; /* return an error on the 10th element */
264 state.special_code = -511;
265 r = dpl_dict_iterate(dict, break_test_cb, &state);
266 dpl_assert_int_eq(-511, r);
267 /* iteration does not proceed beyond the error return */
268 dpl_assert_int_eq(10, state.count);
269
270 /* This also works for positive numbers */
271 state.count = 0; /* initialise */
272 state.special_count = 10; /* return an error on the 10th element */
273 state.special_code = 127;
274 r = dpl_dict_iterate(dict, break_test_cb, &state);
275 dpl_assert_int_eq(127, r);
276 /* iteration does not proceed beyond the error return */
277 dpl_assert_int_eq(10, state.count);
278
279 dpl_dict_free(dict);
280 }
281 END_TEST
282
283 /*
284 * Test replacing an existing value with dpl_dict_add(
285 */
START_TEST(replace_test)286 START_TEST(replace_test)
287 {
288 dpl_dict_t* dict;
289 int i;
290 dpl_status_t r;
291 char valbuf[128];
292 /* strings courtesy http://hipsteripsum.me/ */
293 static const char key0[] = "Sriracha";
294 static const char val0[] = "Banksy";
295 static const char val0_new[] = "polaroid";
296 static const char key1[] = "trust";
297 static const char val1[] = "fund";
298
299 dict = dpl_dict_new(13);
300 dpl_assert_ptr_not_null(dict);
301
302 /* add the values */
303 dpl_dict_add(dict, key0, val0, /* lowered */ 0);
304 dpl_dict_add(dict, key1, val1, /* lowered */ 0);
305 dpl_assert_int_eq(2, dpl_dict_count(dict));
306
307 /* check the values are there */
308 dpl_assert_str_eq(dpl_dict_get_value(dict, key0), val0);
309 dpl_assert_str_eq(dpl_dict_get_value(dict, key1), val1);
310
311 /* replace one of the values */
312 dpl_dict_add(dict, key0, val0_new, /* lowered */ 0);
313
314 /* check the element count is correct */
315 dpl_assert_int_eq(2, dpl_dict_count(dict));
316
317 /* check the new value is there */
318 dpl_assert_str_eq(dpl_dict_get_value(dict, key0), val0_new);
319 /* check the other key is unaffected */
320 dpl_assert_str_eq(dpl_dict_get_value(dict, key1), val1);
321
322 dpl_dict_free(dict);
323 }
324 END_TEST
325
START_TEST(remove_test)326 START_TEST(remove_test)
327 {
328 dpl_dict_t* dict;
329 int i;
330 dpl_status_t r;
331 char valbuf[128];
332
333 /* create with a small table to ensure we have some chains */
334 dict = dpl_dict_new(5);
335 dpl_assert_ptr_not_null(dict);
336
337 /* add all the keys */
338 for (i = 0; i < nkeys; i++) {
339 dpl_dict_add(dict, keys[i], make_value(i, valbuf, sizeof(valbuf)),
340 /* lowered */ 0);
341 }
342 dpl_assert_int_eq(nkeys, dpl_dict_count(dict));
343
344 /* remove the keys again */
345 for (i = 0; i < nkeys; i++) {
346 dpl_dict_var_t* var = dpl_dict_get(dict, keys[i]);
347 dpl_assert_ptr_not_null(var);
348 dpl_assert_str_eq(var->key, keys[i]);
349 dpl_dict_remove(dict, var);
350 dpl_assert_int_eq(nkeys - 1 - i, dpl_dict_count(dict));
351 }
352
353 /* add all the keys back again */
354 for (i = 0; i < nkeys; i++) {
355 dpl_dict_add(dict, keys[i], make_value(i, valbuf, sizeof(valbuf)),
356 /* lowered */ 0);
357 }
358 dpl_assert_int_eq(nkeys, dpl_dict_count(dict));
359
360 /* remove the keys again in reverse order; we do
361 * this to exercise some hash chain manipulation
362 * corner cases */
363 for (i = nkeys - 1; i >= 0; i--) {
364 dpl_dict_var_t* var = dpl_dict_get(dict, keys[i]);
365 dpl_assert_ptr_not_null(var);
366 dpl_assert_str_eq(var->key, keys[i]);
367 dpl_dict_remove(dict, var);
368 dpl_assert_int_eq(i, dpl_dict_count(dict));
369 }
370
371 dpl_dict_free(dict);
372 }
373 END_TEST
374
375 /*
376 * Test the dpl_dict_filter_prefix() and
377 * dpl_dict_filter_no_prefix() functions.
378 */
START_TEST(filter_test)379 START_TEST(filter_test)
380 {
381 dpl_dict_t* dict;
382 dpl_dict_t* d2;
383 int i;
384 dpl_status_t r;
385 const char* exp;
386 const char* act;
387 char valbuf[128];
388 /* strings courtesy http://hipsteripsum.me/ */
389 static const char* const keys[]
390 = {"Sriracha", "mehBanksy", "trust", "fund", "Brooklyn",
391 "mehpolaroid", "Viral", "mehselfies", "mehkogi", "Austin"};
392 static const int nkeys = sizeof(keys) / sizeof(keys[0]);
393 static const int is_meh[] = {0, 1, 0, 0, 0, 1, 0, 1, 1, 0};
394 static const int nmehs = 4;
395
396 dict = dpl_dict_new(13);
397 dpl_assert_ptr_not_null(dict);
398
399 /* add all the keys */
400 for (i = 0; i < nkeys; i++) {
401 dpl_dict_add(dict, keys[i], make_value(i, valbuf, sizeof(valbuf)),
402 /* lowered */ 0);
403 }
404 dpl_assert_int_eq(nkeys, dpl_dict_count(dict));
405
406 /* create a new dict and copy everything across using an empty prefix */
407 d2 = dpl_dict_new(13);
408 r = dpl_dict_filter_prefix(d2, dict, "");
409 dpl_assert_int_eq(DPL_SUCCESS, r);
410 dpl_assert_int_eq(nkeys, dpl_dict_count(dict));
411 dpl_assert_int_eq(nkeys, dpl_dict_count(d2));
412 for (i = 0; i < nkeys; i++) {
413 exp = make_value(i, valbuf, sizeof(valbuf));
414 act = dpl_dict_get_value(d2, keys[i]);
415 dpl_assert_str_eq(exp, act);
416 }
417 dpl_dict_free(d2);
418
419 /* create a new dict and copy just the matching ones */
420 d2 = dpl_dict_new(13);
421 r = dpl_dict_filter_prefix(d2, dict, "meh");
422 dpl_assert_int_eq(DPL_SUCCESS, r);
423 dpl_assert_int_eq(nkeys, dpl_dict_count(dict));
424 dpl_assert_int_eq(nmehs, dpl_dict_count(d2));
425 for (i = 0; i < nkeys; i++) {
426 if (is_meh[i]) {
427 exp = make_value(i, valbuf, sizeof(valbuf));
428 /*
429 * dpl_dict_filter_prefix() inexplicably removes the prefix
430 * from the keys it copies across. This was probably very
431 * convenient for the person who wrote the code, but isn't
432 * all that easy to explain. But this is why we add a
433 * strlen() here.
434 */
435 act = dpl_dict_get_value(d2, keys[i] + strlen("meh"));
436 dpl_assert_str_eq(exp, act);
437 }
438 }
439 dpl_dict_free(d2);
440
441 /* create a new dict and copy just the non-matching ones */
442 d2 = dpl_dict_new(13);
443 r = dpl_dict_filter_no_prefix(d2, dict, "meh");
444 dpl_assert_int_eq(DPL_SUCCESS, r);
445 dpl_assert_int_eq(nkeys, dpl_dict_count(dict));
446 dpl_assert_int_eq(nkeys - nmehs, dpl_dict_count(d2));
447 for (i = 0; i < nkeys; i++) {
448 if (!is_meh[i]) {
449 exp = make_value(i, valbuf, sizeof(valbuf));
450 act = dpl_dict_get_value(d2, keys[i]);
451 dpl_assert_str_eq(exp, act);
452 }
453 }
454 dpl_dict_free(d2);
455
456 dpl_dict_free(dict);
457 }
458 END_TEST
459
460 /*
461 * Test the "lowered" feature, which is almost but not quite
462 * the same as case insensitivity.
463 */
START_TEST(lowered_test)464 START_TEST(lowered_test)
465 {
466 dpl_dict_t* dict;
467 dpl_status_t r;
468 const char* s;
469 dpl_dict_var_t* var;
470 const char value[] = "World";
471 const char value2[] = "Mondo";
472 const char value3[] = "Monde";
473
474 dict = dpl_dict_new(13);
475 dpl_assert_ptr_not_null(dict);
476
477 /* Add a value, lowered */
478 r = dpl_dict_add(dict, "Hello", value, /*lowered*/ 1);
479 dpl_assert_int_eq(DPL_SUCCESS, r);
480 dpl_assert_int_eq(1, dpl_dict_count(dict));
481
482 /* The actual key used is internally lowercased, so
483 * doing a normal get on the key originally given
484 * should fail */
485 dpl_assert_str_eq(NULL, dpl_dict_get_value(dict, "Hello"));
486
487 /* Likewise, doing a normal get on a lowercase version
488 * of the original key should succeed */
489 dpl_assert_str_eq(value, dpl_dict_get_value(dict, "hello"));
490
491 /* dpl_dict_get_lowered internally lowercases the key it's
492 * given, so it can be used with keys of any casing */
493
494 var = BADPOINTER;
495 r = dpl_dict_get_lowered(dict, "hello", &var);
496 dpl_assert_int_eq(DPL_SUCCESS, r);
497 dpl_assert_ptr_not_null(var);
498 dpl_assert_ptr_ne(var, BADPOINTER);
499 dpl_assert_str_eq(value, dpl_sbuf_get_str(var->val->string));
500
501 var = BADPOINTER;
502 r = dpl_dict_get_lowered(dict, "Hello", &var);
503 dpl_assert_int_eq(DPL_SUCCESS, r);
504 dpl_assert_ptr_not_null(var);
505 dpl_assert_ptr_ne(var, BADPOINTER);
506 dpl_assert_str_eq(value, dpl_sbuf_get_str(var->val->string));
507
508 var = BADPOINTER;
509 r = dpl_dict_get_lowered(dict, "HELLO", &var);
510 dpl_assert_int_eq(DPL_SUCCESS, r);
511 dpl_assert_ptr_not_null(var);
512 dpl_assert_ptr_ne(var, BADPOINTER);
513 dpl_assert_str_eq(value, dpl_sbuf_get_str(var->val->string));
514
515 var = BADPOINTER;
516 r = dpl_dict_get_lowered(dict, "hElLo", &var);
517 dpl_assert_int_eq(DPL_SUCCESS, r);
518 dpl_assert_ptr_not_null(var);
519 dpl_assert_ptr_ne(var, BADPOINTER);
520 dpl_assert_str_eq(value, dpl_sbuf_get_str(var->val->string));
521
522 /* check that dpl_dict_get_lowered() will report as missing
523 * some keys that we know not to be present */
524
525 var = BADPOINTER;
526 r = dpl_dict_get_lowered(dict, "HellonEarth", &var);
527 dpl_assert_int_eq(DPL_ENOENT, r);
528 /* the value of `var' on failure is not documented */
529
530 var = BADPOINTER;
531 r = dpl_dict_get_lowered(dict, "Hell", &var);
532 dpl_assert_int_eq(DPL_ENOENT, r);
533 /* the value of `var' on failure is not documented */
534
535 var = BADPOINTER;
536 r = dpl_dict_get_lowered(dict, "daffyduck", &var);
537 dpl_assert_int_eq(DPL_ENOENT, r);
538 /* the value of `var' on failure is not documented */
539
540 /* Verify that inserting another key which maps to the
541 * same lowercased string, replaces the first key */
542
543 r = dpl_dict_add(dict, "hello", value2, /*lowered*/ 1);
544 dpl_assert_int_eq(DPL_SUCCESS, r);
545 dpl_assert_int_eq(1, dpl_dict_count(dict));
546 dpl_assert_str_eq(value2, dpl_dict_get_value(dict, "hello"));
547
548 r = dpl_dict_add(dict, "hELLo", value3, /*lowered*/ 1);
549 dpl_assert_int_eq(DPL_SUCCESS, r);
550 dpl_assert_int_eq(1, dpl_dict_count(dict));
551 dpl_assert_str_eq(value3, dpl_dict_get_value(dict, "hello"));
552
553 dpl_dict_free(dict);
554 }
555 END_TEST
556
dict_suite(void)557 Suite* dict_suite(void)
558 {
559 Suite* s = suite_create("dict");
560 TCase* d = tcase_create("base");
561 tcase_add_test(d, dict_test);
562 tcase_add_test(d, long_key_test);
563 tcase_add_test(d, iterate_break_test);
564 tcase_add_test(d, replace_test);
565 tcase_add_test(d, remove_test);
566 tcase_add_test(d, filter_test);
567 tcase_add_test(d, lowered_test);
568 suite_add_tcase(s, d);
569 return s;
570 }
571