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