Code

c54ea3164dec8b965bb16a4d64368bbac6b3b694
[sysdb.git] / t / utils / dbi_test.c
1 /*
2  * SysDB - t/utils/dbi_test.c
3  * Copyright (C) 2013 Sebastian 'tokkee' Harl <sh@tokkee.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
28 #include "libsysdb_test.h"
29 #include "utils/dbi.h"
31 #include <check.h>
32 #include <dbi/dbi.h>
34 #define TEST_MAGIC ((void *)0x1337)
36 /*
37  * private data-types
38  */
39 typedef union {
40         long long   integer;
41         double      decimal;
42         const char *string;
43         time_t      datetime;
44         struct {
45                 size_t length;
46                 const unsigned char *datum;
47         } binary;
48 } mock_data_t;
50 typedef struct {
51         const char *name;
52         unsigned long long nrows;
53         unsigned long long current_row;
54         unsigned int    nfields;
55         unsigned short *field_types;
56         char          **field_names;
57 } mock_query_t;
59 /*
60  * private variables
61  */
63 static sdb_dbi_client_t *client;
65 /*
66  * mock queries
67  */
69 /* field definitions */
70 static unsigned short field_types[] = {
71         DBI_TYPE_INTEGER,
72         DBI_TYPE_DECIMAL,
73         DBI_TYPE_STRING,
74         DBI_TYPE_DATETIME,
75         DBI_TYPE_BINARY,
76 };
77 static char *field_names[] = {
78         "field0",
79         "field1",
80         "field2",
81         "field3",
82         "field4",
83 };
85 #define DATUM(p) ((const unsigned char *)(p))
86 static mock_data_t golden_data[][5] = {
87         {
88                 { .integer  = 1234   },
89                 { .decimal  = 1.234  },
90                 { .string   = "abcd" },
91                 { .datetime = 0      },
92                 { .binary   = { 1, DATUM("a") } },
93         },
94         {
95                 { .integer  = 2345   },
96                 { .decimal  = 23.45  },
97                 { .string   = "bcde" },
98                 { .datetime = 1      },
99                 { .binary   = { 4, DATUM("bcde") } },
100         },
101         {
102                 { .integer  = 3456   },
103                 { .decimal  = 345.6  },
104                 { .string   = "cd"   },
105                 { .datetime = 2      },
106                 { .binary   = { 0, DATUM(NULL) } },
107         },
108         {
109                 { .integer  = 4567   },
110                 { .decimal  = 4567   },
111                 { .string   = "d"    },
112                 { .datetime = 3      },
113                 { .binary   = { 13, DATUM("defghijklmnop") } },
114         },
115         {
116                 { .integer  = 5678   },
117                 { .decimal  = 56.78  },
118                 { .string   = "efgh" },
119                 { .datetime = 4      },
120                 { .binary   = { 5, DATUM("efghi") } },
121         },
122 };
124 /* query definitions */
125 static mock_query_t mock_queries[] = {
126         { "mockquery0", 5, 1, 0, NULL, NULL },
127         { "mockquery1", 0, 0, 1, field_types, field_names },
128         { "mockquery2", 1, 1, 1, field_types, field_names },
129         { "mockquery3", 2, 1, 1, field_types, field_names },
130         { "mockquery4", 5, 1, 1, field_types, field_names },
131         { "mockquery5", 5, 1, 2, field_types, field_names },
132         { "mockquery6", 5, 1, 3, field_types, field_names },
133         { "mockquery7", 5, 1, 4, field_types, field_names },
134         { "mockquery8", 5, 1, 5, field_types, field_names },
135 };
137 static mock_query_t *current_query = NULL;
139 /*
140  * mocked functions
141  */
143 /* dbi_driver, dbi_conn, dbi_result are void pointers */
145 dbi_driver
146 dbi_driver_open(const char *name)
148         if (strcmp(name, "mockdriver"))
149                 return NULL;
150         return (dbi_driver)strdup(name);
151 } /* dbi_driver_open */
153 dbi_driver
154 dbi_driver_list(dbi_driver curr)
156         if (!curr)
157                 return "mockdriver";
158         return NULL;
159 } /* dbi_driver_list */
161 const char *
162 dbi_driver_get_name(dbi_driver driver)
164         return (const char *)driver;
165 } /* dbi_driver_get_name */
167 int
168 dbi_conn_set_option(dbi_conn __attribute__((unused)) conn,
169                 const char __attribute__((unused)) *key,
170                 const char __attribute__((unused)) *value)
172         return 0;
173 } /* dbi_conn_set_option */
175 const char *
176 dbi_conn_get_option_list(dbi_conn __attribute__((unused)) conn,
177                 const char __attribute__((unused)) *key)
179         return NULL;
180 } /* dbi_conn_get_option_list */
182 dbi_conn
183 dbi_conn_open(dbi_driver driver)
185         if (strcmp((const char *)driver, "mockdriver"))
186                 return NULL;
187         return (dbi_conn)"mockconnection";
188 } /* dbi_conn_open */
190 static unsigned long long dbi_conn_connect_called = 0;
191 int
192 dbi_conn_connect(dbi_conn conn)
194         ++dbi_conn_connect_called;
195         if (strcmp((const char *)conn, "mockconnection"))
196                 return DBI_ERROR_NOCONN;
197         return 0;
198 } /* dbi_conn_connect */
200 int
201 dbi_conn_ping(dbi_conn conn)
203         if (strcmp((const char *)conn, "mockconnection"))
204                 return 0;
205         return 1;
206 } /* dbi_conn_connect */
208 void
209 dbi_conn_close(dbi_conn __attribute__((unused)) conn)
211         return;
212 } /* dbi_conn_close */
214 int
215 dbi_conn_error(dbi_conn conn, const char **errmsg)
217         if ((! conn) || (strcmp((const char *)conn, "mockconnection")))
218                 return DBI_ERROR_BADOBJECT;
219         if (errmsg)
220                 *errmsg = "mockerror";
221         return DBI_ERROR_UNSUPPORTED;
222 } /* dbi_conn_error */
224 static unsigned long long dbi_conn_query_called = 0;
225 dbi_result
226 dbi_conn_query(dbi_conn conn, const char __attribute__((unused)) *statement)
228         size_t i;
230         ++dbi_conn_query_called;
231         if (strcmp((const char *)conn, "mockconnection"))
232                 return NULL;
234         for (i = 0; i < SDB_STATIC_ARRAY_LEN(mock_queries); ++i) {
235                 if (!strcmp(mock_queries[i].name, statement)) {
236                         current_query = &mock_queries[i];
237                         return (dbi_result)current_query;
238                 }
239         }
240         return NULL;
241 } /* dbi_conn_query */
243 unsigned long long
244 dbi_result_get_numrows(dbi_result res)
246         mock_query_t *q = res;
247         if (! q)
248                 return DBI_ROW_ERROR;
249         return q->nrows;
250 } /* dbi_result_get_numrows */
252 unsigned int
253 dbi_result_get_numfields(dbi_result res)
255         mock_query_t *q = res;
256         if (! q)
257                 return DBI_FIELD_ERROR;
258         return q->nfields;
259 } /* dbi_result_get_numfields */
261 unsigned short
262 dbi_result_get_field_type_idx(dbi_result res, unsigned int i)
264         mock_query_t *q = res;
265         if ((! q) || (i > q->nfields))
266                 return DBI_TYPE_ERROR;
267         return q->field_types[i - 1];
268 } /* dbi_result_get_field_type_idx */
270 const char *
271 dbi_result_get_field_name(dbi_result res, unsigned int i)
273         mock_query_t *q = res;
274         if ((! q) || (i > q->nfields))
275                 return NULL;
276         return q->field_names[i - 1];
277 } /* dbi_result_get_field_name */
279 int
280 dbi_result_seek_row(dbi_result res, unsigned long long n)
282         mock_query_t *q = res;
283         if ((! q) || (n > q->nrows))
284                 return 0;
286         q->current_row = n;
287         return 1;
288 } /* dbi_result_seek_row */
290 static mock_data_t
291 get_golden_data(dbi_result res, unsigned int i) {
292         mock_query_t *q = res;
293         fail_unless(q != NULL, "dbi_result_get_*_idx() called with "
294                         "NULL result data; expected valid result object");
296         /* this information is managed by seek_row and, thus,
297          * should never be invalid */
298         fail_unless(q->current_row && q->current_row <= q->nrows,
299                         "INTERNAL ERROR: current row out of range");
301         fail_unless(i && i <= q->nfields,
302                         "dbi_result_get_*_idx() called with index out of range; "
303                         "got: %u; expected [1, %u]", i, q->nfields);
304         return golden_data[q->current_row - 1][i - 1];
305 } /* get_golden_data */
307 long long
308 dbi_result_get_longlong_idx(dbi_result res, unsigned int i)
310         fail_unless(current_query->field_types[i - 1] == DBI_TYPE_INTEGER,
311                         "dbi_result_get_longlong_idx() called for non-integer "
312                         "column type %u", current_query->field_types[i - 1]);
313         return get_golden_data(res, i).integer;
314 } /* dbi_result_get_longlong_idx */
316 double
317 dbi_result_get_double_idx(dbi_result res, unsigned int i)
319         fail_unless(current_query->field_types[i - 1] == DBI_TYPE_DECIMAL,
320                         "dbi_result_get_double_idx() called for non-decimal "
321                         "column type %u", current_query->field_types[i - 1]);
322         return get_golden_data(res, i).decimal;
323 } /* dbi_result_get_double_idx */
325 const char *
326 dbi_result_get_string_idx(dbi_result res, unsigned int i)
328         fail_unless(current_query->field_types[i - 1] == DBI_TYPE_STRING,
329                         "dbi_result_get_string_idx() called for non-string "
330                         "column type %u", current_query->field_types[i - 1]);
331         return get_golden_data(res, i).string;
332 } /* dbi_result_get_string_idx */
334 char *
335 dbi_result_get_string_copy_idx(dbi_result res, unsigned int i)
337         fail_unless(current_query->field_types[i - 1] == DBI_TYPE_STRING,
338                         "dbi_result_get_string_copy_idx() called for non-string "
339                         "column type %u", current_query->field_types[i - 1]);
340         if (! get_golden_data(res, i).string)
341                 return NULL;
342         return strdup(get_golden_data(res, i).string);
343 } /* dbi_result_get_string_copy_idx */
345 time_t
346 dbi_result_get_datetime_idx(dbi_result res, unsigned int i)
348         fail_unless(current_query->field_types[i - 1] == DBI_TYPE_DATETIME,
349                         "dbi_result_get_datetime_idx() called for non-datetime "
350                         "column type %u", current_query->field_types[i - 1]);
351         return get_golden_data(res, i).datetime;
352 } /* dbi_result_get_datetime_idx */
354 size_t
355 dbi_result_get_field_length_idx(dbi_result res, unsigned int i)
357         /* this will check if the parameters are valid */
358         get_golden_data(res, i);
360         switch (current_query->field_types[i - 1]) {
361                 case DBI_TYPE_INTEGER:
362                         return sizeof(long long);
363                         break;
364                 case DBI_TYPE_DECIMAL:
365                         return sizeof(double);
366                         break;
367                 case DBI_TYPE_STRING:
368                         return strlen(get_golden_data(res, i).string) + 1;
369                         break;
370                 case DBI_TYPE_DATETIME:
371                         return sizeof(time_t);
372                         break;
373                 case DBI_TYPE_BINARY:
374                         return get_golden_data(res, i).binary.length;
375                         break;
376         }
378         fail("INTERNAL ERROR: dbi_result_get_field_length_idx() "
379                         "called for unexpected field type %u",
380                         current_query->field_types[i - 1]);
381         return 0;
382 } /* dbi_result_get_field_length_idx */
384 const unsigned char *
385 dbi_result_get_binary_idx(dbi_result res, unsigned int i)
387         fail_unless(current_query->field_types[i - 1] == DBI_TYPE_BINARY,
388                         "dbi_result_get_binary_idx() called for non-binary "
389                         "column type %u", current_query->field_types[i - 1]);
390         return get_golden_data(res, i).binary.datum;
391 } /* dbi_result_get_binary_idx */
393 unsigned char *
394 dbi_result_get_binary_copy_idx(dbi_result res, unsigned int i)
396         const char *data;
397         fail_unless(current_query->field_types[i - 1] == DBI_TYPE_BINARY,
398                         "dbi_result_get_binary_copy_idx() called for non-binary "
399                         "column type %u", current_query->field_types[i - 1]);
400         data = (const char *)get_golden_data(res, i).binary.datum;
401         if (! data)
402                 return NULL;
403         return (unsigned char *)strdup(data);
404 } /* dbi_result_get_binary_copy_idx */
406 static unsigned long long dbi_result_free_called = 0;
407 int
408 dbi_result_free(dbi_result res)
410         mock_query_t *q = res;
412         ++dbi_result_free_called;
413         if (! q)
414                 return -1;
416         current_query = NULL;
417         return 0;
418 } /* dbi_result_free */
420 /*
421  * private helper functions
422  */
424 static void
425 setup(void)
427         client = sdb_dbi_client_create("mockdriver", "mockdatabase");
428         fail_unless(client != NULL,
429                         "sdb_dbi_client_create() = NULL; expected client object");
431         dbi_conn_connect_called = 0;
432 } /* setup */
434 static void
435 connect(void)
437         int check = sdb_dbi_client_connect(client);
438         fail_unless(check == 0,
439                         "sdb_dbi_client_connect() = %i; expected: 0", check);
440 } /* connect */
442 static void
443 teardown(void)
445         sdb_dbi_client_destroy(client);
446         client = NULL;
447 } /* teardown */
449 static unsigned long long query_callback_called = 0;
450 static int
451 query_callback(sdb_dbi_client_t *c,
452                 size_t n, sdb_data_t *data, sdb_object_t *user_data)
454         size_t i;
456         ++query_callback_called;
458         fail_unless(c == client,
459                         "query callback received unexpected client = %p; "
460                         "expected: %p", c, client);
461         fail_unless(n == current_query->nfields,
462                         "query callback received n = %i; expected: %i",
463                         n, current_query->nfields);
464         fail_unless(data != NULL,
465                         "query callback received data = NULL; expected: valid data");
466         fail_unless(user_data == TEST_MAGIC,
467                         "query callback received user_data = %p; expected: %p",
468                         user_data, TEST_MAGIC);
470         for (i = 0; i < n; ++i) {
471                 int expected_type = DBI_TYPE_TO_SC(current_query->field_types[i]);
472                 mock_data_t expected_data;
474                 fail_unless((int)data[i].type == expected_type,
475                                 "query callback received unexpected type %i for "
476                                 "column %zu; expected: %i", data[i].type, i,
477                                 expected_type);
479                 expected_data = golden_data[current_query->current_row - 1][i];
480                 switch (expected_type) {
481                         case SDB_TYPE_INTEGER:
482                                 fail_unless(data[i].data.integer == expected_data.integer,
483                                                 "query callback received unexpected data %lli "
484                                                 "for column %zu; expected: %lli",
485                                                 data[i].data.integer, i, expected_data.integer);
486                                 break;
487                         case SDB_TYPE_DECIMAL:
488                                 fail_unless(data[i].data.decimal == expected_data.decimal,
489                                                 "query callback received unexpected data %g "
490                                                 "for column %zu; expected: %g",
491                                                 data[i].data.decimal, i, expected_data.decimal);
492                                 break;
493                         case SDB_TYPE_STRING:
494                                 fail_unless(!strcmp(data[i].data.string, expected_data.string),
495                                                 "query callback received unexpected data %s "
496                                                 "for column %zu; expected: %s",
497                                                 data[i].data.string, i, expected_data.string);
498                                 break;
499                         case SDB_TYPE_DATETIME:
500                                 fail_unless(data[i].data.datetime
501                                                         == SECS_TO_SDB_TIME(expected_data.datetime),
502                                                 "query callback received unexpected data "PRIscTIME
503                                                 " for column %zu; expected: "PRIscTIME,
504                                                 data[i].data.integer, i,
505                                                 SECS_TO_SDB_TIME(expected_data.integer));
506                                 break;
507                         case SDB_TYPE_BINARY:
508                                 fail_unless(data[i].data.binary.length ==
509                                                         expected_data.binary.length,
510                                                 "query callback received unexpected "
511                                                 "binary data length %zu for column %zu; "
512                                                 "expected: %lli", data[i].data.binary.length, i,
513                                                 expected_data.binary.length);
514                                 fail_unless(!memcmp(data[i].data.binary.datum,
515                                                         expected_data.binary.datum,
516                                                         expected_data.binary.length),
517                                                 "query callback received unexpected binary data %p "
518                                                 "for column %zu; expected: %p",
519                                                 data[i].data.binary.datum, i,
520                                                 expected_data.binary.datum);
521                                 break;
522                         default:
523                                 fail("INTERNAL ERROR: query callback received "
524                                                 "unknown type %i for column %zu",
525                                                 expected_type, i);
526                 }
527         }
528         return 0;
529 } /* query_callback */
531 /*
532  * tests
533  */
535 START_TEST(test_sdb_dbi_client_connect)
537         int check = sdb_dbi_client_connect(client);
538         fail_unless(check == 0,
539                         "sdb_dbi_client_connect() = %i; expected: 0", check);
541         fail_unless(dbi_conn_connect_called == 1,
542                         "sdb_dbi_client_create() called dbi_conn_connect %i times; "
543                         "expected: 1", dbi_conn_connect_called);
545 END_TEST
547 START_TEST(test_sdb_dbi_client_check_conn)
549         int check = sdb_dbi_client_check_conn(client);
550         fail_unless(check == 0,
551                         "sdb_dbi_client_check_conn() = %i; expected: 0", check);
553         /* the first call will actually connect to the database */
554         fail_unless(dbi_conn_connect_called == 1,
555                         "sdb_dbi_client_check_conn() called dbi_conn_connect %i times; "
556                         "expected: 1", dbi_conn_connect_called);
558         dbi_conn_connect_called = 0;
559         check = sdb_dbi_client_check_conn(client);
560         fail_unless(check == 0,
561                         "sdb_dbi_client_check_conn() = %i; expected: 0", check);
563         /* should not reconnect */
564         fail_unless(dbi_conn_connect_called == 0,
565                         "sdb_dbi_client_check_conn() called dbi_conn_connect %i time(s); "
566                         "expected: 0", dbi_conn_connect_called);
568 END_TEST
570 START_TEST(test_sdb_dbi_exec_query)
572         size_t i;
574         int check = sdb_dbi_exec_query(client, "mockquery0", query_callback,
575                         /* user_data = */ TEST_MAGIC, /* n = */ 0);
576         /* not connected yet */
577         fail_unless(check < 0,
578                         "sdb_dbi_exec_query() = %i; expected: < 0", check);
580         connect();
582         for (i = 0; i < SDB_STATIC_ARRAY_LEN(mock_queries); ++i) {
583                 mock_query_t *q = &mock_queries[i];
585                 unsigned long long expected_callback_calls = 0;
587                 dbi_conn_query_called = 0;
588                 query_callback_called = 0;
589                 dbi_result_free_called = 0;
591                 /* sdb_dbi_exec_query will only use as many type arguments are needed,
592                  * so we can safely pass in the maximum number of arguments required
593                  * on each call */
594                 check = sdb_dbi_exec_query(client, q->name, query_callback,
595                                 /* user_data = */ TEST_MAGIC, /* n = */ (int)q->nfields,
596                                 SDB_TYPE_INTEGER, SDB_TYPE_DECIMAL, SDB_TYPE_STRING,
597                                 SDB_TYPE_DATETIME, SDB_TYPE_BINARY);
598                 fail_unless(check == 0,
599                                 "sdb_dbi_exec_query() = %i; expected: 0", check);
601                 fail_unless(dbi_conn_query_called == 1,
602                                 "sdb_dbi_exec_query() called dbi_conn_query %i times; "
603                                 "expected: 1", dbi_conn_query_called);
605                 if (q->nfields)
606                         expected_callback_calls = q->nrows;
608                 fail_unless(query_callback_called == expected_callback_calls,
609                                 "sdb_dbi_exec_query() did not call the registered callback "
610                                 "for each result row; got %i call%s; expected: 0",
611                                 query_callback_called,
612                                 (query_callback_called == 1) ? "" : "s");
614                 fail_unless(dbi_result_free_called == 1,
615                                 "sdb_dbi_exec_query() did not free the query result object");
616         }
618 END_TEST
620 /*
621  * test API
622  */
624 Suite *
625 util_dbi_suite(void)
627         Suite *s = suite_create("utils::dbi");
628         TCase *tc;
630         tc = tcase_create("core");
631         tcase_add_checked_fixture(tc, setup, teardown);
632         tcase_add_test(tc, test_sdb_dbi_client_connect);
633         tcase_add_test(tc, test_sdb_dbi_client_check_conn);
634         tcase_add_test(tc, test_sdb_dbi_exec_query);
635         suite_add_tcase(s, tc);
637         return s;
638 } /* util_llist_suite */
640 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */